零基础用 WorkBuddy 手搓一个 Typecho 主题

📋 文章目录

故事是这么开始的

先交代一下背景:我完全没系统学过代码,博客用的是 Typecho,主题是从别人那抄来改的。

有一天,我打开自己的博客,准备写点东西。

然后我就看到了这个:

Array to string conversion in Widget.php on line 434
Deprecated: strtolower(): Passing null to parameter #1

啥玩意?我就打开个后台,咋还给我报错呢?

点开首页,好家伙,头部一行白字警告,底部评论区空空如也,CSS 样式表到处 404,页面丑得像个没装修完的出租屋。

说实话,这些问题单独拎出来都不算事儿。但凑一块儿,就让人头皮发麻。


第一次挣扎:能修就修

作为一个没有系统学过代码的人,我的第一反应是:能修就修呗

翻了翻报错信息,大概知道问题出在哪儿了。

简单说就是:Typecho 里有些方法会返回"一串数据"(数组),但代码里直接把它当"一段文字"来用了。PHP 8.3 比较严格,不惯这毛病,直接报错。

我的思路是——给这些容易出问题的地方包一层"安全套"。

function safeEcho($fn) {
    ob_start();
    $fn();
    $result = ob_get_clean();
    // 如果输出的是 "Array" 这个鬼东西,说明返回了数组而不是字符串,扔掉
    if ($result === 'Array') return '';
    echo $result;
}

然后把所有可能炸的地方都改成这个写法。

另外还有一个坑——代码里到处都是这种三元运算符:

echo $condition ? $widget->method() : '默认值';

这玩意的逻辑有点绕,出问题不好定位。我一咬牙,全改成 if/else。虽然丑了点,但至少能看懂。


为什么我修了五轮还是没修好

修完第一波,我满怀信心地打包上传。

结果呢?

第一轮:分类显示修好了,标签炸了。

第二轮:标签修好了,评论挂了。

第三轮:评论修好了,RSS 输出乱码。

第四轮、第五轮……

我心态崩了。

后来想明白了个事儿:这套代码没有统一的安全层。每个地方各修各的,补丁越打越多,逻辑越来越乱。修这里忘了那里,补那里又忘了这里。最后连我自己都搞不清楚代码是怎么跑的了。


痛定思痛:推倒重来

4月7号那天,我看着第十次报错,终于下定决心:

不修了,重写。

这次我学乖了,先想清楚要怎么做,再动手。

第一个决定:底层函数必须统一

之前的问题根源是:代码里东一块西一块,没有统一标准。

这次我建了几个核心函数,所有涉及到"读取数据"、"输出到页面"的地方,都必须走这几个函数:

函数干啥的
wechan_str()把任何东西转成字符串,万一遇到 null、数组、对象,通通返回空
wechan_content()安全读取文章内容,万一读取失败还有个保底方案
wechan_url()安全获取链接
wechan_esc()把 HTML 特殊字符转义,防止显示错乱
wechan_stats()直接查数据库,绕过 Typecho 那些不稳定的方法

说白了就是在"数据"和"页面"之间,加一层安检。

第二个决定:不依赖 Typecho 的复杂功能

Typecho 自带的 Widget 类功能很强大,但问题也多。同样一段代码,在这个版本能用,换个版本可能就炸了。

我的方案是:能自己写的就不麻烦它。统计数量我自己查数据库,相邻文章我自己写 SQL。虽然代码多了点,但胜在稳定。


更大的坑:评论系统它又炸了

首页和文章页修完,我以为胜利在望。

然后我去测了一下评论区。

Bug 1:点了发布没反应

我填好评论,点"发布"。

浏览器转了一下。

然后……啥也没发生。

翻了半天代码,发现是表单的"提交地址"是空的。代码用了 commentUrl() 这个方法,但它有时候会返回一个数组,不是字符串。HTML 表单拿到一个数组,也不知道往哪发,干脆就往当前页面发,但当前页面又不处理评论,所以就像往大海里扔漂流瓶一样——一去不回。

解决方案:自己拼一个靠谱的地址。

<?php echo $this->options->siteUrl; ?>action/comments?do=comment

这是 Typecho 的标准接口,稳定可靠,用它!

Bug 2:回复不显示

第一层评论能出来,但点"回复"之后,新评论就是不出来。

这回的问题是嵌套评论的渲染逻辑。Typecho 默认主题用了个叫 threadedComments() 的方法,里面有个回调函数专门处理子评论。但这回调函数里有个隐藏 bug——它执行的时候,$this->options 可能为空。一旦代码里用到了这个选项,分分钟报错。

我的解法是:自己写递归渲染函数

function renderWechatComments($allComments, $parentCid, $depth = 1) {
    // 先把所有评论按"parent"字段分组
    // 从顶级评论(parent=0)开始,一层层往下渲染
    // 每层最多显示2层,再深的就不管了
}

这样完全不依赖 Typecho 内部那些不稳定的东西,渲染逻辑全在自己手里。


样式也顺手改了一下

功能修完了,总算能用了。但我看着评论区那个简陋的样子,总觉得差点意思。

正好我一直挺喜欢微信公众号的留言区风格,就顺手改了一下:

  • 头像改成圆形,加个边框
  • 评论内容放在头像右边
  • 回复内嵌在父评论下面,灰色背景+绿色左边框,看着层次分明
  • 时间改成中文格式,比如"2026年4月8日 20:30"

手机上看着也舒服多了,终于不像是个半成品了。


结尾:一个意外的小 bug

本以为大功告成了,结果有个朋友告诉我:电脑上点"明暗切换"按钮没反应,但手机上好好的。

我又翻了半天代码,发现了一个很傻的问题——

JavaScript 脚本在 <head> 里执行,但切换按钮在 <body> 里。脚本尝试给按钮绑定点击事件的时候,按钮根本还没加载进页面,所以绑定失败,啥反应也没有。

手机上能工作是因为移动端用的另一套逻辑。

解法很简单:把按钮绑定移到页面底部,等按钮加载完再绑定就好了。


最后说两句

这次折腾下来,我的感受是:

做技术项目,最忌讳两种心态——要么"凑合能用就行",要么"必须一步到位"。

"凑合"的结局是历史欠债越积越多,直到有一天炸了收不了场。"一步到位"的结局往往是改着改着,连原本能跑的功能也弄丢了。

这次我的做法是:小步快跑,每修一个问题就打包验证,不行就回退。慢是慢了点,但稳。

就一个博客主题而言,这套方法够用了。

发表评论

邮箱不会被公开。评论可能需要审核后显示。