Bearweb网页模板

Bearweb网页模板基于Bearweb CMS架构。Bearweb网页模板提供了一套简单、响应式设计的模板来显示网页内容(HTML)。这篇文章展示了如何使用Bearweb网页模板。

--by @ Jan 22, 2026

Index

模板结构

Bearweb网页模板包含3个文件:

使用Bearweb CMS并不需要搭配使用Bearweb网页模板。网页模板只控制前端输出,而Bearweb CMS则只负责后端。你可以创建你自己的模板。尽管如此,仍然推荐参考本模板里模板如何与Bearweb CMS交互。

HTML

网页模板的最终目标就是以HTML网页的格式输出我们的数据(一个PHP的resource对象)。关于resource对象的变量,参考这个文档


<!DOCTYPE html>
<html lang="zh">
	<head>
		<!-- 我的head -->
	</dead>
	<body>
		<!-- 我的body -->
	</body>
</html>
		

一个网页包含两部分:不可见的head部分,主要包含一些机器读的元素据;可见的body部分,将会被渲染并显示给用户。

注意此处我们在<html>中使用lang="en"来表示当前页面的语言。这个例子中为中文,无特定地区。

如果要创建一个多语网站,你将需要为每一个语言都创建一个模板。例如,使用page-zh.php作为中文模板,并包含<html lang="zh">;使用page-en.php作为英文模板,并包含<html lang="en">

如你所见,为多语网站创建多个网页模板给维护带来了不小的麻烦。如果要修改模板,你将需要修改所有文件。一个解决方案是在单一模板中包换所有语言,例如,在page-en.php中,我们可以写:


if ($BW->site->template[0] == 'page-zh')
	echo '<html lang="zh">';
else // if ($BW->site->template[0] == 'page-en') // 默认语言
	echo '<html lang="en">';
				

接下来,创建一个名为page-zh.php的快捷方式,并指向page-en.php

如果使用page-en.php,那么只有template[0] == 'page-en'这个if条件内的代码才会被执行。如果使用page-zh.php,那么将会被链接到page-en.php,并只会执行template[0] == 'page-zh'这个if条件内的代码。

HTML <head>

<head>包含了元素据。Bearweb页面模板将会:


<head>
	<?php
		// $this = resource
		string $domain = 'https://example.com/'
		string $sitename = 'Example Site';
		string $srcDomain = 'https://example.com/';
		$meta_title = htmlspecialchars($this->meta['title'] ?? '', ENT_COMPAT);
		$meta_keywords = htmlspecialchars($this->meta['keywords'] ?? '', ENT_COMPAT);
		$meta_description = htmlspecialchars($this->meta['description'] ?? '', ENT_COMPAT);
	?>
		

要输出<head>,我们可以使用resource对象内的帮助函数public function util_html_head(...)

你可以使用$this->property来获取一个变量,或是使用htmlspecialchars($this->property, ENT_COMPAT)来在输出为HTML前转义变量内的HTML关键字。

另外,在网页模板中,你需要硬编码$domain$sitename和JS文件源的$srcDomain


	<title><?= $meta_title ?> - <?= $sitename ?></title>
	<meta property="og:title" content="<?= $meta_title ?>" />
	<meta property="og:site_name" content="<?= $sitename ?>" />
	<meta property="og:type" content="website" />
	<meta name="keywords" content="<?= $meta_keywords ?>" />
	<meta name="description" content="<?= $meta_description ?>" />
	<meta property="og:description" content="<?= $meta_description ?>" />
		

输出页面标题、站点名、关键字、简述。

对于所有的网页,你都应该在resource对象的meta变量中定义titlekeywordsdescription。例如:


'meta' => [
	'title' => '示例页面的标题',
	'description' => '关于该页面的简介。',
	'keywords' => '关键字1, 关键字2, 关键字3'
]
			

使用英文的逗号(,)(ASCII 0x2C)来间隔关键字。


	<meta name="viewport" content="width=device-width, initial-scale=1.0" />
	<meta charset="utf-8" />
		

使用UTF-8编码(现代网页都应该使用该编码)。

为移动设备创建视口。


	<link href="/web/style.css" rel="stylesheet" type="text/css" />
		

定义样式文件。因为CSS的url()函数使用样式文件的URL作为base URL,因此,我们只能使用与网页相同的域名来提供样式文件。


	<script src="<?= $srcDomain ?>web/bearapi.js"></script>
	<script src="<?= $srcDomain ?>web/bearweb.js"></script>
		

定义JavaScript文件。我们可以使用外部域名来提供这些文件,参考$srcDomain


	<link rel="canonical" href="<?= $domain.$this->url ?>" />
	<meta property="og:url" content="<?= $domain.$this->url ?>" />
		

