docs.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. document.addEventListener('DOMContentLoaded', function() {
  2. // 工具映射表(文件名到工具名称的映射)
  3. const toolNameMap = {
  4. 'json-format': 'JSON美化工具',
  5. 'json-diff': 'JSON比对工具',
  6. 'code-beautify': '代码美化工具',
  7. 'code-compress': '代码压缩工具',
  8. 'postman': '简易Postman',
  9. 'websocket': 'Websocket工具',
  10. 'regexp': '正则公式速查',
  11. 'page-timing': '网站性能优化',
  12. 'en-decode': '信息编码转换',
  13. 'trans-radix': '进制转换工具',
  14. 'timestamp': '时间(戳)转换',
  15. 'trans-color': '颜色转换工具',
  16. 'qr-code': '二维码/解码',
  17. 'image-base64': '图片转Base64',
  18. 'svg-converter': 'SVG转为图片',
  19. 'chart-maker': '图表制作工具',
  20. 'poster-maker': '海报快速生成',
  21. 'screenshot': '网页截屏工具',
  22. 'color-picker': '页面取色工具',
  23. 'aiagent': 'AI(智能助手)',
  24. 'sticky-notes': '我的便签笔记',
  25. 'html2markdown': 'Markdown转换',
  26. 'page-monkey': '网页油猴工具',
  27. 'crontab': 'Crontab工具',
  28. 'loan-rate': '贷(还)款利率',
  29. 'password': '随机密码生成',
  30. 'devtools': 'FH开发者工具',
  31. 'index': '文档首页',
  32. 'grid-ruler': '网页标尺工具',
  33. 'excel2json': 'Excel转JSON',
  34. 'naotu': '思维导图工具'
  35. };
  36. // 工具分类映射
  37. const toolCategoryMap = {
  38. 'dev': ['json-format', 'json-diff', 'code-beautify', 'code-compress', 'postman', 'websocket', 'regexp', 'page-timing', 'devtools'],
  39. 'encode': ['en-decode', 'trans-radix', 'timestamp', 'trans-color'],
  40. 'image': ['qr-code', 'image-base64', 'svg-converter', 'chart-maker', 'poster-maker', 'screenshot', 'color-picker'],
  41. 'productivity': ['aiagent', 'sticky-notes', 'html2markdown', 'page-monkey', 'naotu'],
  42. 'calculator': ['crontab', 'loan-rate', 'password'],
  43. 'other': ['grid-ruler', 'excel2json']
  44. };
  45. // 获取URL参数
  46. function getQueryParam(param) {
  47. const urlParams = new URLSearchParams(window.location.search);
  48. return urlParams.get(param);
  49. }
  50. // 初始化 marked 解析器
  51. const markedOptions = {
  52. gfm: true,
  53. breaks: true,
  54. highlight: function(code, lang) {
  55. const language = hljs.getLanguage(lang) ? lang : 'plaintext';
  56. return hljs.highlight(code, { language }).value;
  57. }
  58. };
  59. // 加载工具列表
  60. async function loadToolList() {
  61. try {
  62. const toolListElement = document.getElementById('tool-list');
  63. // 创建"文档首页"链接
  64. const indexItem = document.createElement('li');
  65. const indexLink = document.createElement('a');
  66. indexLink.href = '?tool=index';
  67. indexLink.textContent = '文档首页';
  68. indexLink.dataset.tool = 'index';
  69. indexItem.appendChild(indexLink);
  70. toolListElement.innerHTML = '';
  71. toolListElement.appendChild(indexItem);
  72. // 根据分类创建工具列表
  73. const categories = {
  74. 'dev': '开发工具类',
  75. 'encode': '编解码转换类',
  76. 'image': '图像处理类',
  77. 'productivity': '效率工具类',
  78. 'calculator': '计算工具类',
  79. 'other': '其他工具'
  80. };
  81. // 遍历分类
  82. for (const [category, categoryName] of Object.entries(categories)) {
  83. // 创建分类标题
  84. const categoryHeader = document.createElement('li');
  85. categoryHeader.innerHTML = `<h3 style="padding: 15px 20px 5px; margin: 10px 0 0; font-size: 0.9rem; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.05em;">${categoryName}</h3>`;
  86. toolListElement.appendChild(categoryHeader);
  87. // 获取分类下的工具
  88. const tools = toolCategoryMap[category] || [];
  89. // 创建工具链接
  90. for (const tool of tools) {
  91. if (toolNameMap[tool]) {
  92. const toolItem = document.createElement('li');
  93. const toolLink = document.createElement('a');
  94. toolLink.href = `?tool=${tool}`;
  95. toolLink.textContent = toolNameMap[tool];
  96. toolLink.dataset.tool = tool;
  97. toolItem.appendChild(toolLink);
  98. toolListElement.appendChild(toolItem);
  99. }
  100. }
  101. }
  102. // 为所有工具链接添加点击事件
  103. const toolLinks = document.querySelectorAll('.tool-list a');
  104. toolLinks.forEach(link => {
  105. link.addEventListener('click', function(e) {
  106. e.preventDefault();
  107. const tool = this.dataset.tool;
  108. history.pushState(null, null, `?tool=${tool}`);
  109. loadToolDoc(tool);
  110. // 更新活动状态
  111. toolLinks.forEach(l => l.classList.remove('active'));
  112. this.classList.add('active');
  113. // 在移动设备上自动关闭侧边栏
  114. if (window.innerWidth <= 768) {
  115. document.body.classList.remove('sidebar-open');
  116. document.body.classList.add('sidebar-closed');
  117. }
  118. });
  119. });
  120. // 根据URL参数加载指定工具的文档
  121. const selectedTool = getQueryParam('tool') || 'index';
  122. const activeLink = document.querySelector(`.tool-list a[data-tool="${selectedTool}"]`);
  123. if (activeLink) {
  124. activeLink.classList.add('active');
  125. }
  126. loadToolDoc(selectedTool);
  127. } catch (error) {
  128. console.error('加载工具列表失败:', error);
  129. document.getElementById('tool-list').innerHTML = '<p style="padding: 20px; color: #ef4444;">加载工具列表失败,请刷新页面重试。</p>';
  130. }
  131. }
  132. // 加载工具文档
  133. async function loadToolDoc(toolId) {
  134. const docContainer = document.getElementById('doc-container');
  135. docContainer.innerHTML = '<div class="loader"><div class="loader-spinner"></div></div>';
  136. try {
  137. const response = await fetch(`docs/${toolId}.md`);
  138. if (!response.ok) {
  139. throw new Error(`HTTP error! status: ${response.status}`);
  140. }
  141. const markdownText = await response.text();
  142. const htmlContent = marked.parse(markdownText, markedOptions);
  143. // 添加标题和标签
  144. const toolName = toolNameMap[toolId] || toolId;
  145. let category = '';
  146. // 确定工具所属类别
  147. for (const [cat, tools] of Object.entries(toolCategoryMap)) {
  148. if (tools.includes(toolId)) {
  149. switch(cat) {
  150. case 'dev': category = '开发工具类'; break;
  151. case 'encode': category = '编解码转换类'; break;
  152. case 'image': category = '图像处理类'; break;
  153. case 'productivity': category = '效率工具类'; break;
  154. case 'calculator': category = '计算工具类'; break;
  155. case 'other': category = '其他工具'; break;
  156. }
  157. break;
  158. }
  159. }
  160. const docHeader = `
  161. <div class="doc-header">
  162. <h1>${toolName}</h1>
  163. ${category ? `<div class="tool-tags"><span class="doc-tag">${category}</span></div>` : ''}
  164. </div>
  165. `;
  166. docContainer.innerHTML = `
  167. ${toolId !== 'index' ? docHeader : ''}
  168. <div class="doc-content">${htmlContent}</div>
  169. `;
  170. // 文档加载后,自动滚动到顶部
  171. window.scrollTo(0, 0);
  172. // 为文档中的链接添加点击事件
  173. const docLinks = docContainer.querySelectorAll('a[href^="../"]');
  174. docLinks.forEach(link => {
  175. const href = link.getAttribute('href');
  176. if (href.startsWith('../') && href.endsWith('.md')) {
  177. const toolPath = href.replace('../', '').replace('.md', '');
  178. link.href = `?tool=${toolPath}`;
  179. link.addEventListener('click', function(e) {
  180. e.preventDefault();
  181. history.pushState(null, null, this.href);
  182. loadToolDoc(toolPath);
  183. // 更新侧边栏活动状态
  184. const toolLinks = document.querySelectorAll('.tool-list a');
  185. toolLinks.forEach(l => l.classList.remove('active'));
  186. const activeLink = document.querySelector(`.tool-list a[data-tool="${toolPath}"]`);
  187. if (activeLink) {
  188. activeLink.classList.add('active');
  189. }
  190. });
  191. }
  192. });
  193. } catch (error) {
  194. console.error('加载文档失败:', error);
  195. docContainer.innerHTML = `
  196. <div class="doc-header">
  197. <h1>文档加载失败</h1>
  198. </div>
  199. <div class="doc-content">
  200. <p>抱歉,文档加载失败,请刷新页面重试或返回<a href="?tool=index">文档首页</a>。</p>
  201. <p>错误信息: ${error.message}</p>
  202. </div>
  203. `;
  204. }
  205. }
  206. // 搜索功能
  207. const searchInput = document.getElementById('search-docs');
  208. searchInput.addEventListener('input', function() {
  209. const searchTerm = this.value.toLowerCase();
  210. const toolLinks = document.querySelectorAll('.tool-list a');
  211. toolLinks.forEach(link => {
  212. const toolName = link.textContent.toLowerCase();
  213. if (toolName.includes(searchTerm) || searchTerm === '') {
  214. link.parentElement.style.display = 'block';
  215. } else {
  216. link.parentElement.style.display = 'none';
  217. }
  218. });
  219. // 隐藏/显示类别标题
  220. const categoryHeaders = document.querySelectorAll('.tool-list h3');
  221. categoryHeaders.forEach(header => {
  222. const nextSibling = header.parentElement.nextElementSibling;
  223. let hasVisibleTools = false;
  224. // 检查该类别下是否有可见的工具
  225. let current = nextSibling;
  226. while (current && !current.querySelector('h3')) {
  227. if (current.style.display !== 'none') {
  228. hasVisibleTools = true;
  229. break;
  230. }
  231. current = current.nextElementSibling;
  232. }
  233. header.parentElement.style.display = hasVisibleTools ? 'block' : 'none';
  234. });
  235. });
  236. // 移动端侧边栏切换
  237. const sidebarToggle = document.getElementById('sidebar-toggle');
  238. // 初始化侧边栏状态
  239. function initSidebarState() {
  240. if (window.innerWidth <= 768) {
  241. document.body.classList.add('sidebar-closed');
  242. document.body.classList.remove('sidebar-open');
  243. if (sidebarToggle) {
  244. sidebarToggle.style.display = 'flex';
  245. sidebarToggle.innerHTML = '<i class="fas fa-bars"></i>';
  246. }
  247. } else {
  248. document.body.classList.add('sidebar-open');
  249. document.body.classList.remove('sidebar-closed');
  250. if (sidebarToggle) {
  251. sidebarToggle.style.display = 'none';
  252. }
  253. }
  254. }
  255. // 页面加载时初始化
  256. initSidebarState();
  257. // 侧边栏切换按钮点击事件
  258. if (sidebarToggle) {
  259. sidebarToggle.addEventListener('click', function() {
  260. if (document.body.classList.contains('sidebar-open')) {
  261. document.body.classList.remove('sidebar-open');
  262. document.body.classList.add('sidebar-closed');
  263. this.innerHTML = '<i class="fas fa-bars"></i>';
  264. } else {
  265. document.body.classList.add('sidebar-open');
  266. document.body.classList.remove('sidebar-closed');
  267. this.innerHTML = '<i class="fas fa-times"></i>';
  268. }
  269. });
  270. }
  271. // 监听窗口大小变化
  272. window.addEventListener('resize', function() {
  273. initSidebarState();
  274. });
  275. // 返回顶部按钮
  276. const backToTopButton = document.getElementById('back-to-top');
  277. window.addEventListener('scroll', function() {
  278. if (window.scrollY > 300) {
  279. backToTopButton.classList.add('visible');
  280. } else {
  281. backToTopButton.classList.remove('visible');
  282. }
  283. });
  284. backToTopButton.addEventListener('click', function() {
  285. window.scrollTo({
  286. top: 0,
  287. behavior: 'smooth'
  288. });
  289. });
  290. // 初始化
  291. loadToolList();
  292. // 同步导航栏
  293. const navToggle = document.querySelector('.nav-toggle');
  294. const navMenu = document.querySelector('.nav-menu');
  295. navToggle.addEventListener('click', function() {
  296. navToggle.classList.toggle('active');
  297. navMenu.classList.toggle('active');
  298. });
  299. });