三、以user表为例,用Amis+Sails实现增删改查操作

article/2025/9/14 17:19:38

文章目录

  • CRUD 组件
    • 查询api
    • 分页
      • fetcher参数观察
      • 统一处理method
      • 分页参数提交到后端
      • 自定义分页和页面大小(pageSize)
    • 搜索
    • 排序
    • 头部工具条
      • 列折叠按钮
      • 刷新和导出excel
      • 自定义内容
    • 单条删除
    • 批量删除
    • 新增数据
    • headerToolbar 结果分析
    • 前端数据格式要求
    • 数据修改
    • 重组提交数据
  • 修改左边菜单

CRUD 组件

CRUD是增加(Create)、读取(Read)、更新(Update)和删除(Delete)的意思。Amis 的CRUD 即增删改查组件,主要用来展现数据列表,并支持各类【增】【删】【改】【查】等操作。
一个最简单的CRUD组件的Schema代码如下:

{//第一对大括号代表Amis组件的根节点rootNode"type": "page",//根节点的类型是page"body": {//根节点的body内容"type": "crud",//根节点的body类型是crud"api": "/amis/api/mock2/sample",//crud 组件的api"columns": [//curd组件的列数据(相当于数据库中的字段信息数据){"name": "id",//对应数据库中的字段名称"label": "ID"//列标题},{"name": "engine","label": "Rendering engine"},{"name": "browser","label": "Browser"},{"name": "platform","label": "Platform(s)"},{"name": "version","label": "Engine version"},{"name": "grade","label": "CSS grade"}]}
}

这样的schema渲染(render)出来的页面效果如下:

在这里插入图片描述
后端返回的数据片段如下:

{"status": 0,"msg": "ok","data": {"count": 171,"rows": [{"engine": "Trident - 0jw14","browser": "Internet Explorer 4.0","platform": "Win 95+","version": "4","grade": "X","badgeText": "默认","id": 1}, {"engine": "Trident - acb3t","browser": "Internet Explorer 5.0","platform": "Win 95+","version": "5","grade": "C","badgeText": "危险","id": 2}, {"engine": "Trident - 0j7dqi","browser": "Internet Explorer 5.5","platform": "Win 95+","version": "5.5","grade": "A","id": 3}, {"engine": "Trident - qvlpr","browser": "Internet Explorer 6","platform": "Win 98+","version": "6","grade": "A","id": 4}, {"engine": "Trident - vdyua","browser": "Internet Explorer 7","platform": "Win XP SP2+","version": "7","grade": "A","id": 5}, {"engine": "Trident - w4tz3","browser": "AOL browser (AOL desktop)","platform": "Win XP","version": "6","grade": "A","id": 6}]}
}

上面那个最简单的schema根节点的body有一个api,如下:“api”: “/amis/api/mock2/sample”,这个api是body节点(也就是crud组件)获取数据的api,在组件渲染之后自动触发。

查询api

把这个最简单的schema去除注释(schema文件里面不支持注释)并修改api为"api": “/api/user/find”,然后copy覆盖掉public/json/index.json文件。保存后刷新浏览器,出现界面如下:
在这里插入图片描述
上图中api请求失败,主要是因为method错误,amis修改method比较容易,在api地址前面加上"post:",修改成这样:

"api": "post:/api/user/find", 

再次保存和刷新页面,可以看到请求成功了,界面如下:
在这里插入图片描述
现在接下来需要做的就是把columns里面的列信息改成和后端返回的数据一致,就可以把后端查询出来的数据显示出来了,再次修改schema代码如下:

{"type": "page","body": {"type": "crud","api": "post:/api/user/find",   "columns": [{"name": "id","label": "ID"},{"name": "createdAt","label": "创建时间"},{"name": "email","label": "Email"},{"name": "nickname","label": "昵称"},{"name": "age","label": "年龄"}]}
}

保存并刷新浏览器,呈现出如下的界面:
在这里插入图片描述
现在,我们成功的把后端查询出来的用户表里面的数据在amis的curd组件中呈现出来了。

本测试案例显示出来的用户信息,如果你的数据库里面没有,请用postman的自动运行功能,添加20-30条数据以方便测试

这个界面的呈现是对的,观察后端返回的数据有20多条,amis的crud自动显示前20条,并且自动分页,但是如果我们去点击右边那个第二页按钮,我们会发现返回的数据并没有变化,还是和原来一样,并且界面上面也没有显示第二页的数据内容。

分页

这个是有原因的,我们回到Sails后端的源代码,看看UserController.ts里面的find函数,我们发现它通过query2Sails函数获取前端提交的body里面的page作为当前查询的页码,把body里面的pageSize作为查询时每一页的记录数。
也就是说,我们如果要控制分页查询,我们还需要对api请求进行干涉,我们需要重新组织请求的body部分的内容。为此,我们需要了解curd组件是怎么提交请求的,提交的时候都有什么参数给到最终的request里面。

fetcher参数观察

