林海 3 дней назад
Родитель
Сommit
9d6f389d37
3 измененных файлов с 113 добавлено и 79 удалено
  1. 65 69
      app.py
  2. 30 6
      templates/add_member.html
  3. 18 4
      templates/members.html

+ 65 - 69
app.py

@@ -1445,46 +1445,17 @@ def members():
         return redirect(url_for('login'))
     
     search_name = request.args.get('name', '').strip()
+    search_type = request.args.get('search_type', 'name').strip()  # name | name_word
     page = request.args.get('page', 1, type=int)
     per_page = 10
     offset = (page - 1) * per_page
     
-    print(f"[Members List] Fetching members page: {page}, search: '{search_name}', per_page: {per_page}")
+    print(f"[Members List] Fetching members page: {page}, search: '{search_name}', type: '{search_type}', per_page: {per_page}")
     
     conn = get_db_connection()
     try:
         with conn.cursor() as cursor:
-            # 1. Get total count
-            if search_name:
-                variants = expand_name_search_variants(search_name)
-                where_parts = []
-                params = []
-                for v in variants:
-                    where_parts.append("(name LIKE %s OR simplified_name LIKE %s)")
-                    like = f"%{v}%"
-                    params.extend([like, like])
-                where_clause = " OR ".join(where_parts) if where_parts else "name LIKE %s"
-                if not where_parts:
-                    params = [f"%{search_name}%"]
-                count_sql = f"SELECT COUNT(*) as count FROM family_member_info WHERE {where_clause}"
-                print(f"[Members List] Executing count SQL: {count_sql}")
-                print(f"[Members List] Count SQL parameters: {params}")
-                cursor.execute(count_sql, tuple(params))
-            else:
-                count_sql = "SELECT COUNT(*) as count FROM family_member_info"
-                print(f"[Members List] Executing count SQL: {count_sql}")
-                cursor.execute(count_sql)
-            
-            result = cursor.fetchone()
-            total = result['count'] if result else 0
-            total_pages = (total + per_page - 1) // per_page
-            print(f"[Members List] Total members: {total}, total pages: {total_pages}")
-            
-            # 2. Get paginated results, ordered by modified_time DESC (or create_time if modified is null/same)
-            # Using COALESCE to ensure sort works even if modified_time is NULL
             order_clause = "ORDER BY COALESCE(fmi.modified_time, fmi.create_time) DESC"
-
-            # 父亲信息 JOIN(取亲生/普通父亲,排除入继关系)
             father_join = """
                 LEFT JOIN family_relation_info fri
                     ON fmi.id = fri.child_mid AND fri.relation_type = 1 AND COALESCE(fri.sub_relation_type, 0) != 3
@@ -1492,26 +1463,47 @@ def members():
             """
             father_cols = ", father.id as father_id, father.name as father_name, father.simplified_name as father_simplified_name, fri.child_order as child_order_to_father"
 
-            if search_name:
+            if search_name and search_type == 'name_word':
+                # 字辈精确搜索
+                where_clause = "fmi.name_word = %s"
+                count_params = [search_name]
+                data_params = [search_name]
+            elif search_name:
+                # 姓名模糊搜索(支持繁简变体)
                 variants = expand_name_search_variants(search_name)
                 where_parts = []
-                params = []
+                count_params = []
                 for v in variants:
                     where_parts.append("(fmi.name LIKE %s OR fmi.simplified_name LIKE %s)")
                     like = f"%{v}%"
-                    params.extend([like, like])
+                    count_params.extend([like, like])
                 where_clause = " OR ".join(where_parts) if where_parts else "(fmi.name LIKE %s OR fmi.simplified_name LIKE %s)"
                 if not where_parts:
                     like = f"%{search_name}%"
