pdf_management.html 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. {% extends "layout.html" %}
  2. {% block title %}家谱管理 - 家谱管理系统{% endblock %}
  3. {% block extra_css %}
  4. <style>
  5. .pdf-card {
  6. cursor: pointer;
  7. transition: all 0.3s ease;
  8. border: 2px solid transparent;
  9. border-radius: 8px;
  10. }
  11. .pdf-card:hover {
  12. border-color: #0d6efd;
  13. box-shadow: 0 4px 16px rgba(13,110,253,0.18);
  14. transform: translateY(-2px);
  15. }
  16. .pdf-card.active {
  17. border-color: #0d6efd;
  18. background-color: #f0f6ff;
  19. }
  20. .pdf-card .card-body {
  21. padding: 16px;
  22. }
  23. .pdf-card-icon {
  24. font-size: 1.8rem;
  25. color: #dc3545;
  26. }
  27. .pdf-card-title {
  28. font-size: 0.95rem;
  29. font-weight: 600;
  30. line-height: 1.3;
  31. margin-bottom: 8px;
  32. }
  33. .pdf-card-meta {
  34. font-size: 0.75rem;
  35. color: #6c757d;
  36. line-height: 1.4;
  37. }
  38. .pdf-card-meta-item {
  39. display: flex;
  40. align-items: center;
  41. margin-bottom: 4px;
  42. }
  43. .pdf-card-meta-item i {
  44. font-size: 0.65rem;
  45. margin-right: 6px;
  46. width: 14px;
  47. text-align: center;
  48. }
  49. .pdf-viewer-wrapper {
  50. background: #e9ecef;
  51. border-radius: 8px;
  52. overflow: hidden;
  53. min-height: 80vh;
  54. position: relative;
  55. }
  56. .pdf-viewer-wrapper iframe,
  57. .pdf-viewer-wrapper embed {
  58. width: 100%;
  59. min-height: 80vh;
  60. border: none;
  61. }
  62. .pdf-list-scroll {
  63. max-height: 300px;
  64. overflow-y: auto;
  65. }
  66. .pdf-detail-meta {
  67. display: flex;
  68. flex-wrap: wrap;
  69. gap: 16px;
  70. margin-top: 8px;
  71. padding-top: 12px;
  72. border-top: 1px solid #e9ecef;
  73. }
  74. .pdf-detail-meta-item {
  75. display: flex;
  76. align-items: center;
  77. font-size: 0.85rem;
  78. }
  79. .pdf-detail-meta-item i {
  80. color: #6c757d;
  81. margin-right: 8px;
  82. }
  83. .loading-overlay {
  84. position: absolute;
  85. top: 0;
  86. left: 0;
  87. width: 100%;
  88. height: 100%;
  89. background-color: rgba(255, 255, 255, 0.95);
  90. display: flex;
  91. flex-direction: column;
  92. justify-content: center;
  93. align-items: center;
  94. z-index: 1000;
  95. transition: opacity 0.3s ease;
  96. }
  97. .loading-spinner {
  98. width: 50px;
  99. height: 50px;
  100. border: 5px solid #f3f3f3;
  101. border-top: 5px solid #0d6efd;
  102. border-radius: 50%;
  103. animation: spin 1s linear infinite;
  104. margin-bottom: 16px;
  105. }
  106. .loading-text {
  107. font-size: 1rem;
  108. color: #6c757d;
  109. text-align: center;
  110. }
  111. @keyframes spin {
  112. 0% { transform: rotate(0deg); }
  113. 100% { transform: rotate(360deg); }
  114. }
  115. .loading-progress {
  116. width: 80%;
  117. max-width: 400px;
  118. height: 8px;
  119. background-color: #e9ecef;
  120. border-radius: 4px;
  121. overflow: hidden;
  122. margin: 16px 0;
  123. }
  124. .loading-progress-bar {
  125. height: 100%;
  126. background-color: #0d6efd;
  127. border-radius: 4px;
  128. width: 0%;
  129. transition: width 0.3s ease;
  130. }
  131. </style>
  132. {% endblock %}
  133. {% block content %}
  134. <div class="d-flex justify-content-between align-items-center mb-4">
  135. <h2><i class="bi bi-book"></i> 家谱管理</h2>
  136. </div>
  137. {% if pdfs %}
  138. <div class="card shadow-sm mb-4">
  139. <div class="card-header bg-white py-2">
  140. <span class="fw-bold small text-muted">已上传家谱文件({{ pdfs|length }} 个)</span>
  141. </div>
  142. <div class="card-body p-2 pdf-list-scroll">
  143. <div class="row g-2">
  144. {% for pdf in pdfs %}
  145. <div class="col-md-3 col-sm-6">
  146. <div class="card pdf-card {{ 'active' if selected_pdf and selected_pdf.id == pdf.id }}"
  147. onclick="window.location.href='{{ url_for('pdf_management', view=pdf.id) }}'">
  148. <div class="card-body">
  149. <div class="d-flex align-items-start">
  150. <i class="bi bi-file-earmark-pdf pdf-card-icon me-3 flex-shrink-0"></i>
  151. <div class="flex-grow-1">
  152. <div class="pdf-card-title text-truncate" title="{{ pdf.file_name }}">{{ pdf.file_name }}</div>
  153. <div class="pdf-card-meta">
  154. {% if pdf.version_name %}
  155. <div class="pdf-card-meta-item">
  156. <i class="bi bi-tag"></i>
  157. <span>{{ pdf.version_name }}</span>
  158. </div>
  159. {% endif %}
  160. {% if pdf.version_source %}
  161. <div class="pdf-card-meta-item">
  162. <i class="bi bi-building"></i>
  163. <span>{{ pdf.version_source }}</span>
  164. </div>
  165. {% endif %}
  166. {% if pdf.file_provider %}
  167. <div class="pdf-card-meta-item">
  168. <i class="bi bi-person"></i>
  169. <span>{{ pdf.file_provider }}</span>
  170. </div>
  171. {% endif %}
  172. <div class="pdf-card-meta-item">
  173. <i class="bi bi-calendar"></i>
  174. <span>{{ pdf.upload_time.strftime('%Y-%m-%d') if pdf.upload_time else '未知' }}</span>
  175. </div>
  176. </div>
  177. </div>
  178. </div>
  179. </div>
  180. </div>
  181. </div>
  182. {% endfor %}
  183. </div>
  184. </div>
  185. </div>
  186. {% endif %}
  187. {% if selected_pdf %}
  188. <div class="card shadow-sm">
  189. <div class="card-header bg-white py-3">
  190. <div class="d-flex align-items-center mb-2">
  191. <i class="bi bi-file-earmark-pdf text-danger me-3 fs-5"></i>
  192. <h5 class="mb-0">{{ selected_pdf.file_name }}</h5>
  193. </div>
  194. <div class="pdf-detail-meta">
  195. {% if selected_pdf.version_name %}
  196. <div class="pdf-detail-meta-item">
  197. <i class="bi bi-tag"></i>
  198. <span><strong>版本名称:</strong>{{ selected_pdf.version_name }}</span>
  199. </div>
  200. {% endif %}
  201. {% if selected_pdf.version_source %}
  202. <div class="pdf-detail-meta-item">
  203. <i class="bi bi-building"></i>
  204. <span><strong>版本来源:</strong>{{ selected_pdf.version_source }}</span>
  205. </div>
  206. {% endif %}
  207. {% if selected_pdf.file_provider %}
  208. <div class="pdf-detail-meta-item">
  209. <i class="bi bi-person"></i>
  210. <span><strong>文件提供人:</strong>{{ selected_pdf.file_provider }}</span>
  211. </div>
  212. {% endif %}
  213. <div class="pdf-detail-meta-item">
  214. <i class="bi bi-calendar"></i>
  215. <span><strong>上传时间:</strong>{{ selected_pdf.upload_time.strftime('%Y-%m-%d %H:%M') if selected_pdf.upload_time else '未知' }}</span>
  216. </div>
  217. </div>
  218. <div class="d-flex gap-2 mt-3 justify-content-end">
  219. <form action="{{ url_for('delete_pdf', pdf_id=selected_pdf.id) }}" method="POST" class="d-inline"
  220. onsubmit="return confirm('确定要删除此PDF文件吗?此操作无法撤销。');">
  221. <button type="submit" class="btn btn-sm btn-outline-danger">
  222. <i class="bi bi-trash"></i> 删除
  223. </button>
  224. </form>
  225. </div>
  226. </div>
  227. <div class="card-body p-0">
  228. <div class="pdf-viewer-wrapper">
  229. <div id="loadingOverlay" class="loading-overlay">
  230. <div class="loading-spinner"></div>
  231. <div class="loading-text">正在加载PDF文件,请稍候...</div>
  232. <div class="loading-progress">
  233. <div class="loading-progress-bar" id="loadingProgressBar"></div>
  234. </div>
  235. </div>
  236. <iframe id="pdfViewer" src="{{ selected_pdf.oss_url }}#toolbar=0" type="application/pdf" onload="hideLoading()"></iframe>
  237. </div>
  238. </div>
  239. </div>
  240. {% elif not pdfs %}
  241. <div class="card shadow-sm">
  242. <div class="card-body text-center py-5 text-muted">
  243. <i class="bi bi-file-earmark-pdf fs-1 d-block mb-3 text-secondary"></i>
  244. <h5 class="text-secondary">暂无PDF家谱文件</h5>
  245. <p class="mb-3">在扫描件管理中上传PDF文件后,会自动添加到此处。</p>
  246. </div>
  247. </div>
  248. {% else %}
  249. <div class="card shadow-sm">
  250. <div class="card-body text-center py-5 text-muted">
  251. <i class="bi bi-hand-index-thumb fs-1 d-block mb-3"></i>
  252. <h5 class="text-secondary">请从上方选择一个PDF文件进行查看</h5>
  253. </div>
  254. </div>
  255. {% endif %}
  256. {% endblock %}
  257. {% block extra_js %}
  258. <script>
  259. // 显示加载动画
  260. function showLoading() {
  261. document.getElementById('loadingOverlay').style.display = 'flex';
  262. }
  263. // 隐藏加载动画
  264. function hideLoading() {
  265. const overlay = document.getElementById('loadingOverlay');
  266. overlay.style.opacity = '0';
  267. setTimeout(() => {
  268. overlay.style.display = 'none';
  269. }, 300);
  270. }
  271. // 模拟加载进度
  272. function simulateLoadingProgress() {
  273. const progressBar = document.getElementById('loadingProgressBar');
  274. if (progressBar) {
  275. let progress = 0;
  276. const interval = setInterval(() => {
  277. progress += 5;
  278. if (progress > 90) {
  279. clearInterval(interval);
  280. } else {
  281. progressBar.style.width = progress + '%';
  282. }
  283. }, 200);
  284. }
  285. }
  286. // 页面加载完成后开始模拟加载进度
  287. document.addEventListener('DOMContentLoaded', function() {
  288. // 检查是否有选中的PDF
  289. if (document.getElementById('pdfViewer')) {
  290. simulateLoadingProgress();
  291. }
  292. });
  293. // 监听PDF卡片点击事件,显示加载动画
  294. document.querySelectorAll('.pdf-card').forEach(card => {
  295. card.addEventListener('click', function() {
  296. // 显示加载动画
  297. const overlay = document.createElement('div');
  298. overlay.id = 'loadingOverlay';
  299. overlay.className = 'loading-overlay';
  300. overlay.innerHTML = `
  301. <div class="loading-spinner"></div>
  302. <div class="loading-text">正在加载PDF文件,请稍候...</div>
  303. <div class="loading-progress">
  304. <div class="loading-progress-bar" id="loadingProgressBar"></div>
  305. </div>
  306. `;
  307. document.body.appendChild(overlay);
  308. // 开始模拟加载进度
  309. let progress = 0;
  310. const progressBar = overlay.querySelector('#loadingProgressBar');
  311. const interval = setInterval(() => {
  312. progress += 5;
  313. if (progress > 90) {
  314. clearInterval(interval);
  315. } else {
  316. progressBar.style.width = progress + '%';
  317. }
  318. }, 200);
  319. });
  320. });
  321. </script>
  322. {% endblock %}