《模板开发》
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目录下
如果希望插件也能使用主题模板,那么请将插件用到的模板放置到对应的主题模板目录下
插件打包
插件打包有两种方式,一种是直接将插件目录打包。另一种是根据系统目录结构,将插件文件和模板文件放置,然后再压缩打包。
如果开发的插件不涉及前台预览,建议采用第一种方式打包
如果开发的插件功能庞大,想让其他开发参与主题模板,可以采用第二种打包方式。缺点是卸载时可能有残留文件
此文档不过多详述插件开发,如需了解详情请移步#插件开发文档。