多表查询

针对前台来说,后台某个接口对应的到底是一个表,还是几个表,接口都是一样的。

这里重点看一下后台代码是怎么写的,以及相对单表操作,前台需要注意的一些特殊情况。

1. 后台接口

后台写一个多表查询的 Sql 语句,然后调用一个底层的函数就行。

/**
* overeride
* @param params
* @return
*/
@Override
public AbstractListResponse selectByAntSearchByPage(AntSearchListParams params){
String sql="select S.store_name,S.member_name, A.*,B.class_name class_name_1,C.class_name class_name_2,D.class_name class_name_3 " +
"from ec_store_bind_class A " +
" ,ec_goods_class B " +
" ,ec_goods_class C " +
" ,ec_goods_class D " +
" ,ec_store S " +
"where A.class_1=B.class_id " +
" and A.class_2=C.class_id " +
" and A.class_3=D.class_id " +
" and S.store_id=A.store_id" +
" ORDER BY gmt_create desc"
;
return multiSqlBuilder.selectByAntSearchByPage(sql,params);
}

详细内容可以看后台相关文档

2. 前台功能

后台写一次就可以了,根据前台传入的参数动态拼接 Where 条件与排序。

针对上图所示,实现的功能有:

  • 查询
    • 针对店铺名进行模糊查询
    • 可以进行区间查询
    • 可以进行状态下拉框查询
  • 排序
    • 可以指定按照某个字段排序

3. Service

3.1 data.d.ts

有些代码是重复性工作,可以通过程序自动生成。

首先得到数据结构。

由于是多表,这里在原先的数据结构上,追加了一些附属的结构。 取名叫做StoreBindClassEx

// 店铺绑定商品分类
export type StoreBindClass = {
storeBindClassId: number; // id [最大位数:11]
storeId: number; // 店铺ID [最大位数:11]
commissionRate?: number; // 佣金比例
class1?: number; // 一级分类 [最大位数:11]
class2?: number; // 二级分类 [最大位数:11]
class3?: number; // 三级分类 [最大位数:11]
classFullName?: string; // 分类全名,包含一级 [最大位数:200]
state?: number; // 状态0审核中1已审核 2平台自营店铺 [最大位数:11]
gmtCreate?: string; // 记录创建时间
gmtModified?: string; // 记录修改时间
};
// 店铺绑定商品分类扩展表,因为做了多表查询
export type StoreBindClassEx = StoreBindClass & {
storeName: string;
className1: string;
className2: string;
className3: string;
memberName: string;
};

3.2 service

这里会生成

import { request } from 'umi';
import { PrefixUrl } from '@/services/Common';
// -----------------------------常量-------------------------------------------
const prifix = `${PrefixUrl}/store/list`;
/**
* 得到 店铺可发布商品类目 列表
* @param data 可以传入:检索条件、排序、分页等检索条件
*/
export async function getStoreBindClassList(data: API.SearchListParams) {
return (
request <
API.ResponseInfo <
API.ResponseListData <
Store.StoreBindClass >>>
(`${prifix}/getStoreBindClassList`,
{
method: 'POST',
data,
})
);
}
/**
* 根据Id得到某个 店铺可发布商品类目
*/
export async function getStoreBindClassById(storeBindClassId: number) {
return (
request <
API.ResponseInfo <
Store.StoreBindClass >>
(`${prifix}/getStoreBindClassById`,
{
method: 'GET',
params: {
storeBindClassId,
},
})
);
}
/**
* 根据Id添加或更新某个 店铺可发布商品类目
*/
export async function saveStoreBindClass(data: Store.StoreBindClass) {
return (
request <
API.ResponseInfo <
number >>
(`${prifix}/saveStoreBindClass`,
{
method: 'POST',
data,
})
);
}
/**
* 删除某个店铺可发布商品类目
*/
export async function delStoreBindClassById(storeBindClassId: number) {
return (
request <
API.ResponseInfo <
number >>
(`${prifix}/delStoreBindClassById`,
{
method: 'GET',
params: {
storeBindClassId,
},
})
);
}

4 page

page 部分代码稍微多一点,拿是都是讨论。这里把主要的代码标记出来。

4.1 table