-                    params = [like, like]
-                sql = f"SELECT fmi.id, fmi.name, fmi.simplified_name, fmi.sex, fmi.name_word_generation, fmi.birthday, fmi.occupation, fmi.family_rank, fmi.branch_family_hall, fmi.residential_address, fmi.is_pass_away, fmi.create_time, fmi.modified_time{father_cols} FROM family_member_info fmi {father_join} WHERE {where_clause} {order_clause} LIMIT %s OFFSET %s"
-                print(f"[Members List] Executing members SQL: {sql}")
-                print(f"[Members List] Members SQL parameters: {params + [per_page, offset]}")
-                cursor.execute(sql, tuple(params + [per_page, offset]))
+                    count_params = [like, like]
+                data_params = count_params[:]
+            else:
+                where_clause = None
+                count_params = []
+                data_params = []
+
+            # 1. 计算总数
+            if where_clause:
+                count_sql = f"SELECT COUNT(*) as count FROM family_member_info fmi WHERE {where_clause}"
+                cursor.execute(count_sql, tuple(count_params))
             else:
-                sql = f"SELECT fmi.id, fmi.name, fmi.simplified_name, fmi.sex, fmi.name_word_generation, fmi.birthday, fmi.occupation, fmi.family_rank, fmi.branch_family_hall, fmi.residential_address, fmi.is_pass_away, fmi.create_time, fmi.modified_time{father_cols} FROM family_member_info fmi {father_join} {order_clause} LIMIT %s OFFSET %s"
-                print(f"[Members List] Executing members SQL: {sql}")
-                print(f"[Members List] Members SQL parameters: {[per_page, offset]}")
+                cursor.execute("SELECT COUNT(*) as count FROM family_member_info")
+            result = cursor.fetchone()
+            total = result['count'] if result else 0
+            total_pages = (total + per_page - 1) // per_page
+
+            # 2. 查询分页数据
+            select_cols = f"fmi.id, fmi.name, fmi.simplified_name, fmi.sex, fmi.name_word, fmi.name_word_generation, fmi.birthday, fmi.occupation, fmi.family_rank, fmi.branch_family_hall, fmi.residential_address, fmi.is_pass_away, fmi.create_time, fmi.modified_time{father_cols}"
+            if where_clause:
+                sql = f"SELECT {select_cols} FROM family_member_info fmi {father_join} WHERE {where_clause} {order_clause} LIMIT %s OFFSET %s"
+                cursor.execute(sql, tuple(data_params + [per_page, offset]))
+            else:
+                sql = f"SELECT {select_cols} FROM family_member_info fmi {father_join} {order_clause} LIMIT %s OFFSET %s"
                 cursor.execute(sql, (per_page, offset))
             
             members = cursor.fetchall()
@@ -1566,7 +1558,7 @@ def members():
         print(f"[Members List] Closing database connection")
         conn.close()
     
-    return render_template('members.html', members=members, search_name=search_name, page=page, total_pages=total_pages, total=total)
+    return render_template('members.html', members=members, search_name=search_name, search_type=search_type, page=page, total_pages=total_pages, total=total)
 
 @app.route('/manager/batch_genealogy')
 def batch_genealogy():
@@ -2600,46 +2592,49 @@ def get_members():
     
     page = int(request.args.get('page', 1))
     search = request.args.get('search', '')
+    search_type = request.args.get('search_type', 'name')  # name | name_word
     per_page = 10
     offset = (page - 1) * per_page
     
     conn = get_db_connection()
     try:
         with conn.cursor() as cursor:
-            # Count total members
-            if search:
-                cursor.execute("SELECT COUNT(*) as total FROM family_member_info WHERE name LIKE %s OR simplified_name LIKE %s", 
-                              (f'%{search}%', f'%{search}%'))
+            if search and search_type == 'name_word':
+                where_clause = "fmi.name_word = %s"
+                count_params = (search,)
+                data_params = (search, per_page, offset)
+            elif search:
+                where_clause = "fmi.name LIKE %s OR fmi.simplified_name LIKE %s"
+                count_params = (f'%{search}%', f'%{search}%')
+                data_params = (f'%{search}%', f'%{search}%', per_page, offset)
+            else:
+                where_clause = None
+                count_params = ()
+                data_params = (per_page, offset)
+
+            if where_clause:
+                cursor.execute(f"SELECT COUNT(*) as total FROM family_member_info fmi WHERE {where_clause}", count_params)
             else:
                 cursor.execute("SELECT COUNT(*) as total FROM family_member_info")
             total_result = cursor.fetchone()
             total = total_result['total'] if total_result else 0
             
