简体中文简体中文
EnglishEnglish
简体中文简体中文

《模板开发》

1.MVC开发模式

M模型 - V视图 - C控制器

M模型

用于控制器与视图数据存储交互的object对象称之为模型,控制器将数据放置在一个object对象中,然后传递给视图使用,视图有了模型对象,就可以在模板中使用变量,然后让同一个模板呈现不同的网页内容。

V视图

用于用户查看的效果和操作的界面,视图主要就是开发模板,编写模板文件,将模型用不同的视觉效果呈现给用户

C控制器

用于处理业务逻辑和数据库数据,并将处理好的结果放置在object对象中,然后传递给视图渲染出来

2.模板语法

采用art-template模板引擎,拓展了最简约的模板语法,可通过注释的方式来实现模板逻辑,避免不符合html-css-js的语法造成结构混乱。

语法主要分为4部分组成:使用变量、条件判断、循环输出、引用组件,使用简单,易学易上手

使用变量

${xxx} 此结构用于输出文本变量

${@xxx} 此结构用于含html标签的变量

${globalBag.xxx} 输出全局变量,可以通过${globalBag}查看有哪些全局变量,全局变量为所有用户都一样的变量,例如客服QQ、联系方式等,不须因人或因业务而变得变量

${viewBag.xxx} 输出局部变量,通常为个人信息、使用的主题模板、风格、系统语言等,通过函数钩子传递到页面上的变量

${viewBag.theme.xxx} 是主题变量,可以在后台给主题添加变量,然后在模板中通过该方式使用

${xxx} 输出私有变量,通过控制器执行业务后传递到试图模板的变量,用于输出指定页面业务的内容,例如文章列表、文章详情页等

判断条件

由if()如果、else if()否则如果、else否则和/if结束如果组成,条件判断可以让内容在符合条件的情况下才显示,例如用户评论过文章,才显示某些内容。


<!--{ if() }-->
<!--{ else if() }-->
<!--{ else }-->
<!--{ /if }-->
							    

在html注释语法<!---->的基础上,增加了{}或[],是我们常用的逻辑模板语法。


<!--[ if() ]-->
<!--[ else if() ]-->
<!--[ else ]-->
<!--[ /if ]-->
							

除此之外,还支持以下js和css注释等语法:


<style>
/*[ if() ]*/
/*[ else if() ]*/
/*[ else ]*/
/*[ /if ]*/
</style>
							

循环输出

用于数组循环输出,适用于显示文章列表、排行榜等,支持loop和for循环两种方式


<ul>
	<!--{loop arr value idx}-->
	<li>${ value }</li>
	<!--{/loop}-->
</ul>
								

比起循环字符串,更多的是循环对象


<ul>
	<!--{loop list o i}-->
	<li>${ o.name }</li>
	<!--{/loop}-->
</ul>
								

通常我们使用loop来实现循环输出,但是for循环可以倒序输出


<ul>
	<!--[ for(var i = arr.length - 1; i >= 0; i--) { ]-->
	<li>${ arr[i] ]</li>
	<!--[ } ]-->
</ul>
								

引用组件

一个网站会存在很多相同或相似内容的部分(例如:页头、页脚),为了方便使用这部分相同内容,可以通过引用语法直接引用其他文件的代码


<div>
${@view('./common/header.html', model)}
</div>
							

除了可以引用文件,还可以动态渲染字符串,可以将模板字符串先保存到数据库,然后用渲染语法显示到页面


<div>
${@render(obj.code, model)}
</div>
							

在引用组件时,model模型是非必传的,如果不传则使用从控制器来的相同模型,如果传了,则相同对象属性会被替换

引用挂件

挂件是挂载在某个页面的热拔插组件,可在已有模板的某个位置展现内容。需要模板支持挂件,可以在指定位置加入html注释标签,格式如:<!-- #main -->


							
<html>
	<body>
		<header><!-- #header --></header>
		<main>
			<div class="mm_container">
				<aside><!-- #aside --></aside>
				<article><!-- #article --></article>
				<div class="mm_row">
				<!-- #main -->
				</div>
			</div>
		</main>
		<footer><!-- #footer --></footer>
	</body>
