docs.js 14 KB

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