把博客升级到 Typecho 1.3 之后,遇到一个挺隐蔽的问题:顶部导航栏和移动端侧边栏里的「独立页面」链接全部失效了,鼠标放上去能看到链接,点下去却总是回到首页分类菜单、自定义菜单、搜索结果、文章列表全都正常,只有独立页面(page 类型)出问题
问题现象
- 后台「管理 → 独立页面」里页面一切正常,状态是「公开」;
- 直接在浏览器里手输 /archives/about.html 能正常打开
- 但导航栏里的「关于」「留言板」点下去,地址栏变成首页(https://www.azrb.cn/)
- 移动端侧边栏(offcanvas)同样的问题
- 把主题换成官方 default,独立页面链接又能跳了 —— 说明问题在主题,不在 Typecho 本体
根因
Cuteen 主题的导航栏由 include/navbar.php 渲染,取页面列表的代码长这样:
$this->widget('Widget_Contents_Page_List')->to($pages);再看一下 Typecho 的 var/Typecho/Widget.php 就会发现问题所在:
// 1.3 中属性是「读的时候才算」的
public function __get(string $name)
{
$method = '___' . $name;
$key = '#' . $name;
if (array_key_exists($key, $this->row)) {
return $this->row[$key];
} elseif (method_exists($this, $method)) {
$this->row[$key] = $this->$method(); // ← permalink 在这里才算出来
return $this->row[$key];
}
// ...
return null;
}permalink 不是数据库字段,它由 ___permalink() → ___path() → Router::url() 动态解析路由得到(1.3 里这个方法还带了返回类型声明):
protected function ___permalink(): string
{
return Common::url($this->path, $this->options->index);
}也就是说:
- permalink 只有在当前行通过 $widget->next() 被激活、并且被 __get() 访问过之后,才会被算出来、写进 $this->row;
- 直接 foreach ($pages->stack as $item) 拿到的是原始行数组,里面只有 cid / title / slug / type 这些数据库字段,根本没有 permalink 这个键;
- 于是 $item['permalink'] 取到 null,模板输出成 href="";
- 浏览器对空 href 的处理就是「当前页面」→ 首页,看起来就像「点了没反应」
修复方法
思路:改用官方迭代接口 $pages->next() 安全遍历,在遍历过程中把 slug / permalink / title 这些「解析后」的值转存成普通数组,再基于数组输出。这样既能拿到正确的链接,也不依赖 stack、不依赖版本内部实现
- 修改文件
usr/themes/Cuteen/core/Context.php - 找到 PageEcho(),整体替换为
/**
* @param $s
* @param $pages
* @return void
* 输出页面
*/
public static function PageEcho($s, $pages)
{
// [兼容 Typecho 1.3] 1.3 起 permalink / slug 等属性改为动态解析,
// 直接读组件底层的 $pages->stack 拿不到解析后的链接(href 为空,被浏览器
// 回退成首页地址)。这里改为用官方 API $pages->next() 安全遍历并转存成数组,
// 再基于该数组输出,向下兼容 Typecho 1.2 及更早版本。
$pageItems = array();
while ($pages->next()) {
$pageItems[] = array(
'slug' => $pages->slug,
'permalink' => $pages->permalink,
'title' => $pages->title,
);
}
if (Helper::options()->MergePages) {
$str = '<li class="nav-item dropdown">';
$str .= '<a class="nav-link dropdown-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false">' . Helper::options()->MergePages . '</a>';
$str .= '<ul class="dropdown-menu">';
foreach ($pageItems as $child) {
$str .= ' <li><a class="dropdown-item" href="' . $child['permalink'] . '">' . $child['title'] . '</a></li>';
}
$str .= '</ul></li>';
echo $str;
} else {
foreach ($pageItems as $item) {
echo Context::MenuTag($item['permalink'], $item['title']);
}
}
}改动就两点:两个 foreach ($pages->stack ...) 全部不再出现,数据统一来自 $pageItems。其余输出逻辑、MergePages 折叠菜单的行为都保持原样,且能同时兼容 1.2 和 1.3
- 或直接下载文件覆盖Context.php