设置$domain/$this->url为默认地址。这在有多个URL(例如example.com/resourcewww.example.com/resource)指向同一个资源的情况下定义一个规范的地址,搜索引擎将会使用这里显示的地址进行收录。


	<meta name="robots" content="<?=htmlspecialchars($this->meta['robots'], ENT_COMPAT)?>" />
		

禁用爬虫收录/爬取本页面。这里请求爬虫不要收录/爬取,但是有的坏爬虫根本不管。

大多数情况下,你都会希望你的页面被收录。要达到这个目的,在resource对象的meta变量中不要定义robots

如果要禁用爬虫收录/爬取,设置:


'meta' => [
	'robots' => 'noindex'
//	'robots' => 'nofollow'
//	'robots' => 'noindex, nofollow'
]
			

不要使用noindexnofollownoindex, nofollow以外的任何值。其他的值不一定被爬虫理解。


	<meta property="__og:image" content="/<?=htmlspecialchars($this->meta['img'], ENT_COMPAT)?>" />
		

如果在resource对象的meta变量中定义了img,就将展示代表页面的海报。例如,设置'meta' => ['img' => 'my/image.jpg']就可以使用/my/image.jpg作为海报。注意这里的值是绝对地址,且省略开头的“/”。


	<meta name="author" content="<?=htmlspecialchars($this->owner, ENT_COMPAT)?>" />
		

如果owner不为空字符串(系统),就展示作者。


	<link rel="alternate" hreflang="en" href="/<?=htmlspecialchars($this->aux['lang-en'], ENT_COMPAT)?>" type="text/html" />
	<link rel="alternate" hreflang="zh" href="/<?=htmlspecialchars($this->aux['lang-zh'], ENT_COMPAT)?>" type="text/html" />
</head>
		

多语页面的其他语言。

resource对象的meta变量中定义该页面的各种语言版本(包括当前语言)的URL,例如:


'aux' => ['lang-en' => 'url/en', 'lang-zh' => 'url/zh']
			

HTML <body>

一个页面包含3个部分:<header><main><footer>。再加上一些额外的工具项目。


<body>
	<header>
		<!-- 导航 -->
	</header>
		

<header>部分包含了:

  • 站点名 - 点击回到页面顶端。硬编码。
  • 搜索框 - 打开一个包含站点地图的新页面并执行客户端过滤。
  • 菜单 - 其它页面的链接。硬编码。
  • 语言 - 多语页面切换语言,使用resource->meta->lang-*

	<main>
		<?= $BW->site->content ?>
	</main>
		

resource->content中的HTML内容。具体如何渲染需参考resource->template中定义的子模版。

你不该使用htmlspecialchars($BW->site->content, ENT_COMPAT)来转义资源内容,因为资源内容包含的是使用HTML编码好了的网页部分。相反,使用:

  • <?= $BW->site->content ?>echo $BW->site->content; - 输出数据库支持的资源内容。此类资源体积小,例如HTML文件。
  • $this->site->dumpContent(-1, false); - 直接发送文件支持的资源内容到客户端。此类资源体积大,例如图片。大部分情况下你都不会在网页模板中使用这个方法,毕竟没哪个疯子往HTML中赛那么多内容。

取决于Bearweb_Site::Size_FileBlob设置(默认100kB),一个资源可能会是数据库支持或文件支持的。使用错误的方法仍然工作(除了极端情况下会爆内存),但是不利于性能。参考我的这一篇博客


	<footer>
		<!-- 版权 -->
		<!-- 链接 -->
	</footer>
		

<footer>部分硬编码了其它链接和版权信息。具体要放什么内容可以自由发挥。


	<div>
		<!-- utils -->
	</div>
		

其它工具项目,例如modal()

响应式设计

Bearweb网页模板使用完全客户端的响应式设计。也就是说,Bearweb网页模板将不会知道访客使用的设备,无论访客使用的是电脑还是手机,Bearweb网页模板都将发送相同的HTML。(是的,我就是很讨厌那种区分电脑版和移动版的网站,那是对访客的绑架行为!)接下来,客户端通过使用样式表来合理对页面进行排版。

这里分为3个断点

小屏(窄屏)
小屏(窄屏)
中屏
中屏
大屏(宽屏)
大屏(宽屏)

子模版

Bearweb网页模板内置了一些子模版。通过将resource对象的template变量设置为数组['page', 'x'],就可以使用文件名为x的子模版。例如,['page-en', 'direct'],Bearweb CMS将会使用Bearweb_Site::Dir_Template文件夹中的page-en.php模板,接下来,page-en.php将会使用Bearweb_Site::Dir_Template文件夹中的page-en_direct.php子模版。

