找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 4|回复: 0

[维护日志] [2026-09-16] [功能] [代码高亮] - Discuz! X3.5 帖子详情页代码高亮改造(电脑版+手机版)

[复制链接]
  • 打卡等级:本地老炮

9336

威望

7310

金钱

1万

贡献

管理员

自由的灵魂

积分
107279
主题
5622
回帖
26655
注册时间
2003-4-10
最后登录
2026-9-16
发表于 半小时前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
1、问题缘起

论坛帖子中的代码块长期没有语法高亮,影响技术帖的阅读体验。尝试过以下方案均未成功:

方案问题
mw_syntaxhighlighter 插件为 X3.4 设计,PHP 8.2 下 create_function() 报错;修复后仍不识别 [ code ] 标签(插件只认 [mw_shl_code]
改造 mw_syntaxhighlighter 适配 [ code ]discuzcode 钩子触发时 [ code ] 已被 Discuz! 内置逻辑解析完毕,钩子拿不到原始标签
zxsq_code 插件付费插件
study_codehighlight 插件付费插件,且 PHP 兼容性标注最高到 7.2


核心矛盾
  • 论坛历史帖子全部使用 Discuz! 内置的 [ code ] 标签
  • 大多数高亮插件只识别自己的专用标签(如 [mw_shl_code]
  • 改造老插件适配 [ code ] 的工作量大、风险高




2、解决方案

(1)简要技术分析

放弃 PHP 插件方案,改用纯前端方案。利用开源库 highlight.js 在浏览器端自动扫描并高亮代码块。核心流程:

  • 结构转换:用 JS 将 Discuz! [ code ] 生成的 <div class="blockcode"> 结构转换为 highlight.js 认识的 <pre><code> 结构
  • 自动高亮:highlight.js 自动识别语言并着色
  • 复制按钮:在代码块右上角添加复制按钮
  • 按需加载:仅在帖子详情页(viewthread)加载,不影响其他页面性能

(2)修改文件列表

操作文件路径说明
修改/template/default/forum/viewthread.htm电脑版帖子详情页
修改/template/default/touch/forum/viewthread.htm手机版帖子详情页


(3)关键代码段

电脑版完整代码(插入 viewthread.htm<!--{template common/footer}--> 之前):

  1. <!--{if CURMODULE == 'viewthread'}-->
  2. <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.12.0/styles/github.min.css">
  3. <style>
  4. .code-highlight-wrapper {
  5.     position: relative;
  6.     margin: 15px 0;
  7.     border: 1px solid #ccc;
  8.     border-radius: 4px;
  9.     background: #f8f8f8;
  10.     overflow: hidden;
  11. }
  12. .code-highlight-wrapper .code-header {
  13.     padding: 6px 12px;
  14.     background: #e8e8e8;
  15.     border-bottom: 1px solid #ccc;
  16.     font-size: 12px;
  17.     color: #666;
  18.     font-family: "Microsoft YaHei", sans-serif;
  19. }
  20. .code-highlight-wrapper pre {
  21.     margin: 0;
  22.     border-radius: 0;
  23.     overflow-x: auto;
  24. }
  25. .code-highlight-wrapper pre code {
  26.     font-family: Consolas, Monaco, "Courier New", monospace;
  27.     font-size: 13px;
  28.     line-height: 1.6;
  29.     padding: 15px;
  30.     display: block;
  31.     white-space: pre;
  32.     background: transparent;
  33. }
  34. .code-copy-btn {
  35.     position: absolute;
  36.     top: 6px;
  37.     right: 6px;
  38.     padding: 4px 10px;
  39.     font-size: 12px;
  40.     color: #666;
  41.     background: rgba(255, 255, 255, 0.9);
  42.     border: 1px solid #ddd;
  43.     border-radius: 3px;
  44.     cursor: pointer;
  45.     opacity: 0;
  46.     transition: opacity 0.2s;
  47.     z-index: 10;
  48.     font-family: "Microsoft YaHei", sans-serif;
  49. }
  50. .code-highlight-wrapper:hover .code-copy-btn {
  51.     opacity: 1;
  52. }
  53. .code-copy-btn:hover {
  54.     background: #f0f0f0;
  55.     color: #333;
  56. }
  57. .code-copy-btn.copied {
  58.     color: #28a745;
  59.     border-color: #28a745;
  60. }
  61. </style>
  62. <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.12.0/highlight.min.js"></script>
  63. <script>
  64. document.addEventListener('DOMContentLoaded', function() {
  65.     var codeBlocks = document.querySelectorAll('div.blockcode');
  66.     codeBlocks.forEach(function(block) {
  67.         var codeDiv = block.querySelector('div[id^="code_"]');
  68.         if (!codeDiv) return;
  69.         
  70.         var lines = codeDiv.querySelectorAll('ol li');
  71.         var codeLines = [];
  72.         lines.forEach(function(line) {
  73.             var lineText = line.innerHTML.replace(/<br\s*\/?>/gi, '');
  74.             var tempDiv = document.createElement('div');
  75.             tempDiv.innerHTML = lineText;
  76.             codeLines.push(tempDiv.textContent.replace(/\s+$/g, ''));
  77.         });
  78.         var codeText = codeLines.join('\n');
  79.         
  80.         var wrapper = document.createElement('div');
  81.         wrapper.className = 'code-highlight-wrapper';
  82.         
  83.         var header = document.createElement('div');
  84.         header.className = 'code-header';
  85.         header.textContent = '代码';
  86.         wrapper.appendChild(header);
  87.         
  88.         var pre = document.createElement('pre');
  89.         var code = document.createElement('code');
  90.         code.textContent = codeText;
  91.         pre.appendChild(code);
  92.         wrapper.appendChild(pre);
  93.         
  94.         var copyBtn = document.createElement('button');
  95.         copyBtn.className = 'code-copy-btn';
  96.         copyBtn.textContent = '复制代码';
  97.         copyBtn.onclick = function() {
  98.             var textToCopy = code.textContent;
  99.             if (navigator.clipboard && navigator.clipboard.writeText) {
  100.                 navigator.clipboard.writeText(textToCopy).then(function() {
  101.                     copyBtn.textContent = '已复制';
  102.                     copyBtn.classList.add('copied');
  103.                     setTimeout(function() {
  104.                         copyBtn.textContent = '复制代码';
  105.                         copyBtn.classList.remove('copied');
  106.                     }, 2000);
  107.                 }).catch(function() {
  108.                     fallbackCopy(textToCopy, copyBtn);
  109.                 });
  110.             } else {
  111.                 fallbackCopy(textToCopy, copyBtn);
  112.             }
  113.         };
  114.         wrapper.appendChild(copyBtn);
  115.         
  116.         block.parentNode.replaceChild(wrapper, block);
  117.     });
  118.    
  119.     function fallbackCopy(text, btn) {
  120.         var textarea = document.createElement('textarea');
  121.         textarea.value = text;
  122.         textarea.style.position = 'fixed';
  123.         textarea.style.opacity = '0';
  124.         document.body.appendChild(textarea);
  125.         textarea.select();
  126.         try {
  127.             document.execCommand('copy');
  128.             btn.textContent = '已复制';
  129.             btn.classList.add('copied');
  130.             setTimeout(function() {
  131.                 btn.textContent = '复制代码';
  132.                 btn.classList.remove('copied');
  133.             }, 2000);
  134.         } catch (e) {
  135.             btn.textContent = '复制失败';
  136.         }
  137.         document.body.removeChild(textarea);
  138.     }
  139.    
  140.     document.querySelectorAll('pre code').forEach(function(el) {
  141.         hljs.highlightElement(el);
  142.     });
  143. });
  144. </script>
  145. <!--{/if}-->
复制代码


手机版完整代码(插入 touch/forum/viewthread.htm<!--{template common/footer}--> 之前):

与电脑版结构相同,仅 CSS 需调整:

  1. .code-highlight-wrapper {
  2.     margin: 10px 0;
  3. }
  4. .code-highlight-wrapper .code-header {
  5.     padding: 5px 10px;
  6. }
  7. .code-highlight-wrapper pre code {
  8.     font-size: 12px;
  9.     line-height: 1.5;
  10.     padding: 10px;
  11. }
  12. .code-copy-btn {
  13.     top: 5px;
  14.     right: 5px;
  15.     padding: 3px 8px;
  16.     font-size: 11px;
  17.     opacity: 1;
  18. }
复制代码


JS 部分完全复用(结构转换 + 高亮 + 复制按钮)。




3、最终实现效果

对比项改造前改造后
代码高亮❌ 无✅ 自动语法着色
语言识别✅ 自动识别(PHP、JS、SQL 等)
复制按钮有(Discuz! 自带)有(右上角,悬停/始终显示)
代码外框✅ 保留
缩进保留✅ 完整保留
加载范围✅ 仅帖子详情页
PHP 依赖✅ 零 PHP 依赖
兼容性✅ 纯前端,兼容 PHP 8.2


电脑版与手机版对比

项目电脑版手机版
字体大小13px12px(更小,适配窄屏)
内边距15px10px(减少留白)
行高1.61.5
按钮文字「复制代码」「复制」(更短)
按钮显示悬停时显示始终显示(手机无悬停)





4、重要技术沉淀

需更新到固化摘要

  1. 二十六、代码高亮方案(2026-09-16)

  2. 方案:highlight.js 前端方案
  3. 加载范围:仅帖子详情页(viewthread.htm 电脑版 + touch/forum/viewthread.htm 手机版)
  4. 核心逻辑:JS 转换 Discuz! [ code ] 结构为 <pre><code>,highlight.js 自动高亮
  5. 复制按钮:右上角,电脑版悬停显示,手机版始终显示
  6. 不依赖 PHP 插件,兼容 PHP 8.2
复制代码


踩坑点

问题原因解决
mw_syntaxhighlighter 不识别 [ code ]只认 [mw_shl_code] 标签放弃插件,改用前端方案
discuzcode 钩子拿不到 [ code ]Discuz! 内置逻辑先解析了改用前端 JS 处理最终 HTML
create_function() 报错PHP 8.2 移除该函数已修复但插件其他逻辑仍不兼容
丢失代码外框替换 blockcode 后未保留样式新建 .code-highlight-wrapper 加边框
每行多出空行<li> 自带换行 + <br> 叠加replace(/\s+$/g, '') 去末尾空白
缩进丢失删了每行开头空白只删末尾,保留开头
代码不换行浏览器折叠连续空格white-space: pre
手机版复制按钮手机无悬停改为始终显示 opacity: 1
手机版结构与电脑版一致,仅 copycode(getID('code_xxx')) 写法不同JS 无需区分,仅调整 CSS


核心经验

  • 优先考虑前端方案:对于 Discuz! 老插件兼容性问题,纯前端方案往往比修复 PHP 插件更简单、更可控
  • 按需加载:只在需要的页面(如 viewthread)加载资源,避免全站性能损耗
  • 结构转换是关键:Discuz! 的 [ code ] 生成结构和高亮库期望结构不同,中间用 JS 转换是最灵活的方式
  • 缩进保留:处理代码文本时,绝不能删除每行开头空白,只删末尾
  • 电脑版和手机版结构一致时,JS 可复用,只需调整 CSS




5、进一步优化的可能性

构想说明优先级
本地化 highlight.js把 JS/CSS 下载到服务器,避免 CDN 依赖⭐⭐
支持暗色主题根据用户偏好自动切换配色
手动指定语言允许在 [code=php] 中指定语言
代码折叠超长代码自动折叠⭐ 低
不懂就搜!点此搜点拨论坛。如果本坛没有,请尝试点此问AI,或跟帖提问。
发帖前注意看置顶帖
不定期借助AI对点拨论坛陈年老帖进行挖坟回复,打扰勿怪!
在已经有人回复时,不要在顶楼添加新问题。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|点拨论坛 |网站地图

GMT+8, 2026-9-16 17:06 , Processed in 0.031574 second(s), 4 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表