documentManager.js 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958
  1. /**
  2. * 文档管理模块
  3. */
  4. // 文档列表
  5. let documents = [];
  6. // 当前正在编辑的文档
  7. let currentDocument = null;
  8. // Markdown编辑器实例
  9. let editorMd = null;
  10. // 创建documentManager对象
  11. const documentManager = {
  12. // 初始化文档管理
  13. init: function() {
  14. console.log('初始化文档管理模块...');
  15. // 渲染表头
  16. this.renderDocumentTableHeader();
  17. // 加载文档列表
  18. return this.loadDocuments().catch(err => {
  19. console.error('加载文档列表失败:', err);
  20. return Promise.resolve(); // 即使失败也继续初始化过程
  21. });
  22. },
  23. // 渲染文档表格头部
  24. renderDocumentTableHeader: function() {
  25. try {
  26. const documentTable = document.getElementById('documentTable');
  27. if (!documentTable) {
  28. console.warn('文档管理表格元素 (id=\"documentTable\") 未找到,无法渲染表头。');
  29. return;
  30. }
  31. // 查找或创建 thead
  32. let thead = documentTable.querySelector('thead');
  33. if (!thead) {
  34. thead = document.createElement('thead');
  35. documentTable.insertBefore(thead, documentTable.firstChild); // 确保 thead 在 tbody 之前
  36. console.log('创建了文档表格的 thead 元素。');
  37. }
  38. // 设置表头内容 (包含 ID 列)
  39. thead.innerHTML = `
  40. <tr>
  41. <th style="width: 5%">#</th>
  42. <th style="width: 25%">标题</th>
  43. <th style="width: 20%">创建时间</th>
  44. <th style="width: 20%">更新时间</th>
  45. <th style="width: 10%">状态</th>
  46. <th style="width: 20%">操作</th>
  47. </tr>
  48. `;
  49. console.log('文档表格表头已渲染。');
  50. } catch (error) {
  51. console.error('渲染文档表格表头时出错:', error);
  52. }
  53. },
  54. // 加载文档列表
  55. loadDocuments: async function() {
  56. try {
  57. // 显示加载状态
  58. const documentTableBody = document.getElementById('documentTableBody');
  59. if (documentTableBody) {
  60. documentTableBody.innerHTML = '<tr><td colspan="6" style="text-align: center;"><i class="fas fa-spinner fa-spin"></i> 正在加载文档列表...</td></tr>';
  61. }
  62. // 简化会话检查逻辑,只验证会话是否有效
  63. let sessionValid = true;
  64. try {
  65. const sessionResponse = await fetch('/api/check-session', {
  66. headers: {
  67. 'Cache-Control': 'no-cache',
  68. 'X-Requested-With': 'XMLHttpRequest'
  69. },
  70. credentials: 'same-origin'
  71. });
  72. if (sessionResponse.status === 401) {
  73. console.warn('会话已过期,无法加载文档');
  74. sessionValid = false;
  75. }
  76. } catch (sessionError) {
  77. console.warn('检查会话状态发生网络错误:', sessionError);
  78. // 发生网络错误时继续尝试加载文档
  79. }
  80. // 尝试不同的API路径
  81. const possiblePaths = [
  82. '/api/documents',
  83. '/api/documentation-list',
  84. '/api/documentation'
  85. ];
  86. let success = false;
  87. let authError = false;
  88. for (const path of possiblePaths) {
  89. try {
  90. console.log(`尝试从 ${path} 获取文档列表`);
  91. const response = await fetch(path, {
  92. credentials: 'same-origin',
  93. headers: {
  94. 'Cache-Control': 'no-cache',
  95. 'X-Requested-With': 'XMLHttpRequest'
  96. }
  97. });
  98. if (response.status === 401) {
  99. console.warn(`API路径 ${path} 返回未授权状态`);
  100. authError = true;
  101. continue;
  102. }
  103. if (response.ok) {
  104. const data = await response.json();
  105. documents = Array.isArray(data) ? data : [];
  106. // 确保每个文档都包含必要的时间字段
  107. documents = documents.map(doc => {
  108. if (!doc.createdAt && doc.updatedAt) {
  109. // 如果没有创建时间但有更新时间,使用更新时间
  110. doc.createdAt = doc.updatedAt;
  111. } else if (!doc.createdAt) {
  112. // 如果都没有,使用当前时间
  113. doc.createdAt = new Date().toISOString();
  114. }
  115. if (!doc.updatedAt) {
  116. // 如果没有更新时间,使用创建时间
  117. doc.updatedAt = doc.createdAt;
  118. }
  119. return doc;
  120. });
  121. // 先渲染表头,再渲染文档列表
  122. this.renderDocumentTableHeader();
  123. this.renderDocumentList();
  124. console.log(`成功从API路径 ${path} 加载文档列表`, documents);
  125. success = true;
  126. break;
  127. }
  128. } catch (e) {
  129. console.warn(`从 ${path} 加载文档失败:`, e);
  130. }
  131. }
  132. // 处理认证错误 - 只有当会话检查和API请求都明确失败时才强制登出
  133. if ((authError || !sessionValid) && !success && localStorage.getItem('isLoggedIn') === 'true') {
  134. console.warn('会话检查和API请求均指示会话已过期');
  135. core.showAlert('会话已过期,请重新登录', 'warning');
  136. setTimeout(() => {
  137. localStorage.removeItem('isLoggedIn');
  138. window.location.reload();
  139. }, 1500);
  140. return;
  141. }
  142. // 如果API请求失败但不是认证错误,显示空文档列表
  143. if (!success) {
  144. console.log('API请求失败,显示空文档列表');
  145. documents = [];
  146. // 仍然需要渲染表头
  147. this.renderDocumentTableHeader();
  148. this.renderDocumentList();
  149. }
  150. } catch (error) {
  151. console.error('加载文档失败:', error);
  152. // 在UI上显示错误
  153. const documentTableBody = document.getElementById('documentTableBody');
  154. if (documentTableBody) {
  155. documentTableBody.innerHTML = `<tr><td colspan="6" style="text-align: center; color: red;">加载文档失败: ${error.message} <button onclick="documentManager.loadDocuments()">重试</button></td></tr>`;
  156. }
  157. // 设置为空文档列表
  158. documents = [];
  159. }
  160. },
  161. // 初始化编辑器
  162. initEditor: function() {
  163. try {
  164. const editorContainer = document.getElementById('editor');
  165. if (!editorContainer) {
  166. console.error('找不到编辑器容器元素');
  167. return;
  168. }
  169. // 检查 toastui 是否已加载
  170. console.log('检查编辑器依赖项:', typeof toastui);
  171. // 确保 toastui 对象存在
  172. if (typeof toastui === 'undefined') {
  173. console.error('Toast UI Editor 未加载');
  174. return;
  175. }
  176. // 创建编辑器实例
  177. editorMd = new toastui.Editor({
  178. el: editorContainer,
  179. height: '600px',
  180. initialValue: '',
  181. previewStyle: 'vertical',
  182. initialEditType: 'markdown',
  183. toolbarItems: [
  184. ['heading', 'bold', 'italic', 'strike'],
  185. ['hr', 'quote'],
  186. ['ul', 'ol', 'task', 'indent', 'outdent'],
  187. ['table', 'image', 'link'],
  188. ['code', 'codeblock']
  189. ]
  190. });
  191. console.log('编辑器初始化完成', editorMd);
  192. } catch (error) {
  193. console.error('初始化编辑器出错:', error);
  194. core.showAlert('初始化编辑器失败: ' + error.message, 'error');
  195. }
  196. },
  197. // 检查编辑器是否已初始化
  198. isEditorInitialized: function() {
  199. return editorMd !== null;
  200. },
  201. // 创建新文档
  202. newDocument: function() {
  203. // 首先确保编辑器已初始化
  204. if (!editorMd) {
  205. this.initEditor();
  206. // 等待编辑器初始化完成后再继续
  207. setTimeout(() => {
  208. currentDocument = null;
  209. document.getElementById('documentTitle').value = '';
  210. editorMd.setMarkdown('');
  211. this.showEditor();
  212. }, 500);
  213. } else {
  214. currentDocument = null;
  215. document.getElementById('documentTitle').value = '';
  216. editorMd.setMarkdown('');
  217. this.showEditor();
  218. }
  219. },
  220. // 显示编辑器
  221. showEditor: function() {
  222. document.getElementById('documentTable').style.display = 'none';
  223. document.getElementById('editorContainer').style.display = 'block';
  224. if (editorMd) {
  225. // 确保每次显示编辑器时都切换到编辑模式
  226. editorMd.focus();
  227. }
  228. },
  229. // 隐藏编辑器
  230. hideEditor: function() {
  231. document.getElementById('documentTable').style.display = 'table';
  232. document.getElementById('editorContainer').style.display = 'none';
  233. },
  234. // 取消编辑
  235. cancelEdit: function() {
  236. this.hideEditor();
  237. },
  238. // 保存文档
  239. saveDocument: async function() {
  240. const title = document.getElementById('documentTitle').value.trim();
  241. const content = editorMd.getMarkdown();
  242. if (!title) {
  243. core.showAlert('请输入文档标题', 'error');
  244. return;
  245. }
  246. // 显示保存中状态
  247. core.showLoading();
  248. try {
  249. // 简化会话检查逻辑,只验证会话是否有效
  250. let sessionValid = true;
  251. try {
  252. const sessionResponse = await fetch('/api/check-session', {
  253. headers: {
  254. 'Cache-Control': 'no-cache',
  255. 'X-Requested-With': 'XMLHttpRequest'
  256. },
  257. credentials: 'same-origin'
  258. });
  259. if (sessionResponse.status === 401) {
  260. console.warn('会话已过期,无法保存文档');
  261. sessionValid = false;
  262. }
  263. } catch (sessionError) {
  264. console.warn('检查会话状态发生网络错误:', sessionError);
  265. // 发生网络错误时继续尝试保存操作
  266. }
  267. // 只有在会话明确无效时才退出
  268. if (!sessionValid) {
  269. core.showAlert('您的会话已过期,请重新登录', 'warning');
  270. setTimeout(() => {
  271. localStorage.removeItem('isLoggedIn');
  272. window.location.reload();
  273. }, 1500);
  274. return;
  275. }
  276. // 确保Markdown内容以标题开始
  277. let processedContent = content;
  278. if (!content.startsWith('# ')) {
  279. // 如果内容不是以一级标题开始,则在开头添加标题
  280. processedContent = `# ${title}\n\n${content}`;
  281. } else {
  282. // 如果已经有一级标题,替换为当前标题
  283. processedContent = content.replace(/^# .*$/m, `# ${title}`);
  284. }
  285. const apiUrl = currentDocument && currentDocument.id
  286. ? `/api/documents/${currentDocument.id}`
  287. : '/api/documents';
  288. const method = currentDocument && currentDocument.id ? 'PUT' : 'POST';
  289. console.log(`尝试${method === 'PUT' ? '更新' : '创建'}文档,标题: ${title}`);
  290. const response = await fetch(apiUrl, {
  291. method: method,
  292. headers: {
  293. 'Content-Type': 'application/json',
  294. 'X-Requested-With': 'XMLHttpRequest'
  295. },
  296. credentials: 'same-origin',
  297. body: JSON.stringify({
  298. title,
  299. content: processedContent,
  300. published: currentDocument && currentDocument.published ? currentDocument.published : false
  301. })
  302. });
  303. // 处理响应
  304. if (response.status === 401) {
  305. // 明确的未授权响应
  306. console.warn('保存文档返回401未授权');
  307. core.showAlert('未登录或会话已过期,请重新登录', 'warning');
  308. setTimeout(() => {
  309. localStorage.removeItem('isLoggedIn');
  310. window.location.reload();
  311. }, 1500);
  312. return;
  313. }
  314. if (!response.ok) {
  315. const errorText = await response.text();
  316. let errorData;
  317. try {
  318. errorData = JSON.parse(errorText);
  319. } catch (e) {
  320. // 如果不是有效的JSON,直接使用文本
  321. throw new Error(errorText || '保存失败,请重试');
  322. }
  323. throw new Error(errorData.error || errorData.message || '保存失败,请重试');
  324. }
  325. const savedDoc = await response.json();
  326. console.log('保存的文档:', savedDoc);
  327. // 确保savedDoc包含必要的时间字段
  328. if (savedDoc) {
  329. // 如果返回的保存文档中没有时间字段,从API获取完整文档信息
  330. if (!savedDoc.createdAt || !savedDoc.updatedAt) {
  331. try {
  332. const docId = savedDoc.id || (currentDocument ? currentDocument.id : null);
  333. if (docId) {
  334. const docResponse = await fetch(`/api/documents/${docId}`, {
  335. headers: { 'Cache-Control': 'no-cache' },
  336. credentials: 'same-origin'
  337. });
  338. if (docResponse.ok) {
  339. const fullDoc = await docResponse.json();
  340. Object.assign(savedDoc, {
  341. createdAt: fullDoc.createdAt,
  342. updatedAt: fullDoc.updatedAt
  343. });
  344. console.log('获取到完整的文档时间信息:', fullDoc);
  345. }
  346. }
  347. } catch (timeError) {
  348. console.warn('获取文档完整时间信息失败:', timeError);
  349. }
  350. }
  351. // 更新文档列表中的文档
  352. const existingIndex = documents.findIndex(d => d.id === savedDoc.id);
  353. if (existingIndex >= 0) {
  354. documents[existingIndex] = { ...documents[existingIndex], ...savedDoc };
  355. } else {
  356. documents.push(savedDoc);
  357. }
  358. }
  359. core.showAlert('文档保存成功', 'success');
  360. this.hideEditor();
  361. await this.loadDocuments(); // 重新加载文档列表
  362. } catch (error) {
  363. console.error('保存文档失败:', error);
  364. core.showAlert('保存文档失败: ' + error.message, 'error');
  365. } finally {
  366. core.hideLoading();
  367. }
  368. },
  369. // 渲染文档列表
  370. renderDocumentList: function() {
  371. const tbody = document.getElementById('documentTableBody');
  372. tbody.innerHTML = '';
  373. if (documents.length === 0) {
  374. tbody.innerHTML = '<tr><td colspan="6" style="text-align: center;">没有找到文档</td></tr>';
  375. return;
  376. }
  377. documents.forEach((doc, index) => {
  378. // 确保文档时间有合理默认值
  379. let createdAt = '未知';
  380. let updatedAt = '未知';
  381. try {
  382. // 尝试解析创建时间,如果失败则回退到默认值
  383. if (doc.createdAt) {
  384. const createdDate = new Date(doc.createdAt);
  385. if (!isNaN(createdDate.getTime())) {
  386. createdAt = createdDate.toLocaleString('zh-CN', {
  387. year: 'numeric',
  388. month: '2-digit',
  389. day: '2-digit',
  390. hour: '2-digit',
  391. minute: '2-digit',
  392. second: '2-digit'
  393. });
  394. }
  395. }
  396. // 尝试解析更新时间,如果失败则回退到默认值
  397. if (doc.updatedAt) {
  398. const updatedDate = new Date(doc.updatedAt);
  399. if (!isNaN(updatedDate.getTime())) {
  400. updatedAt = updatedDate.toLocaleString('zh-CN', {
  401. year: 'numeric',
  402. month: '2-digit',
  403. day: '2-digit',
  404. hour: '2-digit',
  405. minute: '2-digit',
  406. second: '2-digit'
  407. });
  408. }
  409. }
  410. // 简化时间显示逻辑 - 直接显示时间戳,不添加特殊标记
  411. if (createdAt === '未知' && updatedAt !== '未知') {
  412. // 如果没有创建时间但有更新时间
  413. createdAt = '未记录';
  414. } else if (updatedAt === '未知' && createdAt !== '未知') {
  415. // 如果没有更新时间但有创建时间
  416. updatedAt = '未更新';
  417. }
  418. } catch (error) {
  419. console.warn(`解析文档时间失败:`, error, doc);
  420. }
  421. const statusClasses = doc.published ? 'status-badge status-running' : 'status-badge status-stopped';
  422. const statusText = doc.published ? '已发布' : '未发布';
  423. const row = document.createElement('tr');
  424. row.innerHTML = `
  425. <td>${index + 1}</td>
  426. <td>${doc.title || '无标题文档'}</td>
  427. <td>${createdAt}</td>
  428. <td>${updatedAt}</td>
  429. <td><span class="${statusClasses}">${statusText}</span></td>
  430. <td class="action-buttons">
  431. <button class="action-btn edit-btn" title="编辑文档" onclick="documentManager.editDocument('${doc.id}')">
  432. <i class="fas fa-edit"></i>
  433. </button>
  434. <button class="action-btn ${doc.published ? 'unpublish-btn' : 'publish-btn'}"
  435. title="${doc.published ? '取消发布' : '发布文档'}"
  436. onclick="documentManager.togglePublish('${doc.id}')">
  437. <i class="fas ${doc.published ? 'fa-toggle-off' : 'fa-toggle-on'}"></i>
  438. </button>
  439. <button class="action-btn delete-btn" title="删除文档" onclick="documentManager.deleteDocument('${doc.id}')">
  440. <i class="fas fa-trash"></i>
  441. </button>
  442. </td>
  443. `;
  444. tbody.appendChild(row);
  445. });
  446. },
  447. // 编辑文档
  448. editDocument: async function(id) {
  449. try {
  450. console.log(`准备编辑文档,ID: ${id}`);
  451. core.showLoading();
  452. // 检查会话状态,优化会话检查逻辑
  453. let sessionValid = true;
  454. try {
  455. const sessionResponse = await fetch('/api/check-session', {
  456. headers: {
  457. 'Cache-Control': 'no-cache',
  458. 'X-Requested-With': 'XMLHttpRequest'
  459. },
  460. credentials: 'same-origin'
  461. });
  462. if (sessionResponse.status === 401) {
  463. console.warn('会话已过期,无法编辑文档');
  464. sessionValid = false;
  465. }
  466. } catch (sessionError) {
  467. console.warn('检查会话状态发生网络错误:', sessionError);
  468. // 发生网络错误时不立即判定会话失效,继续尝试编辑操作
  469. }
  470. // 只有在明确会话无效时才提示重新登录
  471. if (!sessionValid) {
  472. core.showAlert('您的会话已过期,请重新登录', 'warning');
  473. setTimeout(() => {
  474. localStorage.removeItem('isLoggedIn');
  475. window.location.reload();
  476. }, 1500);
  477. return;
  478. }
  479. // 在本地查找文档
  480. currentDocument = documents.find(doc => doc.id === id);
  481. // 如果本地未找到,从API获取
  482. if (!currentDocument && id) {
  483. try {
  484. console.log('从API获取文档详情');
  485. // 尝试多个可能的API路径
  486. const apiPaths = [
  487. `/api/documents/${id}`,
  488. `/api/documentation/${id}`
  489. ];
  490. let docResponse = null;
  491. let authError = false;
  492. for (const apiPath of apiPaths) {
  493. try {
  494. console.log(`尝试从 ${apiPath} 获取文档`);
  495. const response = await fetch(apiPath, {
  496. credentials: 'same-origin',
  497. headers: {
  498. 'X-Requested-With': 'XMLHttpRequest'
  499. }
  500. });
  501. // 只有明确401错误才认定为会话过期
  502. if (response.status === 401) {
  503. console.warn(`API ${apiPath} 返回401未授权`);
  504. authError = true;
  505. continue;
  506. }
  507. if (response.ok) {
  508. docResponse = response;
  509. console.log(`成功从 ${apiPath} 获取文档`);
  510. break;
  511. }
  512. } catch (pathError) {
  513. console.warn(`从 ${apiPath} 获取文档失败:`, pathError);
  514. }
  515. }
  516. // 处理认证错误
  517. if (authError && !docResponse) {
  518. core.showAlert('未登录或会话已过期,请重新登录', 'warning');
  519. setTimeout(() => {
  520. localStorage.removeItem('isLoggedIn');
  521. window.location.reload();
  522. }, 1500);
  523. return;
  524. }
  525. if (docResponse && docResponse.ok) {
  526. currentDocument = await docResponse.json();
  527. console.log('获取到文档详情:', currentDocument);
  528. // 确保文档包含必要的时间字段
  529. if (!currentDocument.createdAt && currentDocument.updatedAt) {
  530. // 如果没有创建时间但有更新时间,使用更新时间
  531. currentDocument.createdAt = currentDocument.updatedAt;
  532. } else if (!currentDocument.createdAt) {
  533. // 如果都没有,使用当前时间
  534. currentDocument.createdAt = new Date().toISOString();
  535. }
  536. if (!currentDocument.updatedAt) {
  537. // 如果没有更新时间,使用创建时间
  538. currentDocument.updatedAt = currentDocument.createdAt;
  539. }
  540. // 将获取到的文档添加到文档列表中
  541. const existingIndex = documents.findIndex(d => d.id === id);
  542. if (existingIndex >= 0) {
  543. documents[existingIndex] = currentDocument;
  544. } else {
  545. documents.push(currentDocument);
  546. }
  547. } else {
  548. throw new Error('所有API路径都无法获取文档');
  549. }
  550. } catch (apiError) {
  551. console.error('从API获取文档详情失败:', apiError);
  552. core.showAlert('获取文档详情失败: ' + apiError.message, 'error');
  553. }
  554. }
  555. // 如果仍然没有找到文档,显示错误
  556. if (!currentDocument) {
  557. core.showAlert('未找到指定的文档', 'error');
  558. return;
  559. }
  560. // 显示编辑器界面并设置内容
  561. this.showEditor();
  562. // 确保编辑器已初始化
  563. if (!editorMd) {
  564. await new Promise(resolve => setTimeout(resolve, 100));
  565. this.initEditor();
  566. await new Promise(resolve => setTimeout(resolve, 500));
  567. }
  568. // 设置文档内容
  569. if (editorMd) {
  570. document.getElementById('documentTitle').value = currentDocument.title || '';
  571. if (currentDocument.content) {
  572. console.log(`设置文档内容,长度: ${currentDocument.content.length}`);
  573. editorMd.setMarkdown(currentDocument.content);
  574. } else {
  575. console.log('文档内容为空,尝试额外获取内容');
  576. // 如果文档内容为空,尝试额外获取内容
  577. try {
  578. const contentResponse = await fetch(`/api/documents/${id}/content`, {
  579. credentials: 'same-origin',
  580. headers: {
  581. 'X-Requested-With': 'XMLHttpRequest'
  582. }
  583. });
  584. // 只有明确401错误才提示重新登录
  585. if (contentResponse.status === 401) {
  586. core.showAlert('会话已过期,请重新登录', 'warning');
  587. setTimeout(() => {
  588. localStorage.removeItem('isLoggedIn');
  589. window.location.reload();
  590. }, 1500);
  591. return;
  592. }
  593. if (contentResponse.ok) {
  594. const contentData = await contentResponse.json();
  595. if (contentData.content) {
  596. currentDocument.content = contentData.content;
  597. editorMd.setMarkdown(contentData.content);
  598. console.log('成功获取额外内容');
  599. }
  600. }
  601. } catch (contentError) {
  602. console.warn('获取额外内容失败:', contentError);
  603. }
  604. // 如果仍然没有内容,设置为空
  605. if (!currentDocument.content) {
  606. editorMd.setMarkdown('');
  607. }
  608. }
  609. } else {
  610. console.error('编辑器初始化失败,无法设置内容');
  611. core.showAlert('编辑器初始化失败,请刷新页面重试', 'error');
  612. }
  613. } catch (error) {
  614. console.error('编辑文档时出错:', error);
  615. core.showAlert('编辑文档失败: ' + error.message, 'error');
  616. } finally {
  617. core.hideLoading();
  618. }
  619. },
  620. // 查看文档
  621. viewDocument: function(id) {
  622. const doc = documents.find(doc => doc.id === id);
  623. if (!doc) {
  624. core.showAlert('未找到指定的文档', 'error');
  625. return;
  626. }
  627. Swal.fire({
  628. title: doc.title,
  629. html: `<div class="document-preview">${marked.parse(doc.content || '')}</div>`,
  630. width: '70%',
  631. showCloseButton: true,
  632. showConfirmButton: false,
  633. customClass: {
  634. container: 'document-preview-container',
  635. popup: 'document-preview-popup',
  636. content: 'document-preview-content'
  637. }
  638. });
  639. },
  640. // 删除文档
  641. deleteDocument: async function(id) {
  642. Swal.fire({
  643. title: '确定要删除此文档吗?',
  644. text: "此操作无法撤销!",
  645. icon: 'warning',
  646. showCancelButton: true,
  647. confirmButtonColor: '#d33',
  648. cancelButtonColor: '#3085d6',
  649. confirmButtonText: '是,删除它',
  650. cancelButtonText: '取消'
  651. }).then(async (result) => {
  652. if (result.isConfirmed) {
  653. try {
  654. console.log(`尝试删除文档: ${id}`);
  655. // 检查会话状态
  656. const sessionResponse = await fetch('/api/check-session', {
  657. headers: { 'Cache-Control': 'no-cache' }
  658. });
  659. if (sessionResponse.status === 401) {
  660. // 会话已过期,提示用户并重定向到登录
  661. core.showAlert('您的会话已过期,请重新登录', 'warning');
  662. setTimeout(() => {
  663. localStorage.removeItem('isLoggedIn');
  664. window.location.reload();
  665. }, 1500);
  666. return;
  667. }
  668. // 使用正确的API路径删除
  669. const response = await fetch(`/api/documents/${id}`, {
  670. method: 'DELETE',
  671. headers: {
  672. 'Content-Type': 'application/json',
  673. 'X-Requested-With': 'XMLHttpRequest'
  674. },
  675. credentials: 'same-origin' // 确保发送cookie
  676. });
  677. if (response.status === 401) {
  678. // 处理未授权错误
  679. core.showAlert('未登录或会话已过期,请重新登录', 'warning');
  680. setTimeout(() => {
  681. localStorage.removeItem('isLoggedIn');
  682. window.location.reload();
  683. }, 1500);
  684. return;
  685. }
  686. if (!response.ok) {
  687. const errorData = await response.json();
  688. throw new Error(errorData.error || '删除文档失败');
  689. }
  690. console.log('文档删除成功响应:', await response.json());
  691. core.showAlert('文档已成功删除', 'success');
  692. await this.loadDocuments(); // 重新加载文档列表
  693. } catch (error) {
  694. console.error('删除文档失败:', error);
  695. core.showAlert('删除文档失败: ' + error.message, 'error');
  696. }
  697. }
  698. });
  699. },
  700. // 切换文档发布状态
  701. togglePublish: async function(id) {
  702. try {
  703. const doc = documents.find(d => d.id === id);
  704. if (!doc) {
  705. throw new Error('找不到指定文档');
  706. }
  707. // 添加加载指示
  708. core.showLoading();
  709. // 检查会话状态
  710. const sessionResponse = await fetch('/api/check-session', {
  711. headers: { 'Cache-Control': 'no-cache' }
  712. });
  713. if (sessionResponse.status === 401) {
  714. // 会话已过期,提示用户并重定向到登录
  715. core.showAlert('您的会话已过期,请重新登录', 'warning');
  716. setTimeout(() => {
  717. localStorage.removeItem('isLoggedIn');
  718. window.location.reload();
  719. }, 1500);
  720. return;
  721. }
  722. console.log(`尝试切换文档 ${id} 的发布状态,当前状态:`, doc.published);
  723. // 构建更新请求数据
  724. const updateData = {
  725. id: doc.id,
  726. title: doc.title,
  727. published: !doc.published // 切换发布状态
  728. };
  729. // 使用正确的API端点进行更新
  730. const response = await fetch(`/api/documentation/toggle-publish/${id}`, {
  731. method: 'PUT',
  732. headers: {
  733. 'Content-Type': 'application/json',
  734. 'X-Requested-With': 'XMLHttpRequest'
  735. },
  736. credentials: 'same-origin', // 确保发送cookie
  737. body: JSON.stringify(updateData)
  738. });
  739. if (response.status === 401) {
  740. // 处理未授权错误
  741. core.showAlert('未登录或会话已过期,请重新登录', 'warning');
  742. setTimeout(() => {
  743. localStorage.removeItem('isLoggedIn');
  744. window.location.reload();
  745. }, 1500);
  746. return;
  747. }
  748. if (!response.ok) {
  749. const errorData = await response.json();
  750. throw new Error(errorData.error || '更新文档状态失败');
  751. }
  752. // 更新成功
  753. const updatedDoc = await response.json();
  754. console.log('文档状态更新响应:', updatedDoc);
  755. // 更新本地文档列表
  756. const docIndex = documents.findIndex(d => d.id === id);
  757. if (docIndex >= 0) {
  758. documents[docIndex].published = updatedDoc.published;
  759. this.renderDocumentList();
  760. }
  761. core.showAlert('文档状态已更新', 'success');
  762. } catch (error) {
  763. console.error('更改发布状态失败:', error);
  764. core.showAlert('更改发布状态失败: ' + error.message, 'error');
  765. } finally {
  766. core.hideLoading();
  767. }
  768. }
  769. };
  770. // 全局公开文档管理模块
  771. window.documentManager = documentManager;
  772. /**
  773. * 显示指定文档的内容
  774. * @param {string} docId 文档ID
  775. */
  776. async function showDocument(docId) {
  777. try {
  778. console.log('正在获取文档内容,ID:', docId);
  779. // 显示加载状态
  780. const documentContent = document.getElementById('documentContent');
  781. if (documentContent) {
  782. documentContent.innerHTML = '<div class="loading-container"><i class="fas fa-spinner fa-spin"></i> 正在加载文档内容...</div>';
  783. }
  784. // 获取文档内容
  785. const response = await fetch(`/api/documentation/${docId}`);
  786. if (!response.ok) {
  787. throw new Error(`获取文档内容失败,状态码: ${response.status}`);
  788. }
  789. const doc = await response.json();
  790. console.log('获取到文档:', doc);
  791. // 更新文档内容区域
  792. if (documentContent) {
  793. if (doc.content) {
  794. // 使用marked渲染markdown内容
  795. documentContent.innerHTML = `
  796. <h1>${doc.title || '无标题'}</h1>
  797. ${doc.lastUpdated ? `<div class="doc-meta">最后更新: ${new Date(doc.lastUpdated).toLocaleDateString('zh-CN')}</div>` : ''}
  798. <div class="doc-content">${window.marked ? marked.parse(doc.content) : doc.content}</div>
  799. `;
  800. } else {
  801. documentContent.innerHTML = `
  802. <h1>${doc.title || '无标题'}</h1>
  803. <div class="empty-content">
  804. <i class="fas fa-file-alt fa-3x"></i>
  805. <p>该文档暂无内容</p>
  806. </div>
  807. `;
  808. }
  809. } else {
  810. console.error('找不到文档内容容器,ID: documentContent');
  811. }
  812. // 高亮当前选中的文档
  813. highlightSelectedDocument(docId);
  814. } catch (error) {
  815. console.error('获取文档内容失败:', error);
  816. // 显示错误信息
  817. const documentContent = document.getElementById('documentContent');
  818. if (documentContent) {
  819. documentContent.innerHTML = `
  820. <div class="error-container">
  821. <i class="fas fa-exclamation-triangle fa-3x"></i>
  822. <h2>加载失败</h2>
  823. <p>无法获取文档内容: ${error.message}</p>
  824. <button class="btn btn-retry" onclick="showDocument('${docId}')">重试</button>
  825. </div>
  826. `;
  827. }
  828. }
  829. }
  830. /**
  831. * 高亮选中的文档
  832. * @param {string} docId 文档ID
  833. */
  834. function highlightSelectedDocument(docId) {
  835. // 移除所有高亮
  836. const docLinks = document.querySelectorAll('.doc-list .doc-item');
  837. docLinks.forEach(link => link.classList.remove('active'));
  838. // 添加当前高亮
  839. const selectedLink = document.querySelector(`.doc-list .doc-item[data-id="${docId}"]`);
  840. if (selectedLink) {
  841. selectedLink.classList.add('active');
  842. // 确保选中项可见
  843. selectedLink.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
  844. }
  845. }