正如你所见,这将需要执行2次PHP文件的“打开-编译-执行”操作,这对性能来说是不利的。虽然Apache2和PHP可以缓存文件路径与二进制程序,“执行”操作仍然是一个额外负担。因此,我么可以将一些常用的子模版集成到页面模板中:


<?php if ($BW->site->template[1] == 'template1'): ?>
	<div class="template1"><?= $BW->site->content ?></div>
<?php elseif ($BW->site->template[1] == 'template2'): ?>
	<div class="template2"><?= $BW->site->content ?></div>
<? else:
//	$template = Bearweb_Site::Dir_Template.'page_'.$this->site->template[1].'.php';
//	if (file_exists($template))
//		include $template;
//	else
		throw new BW_WebServerError('Secondary page template not found: '.$this->site->template[1], 500);
?>
		

如果该页面模板集成了该子模版,Bearweb就会执行该子模版的if条件内的代码。否则,Bearweb可以(可选)尝试在Bearweb_Site::Dir_Template文件夹内查找并执行该模板。如果该子模版不存在,Bearweb将会报服务端错误。

直接输出['page-x', 'direct']

直接输出子模版
直接输出子模版

直接在网页模板中输出resource对象的content变量的内容。该截屏由如下代码生成:


'page/direct/zh' => [
	'category' => 'Template',
	'create' => 1769055184,
	'modify' => 1769055184,
	'template' => ['page-zh','direct'],
	'meta' => [
		'title' => '示例页面',
		'description' => '一个示例页面',
		'keywords' => '网页, HTML',
		'img' => 'web/banner.jpeg',
	],
	'content' => '
		<div class="main_title">
			<h1>示例页面</h1>
		</div>
		<div>
			<h2>标题1</h2>
			<p>内容1</p>
		</div>
		<div>
			<h2>标题2</h2>
			<p>内容2</p>
		</div>
	',
	'aux' => [
		'lang-en' => 'page/direct/en',
		'lang-zh' => 'page/direct/zh'
	]
];
			

此处打开示例:直接输出子模版。

文章['page-x', 'article']

文章子模版
文章子模版

显示resource对象的content变量的内容。下面的代码和上面direct直接输出子模版使用的代码完全相同,但是渲染出来的结果不同:


'page/article/zh' => [
	'category' => 'Template',
	'create' => 1769055184,
	'modify' => 1769055184,
	'template' => ['page-zh','article'],
	'meta' => [
		'title' => '示例页面',
		'description' => '一个示例页面',
		'keywords' => '网页, HTML',
		'img' => 'web/banner.jpeg',
	],
	'content' => '
		<div class="main_title">
			<h1>示例页面</h1>
		</div>
		<div>
			<h2>标题1</h2>
			<p>内容1</p>
		</div>
		<div>
			<h2>标题2</h2>
			<p>内容2</p>
		</div>
	',
	'aux' => [
		'lang-en' => 'page/article/en',
		'lang-zh' => 'page/article/zh'
	]
];
			

可以看到在content的前面增添了:

  1. 标题,根据$resource->meta->title得到。
  2. 简述,根据$resource->meta->description得到。
  3. 关键字,根据$resource->meta->keywords得到,并会分割为彩色的#块。
  4. 作者,根据$resource->owner得到。
  5. 时间戳,根据$resource->create$resource->modify得到。
  6. 其它语言,根据$resource->lang-*得到。
  7. 文章目录,根据文章内的<h*>在用户端生成。点击目录可以跳转到相应章节。
  8. 所有这些都将被放在一个以$resource->meta->img作为背景图片的项目内。

此处打开示例:文章子模版。

图片['page-x', 'image']

图片子模版
图片子模版

图片详情。image子模版和article子模版相似,但不同在于以下3点:

  • img(缩略图) - 对于大图,下载该文件通常花费一定时间。因此,我们可以考虑使用该图片的缩略图。当加载这个页面时,我们先显示这张体积小但下载快的缩略图。虽然图片模糊,但是至少可以让访客看个大概。(不然的话,访客没有耐心等到大图下载,直接关掉页面跑路了。)同时,我们在后台慢慢加载这张高清大图。
  • hd - 高清大图。
  • ratio - 图片比例(宽度/高度)。这个数据方便浏览器在下载图片获得图片的分辨率前就可以正确预留空间排版,主要是为了方便生成照片墙。

'page/image/zh' => [
	'category' => 'Template',
	'create' => 1769055184,
	'modify' => 1769055184,
	'template' => ['page-zh','image'],
	'meta' => [
		'title' => '示例图片',
		'description' => '该图片展示了Bearweb CMS的源码,这张图片在此被用作一个例子。',
		'keywords' => '源代码, 网站开发',
		'img' => 'web/banner.thumb.jpeg',
		'hd' => 'web/banner.jpeg',
		'ratio' => 1.33333
	],
	'content' => '
		<div>
			<p>可选内容</p>
		</div>
	',
	'aux' => [
		'lang-en' => 'page/image/en',
		'lang-zh' => 'page/image/zh'
	]
];
			