</html>
							

3.钩子函数

很多时候,单靠控制器处理业务逻辑不够。但在模板文件中要做复杂的逻辑并不方便,因此可将处理逻辑事务交由钩子函数执行。

行为钩子

行为钩子可让多个业务模块将内容输入到其中,然后统一输出。插件的导航就是通过行为钩子挂载,可随着插件的启用/关闭决定是否显示导航


<div>
${@hook_action('nav_main', 'home')}
</div>
							

行为钩子可在任何时候添加,只需要用$.hook.addAction('xxx', callback)注册,xxx须和页面调用时一致。


/**
 * 导航追加链接
 * @param {Object} data 数据
 * @param {String} type 类型标记
 * @returns {String} 返回页面所需html字符串
 */
$.hook.addAction('nav_main', function nav_main_demo(data, type) {
	return '插件示例';
});
							

过滤钩子

过滤钩子和行为钩子类似,但是输出的内容会被替换,可用于过滤文章的违禁词等。通过$.hook.addFilter('xxx', callback)注册


<div>
${@hook_filter('content')}
</div>
							

函数钩子

用于业务脚本文件中,可将数据添加到模型中,提供给视图一并渲染。通过$.hook.addFunc('xxx', callback)注册


/**
 * SEO优化
 * @param {Object} viewBag 视图背包
 * @param {Object} req 请求参数
 * @param {String} type 类型
 */
$.hook.addFunc('seo', async function seo_main(viewBag, req, type) {
	var list = $.globalBag.seo;
	var p = req.path;
	var seo = {
		title: "",
		description: "",
		keywords: ""
	};
	for (var i = 0; i < list.length; i++) {
		var o = list[i];
		if (o.path && p.indexOf(o.path) === 0) {
			seo = o;
			break
		}
	}
	viewBag.seo = seo;
	return viewBag;
});
							

4.其他语法

除了之前常用到的语法,我们还可能碰到一些情况,可能需要处理一些简单的逻辑,或者先不处理等前端处理等。

使用公式

为了丰富页面展示效果,我们可能需要模板上做一些简单的逻辑,例如:排行榜,炫彩CSS,此时我们就可以公式来实现


<!--{loop list o i}-->
<div class="color_${i % 6}">
${ i + 1 }${ o.name }
</div>
<!--{/loop}-->
							

不做渲染

