因 Refine 文档较之前有较大变更,所以本文档可能不能适配最新的 Refine
Fireboom 非常适合增删改查类操作,因为 Fireboom 里可以直接对数据模型进行批量创建 API,快速实现增删改查业务,再配合上 Fireboom 自动生成的客户端 SDK,从而可以快速、类型安全的实现管理后台功能。
常见的中后台管理系统中,主要由数据概览、统计、业务数据管理、用户角色菜单配置等几个模块构成。要使用 Fireboom 实现这些功能,我们需要用到 Fireboom 的聚合查询或者rawQuery
来进行统计查询,批量创建来生成增删改查的接口和 SDK,自定义的 Prisma schema 以及对应的 API 来实现用户角色菜单功能。下面分别介绍具体实现。
model Pet {
id Int @id @default(autoincrement())
name String
age Int
sex Int
createdAt DateTime @default(now())
}
npm create refine-app@latest my-antd-project
✔ Choose a project template · refine-react
✔ What would you like to name your project?: · admin
✔ Choose your backend service to connect: · data-provider-custom-json-rest
✔ Do you want to use a UI Framework?: · antd
✔ Do you want to add example pages?: · no
✔ Do you need any Authentication logic?: · auth-provider-custom
✔ Do you need i18n (Internationalization) support?: · no
✔ Choose a package manager: · pnpm
✔ Would you mind sending us your choices so that we can improve superplate? · no
Success! Created admin at /path/to/your/workspace/admin
// dataProvider.ts
import { CrudOperators, DataProvider, LogicalFilter } from "@pankod/refine-core";
import { message } from "antd";
import axios, { AxiosError, AxiosResponse } from "axios";
async function resolveResp(respPromise: Promise<AxiosResponse<any, any>>, dataField = 'data') {
let msg
try {
const resp = await respPromise
if (resp.status < 300 && resp.status >= 200) {
return resp.data[dataField]
}
if (resp.data) {
msg = resp.data.message ?? resp.data
} else {
msg = resp.statusText
}
} catch (e) {
const err = e as AxiosError
msg = err.message
}
message.error(msg)
return null
}
export type SimpleGraphqlQueryOperation = 'equals' | 'gt' | 'gte' | 'in' | 'lt' | 'lte' | 'notIn' | 'contains' | 'startsWith' | 'endsWith' | 'mode'
export type GraphqlQueryOperation = SimpleGraphqlQueryOperation | { not: SimpleGraphqlQueryOperation }
/**
* 把 refine 定义的 operator 转换成 graph 的查询语句里的查询条件
*/
const simpleMatch: Record<Extract<CrudOperators, 'eq' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'startswith' | 'endswith' | 'contains'>, SimpleGraphqlQueryOperation> = {
eq: 'equals',
gt: 'gt',
gte: 'gte',
lt: 'lt',
lte: 'lte',
in: 'in',
startswith: 'startsWith',
endswith: 'endsWith',
contains: 'contains'
}
type SimpleMatchKeys = keyof typeof simpleMatch
function isNull(v: any) {
return v === null || v === undefined || v === ''
}
function parseOperatorToGraphQuery(operator: LogicalFilter) {
if (isNull(operator.value) || (Array.isArray(operator.value) && (!operator.value.length || operator.value.every(isNull)))) {
return
}
if (operator.operator in simpleMatch) {
return {
[simpleMatch[operator.operator as SimpleMatchKeys]]: operator.value
}
}
switch (operator.operator) {
case 'between':
return {
// 根据业务决定边界
gte: operator.value[0],
lte: operator.value[1]
}
default:
break;
}
}
export const OperationDataProvider = (apiUrl: string = '/operations'): DataProvider => {
const client = axios.create({
baseURL: apiUrl, paramsSerializer: {
serialize(params) {
return Object.keys(params).reduce<string[]>((arr, key) => {
const curr = params[key]
arr.push(`${key}=${JSON.stringify(curr)}`)
return arr
}, []).join('&')
}
}
})
client.interceptors.response.use((resp) => {
let _message
if (resp.data) {
_message = resp.data.message
} else {
_message = resp.statusText
}
if (_message) {
message.error(_message)
}
return resp
})
return {
async getList({ resource, hasPagination, pagination, metaData, sort, filters }) {
const params: Record<string, any> = {}
if (hasPagination) {
params.take = pagination!.pageSize
params.skip = pagination!.pageSize! * (pagination!.current! - 1)
}
if (sort && sort.length) {
params.orderBy = sort.map(item => ({ [item.field]: item.order }))
}
if (filters && filters.length) {
params.query = filters.reduce<Record<string, any>>((map, cur) => {
const filter = cur as LogicalFilter
map[filter.field] = parseOperatorToGraphQuery(filter)
return map
}, {})
}
const data = await resolveResp(client.get(`/${resource}/Get${resource}List`, { params }))
return data ? data : { total: 0, data: [] }
},
async getMany({ resource, metaData, ids }) {
const data = await resolveResp(client.get(`/${resource}/GetMany${resource}`, { params: { ids } }))
return data ? data.data : []
},
async getOne({ id, resource }) {
const data = await resolveResp(client.get(`/${resource}/GetOne${resource}`, { params: { id: +id } }))
return data.data
},
async create({ resource, variables, metaData }) {
const data = await resolveResp(client.post(`/${resource}/CreateOne${resource}`, variables))
return data
},
async update({ id, resource, variables, metaData }) {
const data = await resolveResp(client.post(`/${resource}/UpdateOne${resource}`, { ...variables, id: +id }))
return data
},
async deleteOne({ id, resource, variables }) {
const data = await resolveResp(client.post(`/${resource}/DeleteOne${resource}`, { ...variables, id: +id }))
return data
},
async custom({ url, method, query, headers, payload}) {
const data = await resolveResp(client({
url,
method,
headers,
params: query,
data: payload
}))
return data
},
getApiUrl() {
return client.getUri()
}
}
}
该文件定义了一个适用于 Refine 的同时又符合 Fireboom 批量新建生成的 API 地址规则的数据源适配器,我们需要按照 Refine 的规则来设置数据源适配器。
// App.tsx
import { Refine } from "@refinedev/core";
import dataProvider from "./dataProvider";
const App: React.FC = () => {
return <Refine dataProvider={dataProvider} />;
};
import { Refine, ResourceProps } from '@pankod/refine-core'
import { PetCreate, PetEdit, PetList, PetShow } from './features/pet'
const resources: ResourceProps[] = [
{
name: 'Pet',
options: { label: '宠物管理' },
list: PetList,
create: PetCreate,
edit: PetEdit,
show: PetShow
}
]
const App: React.FC = () => {
return <Refine dataProvider={dataProvider} resources={resources} />;
};
WIP...