前面我们知道amis组件里面,我们提供fetcher函数的实现给amis组件,当amis组件需要请求api的时候,它就会触发fetcher函数,在fetcher函数里面,我们通过return amisRequest(url, method, { …data, …config }); 代码把api请求的任务交给utils/request.ts里面的umi-request库来实现。
打开utils/request.ts,添加两个控制台输出,console.log(‘[url]:’, url); console.log(‘[data]:’, options);
通过这两行代码,我们可以观察curd组件提交过来的请求url和options都有一些什么,代码片段如下:

export async function amisRequest(url: string, method?: string, options?: { [key: string]: any }) {console.log('[url]:', url);console.log('[data]:', options);let newMethod = '';if (!method) newMethod = 'GET';else newMethod = method.toUpperCase();switch (newMethod) {case 'GET':return remoteRequest.get(url, options);case 'POST':return remoteRequest.post(url, options);case 'DELETE':return remoteRequest.delete(url, options);case 'PUT':return remoteRequest.put(url, options);}// return remoteRequest(url, {//   method: 'Delete',//   ...(options || {}),// });
}

保存后,进入浏览器调试界面观察,看到如下结果:
在这里插入图片描述
传递过来的url没有问题,但是data就比较混乱,这个地方应该是amis组件的fetcher函数没有做好,找到fetcher函数,看到它提交给amisRequest之前,把crud的data转换成string并合并到options,为了更好的调试我们修改一下fetcher,修改后如下:

