knowledge_management.html 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. {% extends "base.html" %}
  2. {% block content %}
  3. <div class="flex h-screen overflow-hidden" id="knowledge-view">
  4. <!-- Sidebar -->
  5. {% include 'partials/sidebar.html' %}
  6. <!-- Main Content -->
  7. <div class="flex-1 flex flex-col min-w-0 overflow-hidden bg-gray-900/80 relative">
  8. <!-- Top Header -->
  9. <header class="h-16 flex items-center justify-between px-6 z-20 bg-gray-900/80 backdrop-blur-md border-b border-blue-900/30">
  10. <button class="md:hidden text-gray-300 focus:outline-none" id="open-sidebar">
  11. <i class="fas fa-bars text-xl"></i>
  12. </button>
  13. <h1 class="text-lg md:text-2xl font-bold tech-title truncate ml-2">样本中心管理</h1>
  14. <div class="flex items-center space-x-4">
  15. <div class="flex items-center space-x-2">
  16. <div class="w-8 h-8 rounded-full bg-blue-500 flex items-center justify-center text-white font-bold border border-cyan-400 shadow-md">A</div>
  17. <span class="hidden md:inline text-sm text-gray-300">{{ current_user.username }}</span>
  18. </div>
  19. </div>
  20. </header>
  21. <!-- Main Scrollable Area -->
  22. <main class="flex-1 overflow-y-auto p-4 md:p-6 scroll-smooth">
  23. <div class="h-full flex flex-col gap-6">
  24. <!-- Tabs -->
  25. <div class="bg-gray-800 rounded-lg shadow-lg">
  26. <div class="flex border-b border-gray-700 px-6 pt-4 gap-6">
  27. <button class="kb-tab active px-4 py-2 text-sm font-bold rounded-t-lg transition-colors" data-tab="list">
  28. <i class="fas fa-list mr-2"></i>知识库列表
  29. </button>
  30. <button class="kb-tab px-4 py-2 text-sm font-bold rounded-t-lg transition-colors text-gray-400 hover:text-white" data-tab="detail">
  31. <i class="fas fa-info-circle mr-2"></i>知识库详情
  32. </button>
  33. <button class="kb-tab px-4 py-2 text-sm font-bold rounded-t-lg transition-colors text-gray-400 hover:text-white" data-tab="tasks">
  34. <i class="fas fa-tasks mr-2"></i>入库任务
  35. </button>
  36. </div>
  37. </div>
  38. <!-- Tab 1: 知识库列表 -->
  39. <div id="tab-list" class="flex-1 bg-gray-800 p-6 rounded-b-lg shadow-lg flex flex-col min-h-0 tab-content">
  40. <div class="flex justify-between items-center mb-4">
  41. <label class="text-gray-400 text-sm font-bold">知识库列表</label>
  42. <div class="flex gap-2 items-center">
  43. <input type="text" id="kb-search" class="bg-gray-700 text-white rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 w-48" placeholder="搜索知识库...">
  44. <button onclick="loadKnowledgeList()" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded text-sm transition-colors">
  45. <i class="fas fa-sync-alt"></i> 刷新
  46. </button>
  47. </div>
  48. </div>
  49. <div class="flex-1 overflow-auto custom-scrollbar">
  50. <table class="w-full text-sm text-gray-300">
  51. <thead class="text-xs text-gray-400 uppercase bg-gray-700/50 sticky top-0">
  52. <tr>
  53. <th class="px-4 py-3 text-left">ID</th>
  54. <th class="px-4 py-3 text-left">名称</th>
  55. <th class="px-4 py-3 text-center">文档数</th>
  56. <th class="px-4 py-3 text-center">状态</th>
  57. <th class="px-4 py-3 text-center">创建时间</th>
  58. <th class="px-4 py-3 text-center">操作</th>
  59. </tr>
  60. </thead>
  61. <tbody id="kb-list-body">
  62. <tr><td colspan="6" class="text-center text-gray-500 py-10">加载中...</td></tr>
  63. </tbody>
  64. </table>
  65. </div>
  66. <div class="flex justify-between items-center mt-4 pt-4 border-t border-gray-700">
  67. <span id="kb-total" class="text-gray-500 text-sm"></span>
  68. <div class="flex gap-2">
  69. <button onclick="changePage(-1)" id="btn-prev" class="hidden bg-gray-700 hover:bg-gray-600 text-white px-3 py-1 rounded text-sm">上一页</button>
  70. <span id="kb-page-info" class="text-gray-400 text-sm px-3 py-1"></span>
  71. <button onclick="changePage(1)" id="btn-next" class="hidden bg-gray-700 hover:bg-gray-600 text-white px-3 py-1 rounded text-sm">下一页</button>
  72. </div>
  73. </div>
  74. </div>
  75. <!-- Tab 2: 知识库详情 -->
  76. <div id="tab-detail" class="flex-1 bg-gray-800 p-6 rounded-b-lg shadow-lg flex flex-col min-h-0 tab-content hidden">
  77. <div id="kb-detail-content">
  78. <div class="text-center text-gray-500 py-20">
  79. <i class="fas fa-info-circle text-4xl mb-4 opacity-50"></i>
  80. <p>请先从知识库列表中选择查看详情</p>
  81. </div>
  82. </div>
  83. </div>
  84. <!-- Tab 3: 入库任务 -->
  85. <div id="tab-tasks" class="flex-1 bg-gray-800 p-6 rounded-b-lg shadow-lg flex flex-col min-h-0 tab-content hidden">
  86. <div class="flex justify-between items-center mb-4">
  87. <label class="text-gray-400 text-sm font-bold">批量入库任务</label>
  88. <button onclick="loadImportTasks()" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded text-sm transition-colors">
  89. <i class="fas fa-sync-alt"></i> 刷新
  90. </button>
  91. </div>
  92. <div class="flex-1 overflow-auto custom-scrollbar">
  93. <table class="w-full text-sm text-gray-300">
  94. <thead class="text-xs text-gray-400 uppercase bg-gray-700/50 sticky top-0">
  95. <tr>
  96. <th class="px-4 py-3 text-left">任务号</th>
  97. <th class="px-4 py-3 text-left">知识库</th>
  98. <th class="px-4 py-3 text-center">状态</th>
  99. <th class="px-4 py-3 text-center">进度</th>
  100. <th class="px-4 py-3 text-center">创建时间</th>
  101. <th class="px-4 py-3 text-center">操作</th>
  102. </tr>
  103. </thead>
  104. <tbody id="task-list-body">
  105. <tr><td colspan="6" class="text-center text-gray-500 py-10">暂无任务</td></tr>
  106. </tbody>
  107. </table>
  108. </div>
  109. </div>
  110. </div>
  111. </main>
  112. </div>
  113. </div>
  114. <!-- 入库弹窗 Modal -->
  115. <div id="import-modal" class="fixed inset-0 bg-black/70 z-50 hidden flex items-center justify-center">
  116. <div class="bg-gray-800 rounded-lg shadow-2xl w-full max-w-2xl mx-4 max-h-[90vh] overflow-y-auto">
  117. <div class="flex justify-between items-center p-6 border-b border-gray-700">
  118. <h2 class="text-xl font-bold text-white"><i class="fas fa-upload mr-2 text-blue-500"></i>批量入库</h2>
  119. <button onclick="closeImportModal()" class="text-gray-400 hover:text-white"><i class="fas fa-times text-xl"></i></button>
  120. </div>
  121. <div class="p-6 space-y-4">
  122. <div>
  123. <label class="block text-gray-400 text-sm mb-1 font-bold">知识库</label>
  124. <input type="text" id="import-kb-name" class="w-full bg-gray-700 text-white rounded p-3 focus:outline-none focus:ring-2 focus:ring-blue-500" readonly>
  125. <input type="hidden" id="import-kb-id">
  126. </div>
  127. <div>
  128. <label class="block text-gray-400 text-sm mb-1 font-bold">任务号 (task_no)</label>
  129. <input type="text" id="import-task-no" class="w-full bg-gray-700 text-white rounded p-3 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="自动生成或手动输入">
  130. </div>
  131. <div>
  132. <label class="block text-gray-400 text-sm mb-1 font-bold">Parents (JSON 数组)</label>
  133. <textarea id="import-parents" class="w-full bg-gray-700 text-white rounded p-3 focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono text-xs" rows="8" placeholder='[{"index": 0, "parent_id": "...", "text": "..."}]'></textarea>
  134. </div>
  135. <div>
  136. <label class="block text-gray-400 text-sm mb-1 font-bold">Children (JSON 数组,可选)</label>
  137. <textarea id="import-children" class="w-full bg-gray-700 text-white rounded p-3 focus:outline-none focus:ring-2 focus:ring-blue-500 font-mono text-xs" rows="6" placeholder='[{"index": 0, "parent_id": "...", "text": "..."}]'></textarea>
  138. </div>
  139. </div>
  140. <div class="flex justify-end gap-3 p-6 border-t border-gray-700">
  141. <button onclick="closeImportModal()" class="bg-gray-600 hover:bg-gray-500 text-white px-6 py-2 rounded transition-colors">取消</button>
  142. <button onclick="submitImport()" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded transition-colors"><i class="fas fa-paper-plane mr-1"></i>提交</button>
  143. </div>
  144. </div>
  145. </div>
  146. <style>
  147. .kb-tab.active {
  148. background-color: #1F2937;
  149. color: white;
  150. border-bottom: 2px solid #3B82F6;
  151. }
  152. .status-pending { color: #F59E0B; }
  153. .status-processing { color: #3B82F6; }
  154. .status-success, .status-completed { color: #10B981; }
  155. .status-failed { color: #EF4444; }
  156. .custom-scrollbar::-webkit-scrollbar { width: 6px; }
  157. .custom-scrollbar::-webkit-scrollbar-track { background: #1F2937; }
  158. .custom-scrollbar::-webkit-scrollbar-thumb { background: #4B5563; border-radius: 3px; }
  159. </style>
  160. <script>
  161. let currentPage = 1;
  162. let currentKbId = '';
  163. $(document).ready(function() {
  164. $('#open-sidebar').click(function() {
  165. $('.sidebar').toggleClass('-translate-x-full');
  166. });
  167. loadKnowledgeList();
  168. loadImportTasks();
  169. // Tab switching
  170. $('.kb-tab').click(function() {
  171. $('.kb-tab').removeClass('active').addClass('text-gray-400');
  172. $(this).addClass('active').removeClass('text-gray-400');
  173. $('.tab-content').addClass('hidden');
  174. $('#tab-' + $(this).data('tab')).removeClass('hidden');
  175. });
  176. // Search
  177. $('#kb-search').on('keyup', function(e) {
  178. if (e.key === 'Enter') {
  179. currentPage = 1;
  180. loadKnowledgeList();
  181. }
  182. });
  183. // Auto-generate task_no
  184. $('#import-task-no').on('focus', function() {
  185. if (!$(this).val()) {
  186. $(this).val('KIT-' + new Date().toISOString().replace(/[-:T.]/g, '').slice(0, 14) + '-' + Math.random().toString(36).slice(2, 6));
  187. }
  188. });
  189. });
  190. function loadKnowledgeList() {
  191. const keyword = $('#kb-search').val().trim();
  192. $.ajax({
  193. url: '/api/v1/knowledge/bases',
  194. method: 'GET',
  195. data: { page: currentPage, page_size: 20 },
  196. headers: { 'Authorization': 'Bearer ' + getJwtToken() },
  197. success: function(resp) {
  198. renderKnowledgeList(resp.data || resp);
  199. },
  200. error: function(xhr) {
  201. $('#kb-list-body').html('<tr><td colspan="6" class="text-center text-red-400 py-10">加载失败: ' + (xhr.responseJSON?.message || '未知错误') + '</td></tr>');
  202. }
  203. });
  204. }
  205. function renderKnowledgeList(data) {
  206. const items = data.items || data;
  207. const total = data.total || items.length;
  208. const tbody = $('#kb-list-body');
  209. tbody.empty();
  210. if (!items || items.length === 0) {
  211. tbody.html('<tr><td colspan="6" class="text-center text-gray-500 py-10">暂无知识库数据</td></tr>');
  212. $('#kb-total').text('');
  213. return;
  214. }
  215. items.forEach(function(item) {
  216. const statusHtml = item.status === 1
  217. ? '<span class="text-green-400"><i class="fas fa-check-circle"></i> 启用</span>'
  218. : '<span class="text-red-400"><i class="fas fa-times-circle"></i> 禁用</span>';
  219. const html = `<tr class="border-b border-gray-700 hover:bg-gray-700/30 transition-colors">
  220. <td class="px-4 py-3 text-gray-400 font-mono text-xs">${item.id}</td>
  221. <td class="px-4 py-3 text-white font-medium">${item.name}</td>
  222. <td class="px-4 py-3 text-center">${item.document_count || 0}</td>
  223. <td class="px-4 py-3 text-center">${statusHtml}</td>
  224. <td class="px-4 py-3 text-center text-gray-400 text-xs">${item.created_at || ''}</td>
  225. <td class="px-4 py-3 text-center">
  226. <button onclick="viewKbDetail('${item.id}', '${item.name.replace(/'/g, "\\'")}')" class="text-blue-400 hover:text-blue-300 text-xs mr-3"><i class="fas fa-eye"></i> 详情</button>
  227. <button onclick="openImportModal('${item.id}', '${item.name.replace(/'/g, "\\'")}')" class="text-green-400 hover:text-green-300 text-xs"><i class="fas fa-upload"></i> 入库</button>
  228. </td>
  229. </tr>`;
  230. tbody.append(html);
  231. });
  232. const pageSize = data.page_size || 20;
  233. const page = data.page || 1;
  234. const totalPages = Math.ceil(total / pageSize);
  235. $('#kb-total').text('共 ' + total + ' 条');
  236. $('#kb-page-info').text('第 ' + page + ' / ' + totalPages + ' 页');
  237. $('#btn-prev').toggleClass('hidden', page <= 1);
  238. $('#btn-next').toggleClass('hidden', page >= totalPages);
  239. }
  240. function changePage(delta) {
  241. currentPage += delta;
  242. if (currentPage < 1) currentPage = 1;
  243. loadKnowledgeList();
  244. }
  245. function viewKbDetail(kbId, kbName) {
  246. // Switch to detail tab
  247. $('.kb-tab').removeClass('active').addClass('text-gray-400');
  248. $('.kb-tab[data-tab="detail"]').addClass('active').removeClass('text-gray-400');
  249. $('.tab-content').addClass('hidden');
  250. $('#tab-detail').removeClass('hidden');
  251. $('#kb-detail-content').html('<div class="text-center text-gray-400 py-20"><i class="fas fa-circle-notch fa-spin text-3xl text-blue-500 mb-4"></i><p>加载中...</p></div>');
  252. $.ajax({
  253. url: '/api/v1/knowledge/bases/' + kbId,
  254. method: 'GET',
  255. headers: { 'Authorization': 'Bearer ' + getJwtToken() },
  256. success: function(resp) {
  257. renderKbDetail(resp.data || resp);
  258. },
  259. error: function(xhr) {
  260. $('#kb-detail-content').html('<div class="text-center text-red-400 py-20">加载失败: ' + (xhr.responseJSON?.message || '未知错误') + '</div>');
  261. }
  262. });
  263. }
  264. function renderKbDetail(data) {
  265. const schemaHtml = data.metadata_schema && data.metadata_schema.length > 0
  266. ? `<div class="mt-6"><h3 class="text-white font-bold mb-3"><i class="fas fa-table mr-2 text-blue-500"></i>元数据定义</h3>
  267. <table class="w-full text-sm text-gray-300">
  268. <thead class="text-xs text-gray-400 uppercase bg-gray-700/50">
  269. <tr><th class="px-4 py-2 text-left">中文名</th><th class="px-4 py-2 text-left">英文名</th><th class="px-4 py-2 text-center">类型</th><th class="px-4 py-2 text-left">描述</th></tr>
  270. </thead>
  271. <tbody>${data.metadata_schema.map(function(f) {
  272. return '<tr class="border-b border-gray-700"><td class="px-4 py-2">' + (f.field_name_cn||'') + '</td><td class="px-4 py-2 font-mono text-xs">' + (f.field_name_en||'') + '</td><td class="px-4 py-2 text-center"><span class="bg-gray-600 px-2 py-0.5 rounded text-xs">' + (f.field_type||'') + '</span></td><td class="px-4 py-2 text-gray-400">' + (f.description||'') + '</td></tr>';
  273. }).join('')}</tbody>
  274. </table></div>`
  275. : '';
  276. $('#kb-detail-content').html(`
  277. <div class="bg-gray-700/50 rounded-lg p-6">
  278. <h2 class="text-xl font-bold text-white mb-4"><i class="fas fa-book mr-2 text-blue-500"></i>${data.name}</h2>
  279. <div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
  280. <div><span class="text-gray-400">ID:</span> <span class="text-white font-mono">${data.id}</span></div>
  281. <div><span class="text-gray-400">文档数:</span> <span class="text-white">${data.document_count || 0}</span></div>
  282. <div><span class="text-gray-400">父表:</span> <span class="text-white font-mono text-xs">${data.parent_table || '-'}</span></div>
  283. <div><span class="text-gray-400">子表:</span> <span class="text-white font-mono text-xs">${data.child_table || '-'}</span></div>
  284. <div><span class="text-gray-400">创建人:</span> <span class="text-white">${data.created_by || '-'}</span></div>
  285. <div><span class="text-gray-400">创建时间:</span> <span class="text-white">${data.created_at || '-'}</span></div>
  286. </div>
  287. ${data.description ? '<p class="mt-4 text-gray-300"><span class="text-gray-400">描述:</span> ' + data.description + '</p>' : ''}
  288. ${schemaHtml}
  289. <div class="mt-6 flex gap-3">
  290. <button onclick="openImportModal('${data.id}', '${data.name.replace(/'/g, "\\'")}')" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded transition-colors"><i class="fas fa-upload mr-1"></i>批量入库</button>
  291. <button onclick="switchTab('list')" class="bg-gray-600 hover:bg-gray-500 text-white px-6 py-2 rounded transition-colors"><i class="fas fa-arrow-left mr-1"></i>返回列表</button>
  292. </div>
  293. </div>
  294. `);
  295. }
  296. function switchTab(name) {
  297. $('.kb-tab').removeClass('active').addClass('text-gray-400');
  298. $('.kb-tab[data-tab="' + name + '"]').addClass('active').removeClass('text-gray-400');
  299. $('.tab-content').addClass('hidden');
  300. $('#tab-' + name).removeClass('hidden');
  301. }
  302. function loadImportTasks() {
  303. $.ajax({
  304. url: '/api/v1/knowledge/import-tasks',
  305. method: 'GET',
  306. data: { page: 1, page_size: 50 },
  307. headers: { 'Authorization': 'Bearer ' + getJwtToken() },
  308. success: function(resp) {
  309. renderImportTasks(resp.data || resp);
  310. },
  311. error: function(xhr) {
  312. $('#task-list-body').html('<tr><td colspan="6" class="text-center text-red-400 py-10">加载失败</td></tr>');
  313. }
  314. });
  315. }
  316. function renderImportTasks(data) {
  317. const items = data.items || [];
  318. const tbody = $('#task-list-body');
  319. tbody.empty();
  320. if (items.length === 0) {
  321. tbody.html('<tr><td colspan="6" class="text-center text-gray-500 py-10">暂无入库任务</td></tr>');
  322. return;
  323. }
  324. const statusMap = {
  325. 'pending': '<span class="status-pending"><i class="fas fa-clock"></i> 等待中</span>',
  326. 'processing': '<span class="status-processing"><i class="fas fa-spinner fa-spin"></i> 处理中</span>',
  327. 'success': '<span class="status-success"><i class="fas fa-check-circle"></i> 已完成</span>',
  328. 'completed': '<span class="status-success"><i class="fas fa-check-circle"></i> 已完成</span>',
  329. 'failed': '<span class="status-failed"><i class="fas fa-times-circle"></i> 失败</span>',
  330. };
  331. items.forEach(function(item) {
  332. const progressHtml = item.progress
  333. ? item.progress.processed + '/' + item.progress.total
  334. : '-';
  335. const html = `<tr class="border-b border-gray-700 hover:bg-gray-700/30 transition-colors">
  336. <td class="px-4 py-3 font-mono text-xs text-gray-300">${item.task_no}</td>
  337. <td class="px-4 py-3 text-white">${item.kb_name || item.kb_id}</td>
  338. <td class="px-4 py-3 text-center">${statusMap[item.status] || item.status}</td>
  339. <td class="px-4 py-3 text-center text-sm">${progressHtml}</td>
  340. <td class="px-4 py-3 text-center text-gray-400 text-xs">${item.created_at || ''}</td>
  341. <td class="px-4 py-3 text-center">
  342. ${item.status === 'processing' || item.status === 'pending'
  343. ? '<button onclick="refreshTaskStatus(\'' + item.task_no + '\')" class="text-blue-400 hover:text-blue-300 text-xs"><i class="fas fa-sync-alt"></i> 刷新</button>'
  344. : (item.error_message ? '<span class="text-red-400 text-xs" title="' + item.error_message + '"><i class="fas fa-exclamation-triangle"></i></span>' : '')}
  345. </td>
  346. </tr>`;
  347. tbody.append(html);
  348. });
  349. }
  350. function refreshTaskStatus(taskNo) {
  351. $.ajax({
  352. url: '/api/v1/knowledge/import-tasks/' + taskNo,
  353. method: 'GET',
  354. headers: { 'Authorization': 'Bearer ' + getJwtToken() },
  355. success: function() {
  356. loadImportTasks();
  357. }
  358. });
  359. }
  360. function openImportModal(kbId, kbName) {
  361. $('#import-kb-id').val(kbId);
  362. $('#import-kb-name').val(kbName);
  363. $('#import-task-no').val('');
  364. $('#import-parents').val('');
  365. $('#import-children').val('');
  366. $('#import-modal').removeClass('hidden');
  367. }
  368. function closeImportModal() {
  369. $('#import-modal').addClass('hidden');
  370. }
  371. function submitImport() {
  372. const kbId = $('#import-kb-id').val();
  373. const taskNo = $('#import-task-no').val().trim();
  374. let parents, children;
  375. if (!taskNo) { alert('请输入任务号'); return; }
  376. try {
  377. parents = JSON.parse($('#import-parents').val());
  378. } catch(e) { alert('Parents JSON 格式错误: ' + e.message); return; }
  379. const childrenVal = $('#import-children').val().trim();
  380. children = childrenVal ? JSON.parse(childrenVal) : null;
  381. $.ajax({
  382. url: '/api/v1/knowledge/bases/' + kbId + '/batch-import',
  383. method: 'POST',
  384. contentType: 'application/json',
  385. headers: { 'Authorization': 'Bearer ' + getJwtToken() },
  386. data: JSON.stringify({
  387. task_no: taskNo,
  388. kb_name: $('#import-kb-name').val(),
  389. parents: parents,
  390. children: children,
  391. }),
  392. success: function(resp) {
  393. alert('入库任务已提交!任务号: ' + taskNo);
  394. closeImportModal();
  395. switchTab('tasks');
  396. loadImportTasks();
  397. },
  398. error: function(xhr) {
  399. alert('提交失败: ' + (xhr.responseJSON?.message || '未知错误'));
  400. }
  401. });
  402. }
  403. function getJwtToken() {
  404. // SSO 登录后保存在 localStorage 中的 key 是 'token'
  405. return localStorage.getItem('token') || localStorage.getItem('jwt_token') || '';
  406. }
  407. // 自动刷新任务状态(每 10 秒)
  408. setInterval(function() {
  409. if (!$('#tab-tasks').hasClass('hidden')) {
  410. loadImportTasks();
  411. }
  412. }, 10000);
  413. </script>
  414. {% endblock %}