bot-admin 常用写法
这份文档用于统一后台模块的常见实现。新增页面、接口、列表、编辑页时先按这里写,确实不适合再局部说明原因,避免每个模块各写一套。
1. 文件放置
- 后端接口放在
routers/,按业务域拆分,例如routers/group.js、routers/point.js。 - 前端页面逻辑放在
public/app/,文件名与路由名一致,例如point_products.js。 - 页面模板放在
templates/,文件名与路由名一致,例如point_products.html。 - 应用内路由需要登记
routers/app.js的routeModuleMap。 - 应用内路由需要在
public/js/menu.js加菜单项。 - 应用内路由需要有
templates/与public/app/对应文件。 - 通用数据处理优先放到当前业务 router 顶部的小函数。跨 router 复用两次以上,再放到
utils/。
2. 后端 router 写法
2.1 基础结构
import Router from "koa-router";
import monk from "monk";
import db from "../db.js";
let router = new Router();
router.post("/token/ca/get_demo_items", async (ctx) => {
const { reg_token } = ctx.request.body || {};
const items = await db.DEMO.find({ reg_token }, { sort: { _id: -1 } });
const total = await db.DEMO.count({ reg_token });
ctx.body = { items, total };
});
export default router;
- 使用 ES module
import/export default。 - router 实例统一写
let router = new Router();。 - 后台管理接口优先使用
/token/ca/...前缀。 - body/query 一律加兜底:
ctx.request.body || {}、ctx.query || {}。 - 数据库查询条件先组装成
query,不要在多个地方手写同一段条件。
2.2 返回格式
- 列表接口返回
{ listName, total },例如{ users, total }、{ point_rules, total }。 - 详情接口返回
{ itemName },例如{ rule }、{ product }。 - 保存、删除、复制这类无额外数据的接口返回字符串
"ok"。 - 给前端弹出明确原因时返回
{ error: "错误信息" }。 - 不要同一个模块里混用
ok: true、"ok"、{ status: "success" },除非是外部 API 兼容接口。
2.3 错误处理
if (!name) {
ctx.throw(400, "名称不能为空");
}
if (!item) {
ctx.status = 404;
ctx.body = { error: "数据不存在" };
return;
}
- 参数校验失败优先用
ctx.throw(400, "...")。 - 需要给前端结构化错误时,设置
ctx.status后return。 - 不在每个接口外层重复包
try/catch。只有需要降级、清理资源、隐藏第三方错误时才局部捕获。 - 捕获错误时不要吞掉上下文,至少保留
err.message。
2.4 ID 与时间
const item = await db.DEMO.findOne({
reg_token,
_id: monk.id(id),
});
item.createTime = item._id.getTimestamp();
- Mongo
_id查询统一使用monk.id(id)。 - 列表展示创建时间时,用
_id.getTimestamp()补createTime。 - 前端展示时间再用
dayjs格式化,后端不混入展示文案。
2.5 账号范围
- 接口必须带
reg_token过滤,不能只按_id查。 - 涉及子账号权限时,像积分模块一样先封装
getCurrentAccount(ctx)、scopedRuleQuery(ctx, extra)这类小函数。 - owner/sub 的权限判断集中到函数里,不在每个接口散写
ctx.session?.account?.role。
3. 前端页面写法
3.1 基础结构
BOT_ROUTES["demo_items"] = {
el: "#bot-app",
data() {
return {
list: [],
total: 0,
loading: false,
dialogVisible: false,
editing: defaultDemoItem(),
};
},
async created() {
await this.refresh();
},
methods: {
async refresh() {
const { data } = await axios.post(
BOT_HOST + "/token/ca/get_demo_items",
{
reg_token: window.tk,
}
);
this.list = data.items || [];
this.total = data.total || 0;
},
},
};
function defaultDemoItem() {
return {
title: "",
active: true,
sort: 0,
};
}
BOT_ROUTES["route_name"]必须与模板、菜单、routers/app.js路由名一致。- 页面初始化统一放
created()调refresh()。 - 列表数据字段统一叫
list,总数叫total。除非模板已有明确业务名。 - 新建默认值抽成
defaultXxx(),新增和编辑都复用,避免字段漏填。
3.2 请求写法
const { data } = await axios.post(BOT_HOST + "/token/ca/set_demo_item", {
reg_token: window.tk,
form: this.editing,
});
- 后台接口统一带
reg_token: window.tk。 - 保存接口表单统一放
form字段。 - 页面跳转使用
barba.go("route?key=value")或window.navigateAppLink(),不要直接拼完整/app/${window.tk}/...。 - 读取 URL 参数使用
new URLSearchParams(location.search).get("key") || ""。
3.3 常用交互
async remove(item) {
if (!confirm("确认删除该数据?")) return;
await axios.post(BOT_HOST + "/token/ca/rm_demo_item", {
reg_token: window.tk,
id: item._id,
});
await this.refresh();
},
async save() {
await axios.post(BOT_HOST + "/token/ca/set_demo_item", {
reg_token: window.tk,
form: this.editing,
});
this.dialogVisible = false;
await this.refresh();
},
- 删除前确认,删除后
refresh()。 - 保存成功后先关闭弹窗,再
refresh()。 - 开关类操作直接提交完整业务对象加变更字段,保存后刷新。
- 需要防重复点击时加
loading或saving,在finally里恢复。
3.4 前端错误提示
try {
await this.saveData();
this.$message?.success?.("保存成功");
} catch (err) {
const message = err?.response?.data?.error || err?.response?.data || "保存失败";
if (this.$message?.error) {
this.$message.error(message);
} else {
alert(message);
}
}
- 用户可处理的失败要提示,不只
console.error。 - 后端返回
{ error }时优先展示error。 - 只有调试信息才写
console.error。
4. 命名约定
- 列表接口:
get_xxxs - 详情接口:
get_xxx - 保存接口:
set_xxx - 删除接口:
rm_xxx - 复制接口:
copy_xxx - 执行接口:
run_xxx或send_xxx - 同步接口:
sync_xxx - 前端列表页:
xxxs - 前端编辑页:
xxx_edit
示例:
get_group_replysget_group_replyset_group_replyrm_group_replycopy_group_replygroup_replysgroup_reply_edit
5. 模板约定
templates/*.html只放当前页面结构,不写大量业务脚本。- 页面脚本放
public/app/*.js。 - 复用选择器使用已有公共模板,例如
selector_users、selector_groups、selector_messages。 - 列表页保留空数据状态,按钮文案与接口动作一致。
- 编辑页的字段名尽量与后端
form字段一致,不做二次映射。
6. 新增模块检查清单
- 已新增
routers/<module>.js或追加到对应业务 router。 - 已在
routers/index.js导出并在bot-admin.js挂载。 - 已新增
templates/<route>.html。 - 已新增
public/app/<route>.js。 - 已在
routers/app.js登记routeModuleMap。 - 已在
public/js/menu.js加菜单入口。 - 接口查询都带
reg_token。 - 保存接口使用
form。 - 删除/详情接口同时校验
reg_token和_id。 - 前端新增、编辑共用
defaultXxx()。 - 不新增与现有模块重复但命名不同的 helper。
7. 什么时候抽公共 helper
- 同一段数据归一化逻辑出现第二次,抽到当前 router 顶部函数。
- 同一段逻辑跨两个 router 出现,抽到
utils/。 - 只被一个接口使用的一次性逻辑,留在接口附近即可。
- 不为了“看起来通用”提前抽象;后台模块更需要稳定一致,少需要复杂继承。