fetcher: ({url, // 接口地址method, // 请求方法 get、post、put、deletedata, // 请求数据responseType,config, // 其他配置headers, // 请求头}: any) => {// eslint-disable-next-line no-param-reassignconfig = config || {};config.withCredentials = true;// eslint-disable-next-line @typescript-eslint/no-unused-expressionsresponseType && (config.responseType = responseType);if (config.cancelExecutor) {request.CancelToken = config.cancelExecutor;//config.cancelToken = new (axios as any).CancelToken(config.cancelExecutor);}config.headers = headers || {};if (method !== 'post' && method !== 'put' && method !== 'patch') {if (data) {config.params = data;}//这个地方放弃合并,提交两个内容,一个是data,一个是configreturn amisRequest(url, method, data, config); // (axios as any)[method](url, config);} else if (data && data instanceof FormData) {config.headers = config.headers || {};config.headers['Content-Type'] = 'multipart/form-data';} else if (data &&typeof data !== 'string' &&!(data instanceof Blob) &&!(data instanceof ArrayBuffer)) {// eslint-disable-next-line no-param-reassign//data = JSON.stringify(data); 屏蔽把data转换成string的代码config.headers = config.headers || {};config.headers['Content-Type'] = 'application/json';}//return (axios as any)[method](url, data, config);return amisRequest(url, method, data, config);},

因为data和config不再合并,amisRequest函数也应该做相应修改:

/** amis的schema文件专用request*/
export async function amisRequest(url: string, method?: string, data?: any, options?: { [key: string]: any }) {console.log('[url]:', url);console.log('[data]:', data);let newMethod = '';if (!method) newMethod = 'GET';else newMethod = method.toUpperCase();  switch (newMethod) {case 'GET':return remoteRequest.get(url, options);case 'POST':return remoteRequest.post(url, options);case 'DELETE':return remoteRequest.delete(url, options);case 'PUT':return remoteRequest.put(url, options);}
}

修改后,可以观察到提交的url和data数据了:
在这里插入图片描述

统一处理method

在amisRequest函数里面,我们是可以根据fetcher函数提交过来的url来重新组织后端请求的。这样schema里面的“api”:“post:…”,这种写法就可以简化一些了,因为method可以在amisRequest里面最终决定,并且我们的后端也主张尽量使用post,所以我们不要在amis的schema中指定api的method,因为最后面我们的schema可能要交给经过我们培训的非编程人员来修改,对他们来说能少理解一个专业术语就尽量少理解一个是最好的。综上,修改public/json/index.json中的api如下:

 "api": "/api/user/find",   

保存并刷新后观察到控制台输出是这样的:

[url]: /api/user/find?page=1&perPage=10
[data]: undefined

也就是说,crud对非post的api,默认会把查询参数page和perPage写到url里面,我们再次修改utils/request.ts,对传递过来的url和data重新处理,利用正则表达式把url里面的参数提取出来,代码如下:

/* eslint-disable */
import { extend, RequestOptionsInit } from 'umi-request';
import { getToken } from './myStorage';
//api 网址前缀,为了方便日后更换服务器,直接用一个常量定义
const urlPrefix = 'http://localhost:1898';
const remoteRequest = extend({// 路径前缀(基础路径)prefix: urlPrefix,timeout: 5000,
});
/*** 读取本地文件*/
export const localRequest = extend({prefix: '',timeout: 5000,
});
//请求拦截器,拦截每个请求,添加完token之后再发送到后端
remoteRequest.interceptors.request.use((url: string, options: RequestOptionsInit) => {const headers = getToken()? {authorization: `Bearer ${getToken()}`,}: { authorization: ' ' };return {url,options: { ...options, interceptors: true, headers: headers },};
});
/*** 用正则表达式捕获网址里面的分页信息* @param url*/
function getPageInfoByReg(url: string): { page: number; pageSize: number } {let res = { page: 1, pageSize: 15 };let pageRegex = /page=(\d+)/;let perPageRegex = /perPage=(\d+)/;let pageMatch = url.match(pageRegex);let perPageMatch = url.match(perPageRegex);if (pageMatch && pageMatch.length > 1) res.page = parseInt(pageMatch[1]);if (perPageMatch && perPageMatch.length > 1) res.pageSize = parseInt(perPageMatch[1]);return res;
}
/*** 分解出Url里面的查询参数,并返回数据对象*/
export function splitUrl(url: string): any {let urlDataList = url.split('?');let apiUrl = urlDataList[0];let searchData = {}, pageInfo = getPageInfoByReg(url);if (urlDataList.length > 1) {let paramsLst = urlDataList[1].split('&');for (var i = 0; i < paramsLst.length; i++) {var pair = paramsLst[i].split('=');if (pair.length < 2) continue;pair[1] = decodeURI(pair[1]);if (pair[0] == 'page' || pair[0] == 'perPage') continue;else searchData[pair[0]] = pair[1];}}searchData = {url: apiUrl,data: { ...searchData, page: pageInfo.page, pageSize: pageInfo.pageSize },};return searchData;
}
/** amis的schema文件专用request*/
export async function amisRequest(url: string, method?: string, data?: any, options?: { [key: string]: any }) {console.log('[url]:', url);console.log('[data]:', data);let newMethod = '';if (!method) newMethod = 'GET';else newMethod = method.toUpperCase();let sd = splitUrl(url);if (sd.url == '/api/user/find') {return remoteRequest(sd.url, {method: 'POST',data: sd.data,...(options || {}),});}switch (newMethod) {case 'GET':return remoteRequest.get(url, options);case 'POST':return remoteRequest.post(url, options);case 'DELETE':return remoteRequest.delete(url, options);case 'PUT':return remoteRequest.put(url, options);}
}
export default remoteRequest;

分页参数提交到后端

crud提交过来的page和perPage分别代表当前页码和每一页记录数,现在我们通过splitUrl把这两个参数提取出来了,但是不能直接发送给后端,因为根据后端sails对分页的参数是要求名称分别page和pageSize,上述代码中,splitUrl函数已经把这个工作给做了,所以在提交的时候,提交sd.data里面就包含有page和pageSize了,请注意观察:

//splitUrl 函数返回的searchData对象把?后面的参数解析成data,把?前面的url返回到searchData.url
searchData = {url: apiUrl,data: { ...searchData, page: pageInfo.page, pageSize: pageInfo.pageSize },};
.....................
//amisRequest请求里面把解析后的sd.data提交到body里面:if (sd.url == '/api/user/find') {return remoteRequest(url, {method: 'POST',data: sd.data,...(options || {}),});}

以上request.ts代码修改后,发现分页按钮可以用了,点击不同的分页按钮,后端返回的数据和curd组件里面呈现的数据都符合要求了。

自定义分页和页面大小(pageSize)

目前我可以看到的是crud自动设置的分页pageSize大小是每页10条数据,应该有更个性化的设置,在schema里面columns前面添加如下内容:

"footerToolbar": ["statistics",//显示统计数据,比如1/3 总共:22 项"switch-per-page",//可以切换perPage值(设置pageSize)"pagination"//分页组件],

保存后,界面就有可以设置分页大小的按钮了。如果需要强制设置分页大小,也可以指定api的默认参数,比如:

"defaultParams": {"perPage": 15}, 

搜索

单有数据展示是不够的,我们经常会需要根据关键词进行搜索过滤的功能,crud组件提供非常方便的写法:只需要在body里面添加一条"autoGenerateFilter": true 即可,代码片段如下:

"type": "crud","api": "/api/user/find","autoGenerateFilter": true,

如果现在保存并刷新,界面上看不到不一样,因为我们只是开启了过滤功能,并没有指定过滤字段,接下来我们尝试可以通过id搜索指定id值的记录,修改columns里面的id字段设置,增加一条"searchable": true, 代码片段如下:

{"name": "id","searchable": true,"label": "ID"},

刷新保存后,可以看到如下界面:
在这里插入图片描述
id搜索的界面有了,我们尝试在输入框中输入3,点击搜索按钮,F12可以看到,这个时候提交过来的URL里面带有我们要搜索的id值了:
在这里插入图片描述
我们知道,Sails后端如果要搜索id=3,我们当时在postman里面是要在body里面设置{id:3} 才可以的,现在通过url里面的参数为什么也可以成功的返回id=3的数据呢,这个是因为我们前面在utils/request.ts里面添加的splitUrl函数,这个函数会把参数转换成data,然后写到post的body里面去。可以往前翻看一下splitUrl的代码。

id搜索目前看起来都不错,但是我们希望能有更友好一点的界面,比如通过placeholder提示用户怎么操作,我们可以修改searchable的值,从而做更具体的设置:

 "searchable": {"type": "input-text","name": "id","label": "主键","placeholder": "输入id"
}

还可以添加更多的过滤字段,比如email:

 "searchable": {"type": "input-text","name": "email","label": "Email","placeholder": "Email 模糊搜索"}

在这里插入图片描述

排序

对于呈现出来的数据,还会有排序的要求。crud依然提供非常方便的操作,想要通过哪个字段排序,只需要简单的添加一个 “sortable”: true,就可以了。比如我们希望可以按照id排序,可以按照createdAt字段排序,那么我们的columns里面可以这样改:

{"name": "id","label": "ID","sortable": true,//增加sortable"searchable": {"type": "input-text","name": "id","label": "主键","placeholder": "输入id"}},{"name": "createdAt","label": "创建时间","sortable": true     //增加sortable   },

保存后界面会变成这样:
在这里插入图片描述
打开F12,点击ID排序按钮,观察传递过来的url和data:
在这里插入图片描述
可以看到url的参数里面增加了orderBy和orderDir,同时Sails后端返回一个500内部错误的信号,观察network 可以看到提交的body里面是这样的:
在这里插入图片描述
我看到前端把orderBy和orderDir提交到后端了,切换到Sails源代码,找到控制器的find函数,代码片段如下:

 let query = query2Sails('user', req._sails, req.body);if (query == false) {res.serverError("查询参数有误,请检查字段名称是否正确");return;}

这个地方,把前端的body拿去做转换,这个转换是为了完成前后端查询的约定。(相关博文在《二、 在Sails中使用Typescript》中有解释,需要回顾可以点击跳转过去)

query2Sails函数里面对排序字段的要求是options.sort = body.sort;为了防止查询参数有误,如果查询的参数不是数据库的字段或查询允许的其他关键词(比如page,pageSize,sort)该函数会返回false,告诉前端查询参数有问题,需要调整。

基于上述内容,前端需要对排序参数进行重新组织:用正则表达式把Url里面的orderBy和orderDir提取出来:

/*** 用正则表达式捕获网址里面的sort* @param url*/
function getSortByReg(url: string): string {let res = '';let orderByRegex = /orderBy=(\w+)/;let orderDirRegex = /orderDir=(\w+)/;let orderByMatch = url.match(orderByRegex);let orderDirMatch = url.match(orderDirRegex);if (orderByMatch && orderByMatch.length > 1) res = orderByMatch[1];if (orderDirMatch && orderDirMatch.length > 1) res = `${res} ${orderDirMatch[1]}`;return res;
}

修改splitUrl函数如下:

/*** 分解出Url里面的查询参数,并返回数据对象*/
export function splitUrl(url: string): any {let urlDataList = url.split('?');let apiUrl = urlDataList[0];let searchData = {},pageInfo = getPageInfoByReg(url),sort = getSortByReg(url);if (urlDataList.length > 1) {let paramsLst = urlDataList[1].split('&');for (var i = 0; i < paramsLst.length; i++) {var pair = paramsLst[i].split('=');if (pair.length < 2) continue;pair[1] = decodeURI(pair[1]);if (pair[0] == 'page' || pair[0] == 'perPage') continue;else if (pair[0] == 'orderBy' || pair[0] == 'orderDir') continue;else searchData[pair[0]] = pair[1];}}searchData = {url: apiUrl,data: { ...searchData, page: pageInfo.page, pageSize: pageInfo.pageSize, sort: sort },};return searchData;
}

保存,刷新。现在我们可以正常排序了,单击id或创建时间旁边的排序小按钮,观察输出,观察network里面,我们提交到find的body。(Request Payload)

有时候我们还会要求,crud一开始就按照某种要求排序,比如最新的数据排在最前面(order by id desc),这种情况很好出来,直接把排序参数写在api里面,这样渲染后的第一次请求就会按照参数里面写的排序,比如:

 "api": "/api/user/find?orderBy=id&&orderDir=desc",

保存刷新后,可以看到程序出来的数据已经按照id降序了。

头部工具条

列折叠按钮

关于查询,crud提供了更多的功能,可以通过头部工具条呈现。比如列显示折叠按钮,在columns前面添加如下代码:

"headerToolbar": [{"type": "columns-toggler",        "align":"right" //可以通过指定对齐方式控制按钮在左边还是右边}],

保存并刷新,可以看到如下界面:
在这里插入图片描述

Bug踩坑记:amis 的列折叠按钮不能有"draggable": true 的选项,以下代码正常:
{
“type”: “columns-toggler”,
“align”:“right”
},
如果增加一个可拖动,变成
{
“type”: “columns-toggler”,
“draggable”: true,
“align”:“right”
},
就会出现 [mobx-state-tree] You are trying to read or write to an object that is no longer part of a state tree.

这个bug 已经提交到官方issues.

刷新和导出excel

"headerToolbar": [{"type": "reload","icon": "fa fa-refresh"},"export-excel",{"type": "columns-toggler",        "align":"right"}],

自定义内容

还可以添加自定义的内容,比如显示一些我们希望呈现给用户的其他信息,在headerToolbar里面添加如下代码试试:

{"type": "tpl","tpl": "一共有 ${count} 行数据。",//${count} 可以获取api返回的data里面的count,还可以尝试${sql} 看看有什么效果"className": "v-middle"
},

tpl是amis提供的模板,可以参考:

https://aisuda.bce.baidu.com/amis/zh-CN/docs/concepts/template
https://aisuda.bce.baidu.com/amis/zh-CN/components/tpl

amis 还提供更多其他按钮,可以到amis官网了解更多

可以提供两种删除功能

单条删除

在columns数组最后面添加一个操作列,操作列里面添加删除功能,代码如下:

 "columns": [......{//添加操作栏"type": "operation","label": "操作","buttons": [ //操作栏里面可以有许多操作按钮,所以这个地方buttons后面跟的是一个数组{"label": "删除","type": "button","actionType": "ajax","level": "danger","confirmText": "确认要删除?","api": {//点击按钮之后提交的api"url": "/api/userDel","data": {"id": "${id}"}}}]}
]//columns结束

保存刷新后,可以看到删除按钮,但是点击删除按钮后发现Sails返回“不可以无条件删除的错误”,跟踪点击按钮之后提交的api请求,控制台输出如下:

[url]: /api/userDel
[data]: {id: 22}

这里我们看到id并没有如预期的写在url参数里面,这和我们schema源代码里面api的新写法有关系,这里,通过${id}可以获取当前操作按钮所在行的数据的id值。考虑后面我们在批量删除的时候还有更多的id变换,直接写在url参数里面直观性比较差,amis 的api同时提供两种写法,直接写在url里面的在参数要求比较直观的时候比较合适。如果需要对参数进行变换或是参数比较多的时候,可以通过“data”属性进行设置,并且设置在data里面的数据是通过提交到data参数而不是url后面加“?”来传递到fetcher的。所以我们上述的写法,url里面没有参数,data里面有{id:22}

如果我们希望把删除的条件id=22写在查询参数里面,可以这样:

 "api": "/api/userDel?id=${id}",

现在我们针对新的写法,utils/request.ts里面也应该增加这种情况下,对data数据的获取,这个修改比较简单:

增加一条
if (sd.url == '/api/userDel') sd.data = data;
同时对sd.url的判断里面增加一个||sd.url= ='/api/userDel'即可

修改后的amisRequest函数如下:

export async function amisRequest(url: string, method?: string, data?: any, options?: { [key: string]: any }) {console.log('[url]:', url);console.log('[data]:', data);let newMethod = '';if (!method) newMethod = 'GET';else newMethod = method.toUpperCase();let sd = splitUrl(url);if (sd.url == '/api/userDel') sd.data = data;if (sd.url == '/api/user/find'||sd.url=='/api/userDel') {return remoteRequest(sd.url, {method: 'POST',data: sd.data,...(options || {}),});}switch (newMethod) {case 'GET':return remoteRequest.get(url, options);case 'POST':return remoteRequest.post(url, options);case 'DELETE':return remoteRequest.delete(url, options);case 'PUT':return remoteRequest.put(url, options);}
}

保存再执行删除,观察network里面的Payload。

点击删除按钮,数据被删除并且提示删除成功。同时我们看到界面里面的对应数据也没有了。这就说明我们点击删除按钮之后,crud组件会自动重载

批量删除

有时候我们希望可以一次性删除多条数据,crud提供批量操作bulkActions,我们可以把它添加在headerToolbar中,代码片段如下:

"headerToolbar": ["bulkActions",{"type": "reload","icon": "fa fa-refresh"},.........
]

当然保存之后界面上面不会有变化,因为我们只是在headerToolbar里面添加了一个批量动作的占位符而已,我们还需要对批量动作进行具体定义,在headerToolbar 后面添加同级内容bulkActions:

"headerToolbar": [
....
],
"bulkActions": [{"label": "批量删除","actionType": "ajax","api": {"url": "/api/userDel","data": {"id": "${CONCATENATE('in$(',ids,')')}"}},"confirmText": "确定要批量删除?"}
],

批量删除里面有个actionType,这个是动作类型,和button按钮类似。其中ajax标识这个动作是一个提交到后端的ajax操作(也就是发出api请求),api里面的内容是点击批量删除按钮之后发出的api,通过采用data的方式传递数据,“id”: "KaTeX parse error: Expected '}', got 'EOF' at end of input: …CONCATENATE('in(‘,ids,’)')}"这个语句利用amis提供的字符串连接公式,更多信息可以参考:amis表达式

因为我们要根据提供的id值删除多条数据,批量操作的 时候crud会提供当前选择的所有id值到ids变量,比如我们选择id值为18,19两个数据,ids的值就应该是18,19

我们的sails源代码里面,对删除多条id的要求是提交的body里面应该这样写:id: “in$(18,19)”,删除函数会根据前后端约定要求,转换成符合sails的形式:{id:in: [18,19] } 最终再翻译成对应的sql查询语句的where条件。

为此我们通过amis表达式:CONCATENATE,合成一个in$(18,19) 的参数给amisRequest,最终提交到后端我们可以成功删除多条数据了。如下图:

在这里插入图片描述
保存并刷新,观察network。

新增数据

在headerToolbar里面添加一个可以打开对话框( “actionType”: “dialog”)的“新增”按钮,

  {"label": "新增","type": "button","actionType": "dialog",//action类型是打开对话框"level": "primary","className": "m-b-sm","dialog": {//对话框节点"title": "添加用户",//对话框title"body": {"type": "form",//对话框body里面添加form表单节点"api": "/api/userCreate",//form提交是请求的api"body": [//form里面的项目{"type": "input-text",//文本输入框组件"name": "email",              "label": "Email"},{"type": "input-text","name": "nickname",              "label": "昵称"},{"type": "native-number","name": "age","label": "年龄"},{"type": "hidden",//隐藏类型组件"name": "password","value": "123456"}]}}},

就这样,我们添加了一个可以新增用户数据的功能,点击新增按钮,可以弹出新增对话框,在对话框里面填写数据并提交后,后请求/api/userCreate这个后端api。

保存刷新:
在这里插入图片描述
界面已经满足预期,打开F12,任意添加一些数据后点击确定,观察控制台输出:

[url]: /api/userCreate
[data]: {password: '123456', email: '89898', nickname: '88', age: '88', __super: {}}

这个调用fetcher的url和data也满足预期,接下来在utls/request.ts里面添加对userCreate这个api的拦截处理,把data post到userCreate的body 就可以了:

/*** 需要拦截处理的url名单 要处理的api越来越多,用数组来判断*/
const toBeProcessUrl = ['/api/user/find', '/api/userCreate', '/api/userDel', '/api/user/updateOne'];
/** amis的schema文件专用request*/
export async function amisRequest(url: string, method?: string, data?: any, options?: { [key: string]: any }) {console.log('[url]:', url);console.log('[data]:', data);let newMethod = '';if (!method) newMethod = 'GET';else newMethod = method.toUpperCase();let sd = splitUrl(url);if (sd.url == '/api/userDel') sd.data = data;else sd.data = { ...sd.data, ...data };if (toBeProcessUrl.includes(sd.url)) {//要处理的api越来越多,用数组来判断return remoteRequest(sd.url, {method: 'POST',data: sd.data,...(options || {}),});}switch (newMethod) {case 'GET':return remoteRequest.get(url, options);case 'POST':return remoteRequest.post(url, options);case 'DELETE':return remoteRequest.delete(url, options);case 'PUT':return remoteRequest.put(url, options);}
}

保存刷新后,可以新增用户数据了。

headerToolbar 结果分析

一步一步过来,这个schema越来越复杂了,为了更加清晰的了解整个schema结构,我们把headerToolbar结构分析成如下图:
在这里插入图片描述

在编写schema 文档的时候,可以尽量的利用vscode的代码折叠功能,把复杂的当前不关心的节点折叠起来,这样有利于我们修改和管理schema源代码

前端数据格式要求

在测试过程,随意的添加了一个email,有时候根本不符合email的格式,但是依然可以顺利的提交并完成添加。这样的程序是不够严谨的,在前端,用户输入的时候,就应该对用户的输入做一些必要的格式检查。
amis 有提供格式校验的功能,详细资料可以参考:amis 格式校验

现在我们来实践一下对email的输入格式校验,在新增按钮的form里面,找到email输入项,修改如下:

{"type": "input-text","name": "email","required": true,//不允许空白"validations": {//校验参数"isEmail": true},"label": "Email"
},

修改后再随意提交,就会被拒绝:

在这里插入图片描述

数据修改

修改是需要针对某一条数据的,所以我们应该把修改按钮放置在操作栏里面,在操作栏中添加修改按钮,该按钮和新增按钮一样需要弹窗:

 "columns": [................{"type": "operation","label": "操作","buttons": [............{"label": "修改",           "type": "button","level": "warning",//按钮颜色和删除按钮区分一下"actionType": "dialog","dialog": {"title": "信息修改","body": {"type": "form","api": "/api/user/updateOne","body": [{"type": "hidden","name": "id","value":"${id}" //修改需要提交当前记录的id,用隐藏字段实现},{"type": "hidden","name": "version","value":"${version}" //修改需要提交当前记录的版本号,用隐藏字段实现},{"type": "input-text","name": "createdAt","disabled": true,//创建时间应该是不可改的"label": "创建时间"},{"type": "input-text","name": "email","required": true,"validations": {"isEmail": true},"label": "Email"},{"type": "input-text","required": true,"name": "nickname","label": "昵称"},{"type": "native-number","name": "age","label": "年龄"}]}}},]}
]

保存后,界面已经满足预期,但是提交到api依然错误:
在这里插入图片描述

重组提交数据

我们还需要对提交到修改api的数据进行重新组织,打开sails源代码里面wlSimulate.ts文件,找到updateOne函数,可以看到注释里面有提到修改api的测试数据格式:

/**
* postmant 测试数据:
* 
{  "where":{"id":51},    "valuesToSet":{"email":"updateTest","age":38},"oldVer":0
}
*/

根据这个格式要求,修改前端代码utils/request.ts里面的amisRequest如下:

/** amis的schema文件专用request*/
export async function amisRequest(url: string, method?: string, data?: any, options?: { [key: string]: any }) {console.log('[url]:', url);console.log('[data]:', data);let newMethod = '';if (!method) newMethod = 'GET';else newMethod = method.toUpperCase();let sd = splitUrl(url);if (sd.url == '/api/userDel') sd.data = data;else if (sd.url == '/api/user/updateOne') {//处理修改数据sd.data = {where: { id: data.id },valuesToSet: data,oldVer: data.version,};    } else sd.data = { ...sd.data, ...data };if (toBeProcessUrl.includes(sd.url)) {return remoteRequest(sd.url, {method: 'POST',data: sd.data,...(options || {}),});}switch (newMethod) {case 'GET':return remoteRequest.get(url, options);case 'POST':return remoteRequest.post(url, options);case 'DELETE':return remoteRequest.delete(url, options);case 'PUT':return remoteRequest.put(url, options);}
}

保存修改并刷新,测试修改数据,显示修改成功。打开HeidiSQL查看被修改过的数据,可以看到版本号version已经自动增加,说明修改功能符合预期。

修改左边菜单

修改左边菜单数据,打开public/json/menu/zh_CN.json 修改第一条为:

{"path": "index","name": "user表的增删改查"        
},

最终程序界面:

在这里插入图片描述

至此,对一个表的增删改查排序等操作都得以实现。熟悉schema比自己从头开始写代码做UI,amis的schema还是要高效非常多的。我们甚至连一行css代码都没有写,就搞定了一个高可用的增删改查页面了。

感谢amis团队!


http://chatgpt.dhexx.cn/article/INyBNxm1.shtml

相关文章

三、Sails 中使用Jwt进行身份认证

文章目录 Jwt 概述为什么要用JwtJwt原理 Jwt认证安装 Jwt 库登录ApiVerify Signature过期时间Nodejs 单线程易崩问题 验证程序修改配置积极策略消极策略多重验证 Jwt 测试正常登录过期或错误密钥测试 Jwt 概述 由于我们是完全前后端分离的开发模式&#xff0c;我们的后端对前端…

Sails基础之Controller层

通过前面的使用&#xff0c;我们可以看出Sails中MVC的结构更倾向于MVP的概念&#xff0c;Presenter在Sails中被称之为Actions&#xff1a; They often act as a middleman between your models and views. Controller层这个结构上的变化是Sails v1.0中新提出的方案&#xff0c;…

二、 在Sails中使用Typescript

文章目录 Typescript 基础Typescript 安装TypeScript 问题最简单的改造 Sails重定义Waterline&#xff08;Orm&#xff09; 重写ModelsTypescript 重写控制器User Model的进一步优化前后端约定 路径别名tsconfig.jsonmodule-alias安装使用 Jest测试 Typescript 基础 Typescrip…

四、Sails项目的Api文档——集成Swagger解决方案

文章目录 Api的迷思SwaggerSwagger概述在Sails中集成Swagger安装Swagger 生成设置生成的内容SwaggerUI Assets和.tmpBlueprint 蓝图Blueprint是什么Blueprint 配置local.js 进一步控制Swagger输出路由过滤路由的Swagger配置进一步优化Authorization Api的迷思 我们都知道写代码…

sails mysql_Sails+MVC+Mysql+Node+学习笔记一

项目构建 安装Node就不多说了&#xff0c; 1.sails安装与项目新建运行 npm install sails -g//全局安装 sails new project-name//新建项目 cd project-name //进入刚才新建项目的目录 sails lift //运行项目&#xff0c;运行原理也是直接在项目目录路径下使用node app.js npm …

Sails.js自动化Api实践与测试

开发中为了快速交互数据库&#xff0c;于是需要一个能便捷搭建api的平台。于是学习了一下sails.js框架。本次实践是一次摸索&#xff0c;使用了winston日志记录&#xff0c;supertest单元测试&#xff0c;mongo数据库&#xff0c;hashids哈希值解密。 模块: winstonsupertestmo…

五、解读Sails之Waterline源代码

文章目录 sql调试代码跟踪package.json启动调试Auto-Migrating备份原始数据删除所有表再重建回写备份数据 加密库 encrypted-attraes-256-gcm算法encrypted-att 的使用密钥 sql转义 sqlstring日期处理三种方式比较mariaDB&#xff08;或my-sql&#xff09;中的日期时间string 对…

一、Sails基础操作

本篇目录 Sails 安装App结构修改端口跨域问题第一个Api控制器用Postman 做Api调试MySql命令行操作MySql8.0版本加密问题 Sails 操作Mysql创建第一个model实现一个model的增删改查 Sails 安装 Sailsjs提供安装脚手架&#xff0c;使用之前可以先安装Sailjs npm install sails -…

sails

sails介绍 node.js的MVC框架&#xff0c;完全继承Express&socket.io的一些API 使用 全局安装 npm install -g sails创建项目 sails new 项目名称选2 选2 启动项目 sails liftsails框架目录介绍 api MVC结构项目代码目录controller层controller层尽量只做数据封装&…

Sails基础之Models层的config/datastores配置

配置与使用 Sails提供并支持多种Models层的存储&#xff08;https://sailsjs.com/documentation/concepts/extending-sails/adapters/available-adapters&#xff09;&#xff0c; 使用时需要在应用程序项目下安装对应的adapter并且在config/datastores或config/env/productio…

Sails的简单学习

这里贴出Sails的官方 一.Sails的简单介绍 官网上说&#xff1a; The web framework of your dreams.你梦想中的web框架。 Sails让创建自定义、企业级的Node.js应用的工作变得简单。它模拟了大家熟悉的诸如Ruby on Rails这种框架的MVC设置模式&#xff0c;但是也拥有满足现代…

什么是Sails

Sails的关键字 Realtime MVC Framework for Node.js Node.js Sails采用纯粹的Node.js进行构建&#xff0c;你只需要掌握一门javascript编程语言就可以构建Sails应用程序&#xff08;Web程序&#xff09;&#xff1b; MVC Framework Sails提供了基于MVC结构组织Web程序的基础…

sublime插件anaconda的设置

在 python 编辑环境下&#xff0c;使用 anaconda 完成一些代码补全和提示 具体设置如下 {//由于Anaconda插件本身无法知道Python安装的路径&#xff0c;所以需要设置Python主程序的实际位置"python_interpreter": "../python.exe",//忽略各种空格不对, 超…

Sublime 插件安装

1、百度搜索 Sublime3&#xff0c;进入 Sublime3 官网下载安装文件 注意&#xff1a;不要走到 Sublime2 的官网去下载&#xff0c;也不要使用 Sublime 的其它中文汉化版本&#xff1b; 2、下载 github 的 Package Control 包&#xff0c;下载地址&#xff1a; https://githu…

Sublime插件安装

1.PackageControl 功能&#xff1a;安装包管理 简介&#xff1a;sublime插件控制台&#xff0c;提供添加、删除、禁用、查找插件等功能 使用&#xff1a;https://sublime.wbond.net/installation 安装方法&#xff1a; CTRL &#xff0c;出现控制台粘贴以下代码至控制台 …

解决sublime无法下载插件问题

解决sublime无法下载插件问题 最近遇到了无法在sublime下载插件的问题&#xff0c;解决方法如下。 首先下载一个文件&#xff0c;地址如下 https://pan.baidu.com/s/1OlC0q8MwiZ_cEbs56SIwzw&#xff0c;提取码为vef9 下载完成后将其解压 再放入sublime文件夹中 接着点开P…

mac sublime安装插件

sublime 安装插件 需要等待一会&#xff0c;在弹出的新窗口&#xff0c;输入要添加的插件的名称&#xff0c;选择确认就可以了 以 Pretty JSON 插件为例&#xff0c;在将json字符串粘贴到文件之后&#xff0c;使用&#xff1a; control command j 进行格式化 调用搜索栏 shi…

java开发sublime插件_开发者最常用的8款Sublime text 3插件

5. SublimeCodeIntel Sublime​Code​Intel 作为一个代码提示和补全插件&#xff0c;支持Javascript、Mason、XBL、XUL、RHTML、SCSS、python、HTML、Ruby、Python3、XML、Sass、XSLT、Django、HTML5、Perl、CSS、Twig、Less、Smarty、Node.js、Tcl、TemplateToolkit 和 PHP 等…

Sublime Text - SublimeREPL插件的配置

1. 菜单Preferences -> Browse Packages&#xff0c;打开安装组件所在的文件夹&#xff0c;进入文件夹Data\Packages\SublimeREPL\config\Python&#xff0c;打开文件Default.sublime-commands&#xff0c;复制如下代码 { "caption": "SublimeREPL: Pyth…

Sublime常用c语言插件

1. Alignment 按等号对齐&#xff0c;强迫症患者必备 Alignment&#xff1a;选中并按ctrlalta就可以使其按照等号对齐 2. 配色方案Enki或者earthbound 3.A file icon 文件图标 4.CoolFormat&#xff1a;C代码格式化 简单好用的代码格式化工具&#xff0c;相当于简化版的Astyl…