Просмотр исходного кода

commit 优化成员管理样式

林海 1 день назад
Родитель
Сommit
058b1e1e23
4 измененных файлов с 154 добавлено и 9 удалено
  1. 2 2
      app.py
  2. 3 1
      static/css/main.css
  3. 1 1
      templates/add_member.html
  4. 148 5
      templates/members.html

+ 2 - 2
app.py

@@ -1532,7 +1532,7 @@ def members():
             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}"
+            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, fmi.suspected_error{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]))
@@ -1592,7 +1592,7 @@ def members():
         print(f"[Members List] Closing database connection")
         conn.close()
     
-    return render_template('members.html', members=members, search_name=search_name, search_type=search_type, 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, per_page=per_page)
 
 @app.route('/manager/batch_genealogy')
 def batch_genealogy():

+ 3 - 1
static/css/main.css

@@ -291,7 +291,9 @@ body {
 .page-actions {
     display: flex;
     gap: var(--spacing-sm);
-    flex-wrap: wrap;
+    flex-wrap: nowrap;
+    align-items: center;
+    overflow-x: auto;
 }
 
 .card {

+ 1 - 1
templates/add_member.html

@@ -1332,7 +1332,7 @@
         });
         
         newRow.querySelector('.select-member-btn').addEventListener('click', function() {
-            window.currentRelationIndex = newIndex;
+            currentSelectIndex = parseInt(this.closest('.relation-row').dataset.index);
         });
     }
 

+ 148 - 5
templates/members.html

@@ -7,13 +7,18 @@
     .search-bar {
         display: flex;
         gap: var(--spacing-sm);
-        flex-wrap: wrap;
+        flex-wrap: nowrap;
         align-items: center;
+        flex-shrink: 0;
+        min-width: 380px;
+        max-width: 420px;
     }
     
     .search-bar .input-group {
         display: flex;
         align-items: stretch;
+        flex: 1;
+        min-width: 0;
     }
     
     .search-bar .form-select {
@@ -134,6 +139,78 @@
         margin-bottom: var(--spacing-md);
         opacity: 0.5;
     }
+    
+    .error-indicator {
+        display: inline-flex;
+        align-items: center;
+        justify-content: center;
+        width: 18px;
+        height: 18px;
+        background: linear-gradient(135deg, #dc3545, #c82333);
+        color: white;
+        border-radius: 50%;
+        font-size: 0.7rem;
+        font-weight: bold;
+        margin-left: 4px;
+        cursor: pointer;
+        box-shadow: 0 2px 6px rgba(220, 53, 69, 0.3);
+        transition: all 0.2s ease;
+        position: relative;
+        top: -1px;
+    }
+    
+    .error-indicator:hover {
+        transform: scale(1.2);
+        box-shadow: 0 3px 10px rgba(220, 53, 69, 0.5);
+    }
+    
+    .error-popover {
+        display: none;
+        position: absolute;
+        z-index: 1000;
+        background: var(--color-bg-card);
+        border: 1px solid rgba(220, 53, 69, 0.3);
+        border-radius: var(--radius-md);
+        box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+        padding: var(--spacing-md);
+        min-width: 280px;
+        max-width: 400px;
+        margin-top: 8px;
+    }
+    
+    .error-popover.show {
+        display: block;
+    }
+    
+    .error-popover-header {
+        display: flex;
+        align-items: center;
+        gap: var(--spacing-xs);
+        color: #dc3545;
+        font-weight: bold;
+        margin-bottom: var(--spacing-sm);
+        padding-bottom: var(--spacing-xs);
+        border-bottom: 1px solid rgba(220, 53, 69, 0.1);
+    }
+    
+    .error-popover-content {
+        font-size: 0.85rem;
+        color: var(--color-text-secondary);
+        line-height: 1.6;
+        white-space: pre-wrap;
+    }
+    
+    .error-popover-arrow {
+        position: absolute;
+        top: -6px;
+        left: 12px;
+        width: 12px;
+        height: 12px;
+        background: var(--color-bg-card);
+        border-left: 1px solid rgba(220, 53, 69, 0.3);
+        border-top: 1px solid rgba(220, 53, 69, 0.3);
+        transform: rotate(45deg);
+    }
 </style>
 {% endblock %}
 
@@ -160,10 +237,10 @@
                        value="{{ search_name or '' }}">
                 <button class="btn btn-primary" type="submit"><i class="bi bi-search"></i></button>
             </div>
-            {% if search_name %}
-            <a href="{{ url_for('members') }}" class="btn btn-secondary">重置</a>
-            {% endif %}
         </form>
+        {% if search_name %}
+        <a href="{{ url_for('members') }}" class="btn btn-secondary">重置</a>
+        {% endif %}
         <a href="{{ url_for('add_member') }}" class="btn btn-primary">
             <i class="bi bi-plus-lg"></i> 录入新成员
         </a>
@@ -201,10 +278,13 @@
                 <tbody>
                     {% for member in members %}
                     <tr>
-                        <td class="px-4 text-muted small">{{ loop.index + (page - 1) * 20 }}</td>
+                        <td class="px-4 text-muted small">{{ loop.index + (page - 1) * per_page }}</td>
                         <td>
                             <div class="member-name">
                                 {{ member.name }}{% if member.simplified_name %}({{ member.simplified_name }}){% endif %}
+                                {% if member.suspected_error and member.suspected_error.strip() %}
+                                <span class="error-indicator" data-error="{{ member.suspected_error.strip() | e }}">!</span>
+                                {% endif %}
                             </div>
                             <div class="member-meta">
                                 {{ '男' if member.sex == 1 else '女' }} | {{ member.birthday_str or '未知' }}
@@ -375,5 +455,68 @@ function confirmDelete(id, name) {
         form.submit();
     }
 }
+
+let currentPopover = null;
+
+document.addEventListener('DOMContentLoaded', function() {
+    document.querySelectorAll('.error-indicator').forEach(indicator => {
+        indicator.addEventListener('mouseenter', function(e) {
+            showErrorPopover(this, e);
+        });
+        
+        indicator.addEventListener('mouseleave', function() {
+            hideErrorPopover();
+        });
+        
+        indicator.addEventListener('click', function(e) {
+            e.stopPropagation();
+            if (currentPopover && currentPopover.dataset.visible === 'true') {
+                hideErrorPopover();
+            } else {
+                showErrorPopover(this, e);
+            }
+        });
+    });
+    
+    document.addEventListener('click', function(e) {
+        if (currentPopover && !currentPopover.contains(e.target) && !e.target.classList.contains('error-indicator')) {
+            hideErrorPopover();
+        }
+    });
+});
+
+function showErrorPopover(indicator, event) {
+    hideErrorPopover();
+    
+    const errorText = indicator.dataset.error;
+    if (!errorText) return;
+    
+    const popover = document.createElement('div');
+    popover.className = 'error-popover show';
+    popover.dataset.visible = 'true';
+    
+    popover.innerHTML = `
+        <div class="error-popover-arrow"></div>
+        <div class="error-popover-header">
+            <i class="bi bi-exclamation-circle"></i>
+            <span>疑似错误</span>
+        </div>
+        <div class="error-popover-content">${errorText}</div>
+    `;
+    
+    const rect = indicator.getBoundingClientRect();
+    popover.style.left = rect.left + 'px';
+    popover.style.top = (rect.bottom + 8) + 'px';
+    
+    document.body.appendChild(popover);
+    currentPopover = popover;
+}
+
+function hideErrorPopover() {
+    if (currentPopover) {
+        currentPopover.remove();
+        currentPopover = null;
+    }
+}
 </script>
 {% endblock %}