前情提要:昨天我试验构造queryWrapper对象传入接口成功后,决定在前端提供一个通用的条件构造工具。
早期开发前端时发现不会构造复杂的查询条件,于是大部分情况都委托后端开发对ts标准查询接口进行了深度的魔改,与最初的ts设计目的相悖了。
本次构造器的出现就是为了解决这个问题,旨在由前端自行构造较为复杂的查询条件,它也可以用于取代早期的searchWhereFromForm,因为后者只构造whereQueryCondition,而queryWrapper构造器可以构造整个payload。
在浏览器侧用链式 API 组装分页查询 JSON,作为 JSAPI page / query 的请求 body。
模块:web-designer-comps-shared(src/utils/query-wrapper/)
1. 用途
默认做条件 + 分页 + 排序。需要按关联表字段过滤主表时,再加 JOIN。
不提供 from / select:主表由原子服务实体决定。JOIN 只过滤主表行,结果集仍是主表记录,不会带出外表字段。
不提供裸 SQL where(...)、单参数 Raw on(...)、子查询 JOIN。
2. 导入
import { QueryWrapper } from "web-designer-comps-shared";
分包:
import { QueryWrapper } from "web-designer-comps-shared/utils/query-wrapper";
类型:
import type { QueryWrapperPayload } from "web-designer-comps-shared";
请求 body 一律使用 toPayload(),不要手写 whereQueryCondition / joins 结构。
3. 单表查询
不调用 innerJoin / leftJoin / rightJoin。payload 仅含已赋值字段:limitOffset、limitRows、whereQueryCondition、orderBys。
3.1 分页列表
import { QueryWrapper } from "web-designer-comps-shared";
import request from "@/api";
import { normalizePageRequest } from "@/common/queryBuilders";
export function pageExample(params: {
pagination?: { current?: unknown; pageSize?: unknown };
code?: string;
name?: string;
isEnable?: string;
}) {
const { limitOffset, limitRows } = normalizePageRequest(params.pagination, 20);
const qw = QueryWrapper.create().limit(limitOffset, limitRows);
if (params.code?.trim()) {
qw.andColumn("code").like(params.code.trim());
}
if (params.name?.trim()) {
qw.andColumn("name").like(params.name.trim());
}
if (params.isEnable) {
qw.andColumn("isEnable").eq(params.isEnable);
}
return request({
url: "/mdm/v1/example/page",
method: "post",
data: qw.toPayload(),
});
}
仅分页:
QueryWrapper.create().limit(0, 20).toPayload();
产出:{ "limitOffset": 0, "limitRows": 20 }
limit(offset, rows) 不换算页码。页码需先转为 offset / rows 再传入。
3.2 条件与排序
const qw = QueryWrapper.create();
qw.andColumn("code").like("A01");
qw.andColumn("code").likeLeft("A01");
qw.andColumn("isEnable").eq("1");
qw.andColumn("qty").ge(1);
qw.andColumn("id").in(ids);
qw.andColumn("remark").isNull();
qw.orColumn("status").eq("DRAFT");
qw.orderByDesc("updateDate").orderByAsc("code");
qw.limit(limitOffset, limitRows);
单表列只写字段名:andColumn("code") → { name: "code" }。不要加表前缀。
| 方法 | logic | value |
|---|---|---|
eq(v) |
= |
标量转字符串;布尔值为 "1" / "0" |
ne(v) |
!= |
同上 |
gt / ge / lt / le |
> / >= / < / <= |
字符串 |
like(v) |
LIKE |
自动包成 %v%,调用方不要再加 % |
likeLeft(v) |
LIKE |
v% |
likeRight(v) |
LIKE |
%v |
in(arr) / notIn(arr) |
IN / NOT IN |
数组 |
isNull() / isNotNull() |
IS NULL / IS NOT NULL |
null |
andColumn 节点的 connector 为 AND,orColumn 为 OR,多条件以 next 串联。
3.3 单表 payload
QueryWrapper.create()
.andColumn("code")
.like("T01")
.andColumn("isEnable")
.eq("1")
.orderByDesc("updateDate")
.limit(0, 10)
.toPayload();
{
"limitOffset": 0,
"limitRows": 10,
"whereQueryCondition": {
"effective": true,
"connector": "AND",
"column": { "name": "code" },
"logic": " LIKE ",
"value": "%T01%",
"next": {
"effective": true,
"connector": "AND",
"column": { "name": "isEnable" },
"logic": " = ",
"value": "1"
}
},
"orderBys": [
{
"queryColumn": {
"column": { "name": "updateDate" },
"orderType": "DESC"
}
}
]
}
4. 链式规则
QueryWrapper.create() 为入口。
andColumn / orColumn / innerJoin / leftJoin / rightJoin 返回中间对象,必须立刻调用 .eq / .like / .on 等,不能把中间对象赋给 QueryWrapper 变量。
可选条件写在已创建的实例上:
const qw = QueryWrapper.create().limit(0, 20);
if (code) {
qw.andColumn("code").like(code);
}
5. JOIN 查询
筛选字段不在主表时使用。JOIN 的表名为 数据库表名。
QueryWrapper.create()
.innerJoin("mdm_tooling_model")
.on("mdm_tooling.tooling_model_id", "mdm_tooling_model.id")
.andColumn("mdm_tooling_model", "is_barcode_control")
.eq("Y")
.andColumn("code")
.like(code)
.limit(limitOffset, limitRows)
.toPayload();
| 方法 | 写入的 JOIN 类型(含两侧空格,由构造器生成) |
|---|---|
innerJoin(table) |
" INNER JOIN " |
leftJoin(table) |
" LEFT JOIN " |
rightJoin(table) |
" RIGHT JOIN " |
as(alias):外表别名。on(leftColumn, rightColumn):两列等值,生成left = right。- 表名、列名、别名:
[A-Za-z_][A-Za-z0-9_]*;列引用可以是table.column。非法标识符会抛错。 - 每次
on()同时写入joins与joinTables,二者必须成对。
外表条件必须限定表:
| 写法 | 含义 |
|---|---|
andColumn("code") |
主表列 |
andColumn("mdm_tooling_model.is_barcode_control") |
外表列 |
andColumn("mdm_tooling_model", "is_barcode_control") |
外表列(同上) |
const qw = QueryWrapper.create()
.innerJoin("mdm_tooling_model")
.on("mdm_tooling.tooling_model_id", "mdm_tooling_model.id")
.andColumn("mdm_tooling_model", "is_barcode_control")
.eq("Y")
.limit(limitOffset, limitRows);
if (code) {
qw.andColumn("code").like(code);
}
return request({
url: "/mdm/v1/tooling/page",
method: "post",
data: qw.toPayload(),
});
{
"limitOffset": 0,
"limitRows": 10,
"whereQueryCondition": {
"effective": true,
"connector": "AND",
"column": {
"name": "is_barcode_control",
"table": { "name": "mdm_tooling_model" }
},
"logic": " = ",
"value": "Y"
},
"joins": [
{
"type": " INNER JOIN ",
"queryTable": { "name": "mdm_tooling_model" },
"on": {
"content": "mdm_tooling.tooling_model_id = mdm_tooling_model.id"
}
}
],
"joinTables": [{ "name": "mdm_tooling_model" }]
}
6. 约束与错误用法
| 错误 | 说明 |
|---|---|
like 自行再包 % |
会变成 %%值%% |
把 andColumn(...) 的返回值当作 QueryWrapper |
后续无法继续链式调用 |
单表使用 andColumn("表名.列名") |
单表只写字段名 |
| JOIN 后外表条件不带表名 | 列会落到主表 |
on 只传一段 SQL 字符串 |
必须是两个列名参数 |
用户输入写入表名或 on 的列名 |
只允许把用户输入放在比较值里 |
手写 joins / joinTables / logic |
一律由 toPayload() 生成 |
7. 导出
| 名称 | 说明 |
|---|---|
QueryWrapper |
构造器入口 |
QueryWrapperPayload |
toPayload() 返回类型 |
JoinType |
JOIN 类型常量 |
JoinBuilder / QueryConditionBuilder |
链式中间类型 |