在“${”的前面加“\”可以不做渲染,同样的在其他的语法标签前面加\也可以不做渲染。通常适用于前后端混编,例如js自带模板语法`${xxx}`


// 渲染前
var o = {name: "张三", age: 18};
var str = `姓名:\${o.name},年龄:\${o.age}`;
console.log(str);
							
// 渲染后
var o = {name: "张三", age: 18};
var str = `姓名:${o.name},年龄:${o.age}`;
console.log(str);
							

5.与MVVM混编

主流开发模式有MVC和MVVM,MVC和MVVM最大不同之处是先渲染成页面代码,然后直接通过浏览器查看。而MVVM模式则是先拿到逻辑代码,再在浏览器执行逻辑渲染成页面。

MVC模式对于搜索引擎收录更加友好,而MVVM则对开发操作更加方便

国内主流的MVVM框架有Vue、Angular、React框架,在此我们以vue + art-template实现混编

vue + art-template

混编可以让一些内容直接放在在访问的html中,可以避免浏览器二次请求,同时让请求更安全,例如将token直接传入。


<html>
	<head>
		<script src="/js/vue/vue2.js"></script>
	</head>
	<body>
		<div id="app" class="page">
			您的临时访问牌(token)为: {{ token }}
		</div>
		<script>
			var app = new Vue({
				el: "#app",
				data() {
					return {
						token: "${user.token}"
					}
				}
			});
		</script>
	</body>
</html>
							

传递参数

除了可以向vue传递字符串,还可以直接传递整个对象


// 渲染前
<div id="app" class="page">
	您的账号为: {{ user.username }}
</div>
<script>
	var app = new Vue({
		el: "#app",
		data() {
			return {
				user: ${user}
			}
		}
	});
</script>
// 渲染后
<div id="app" class="page">
	您的账号为: {{ user.username }}
</div>
<script>
	var app = new Vue({
		el: "#app",
		data() {
			return {
				user: {"user_id":1,"gm":5,"vip":5,"avatar":"/img/avatar.png","nickname":"测试","username":"demo"}
			}
		}
	});
</script>
							

需要注意的是${user}并不是js直接支持的语法,如果使用后进行格式化代码可能会出现BUG。因此最好是先渲染为js模板字符串,然后再转为对象


// 渲染前
var app = new Vue({
	el: "#app",
	data() {
		return {
			user: JSON.parse(`${user}`)
		}
	}
});
// 渲染后
var app = new Vue({
	el: "#app",
	data() {
		return {
			user: JSON.parse(`{"user_id":1,"gm":5,"vip":5,"avatar":"/img/avatar.png","nickname":"测试","username":"demo"}`)
		}
	}
});
							

也许你会好奇`${user}`不是js自带的模板语法吗?为什么不会报错?这就是渲染优先级问题,art渲染后浏览器实际上拿到的并不是`${user}`

6.进阶开发

除了可以修改现有的模板文件,还可以自定义模板文件,做一套完整的主题模板或插件。

主题模板

想添加一套主题模板十分简单,可以在/static/template目录下新建一个目录,并创建一个名为template.json的配置文件即可。配置如下:


{
	// 模板的唯一标识,名字通常与目录名相同
	"name": "game",
	// 标题名,用于后台选择模板时展示的名字
	"title": "游戏主题",
	// 预览图,用于后台选择模板时,可不填写,直接在模板目录下放置preview.png图片即可。支持外链图片,但不建议使用
	"preview": "./preview.png",
	// 预览大图,用于查看模板详情时,可不填写,直接在模板目录下放置preview_large.png图片即可。支持外链图片,但不建议使用
	"preview_large": "./preview_large.png",
	// 介绍该模板的特点,可不填写,可在模板目录放一个名为README.md文件,以此文件内容作为描述
	"description": "参照王者游戏的UI风格设计的一套网站模板",
	// 当前版本,可用作于更新
	"version": "1.0.0",
	// git地址,用于检测更新和一键下载
	"git": "https://gitee.com/qiuwenwu91/mm_theme_game.git"
}
							

模板文件的目录结构和命名采用路由即路径的原则,当路由为/home/search时,那么对应文件为该主题目录/home/search.html文件

当新主题存在模板文件会优先选择该模板文件进行渲染。当主题模板文件不存在时,未避免页面无法访问,会选择默认主题模板文件进行渲染

模板打包

可以将整个主题模板目录压缩为ZIP格式文件,然后就可以通过后台的主题上传功能,将主题模板上传并解压

也可以直接在后台导出主题模板,导出后直接下载为xxx.zip压缩包

插件开发

在插件开发上也可以使用art-template模板引擎,只需要实例化一个Tpl类函数,然后调用该类的view()函数即可。使用方法如下:


var model = {
	name: "一个简单的插件"
};
var tpl = new $.Tpl();
// ./home/xxx.html中的./代表当前模板主题根目录,也可以使用全路径,不使用主题模板,自定义一个模板。
var body = tpl.view('./home/xxx.html', model);
return body;
							

虽然可以自定义模板路径,但是为了方便引用,建议将插件模板放置在 插件根目录/static/template目录下

如果希望插件也能使用主题模板,那么请将插件用到的模板放置到对应的主题模板目录下

插件打包

插件打包有两种方式,一种是直接将插件目录打包。另一种是根据系统目录结构,将插件文件和模板文件放置,然后再压缩打包。

如果开发的插件不涉及前台预览,建议采用第一种方式打包

如果开发的插件功能庞大,想让其他开发参与主题模板,可以采用第二种打包方式。缺点是卸载时可能有残留文件

此文档不过多详述插件开发,如需了解详情请移步#插件开发文档。