documentManager.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. /**
  2. * 文档管理模块
  3. */
  4. // 文档列表
  5. let documents = [];
  6. // 创建documentManager对象
  7. const documentManager = {
  8. // 初始化文档管理
  9. init: function() {
  10. // console.log('初始化文档管理模块...');
  11. // 渲染表头
  12. this.renderDocumentTableHeader();
  13. // 加载文档列表
  14. return this.loadDocuments().catch(err => {
  15. // console.error('加载文档列表失败:', err);
  16. return Promise.resolve(); // 即使失败也继续初始化过程
  17. });
  18. },
  19. // 渲染文档表格头部
  20. renderDocumentTableHeader: function() {
  21. try {
  22. const documentTable = document.getElementById('documentTable');
  23. if (!documentTable) {
  24. // console.warn('文档管理表格元素 (id=\"documentTable\") 未找到,无法渲染表头。');
  25. return;
  26. }
  27. // 查找或创建 thead
  28. let thead = documentTable.querySelector('thead');
  29. if (!thead) {
  30. thead = document.createElement('thead');
  31. documentTable.insertBefore(thead, documentTable.firstChild); // 确保 thead 在 tbody 之前
  32. // console.log('创建了文档表格的 thead 元素。');
  33. }
  34. // 设置表头内容 (包含 ID 列)
  35. thead.innerHTML = `
  36. <tr>
  37. <th style="width: 5%">#</th>
  38. <th style="width: 25%">标题</th>
  39. <th style="width: 20%">创建时间</th>
  40. <th style="width: 20%">更新时间</th>
  41. <th style="width: 10%">状态</th>
  42. <th style="width: 20%">操作</th>
  43. </tr>
  44. `;
  45. // console.log('文档表格表头已渲染。');
  46. } catch (error) {
  47. // console.error('渲染文档表格表头时出错:', error);
  48. }
  49. },
  50. // 加载文档列表
  51. loadDocuments: async function() {
  52. try {
  53. // 显示加载状态
  54. const documentTableBody = document.getElementById('documentTableBody');
  55. if (documentTableBody) {
  56. documentTableBody.innerHTML = '<tr><td colspan="6" style="text-align: center;"><i class="fas fa-spinner fa-spin"></i> 正在加载文档列表...</td></tr>';
  57. }
  58. // 简化会话检查逻辑,只验证会话是否有效
  59. let sessionValid = true;
  60. try {
  61. const sessionResponse = await fetch('/api/check-session', {
  62. headers: {
  63. 'Cache-Control': 'no-cache',
  64. 'X-Requested-With': 'XMLHttpRequest'
  65. },
  66. credentials: 'same-origin'
  67. });
  68. if (sessionResponse.status === 401) {
  69. // console.warn('会话已过期,无法加载文档');
  70. sessionValid = false;
  71. }
  72. } catch (sessionError) {
  73. // console.warn('检查会话状态发生网络错误:', sessionError);
  74. // 发生网络错误时继续尝试加载文档
  75. }
  76. // 尝试不同的API路径
  77. const possiblePaths = [
  78. '/api/documents',
  79. '/api/documentation-list',
  80. '/api/documentation'
  81. ];
  82. let success = false;
  83. let authError = false;
  84. for (const path of possiblePaths) {
  85. try {
  86. // console.log(`尝试从 ${path} 获取文档列表`);
  87. const response = await fetch(path, {
  88. credentials: 'same-origin',
  89. headers: {
  90. 'Cache-Control': 'no-cache',
  91. 'X-Requested-With': 'XMLHttpRequest'
  92. }
  93. });
  94. if (response.status === 401) {
  95. // console.warn(`API路径 ${path} 返回未授权状态`);
  96. authError = true;
  97. continue;
  98. }
  99. if (response.ok) {
  100. const data = await response.json();
  101. documents = Array.isArray(data) ? data : [];
  102. // 确保每个文档都包含必要的时间字段
  103. documents = documents.map(doc => {
  104. if (!doc.createdAt && doc.updatedAt) {
  105. // 如果没有创建时间但有更新时间,使用更新时间
  106. doc.createdAt = doc.updatedAt;
  107. } else if (!doc.createdAt) {
  108. // 如果都没有,使用当前时间
  109. doc.createdAt = new Date().toISOString();
  110. }
  111. if (!doc.updatedAt) {
  112. // 如果没有更新时间,使用创建时间
  113. doc.updatedAt = doc.createdAt;
  114. }
  115. return doc;
  116. });
  117. // 先渲染表头,再渲染文档列表
  118. this.renderDocumentTableHeader();
  119. this.renderDocumentList();
  120. // console.log(`成功从API路径 ${path} 加载文档列表`, documents);
  121. success = true;
  122. break;
  123. }
  124. } catch (e) {
  125. // console.warn(`从 ${path} 加载文档失败:`, e);
  126. }
  127. }
  128. // 处理认证错误 - 只有当会话检查和API请求都明确失败时才强制登出
  129. if ((authError || !sessionValid) && !success && localStorage.getItem('isLoggedIn') === 'true') {
  130. // console.warn('会话检查和API请求均指示会话已过期');
  131. core.showAlert('会话已过期,请重新登录', 'warning');
  132. setTimeout(() => {
  133. localStorage.removeItem('isLoggedIn');
  134. window.location.reload();
  135. }, 1500);
  136. return;
  137. }
  138. // 如果API请求失败但不是认证错误,显示空文档列表
  139. if (!success) {
  140. // console.log('API请求失败,显示空文档列表');
  141. documents = [];
  142. // 仍然需要渲染表头
  143. this.renderDocumentTableHeader();
  144. this.renderDocumentList();
  145. }
  146. } catch (error) {
  147. // console.error('加载文档失败:', error);
  148. // 在UI上显示错误
  149. const documentTableBody = document.getElementById('documentTableBody');
  150. if (documentTableBody) {
  151. documentTableBody.innerHTML = `<tr><td colspan="6" style="text-align: center; color: red;">加载文档失败: ${error.message} <button onclick="documentManager.loadDocuments()">重试</button></td></tr>`;
  152. }
  153. // 设置为空文档列表
  154. documents = [];
  155. }
  156. },
  157. // 创建新文档
  158. newDocument: function() {
  159. // 跳转到专门的文档编辑页面
  160. window.open('/document-editor.html', '_blank');
  161. },
  162. // 渲染文档列表
  163. renderDocumentList: function() {
  164. const tbody = document.getElementById('documentTableBody');
  165. tbody.innerHTML = '';
  166. if (documents.length === 0) {
  167. tbody.innerHTML = '<tr><td colspan="6" style="text-align: center;">没有找到文档</td></tr>';
  168. return;
  169. }
  170. documents.forEach((doc, index) => {
  171. // 确保文档时间有合理默认值
  172. let createdAt = '未知';
  173. let updatedAt = '未知';
  174. try {
  175. // 尝试解析创建时间,如果失败则回退到默认值
  176. if (doc.createdAt) {
  177. const createdDate = new Date(doc.createdAt);
  178. if (!isNaN(createdDate.getTime())) {
  179. createdAt = createdDate.toLocaleString('zh-CN', {
  180. year: 'numeric',
  181. month: '2-digit',
  182. day: '2-digit',
  183. hour: '2-digit',
  184. minute: '2-digit',
  185. second: '2-digit'
  186. });
  187. }
  188. }
  189. // 尝试解析更新时间,如果失败则回退到默认值
  190. if (doc.updatedAt) {
  191. const updatedDate = new Date(doc.updatedAt);
  192. if (!isNaN(updatedDate.getTime())) {
  193. updatedAt = updatedDate.toLocaleString('zh-CN', {
  194. year: 'numeric',
  195. month: '2-digit',
  196. day: '2-digit',
  197. hour: '2-digit',
  198. minute: '2-digit',
  199. second: '2-digit'
  200. });
  201. }
  202. }
  203. // 简化时间显示逻辑 - 直接显示时间戳,不添加特殊标记
  204. if (createdAt === '未知' && updatedAt !== '未知') {
  205. // 如果没有创建时间但有更新时间
  206. createdAt = '未记录';
  207. } else if (updatedAt === '未知' && createdAt !== '未知') {
  208. // 如果没有更新时间但有创建时间
  209. updatedAt = '未更新';
  210. }
  211. } catch (error) {
  212. // console.warn(`解析文档时间失败:`, error, doc);
  213. }
  214. const statusClasses = doc.published ? 'status-badge status-running' : 'status-badge status-stopped';
  215. const statusText = doc.published ? '已发布' : '未发布';
  216. const row = document.createElement('tr');
  217. row.innerHTML = `
  218. <td>${index + 1}</td>
  219. <td>${doc.title || '无标题文档'}</td>
  220. <td>${createdAt}</td>
  221. <td>${updatedAt}</td>
  222. <td><span class="${statusClasses}">${statusText}</span></td>
  223. <td class="action-buttons">
  224. <button class="action-btn edit-btn" title="编辑文档" onclick="documentManager.editDocument('${doc.id}')">
  225. <i class="fas fa-edit"></i>
  226. </button>
  227. <button class="action-btn ${doc.published ? 'unpublish-btn' : 'publish-btn'}"
  228. title="${doc.published ? '取消发布' : '发布文档'}"
  229. onclick="documentManager.togglePublish('${doc.id}')">
  230. <i class="fas ${doc.published ? 'fa-toggle-off' : 'fa-toggle-on'}"></i>
  231. </button>
  232. <button class="action-btn delete-btn" title="删除文档" onclick="documentManager.deleteDocument('${doc.id}')">
  233. <i class="fas fa-trash"></i>
  234. </button>
  235. </td>
  236. `;
  237. tbody.appendChild(row);
  238. });
  239. },
  240. // 编辑文档
  241. editDocument: async function(id) {
  242. // 跳转到专门的文档编辑页面,并传递文档ID
  243. window.open(`/document-editor.html?id=${id}`, '_blank');
  244. },
  245. // 查看文档
  246. viewDocument: function(id) {
  247. const doc = documents.find(doc => doc.id === id);
  248. if (!doc) {
  249. core.showAlert('未找到指定的文档', 'error');
  250. return;
  251. }
  252. Swal.fire({
  253. title: doc.title,
  254. html: `<div class="document-preview">${marked.parse(doc.content || '')}</div>`,
  255. width: '70%',
  256. showCloseButton: true,
  257. showConfirmButton: false,
  258. customClass: {
  259. container: 'document-preview-container',
  260. popup: 'document-preview-popup',
  261. content: 'document-preview-content'
  262. }
  263. });
  264. },
  265. // 删除文档
  266. deleteDocument: async function(id) {
  267. Swal.fire({
  268. title: '确定要删除此文档吗?',
  269. text: "此操作无法撤销!",
  270. icon: 'warning',
  271. showCancelButton: true,
  272. confirmButtonColor: '#d33',
  273. cancelButtonColor: '#3085d6',
  274. confirmButtonText: '是,删除它',
  275. cancelButtonText: '取消'
  276. }).then(async (result) => {
  277. if (result.isConfirmed) {
  278. try {
  279. // console.log(`尝试删除文档: ${id}`);
  280. // 检查会话状态
  281. const sessionResponse = await fetch('/api/check-session', {
  282. headers: { 'Cache-Control': 'no-cache' }
  283. });
  284. if (sessionResponse.status === 401) {
  285. // 会话已过期,提示用户并重定向到登录
  286. core.showAlert('您的会话已过期,请重新登录', 'warning');
  287. setTimeout(() => {
  288. localStorage.removeItem('isLoggedIn');
  289. window.location.reload();
  290. }, 1500);
  291. return;
  292. }
  293. // 使用正确的API路径删除
  294. const response = await fetch(`/api/documents/${id}`, {
  295. method: 'DELETE',
  296. headers: {
  297. 'Content-Type': 'application/json',
  298. 'X-Requested-With': 'XMLHttpRequest'
  299. },
  300. credentials: 'same-origin' // 确保发送cookie
  301. });
  302. if (response.status === 401) {
  303. // 处理未授权错误
  304. core.showAlert('未登录或会话已过期,请重新登录', 'warning');
  305. setTimeout(() => {
  306. localStorage.removeItem('isLoggedIn');
  307. window.location.reload();
  308. }, 1500);
  309. return;
  310. }
  311. if (!response.ok) {
  312. const errorData = await response.json();
  313. throw new Error(errorData.error || '删除文档失败');
  314. }
  315. // console.log('文档删除成功响应:', await response.json());
  316. core.showAlert('文档已成功删除', 'success');
  317. await this.loadDocuments(); // 重新加载文档列表
  318. } catch (error) {
  319. // console.error('删除文档失败:', error);
  320. core.showAlert('删除文档失败: ' + error.message, 'error');
  321. }
  322. }
  323. });
  324. },
  325. // 切换文档发布状态
  326. togglePublish: async function(id) {
  327. try {
  328. const doc = documents.find(d => d.id === id);
  329. if (!doc) {
  330. throw new Error('找不到指定文档');
  331. }
  332. // 添加加载指示
  333. core.showLoading();
  334. // 检查会话状态
  335. const sessionResponse = await fetch('/api/check-session', {
  336. headers: { 'Cache-Control': 'no-cache' }
  337. });
  338. if (sessionResponse.status === 401) {
  339. // 会话已过期,提示用户并重定向到登录
  340. core.showAlert('您的会话已过期,请重新登录', 'warning');
  341. setTimeout(() => {
  342. localStorage.removeItem('isLoggedIn');
  343. window.location.reload();
  344. }, 1500);
  345. return;
  346. }
  347. // console.log(`尝试切换文档 ${id} 的发布状态,当前状态:`, doc.published);
  348. // 构建更新请求数据
  349. const updateData = {
  350. id: doc.id,
  351. title: doc.title,
  352. published: !doc.published // 切换发布状态
  353. };
  354. // 使用正确的API端点进行更新
  355. const response = await fetch(`/api/documentation/toggle-publish/${id}`, {
  356. method: 'PUT',
  357. headers: {
  358. 'Content-Type': 'application/json',
  359. 'X-Requested-With': 'XMLHttpRequest'
  360. },
  361. credentials: 'same-origin', // 确保发送cookie
  362. body: JSON.stringify(updateData)
  363. });
  364. if (response.status === 401) {
  365. // 处理未授权错误
  366. core.showAlert('未登录或会话已过期,请重新登录', 'warning');
  367. setTimeout(() => {
  368. localStorage.removeItem('isLoggedIn');
  369. window.location.reload();
  370. }, 1500);
  371. return;
  372. }
  373. if (!response.ok) {
  374. const errorData = await response.json();
  375. throw new Error(errorData.error || '更新文档状态失败');
  376. }
  377. // 更新成功
  378. const updatedDoc = await response.json();
  379. // console.log('文档状态更新响应:', updatedDoc);
  380. // 更新本地文档列表
  381. const docIndex = documents.findIndex(d => d.id === id);
  382. if (docIndex >= 0) {
  383. documents[docIndex].published = updatedDoc.published;
  384. this.renderDocumentList();
  385. }
  386. core.showAlert('文档状态已更新', 'success');
  387. } catch (error) {
  388. // console.error('更改发布状态失败:', error);
  389. core.showAlert('更改发布状态失败: ' + error.message, 'error');
  390. } finally {
  391. core.hideLoading();
  392. }
  393. }
  394. };