Skip to content

前端交互约定

本文档列出主题系统中 真正影响模板写作的系统级约定 — 即后端/模板引擎注入到前端的关键变量和机制。具体的 JS 实现(用 jQuery 还是原生 JS、事件委托用什么 class 名)完全由开发者自行决定。


JS 加载方式

JS 在 EJS 模板中通过 <script> 标签引入,通常入口统一放在 view/common/script.ejs,由 layout 底部引用:

ejs
<!-- view/layout/default.ejs -->
<body>
    ...
    <%- await include('../common/script'); %>
</body>
ejs
<!-- view/common/script.ejs — 可以引入任意 JS 文件或写内联脚本 -->
<script src="<%= __TPL_STATIC__ %>js/jquery.min.js"></script>
<script src="<%= __TPL_STATIC__ %>js/theme.js?v=<%= global.DJ_VERSION %>"></script>

开发/生产模式切换可利用 dev 变量:

ejs
<% if (dev) { %>
    <script src="http://127.0.0.1:4000/js/theme.js"></script>
<% } else { %>
    <script src="<%= __TPL_STATIC__ %>js/theme.js?v=<%= global.DJ_VERSION %>"></script>
<% } %>

全局 JS 对象 AOD

view/common/public.ejs 自动注入一个全局 JS 对象,将服务端变量暴露给前端:

ejs
<script>
    var AOD = {
        __TPL_STATIC__: "<%= __TPL_STATIC__ %>",  // 主题静态资源路径前缀
        plugins: <%- JSON.stringify(plugins) %>,   // 已启用的插件列表
        progressColor: '<%- progressColor %>'       // 播放进度条颜色
    }
</script>

前端 JS 通过 window.AOD.__TPL_STATIC__ 等获取这些值。


页面初始化标识 pageName

每个 EJS 模板可在末尾声明 pageName,供 JS 入口根据当前页面执行对应的初始化逻辑:

ejs
<script>
  var pageName = "music/detail";
</script>

JS 入口的典型写法:

js
const pages = {
    "index/index": initIndex,
    "music/detail": initMusicDetail,
    "user/login": initLogin,
};

document.addEventListener("DOMContentLoaded", () => {
    if (window.pageName && pages[window.pageName]) {
        pages[window.pageName]();
    }
});

约定pageName 格式为 "模块名/页面名",与控制器方法的模板路径对应。这是 JS 与模板之间的唯一命名约定,其余 class 名、选择器等由开发者自定。


系统内置了一个 AJAX 页面导航机制(由 singlePageApplication 函数提供)。链接添加 data-link="ajax" 属性后,点击不刷新整页,而是通过 AJAX 加载目标页并替换内容区:

ejs
<a href="<%= url('Music/detail', { id: song._id }) %>" data-link="ajax">
    <%= song.title %>
</a>

工作原理

  1. 拦截 a[data-link="ajax"] 的 click 事件
  2. fetch(url, { headers: { 'x-theme-layout': 'off' } }) — 该头部告诉服务端不渲染 layout,只返回 #app 内容区
  3. 服务端返回纯内容 HTML(不含 <html> <head> 等外层结构)
  4. 提取并执行其中的 <script> 标签
  5. document.getElementById('app').innerHTML = html
  6. history.pushState(url) 更新 URL

不使用此特性:不加 data-link="ajax",所有链接走传统整页刷新。


x-theme-layout 请求头

AJAX 导航依赖这个请求头。当请求携带 x-theme-layout: off 时,服务端 Base 基类的 preHandler 会自动禁用 layout 渲染:

js
// Base.setupRequestHooks() 中
const layoutHeader = request.headers['x-theme-layout'];
if (layoutHeader === 'off') {
    request._viewCtx.layout = false;
    request._viewCtx.isLayoutLocked = true;
}

这意味着服务端只返回 EJS 页面模板的内容部分(<%- body %>),不包裹 layout 的 HTML 骨架。


弹窗类接口约定

登录、注册、VIP 购买等弹窗通常通过 AJAX 加载 HTML 片段。服务端应设置 layout(false) 仅返回表单内容:

js
// controller/User.js
async ajax_login(request, reply) {
    this.layout(false);  // 不渲染 layout,只返回表单 HTML
    return this.fetch('user/ajax_login');
}

对应的模板文件通常命名为 ajax_*.ejs(如 user/ajax_login.ejs),只包含表单结构,不含 <html> <head> <body> 等外层标签。


__TPL_STATIC__global.DJ_VERSION

变量用途
__TPL_STATIC__主题静态资源路径前缀(如 /default_pc/),用于构建 JS/CSS/图片的 URL
global.DJ_VERSION系统版本号,附加在资源 URL 后用于缓存破坏
ejs
<link rel="stylesheet" href="<%= __TPL_STATIC__ %>css/theme.css?v=<%= global.DJ_VERSION %>">
<script src="<%= __TPL_STATIC__ %>js/theme.js?v=<%= global.DJ_VERSION %>"></script>
<img src="<%= __TPL_STATIC__ %>images/logo.svg">

CSS 变量与主题模式

主题色在模板中注入为 CSS 变量:

ejs
<style>
:root {
    --theme-color: <%= themeColor %>;
}
</style>

主题模式(light/dark/system)通过 <html>data-theme 属性控制:

ejs
<html data-theme="<%= design?.config?.theme_mode || 'light' %>">

可用 API 接口

交互行为需要调用后端 API。完整接口参考见 API 接口文档,核心接口速查:

功能接口方法鉴权
数据列表加载/ajax/indexGET
关注/取消/ajax/followPOST
收藏/取消/ajax/collectPOST
加入购物车/cart/addPOST
点赞/踩/ajax/reactionsPOST
购买下载/ajax/buyDownloadPOST
提交订单/order/submitPOST
查询订单状态/order/statusGET
生成下载签名/ajax/signaturePOST
发送短信验证码/ajax/sendSmsPOST否(需 captcha)
发送邮件验证码/ajax/sendEmailPOST否(需 captcha)
获取点选验证码/ajax/captchaGET
未读通知数量/ajax/getNotifyCountGET
购物车数量/ajax/getCartCountGET
音频播放数据/ajax/audioplayerGET否(WASM 加密)
分享/ajax/sharePOST
文件上传/ajax/uploadConfigGET是(签名字段)

Released under the MIT License.