View 模板渲染

发布时间:

这里只列出 CmsWing 特有的,更详细的内容请阅读 Egg.js 文档 View 模板渲染

绝大多数情况,我们都需要读取数据后渲染模板,然后呈现给用户。故我们需要引入对应的模板引擎,这里我们使用的是 Nunjuck。

模板目录

模板目录 app/view ,我们推荐您如果开发自己的应用,请以您的目录名称放置您的模板文件。比如官方的cms的模板文件都放置在 app/view/cms目录下。

渲染页面

框架在 Context 上提供了 3 个接口,返回值均为 Promise:

  • render(name, locals) 渲染模板文件, 并赋值给 ctx.body
  • renderView(name, locals) 渲染模板文件, 仅返回不赋值
  • renderString(tpl, locals) 渲染模板字符串, 仅返回不赋值
// {app_root}/app/controller/home.js
class HomeController extends Controller {
  async index() {
    const data = { name: 'cmswing' };

    // render a template, path relate to `app/view`
    await ctx.render('home/index', data);

    // or manually set render result to ctx.body
    ctx.body = await ctx.renderView('path/to/file', data);

    // or render string directly
    ctx.body = await ctx.renderString('hi, {{ name }}', data, {
      viewEngine: 'nunjucks',
    });
  }
}

 

Locals

在渲染页面的过程中,我们通常需要一个变量来收集需要传递给模板的变量,在框架里面,我们提供了 app.locals 和 ctx.locals

  • app.locals 为全局的,一般在 app.js 里面配置全局变量。
  • ctx.locals 为单次请求的,会合并 app.locals
  • 可以直接赋值对象,框架在对应的 setter 里面会自动 merge。
// `app.locals` 会合并到 `ctx.locals
ctx.app.locals = { a: 1 };
ctx.locals.b = 2;
console.log(ctx.locals); // { a: 1, b: 2 }

// 一次请求过程中,仅会在第一次使用 `ctx.locals` 时把 `app.locals` 合并进去。
ctx.app.locals = { a: 2 };
console.log(ctx.locals); // 上面已经合并过一次,故输出还是 { a: 1, b: 2 }

// 也可以直接赋值整个对象,不用担心会覆盖前面的值,我们通过 setter 做了自动合并。
ctx.locals.c = 3;
ctx.locals = { d: 4 };
console.log(ctx.locals); // { a: 1, b: 2, c: 3, d: 4 }

但在实际业务开发中,controller 中一般不会直接使用这 2 个对象,直接使用 ctx.render(name, data) 即可:

  • 框架会自动把 data 合并到 ctx.locals
  • 框架会自动注入 ctxrequesthelper 方便使用。
ctx.app.locals = { appName: 'showcase' };
const data = { name: 'egg' };

// will auto merge `data` to `ctx.locals`, output: egg - showcase
await ctx.renderString('{{ name }} - {{ appName }}', data);

// helper, ctx, request will auto inject
await ctx.renderString(
  '{{ name }} - {{ helper.lowercaseFirst(ctx.app.config.baseDir) }}',
  data,
);

注意:

  • ctx.locals 有缓存,只在第一次访问 ctx.locals 时合并 app.locals。
  • 原 Koa 中的 ctx.state,由于容易产生歧义,在框架中被覆盖为 locals,即 ctx.state 和 ctx.locals 等价,我们建议使用后者。

 

Helper

在模板中可以直接使用 helper 上注册的方法

// app/extend/helper.js
exports.lowercaseFirst = (str) => str[0].toLowerCase() + str.substring(1);

// app/controller/home.js
await ctx.renderString('{{ helper.lowercaseFirst(name) }}', data);

 

模板标签

我们在应用开发中可以使用模板标签直接在html中调取数据

标签书写规范

在编写标签条件时 如果遇到两个连个花括号连写 比如 {where:{id:1}} 请用空格或者换行隔开,因为}}会跟 Nunjuck标签冲突,

错误例子

<!-- {where:{id:3}} 3后面的两个 }} 没有用空格隔开会报错 -->
{%set memeber = 'mc_member'|@findOne({where:{id:3}})%}


正确例子

<!--用空格隔开-->
{%set memeber = 'mc_member'|@findOne({where:{id:3} })%}