页面中所有的表格代码都是这样,无非把下面三个地方替换了:

  • StoreBindClassEx 新生成的数据结构
  • rowKey="storeBindClassId" 表格的主键
  • getStoreBindClassList 要检索的函数名。
return (
<React.Fragment>
<Help>
<p>此处可以对商家新申请的经营类目进行 审核/删除 操作</p>
</Help>
<ProTable<Store.StoreBindClassEx>
actionRef={tableRef}
columns={columns}
rowKey="storeBindClassId"
request={async (params, sort) => {
// getWheres 与 getOrderStr 这两个函数,会按照规则,转换成后台能统一识别的查询条件
const result = await getStoreBindClassList({
current: params.current,
pageSize: params.pageSize,
wheres: getWheres(params, whereNameRules),
order: getOrderStr(sort),
});
return result.data;
}}
/>
</React.Fragment>
);

这几行代码就定义出了这个界面

4.2 columns

主要定义 Table 中的检索条件与 Table 的显示内容,下面重点的内容有:

  • 查询部分:有些特殊查询,通过hideInTable: true不显示在表格中。

    • 普通的查询
    • 按照时间区间查询
    • 下拉框查询
  • 表格部分:

    • 经营类目:通过render可以实现多个字段的内容。

    • 通过valueType: 'dateTime' 格式化显示内容

    • sorter: true 可以进行排序

    • search: false 不进行查询

    • search 指定一个特殊的函数,应该格式化查询参数。gmtCreate_between做了特殊的定义。

      search: {transform: (values) => {return {gmtCreate_between: values.join(','),};},},

const columns: ProColumns<Store.StoreBindClassEx>[] = [
{
title: '经营类目',
dataIndex: 'class1',
search: false,
render: (_: React.ReactNode, row: Store.StoreBindClassEx) => (
<React.Fragment>
{row.className1} {row.className2} {row.className3}
</React.Fragment>
),
},
{
title: '店铺',
dataIndex: 'storeName',
hideInTable: true,
},
{
title: '店铺',
dataIndex: 'storeId',
search: false,
render: (_: React.ReactNode, row: Store.StoreBindClassEx) => (
<React.Fragment>
{row.storeName}[ {row.storeId}]
</React.Fragment>
),
},
{
title: '店主账号',
dataIndex: 'memberName',
sorter: true,
search: false,
},
{
title: '申请时间',
dataIndex: 'gmtCreate',
hideInTable: true,
valueType: 'dateRange',
search: {
transform: (values) => {
return {
gmtCreate_between: values.join(','),
};
},
},
},
{
title: '提交时间',
dataIndex: 'gmtCreate',
valueType: 'dateTime',
sorter: true,
search: false,
},
{
title: '分佣比例',
dataIndex: 'commissionRate',
search: false,
valueType: 'percent',
},
{
title: '申请状态',
dataIndex: 'state',
valueType: 'select',
valueEnum,
},
{
title: '操作',
valueType: 'option',
render: (_, record) => getOption(record),
},
];

5 附加功能

5.1 模糊查询

可以方便的实现 like beteen in 等特殊功能。 例如下面,就是对storeName进行like查询。

/** 定义一个`whereNameRules` 转换的规则,其中字段名必须与数据库中的一致。 */
const whereNameRules = {
storeName: 'storeName_like',
};

5.2 自定义下拉数据

例如上面的审核与待审核,用自定义就可以了。

const valueEnum = {
0: { text: '待审核', status: 'Processing' },
1: { text: '已审核', status: 'Success' },
};

5.3 数据库下拉数据

有些下拉框的数据,是从其他表的中得到的,这应该怎么做呢?

例如下面的例子中,所属等级是从StoreGrade获取的。

在 service 中定义一个查询函数

/**
* 将店铺等级转换成 select table下拉框可以读取的格式。
* @param initValues 不是从数据库中读取的一些数值,例如下拉框中的全部。
*/
export async function getStoreGradeListOptions(initValues: any[]) {
const result = await getStoreGradeList();
return convertToOptions(initValues, result.data, 'name', 'storeGradeId');
}

在 Page 中的column定义中,使用下面的数据。

{
title: '店铺等级',
dataIndex: 'gradeId',
request: () => getStoreGradeListOptions([]),
params: {},
valueType: 'select',
},