活力39246
在线时间13595 小时
阅读权限200
管理员
自由的灵魂
- 积分
- 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}--> 之前):
- <!--{if CURMODULE == 'viewthread'}-->
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.12.0/styles/github.min.css">
- <style>
- .code-highlight-wrapper {
- position: relative;
- margin: 15px 0;
- border: 1px solid #ccc;
- border-radius: 4px;
- background: #f8f8f8;
- overflow: hidden;
- }
- .code-highlight-wrapper .code-header {
- padding: 6px 12px;
- background: #e8e8e8;
- border-bottom: 1px solid #ccc;
- font-size: 12px;
- color: #666;
- font-family: "Microsoft YaHei", sans-serif;
- }
- .code-highlight-wrapper pre {
- margin: 0;
- border-radius: 0;
- overflow-x: auto;
- }
- .code-highlight-wrapper pre code {
- font-family: Consolas, Monaco, "Courier New", monospace;
- font-size: 13px;
- line-height: 1.6;
- padding: 15px;
- display: block;
- white-space: pre;
- background: transparent;
- }
- .code-copy-btn {
- position: absolute;
- top: 6px;
- right: 6px;
- padding: 4px 10px;
- font-size: 12px;
- color: #666;
- background: rgba(255, 255, 255, 0.9);
- border: 1px solid #ddd;
- border-radius: 3px;
- cursor: pointer;
- opacity: 0;
- transition: opacity 0.2s;
- z-index: 10;
- font-family: "Microsoft YaHei", sans-serif;
- }
- .code-highlight-wrapper:hover .code-copy-btn {
- opacity: 1;
- }
- .code-copy-btn:hover {
- background: #f0f0f0;
- color: #333;
- }
- .code-copy-btn.copied {
- color: #28a745;
- border-color: #28a745;
- }
- </style>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.12.0/highlight.min.js"></script>
- <script>
- document.addEventListener('DOMContentLoaded', function() {
- var codeBlocks = document.querySelectorAll('div.blockcode');
- codeBlocks.forEach(function(block) {
- var codeDiv = block.querySelector('div[id^="code_"]');
- if (!codeDiv) return;
-
- var lines = codeDiv.querySelectorAll('ol li');
- var codeLines = [];
- lines.forEach(function(line) {
- var lineText = line.innerHTML.replace(/<br\s*\/?>/gi, '');
- var tempDiv = document.createElement('div');
- tempDiv.innerHTML = lineText;
- codeLines.push(tempDiv.textContent.replace(/\s+$/g, ''));
- });
- var codeText = codeLines.join('\n');
-
- var wrapper = document.createElement('div');
- wrapper.className = 'code-highlight-wrapper';
-
- var header = document.createElement('div');
- header.className = 'code-header';
- header.textContent = '代码';
- wrapper.appendChild(header);
-
- var pre = document.createElement('pre');
- var code = document.createElement('code');
- code.textContent = codeText;
- pre.appendChild(code);
- wrapper.appendChild(pre);
-
- var copyBtn = document.createElement('button');
- copyBtn.className = 'code-copy-btn';
- copyBtn.textContent = '复制代码';
- copyBtn.onclick = function() {
- var textToCopy = code.textContent;
- if (navigator.clipboard && navigator.clipboard.writeText) {
- navigator.clipboard.writeText(textToCopy).then(function() {
- copyBtn.textContent = '已复制';
- copyBtn.classList.add('copied');
- setTimeout(function() {
- copyBtn.textContent = '复制代码';
- copyBtn.classList.remove('copied');
- }, 2000);
- }).catch(function() {
- fallbackCopy(textToCopy, copyBtn);
- });
- } else {
- fallbackCopy(textToCopy, copyBtn);
- }
- };
- wrapper.appendChild(copyBtn);
-
- block.parentNode.replaceChild(wrapper, block);
- });
-
- function fallbackCopy(text, btn) {
- var textarea = document.createElement('textarea');
- textarea.value = text;
- textarea.style.position = 'fixed';
- textarea.style.opacity = '0';
- document.body.appendChild(textarea);
- textarea.select();
- try {
- document.execCommand('copy');
- btn.textContent = '已复制';
- btn.classList.add('copied');
- setTimeout(function() {
- btn.textContent = '复制代码';
- btn.classList.remove('copied');
- }, 2000);
- } catch (e) {
- btn.textContent = '复制失败';
- }
- document.body.removeChild(textarea);
- }
-
- document.querySelectorAll('pre code').forEach(function(el) {
- hljs.highlightElement(el);
- });
- });
- </script>
- <!--{/if}-->
复制代码
手机版完整代码(插入 touch/forum/viewthread.htm 的 <!--{template common/footer}--> 之前):
与电脑版结构相同,仅 CSS 需调整:
- .code-highlight-wrapper {
- margin: 10px 0;
- }
- .code-highlight-wrapper .code-header {
- padding: 5px 10px;
- }
- .code-highlight-wrapper pre code {
- font-size: 12px;
- line-height: 1.5;
- padding: 10px;
- }
- .code-copy-btn {
- top: 5px;
- right: 5px;
- padding: 3px 8px;
- font-size: 11px;
- opacity: 1;
- }
复制代码
JS 部分完全复用(结构转换 + 高亮 + 复制按钮)。
3、最终实现效果
| 对比项 | 改造前 | 改造后 | | 代码高亮 | ❌ 无 | ✅ 自动语法着色 | | 语言识别 | — | ✅ 自动识别(PHP、JS、SQL 等) | | 复制按钮 | 有(Discuz! 自带) | 有(右上角,悬停/始终显示) | | 代码外框 | 有 | ✅ 保留 | | 缩进保留 | — | ✅ 完整保留 | | 加载范围 | — | ✅ 仅帖子详情页 | | PHP 依赖 | — | ✅ 零 PHP 依赖 | | 兼容性 | — | ✅ 纯前端,兼容 PHP 8.2 |
电脑版与手机版对比:
| 项目 | 电脑版 | 手机版 | | 字体大小 | 13px | 12px(更小,适配窄屏) | | 内边距 | 15px | 10px(减少留白) | | 行高 | 1.6 | 1.5 | | 按钮文字 | 「复制代码」 | 「复制」(更短) | | 按钮显示 | 悬停时显示 | 始终显示(手机无悬停) |
4、重要技术沉淀
需更新到固化摘要:
- 二十六、代码高亮方案(2026-09-16)
- 方案:highlight.js 前端方案
- 加载范围:仅帖子详情页(viewthread.htm 电脑版 + touch/forum/viewthread.htm 手机版)
- 核心逻辑:JS 转换 Discuz! [ code ] 结构为 <pre><code>,highlight.js 自动高亮
- 复制按钮:右上角,电脑版悬停显示,手机版始终显示
- 不依赖 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] 中指定语言 | ⭐ | | 代码折叠 | 超长代码自动折叠 | ⭐ 低 |
|
|