| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342 |
- {% extends "layout.html" %}
- {% block title %}批量处理族谱原文 - 家谱管理系统{% endblock %}
- {% block extra_css %}
- <style>
- .batch-container {
- max-width: 1200px;
- margin: 0 auto;
- }
- .member-card {
- border: 2px solid #e9ecef;
- border-radius: 8px;
- padding: 15px;
- cursor: pointer;
- transition: all 0.2s;
- }
- .member-card:hover {
- border-color: #0d6efd;
- box-shadow: 0 2px 8px rgba(13, 110, 253, 0.15);
- }
- .member-card.selected {
- border-color: #0d6efd;
- background: #e7f3ff;
- }
- .batch-actions {
- position: sticky;
- top: 10px;
- z-index: 100;
- }
- .result-modal-body {
- max-height: 60vh;
- overflow-y: auto;
- }
- .success-badge {
- background-color: #d1fae5;
- color: #065f46;
- }
- .error-badge {
- background-color: #fee2e2;
- color: #991b1b;
- }
- </style>
- {% endblock %}
- {% block content %}
- <div class="batch-container">
- <div class="d-flex justify-content-between align-items-center mb-4">
- <h2><i class="bi bi-file-text"></i> 批量处理族谱原文</h2>
- <div class="btn-group batch-actions">
- <button id="selectAllBtn" class="btn btn-outline-primary">全选</button>
- <button id="clearSelectionBtn" class="btn btn-outline-secondary">清除选择</button>
- <button id="processBtn" class="btn btn-primary" disabled>
- <i class="bi bi-cpu"></i> 批量生成族谱原文 (0/10)
- </button>
- </div>
- </div>
- <!-- 处理结果弹窗 -->
- <div class="modal fade" id="resultModal" tabindex="-1" aria-labelledby="resultModalLabel" aria-hidden="true">
- <div class="modal-dialog modal-lg">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title" id="resultModalLabel">处理结果</h5>
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
- </div>
- <div class="modal-body result-modal-body" id="resultContent">
- <div class="text-center py-4">
- <div class="spinner-border text-primary" role="status">
- <span class="visually-hidden">处理中...</span>
- </div>
- <p class="mt-2">正在处理,请稍候...</p>
- </div>
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
- <button type="button" class="btn btn-primary" id="refreshBtn">刷新列表</button>
- </div>
- </div>
- </div>
- </div>
- <!-- 成员列表 -->
- <div class="row g-3" id="membersContainer">
- <div class="col-12 text-center py-8">
- <div class="spinner-border text-primary" role="status">
- <span class="visually-hidden">加载中...</span>
- </div>
- <p class="mt-2">加载成员列表中...</p>
- </div>
- </div>
- <!-- 分页 -->
- <nav class="mt-4" id="paginationContainer">
- </nav>
- </div>
- {% endblock %}
- {% block extra_js %}
- <script>
- let selectedIds = new Set();
- let currentPage = 1;
- let totalPages = 1;
- document.addEventListener('DOMContentLoaded', function() {
- loadMembers(currentPage);
-
- document.getElementById('selectAllBtn').addEventListener('click', selectAll);
- document.getElementById('clearSelectionBtn').addEventListener('click', clearSelection);
- document.getElementById('processBtn').addEventListener('click', processSelected);
- document.getElementById('refreshBtn').addEventListener('click', function() {
- clearSelection();
- loadMembers(1);
- const modal = bootstrap.Modal.getInstance(document.getElementById('resultModal'));
- if (modal) modal.hide();
- });
- });
- function loadMembers(page) {
- fetch(`/manager/api/members/empty_genealogy?page=${page}&per_page=20`)
- .then(response => response.json())
- .then(data => {
- if (data.success) {
- renderMembers(data.members);
- renderPagination(data.total, page);
- currentPage = page;
- } else {
- document.getElementById('membersContainer').innerHTML =
- '<div class="col-12 text-center py-8"><p class="text-danger">加载失败: ' + data.message + '</p></div>';
- }
- })
- .catch(error => {
- document.getElementById('membersContainer').innerHTML =
- '<div class="col-12 text-center py-8"><p class="text-danger">加载失败: ' + error.message + '</p></div>';
- });
- }
- function renderMembers(members) {
- if (members.length === 0) {
- document.getElementById('membersContainer').innerHTML =
- '<div class="col-12 text-center py-8"><i class="bi bi-check-circle text-success" style="font-size: 48px;"></i><p class="mt-3 text-success">所有成员的族谱原文都已填写完毕!</p></div>';
- return;
- }
-
- let html = '';
- members.forEach(member => {
- const isSelected = selectedIds.has(member.id);
- html += `
- <div class="col-md-6 col-lg-4">
- <div class="member-card ${isSelected ? 'selected' : ''}" data-id="${member.id}" onclick="toggleSelect(${member.id})">
- <div class="d-flex justify-content-between align-items-start">
- <div>
- <h5 class="mb-1">${member.name}</h5>
- ${member.simplified_name && member.simplified_name !== member.name ? `<p class="text-sm text-muted">(${member.simplified_name})</p>` : ''}
- </div>
- <div class="form-check" onclick="event.stopPropagation()">
- <input type="checkbox" class="form-check-input" ${isSelected ? 'checked' : ''} onchange="toggleSelect(${member.id})" style="transform: scale(1.2);">
- </div>
- </div>
- <hr class="my-2">
- <div class="text-sm text-muted">
- ${member.name_word_generation ? `<div><i class="bi bi-tree"></i> ${member.name_word_generation}</div>` : ''}
- ${member.father_name ? `<div><i class="bi bi-user"></i> 父:${member.father_name}</div>` : ''}
- ${member.mother_name ? `<div><i class="bi bi-user"></i> 母:${member.mother_name}</div>` : ''}
- </div>
- </div>
- </div>
- `;
- });
- document.getElementById('membersContainer').innerHTML = html;
- }
- function renderPagination(total, page) {
- const perPage = 20;
- totalPages = Math.ceil(total / perPage);
-
- if (totalPages <= 1) {
- document.getElementById('paginationContainer').innerHTML = '';
- return;
- }
-
- let html = `<ul class="pagination justify-content-center">`;
-
- // 上一页
- html += `<li class="page-item ${page === 1 ? 'disabled' : ''}">
- <a class="page-link" href="#" onclick="loadMembers(${page - 1})">上一页</a>
- </li>`;
-
- // 页码
- for (let i = 1; i <= totalPages; i++) {
- html += `<li class="page-item ${i === page ? 'active' : ''}">
- <a class="page-link" href="#" onclick="loadMembers(${i})">${i}</a>
- </li>`;
- }
-
- // 下一页
- html += `<li class="page-item ${page === totalPages ? 'disabled' : ''}">
- <a class="page-link" href="#" onclick="loadMembers(${page + 1})">下一页</a>
- </li>`;
-
- html += `</ul>`;
- document.getElementById('paginationContainer').innerHTML = html;
- }
- function toggleSelect(id) {
- if (selectedIds.has(id)) {
- selectedIds.delete(id);
- } else {
- if (selectedIds.size >= 10) {
- alert('最多选择10个成员进行处理');
- return;
- }
- selectedIds.add(id);
- }
- updateSelection();
- }
- function selectAll() {
- const cards = document.querySelectorAll('.member-card');
- const notSelected = [];
-
- cards.forEach(card => {
- const id = parseInt(card.dataset.id);
- if (!selectedIds.has(id)) {
- notSelected.push(id);
- }
- });
-
- // 最多选择10个,从未选择的中选
- notSelected.slice(0, 10 - selectedIds.size).forEach(id => {
- selectedIds.add(id);
- });
-
- updateSelection();
- }
- function clearSelection() {
- selectedIds.clear();
- updateSelection();
- }
- function updateSelection() {
- // 更新卡片样式
- document.querySelectorAll('.member-card').forEach(card => {
- const id = parseInt(card.dataset.id);
- card.classList.toggle('selected', selectedIds.has(id));
- const checkbox = card.querySelector('.form-check-input');
- if (checkbox) checkbox.checked = selectedIds.has(id);
- });
-
- // 更新按钮状态
- const btn = document.getElementById('processBtn');
- if (selectedIds.size > 0) {
- btn.disabled = false;
- btn.innerHTML = `<i class="bi bi-cpu"></i> 批量生成族谱原文 (${selectedIds.size}/10)`;
- } else {
- btn.disabled = true;
- btn.innerHTML = '<i class="bi bi-cpu"></i> 批量生成族谱原文 (0/10)';
- }
- }
- function processSelected() {
- const ids = Array.from(selectedIds);
- console.log('processSelected called, selected ids:', ids);
-
- if (ids.length === 0) {
- alert('请先选择成员');
- return;
- }
-
- document.getElementById('resultContent').innerHTML = `
- <div class="text-center py-4">
- <div class="spinner-border text-primary" role="status">
- <span class="visually-hidden">处理中...</span>
- </div>
- <p class="mt-2">正在处理 ${ids.length} 个成员,请稍候...</p>
- </div>
- `;
-
- const modal = new bootstrap.Modal(document.getElementById('resultModal'));
- modal.show();
- console.log('Modal shown');
-
- fetch('/manager/api/members/batch_process_genealogy', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({ member_ids: ids })
- })
- .then(response => response.json())
- .then(data => {
- if (data.success) {
- let html = '<div class="mb-4"><h6 class="text-primary">处理完成</h6></div>';
- html += '<div class="list-group">';
-
- data.results.forEach(result => {
- html += `
- <div class="list-group-item">
- <div class="d-flex justify-content-between align-items-start">
- <div>
- <strong>${result.name}</strong>
- ${result.success ? '' : `<span class="text-muted"> (ID: ${result.member_id})</span>`}
- </div>
- <span class="badge ${result.success ? 'success-badge' : 'error-badge'}">
- ${result.success ? '成功' : '失败'}
- </span>
- </div>
- ${result.success ? `
- <div class="mt-2 text-sm">
- <div class="mb-1">
- <span class="text-muted">繁体:</span>
- <span>${result.traditional}</span>
- </div>
- <div>
- <span class="text-muted">简体:</span>
- <span>${result.simplified}</span>
- </div>
- </div>
- ` : `
- <div class="mt-2 text-sm text-danger">
- 原因:${result.message}
- </div>
- `}
- </div>
- `;
- });
-
- html += '</div>';
- document.getElementById('resultContent').innerHTML = html;
- } else {
- document.getElementById('resultContent').innerHTML =
- `<div class="text-center py-4"><p class="text-danger">处理失败: ${data.message}</p></div>`;
- }
- })
- .catch(error => {
- document.getElementById('resultContent').innerHTML =
- `<div class="text-center py-4"><p class="text-danger">请求失败: ${error.message}</p></div>`;
- });
- }
- </script>
- {% endblock %}
|