<!--或者换行-->
{%set memeber = 'mc_member'|@findOne({
where:{id:3} 
})%}

 

'表名'|@findAll('条件')

  • 表名:要调用的数据表
  • 条件: {} 

@findAll 同等于 sequelize 的 findAll,具体可以参考sequelize文档 它生成一个标准的 SELECT 查询,该查询将从表中检索所有条目(除非受到 where 子句的限制).

比如要调取 cms_doc表内 classify_id等于 2的并且 position 字段是 '1,2,3' 这样的存储格式,需要postition 等于1,调取10条,并且要关联cms_classify表。

{% set position = 'cms_doc'|@findAll({
        include : 'cms_classify',
        where:{
        classify_id:2,
        FIND_IN_SET:['position',1]
        },
        offset: 0,
        limit: 10
        })%}

比如要调取所id大于3的用户

{% set memeber = 'mc_member'|@findAll({
where:{
id:{op_gt:3} 
} 
})%}

{%for item in memeber%}
....
{%endfor%}

上面代码 大于3 在Sequelize中的默认操作符是 {[Op.gt]: 3} 在 cms模版标签中是{op_gt:3},操作法对映如下,具体可以参考 Sequelize操作符

 operatorsAliases: {
    op_eq: Op.eq, //等于
    op_ne: Op.ne, //不等于
    op_gte: Op.gte,//大于等于
    op_gt: Op.gt,//大于
    op_lte: Op.lte,//小于等于
    op_lt: Op.lt,//小于
    op_not: Op.not, //NOT
    op_in: Op.in, //IN
    op_notIn: Op.notIn
    op_is: Op.is,
    op_like: Op.like,
    op_notLike: Op.notLike,
    op_iLike: Op.iLike,
    op_notILike: Op.notILike,
    op_startsWith: Op.startsWith,
    op_endsWith: Op.endsWith,
    op_substring: Op.substring,
    op_regexp: Op.regexp,
    op_notRegexp: Op.notRegexp,
    op_iRegexp: Op.iRegexp,
    op_notIRegexp: Op.notIRegexp,
    op_between: Op.between,
    op_notBetween: Op.notBetween,
    op_overlap: Op.overlap,
    op_contains: Op.contains,
    op_contained: Op.contained,
    op_adjacent: Op.adjacent,
    op_strictLeft: Op.strictLeft,
    op_strictRight: Op.strictRight,
    op_noExtendRight: Op.noExtendRight,
    op_noExtendLeft: Op.noExtendLeft,
    op_and: Op.and,
    op_or: Op.or,
    op_any: Op.any,
    op_all: Op.all,
    op_values: Op.values,
    op_col: Op.col,
  },

 

'表名'|@findOne('条件')

  • 表名:要调用的数据表
  • 条件:{}

@findOne 方法获得它找到的第一个条目(它可以满足提供的可选查询参数).

查询 id =3的用户

{%set memeber = 'mc_member'|@findOne({
where:{id:3}
})%}
{{memeber|dump}}
 

ctx.userInfo.uuid|@mc_menu

用户中心(MC)菜单标签

{% set menu=ctx.userInfo.uuid|@mc_menu %}
{%for item in menu%}
<li
class="nav-item {%if (item.path ==ctx.url) or (ctx.url in (item.arrPath or [])) %}active{%endif%}">
{%if item.children%}
<a class="nav-link px-0" href="#">
<span class="group-icon">
<i class="fi fi-arrow-end"></i>
<i class="fi fi-arrow-down"></i>
</span>
<span class="px-2 d-inline-block"> {{item.name}} </span>
</a>
{%else%}
<a class="nav-link px-0" href="{{item.path}}">
<i class="fi fi-arrow-end m-0 smaller"></i>
<span class="px-2 d-inline-block"> {{item.name}} </span>
</a>
{%endif%} {%if item.children%}
<ul class="nav flex-column ps-2">
{%for _item in item.children%}
<li class="nav-item {%if _item.path ==ctx.url%}active{%endif%}">
<a class="nav-link" href="{{_item.path}}"> {{_item.name}} </a>
</li>
{%endfor%}
</ul>
{%endif%}
</li>
{%endfor%}

 

最后更新时间: 2024-04-24 16:57:46