-            # Get members for current page with father information
-            if search:
-                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))
+            base_sql = """
+                SELECT 
+                    fmi.id, fmi.name, fmi.simplified_name, fmi.sex,
+                    fmi.name_word, 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
+            """
+            if where_clause:
+                cursor.execute(base_sql + f" WHERE {where_clause} LIMIT %s OFFSET %s", data_params)
             else:
-                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))
+                cursor.execute(base_sql + " LIMIT %s OFFSET %s", data_params)
             members = cursor.fetchall()
             
-            # Convert to list of dictionaries if needed
             members_list = []
             for member in members:
                 members_list.append({
@@ -2647,6 +2642,7 @@ def get_members():
                     'name': member['name'],
                     'simplified_name': member['simplified_name'],
                     'sex': member['sex'],
+                    'name_word': member.get('name_word'),
                     'name_word_generation': member.get('name_word_generation'),
                     'father_name': member.get('father_name'),
                     'father_simplified_name': member.get('father_simplified_name'),

+ 30 - 6
templates/add_member.html

@@ -566,6 +566,10 @@
                 <div class="modal-body">
                     <div class="mb-4">
                         <div class="input-group">
+                            <select id="member-search-type" class="form-select" style="max-width:90px;border-right:0;border-radius:6px 0 0 6px;font-size:13px;">
+                                <option value="name">姓名</option>
+                                <option value="name_word">字辈</option>
+                            </select>
                             <input type="text" id="member-search" class="form-control" placeholder="搜索成员姓名(支持繁体和简体)">
                             <button type="button" class="btn btn-primary" onclick="searchMembers()">
                                 <i class="bi bi-search"></i> 搜索
@@ -2781,9 +2785,10 @@
     });
 
     // 加载成员数据