对于不需要缩略图的小图片,使用图片的URL作为imghd的值。这样才能正确渲染这个页面并且提供正确的Open Graph图片元素据。

此处打开示例:图片子模版。

索引['page-x', 'catalog']

索引子模版
索引子模版

使用这个子模版来显示索引(文章列表),例如:


<div class="list_horizontal" id="categorylist">
	<a href="/example/tree.zh.html" style="--bgimg:url(/example/tree.thumb.jpg)">
		<h2>樱</h2>
		<p>夜晚的樱花树</p>
		<div class="keywords">樱花, 夜景</div>
		<p><i>--by Captdam @ Jan 22, 2026</i></p>
	</a>
	<!-- More elements -->
</div>
			

该页面将有且只有一个<main>下面的<div id="categorylist">项目表示索引列表。这个项目内,你可以有零个或任意数量个<a>项目,每一个<a>都代表一篇文章,且包含:

  • href - 文章(当然也可以时其它类型资源)的链接。
  • bgimg - 背景图片,可以使用文章的resource->meta->img变量。
  • h2 - 标题,可以使用文章的resource->meta->title变量。
  • p - 简述,可以使用文章的resource->meta->description变量。
  • keywords - 背景图片关键字,可以使用文章的resource->meta->keywords变量。
  • p - 作者与时间戳,可以使用文章的resource->ownerresource->createresource->modify变量。

另外,你可以使用页眉的搜索栏对资源进行内容(标题、简述、关键字)过滤。

此处打开示例:索引子模版。

照片墙['page-x', 'bulletin']

照片墙子模版
照片墙子模版

使用这个子模版来显示照片墙(图片列表),例如:


<div class="list_vertical" id="bulletinlist">
	<a href="/example/tree.zh.html" style="aspect-ratio:0.97552">
		<img src="/example/tree.thumb.jpg" title="樱" alt="夜晚的樱花树。" loading="lazy">
	</a>
	<!-- More elements -->
</div>
			

该页面将有且只有一个<main>下面的<div id="bulletinlist">项目表示索引列表。这个项目内,你可以有零个或任意数量个<a>项目,每一个<a>都代表一张图片,且包含:

  • href - 图片页面(当然也可以时其它类型资源)的链接。
  • aspect-ratio - 方便浏览器在下载图片获得图片的分辨率前就可以正确预留空间排版。
  • img - 缩略图,可以使用图片页面的resource->meta->img变量。
  • title - 标题,可以使用图片页面的resource->meta->title变量。
  • alt - 简述,可以使用图片页面的resource->meta->description变量。

为了减小带宽消耗,可以使用loading="lazy",只有当需要时才会下载图片。

这里没有搜索栏,因为照片墙不显示文字。

此处打开示例:照片墙子模版。

语言选择['page-x', 'langsel']

语言选择子模版
语言选择子模版

判断访客语言的着陆页。

你必须在resource对象的aux变量中指定目标语言的URL,例如:


'' => [
	'category' => 'Home',
	'template'=> ['page-en','langsel'],
	'aux' => ['lang-en' => 'en', 'lang-zh' => 'zh']
];
			

注意这里的URL都是绝对地址,且省略开头的“/”。

如果访客浏览器提交的ACCEPT-LANGUAGE HTTP头中的一个语言与lang-*中一项匹配,服务器将会返回一个302 Found头来发起跳转到合适的URL。如果lang-*中没有匹配项,服务器将会返回一个网页罗列可选的语言项目让访客选择。

如果有多个语言项目都匹配,那么ACCEPT-LANGUAGE中的第一项(最高的q值)将会被使用。

不推荐(但是你可以)在resource对象的其它变量中存放信息。这些信息不会被模板使用,但会拖累数据库IO与内存占用量。

当你想要分享一个多语言的网页时,分享这个着陆页或许是一个不错的选择,这让服务器可以根据访客语言提供合适的内容,但有悖于统一性。

此处打开示例:主页着陆页,支持“en”与“zh”。

错误

错误子模版
错误子模版

Bearweb CMS使用该子模版来显示错误信息:

  • 它包括了在通过throw new BW_Error('message', 123);抛出错误时设置的错误的名称与信息。出于安全考虑,你可以通过设置Bearweb::HideServerError来隐藏BW_ServerError类错误的名称与信息。
  • 它包括当前请求ID。你可以通过请求ID在对话数据库中查找到这个请求。

不要为你的资源使用这个子模版。

此处打开示例:链接指向不存在的资源。