-    function loadMembers(page = 1, search = '') {
-        console.log('Loading members...', { page, search });
-        fetch(`/manager/api/members?page=${page}&search=${encodeURIComponent(search)}`, {
+    function loadMembers(page = 1, search = '', searchType = '') {
+        const st = searchType || (document.getElementById('member-search-type') ? document.getElementById('member-search-type').value : 'name');
+        console.log('Loading members...', { page, search, st });
+        fetch(`/manager/api/members?page=${page}&search=${encodeURIComponent(search)}&search_type=${encodeURIComponent(st)}`, {
             credentials: 'include'
         })
             .then(response => {
@@ -2848,13 +2853,16 @@
             item.onclick = function() {
                 selectMemberById(member.id);
             };
+            const nameWordBadge = member.name_word
+                ? `<span style="display:inline-flex;align-items:center;gap:2px;padding:1px 6px;border-radius:8px;background:#f0f4ff;color:#3b5bdb;font-size:11px;font-weight:500;border:1px solid #d0d9ff;margin-left:5px;">★ 字辈:${member.name_word}</span>`
+                : '';
             item.innerHTML = `
                 <div class="d-flex justify-content-between align-items-center">
                     <div>
-                        <h6 class="mb-0">
+                        <h6 class="mb-1">
                             <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>
+                            </a>${nameWordBadge}
                         </h6>
                         <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>` : ''}
@@ -2907,10 +2915,26 @@
         loadMembers(page, search);
     }
 
+    // 搜索类型切换时更新 placeholder
+    const memberSearchTypeEl = document.getElementById('member-search-type');
+    const memberSearchInputEl = document.getElementById('member-search');
+    if (memberSearchTypeEl && memberSearchInputEl) {
+        memberSearchTypeEl.addEventListener('change', function() {
+            memberSearchInputEl.placeholder = this.value === 'name_word'
+                ? '输入字辈精确搜索...'
+                : '搜索成员姓名(支持繁体和简体)';
+            memberSearchInputEl.value = '';
+        });
+        memberSearchInputEl.addEventListener('keydown', function(e) {
+            if (e.key === 'Enter') { e.preventDefault(); searchMembers(); }
+        });
+    }
+
     // 搜索成员
     function searchMembers() {
         const search = document.getElementById('member-search').value;
-        loadMembers(1, search);
+        const searchType = document.getElementById('member-search-type') ? document.getElementById('member-search-type').value : 'name';
+        loadMembers(1, search, searchType);
     }
 
     // 选择成员(支持多条关系)

+ 18 - 4
templates/members.html

@@ -87,7 +87,13 @@
     <div class="d-flex gap-2">
         <form class="d-flex" action="{{ url_for('members') }}" method="GET">
             <div class="input-group">
-                <input type="text" name="name" class="form-control" placeholder="输入姓名搜索..." value="{{ search_name or '' }}">
+                <select name="search_type" class="form-select" style="max-width:100px; border-right:0; border-radius:6px 0 0 6px;">
+                    <option value="name" {% if search_type != 'name_word' %}selected{% endif %}>姓名</option>
+                    <option value="name_word" {% if search_type == 'name_word' %}selected{% endif %}>字辈</option>
+                </select>
+                <input type="text" name="name" class="form-control"
+                       placeholder="{{ '输入字辈精确搜索...' if search_type == 'name_word' else '输入姓名搜索...' }}"
+                       value="{{ search_name or '' }}">
                 <button class="btn btn-outline-primary" type="submit"><i class="bi bi-search"></i></button>
                 {% if search_name %}
                     <a href="{{ url_for('members') }}" class="btn btn-outline-secondary">重置</a>
@@ -129,6 +135,14 @@
                             <div class="small text-muted">
                                 {{ '男' if member.sex == 1 else '女' }} | {{ member.birthday_str or '未知' }}
                             </div>
+                            {% if member.name_word %}
+                            <div class="mt-1">
+                                <span style="display:inline-flex;align-items:center;gap:3px;padding:1px 7px;border-radius:10px;background:#f0f4ff;color:#3b5bdb;font-size:11px;font-weight:500;border:1px solid #d0d9ff;">
+                                    <svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>
+                                    字辈:{{ member.name_word }}
+                                </span>
+                            </div>
+                            {% endif %}
                         </td>
                         <td>
                             {% if member.name_word_generation %}
@@ -252,7 +266,7 @@
     <nav class="mt-4">
         <ul class="pagination justify-content-center">
             <li class="page-item {{ 'disabled' if page == 1 }}">
-                <a class="page-link" href="{{ url_for('members', page=page-1, name=search_name) }}">上一页</a>
+                <a class="page-link" href="{{ url_for('members', page=page-1, name=search_name, search_type=search_type) }}">上一页</a>
             </li>
             
             {% for p in range(1, total_pages + 1) %}
@@ -261,7 +275,7 @@
                 {% else %}
                     {# Show first, last, and around current page #}
                     {% if p == 1 or p == total_pages or (p >= page - 2 and p <= page + 2) %}
-                        <li class="page-item"><a class="page-link" href="{{ url_for('members', page=p, name=search_name) }}">{{ p }}</a></li>
+                        <li class="page-item"><a class="page-link" href="{{ url_for('members', page=p, name=search_name, search_type=search_type) }}">{{ p }}</a></li>
                     {% elif p == page - 3 or p == page + 3 %}
                         <li class="page-item disabled"><span class="page-link">...</span></li>
                     {% endif %}
@@ -269,7 +283,7 @@
             {% endfor %}
             
             <li class="page-item {{ 'disabled' if page == total_pages }}">
-                <a class="page-link" href="{{ url_for('members', page=page+1, name=search_name) }}">下一页</a>
+                <a class="page-link" href="{{ url_for('members', page=page+1, name=search_name, search_type=search_type) }}">下一页</a>
             </li>
         </ul>
         <div class="text-center text-muted small mt-2">