### 前端JS部分的ajax使用介绍:
FA的ajax使用Fast.api.ajax();
在public\assets\js\fast.js 中可以看到FA对jquery的ajax方法进行了封装处理:
```
//发送Ajax请求
ajax: function (options, success, error) {
//如果options 是字符串,则默认是目标url的值,也就是说,我们即使只传一个参数就可以完成整个ajax
options = typeof options === 'string' ? {url: options} : options;
var index;//这个index 是 Layer.load的索引,后面用来关闭这个弹出层
if (typeof options.loading === 'undefined' || options.loading) {
//如果loading参数没有,或者是 ture,就以0号默认风格显示loading图标
index = Layer.load(options.loading || 0);
}
//使用jquery的extend方法,把options合并入默认的参数对象,得到一个新的参数对象, 后面的参数的属性会替换掉前面的属性
options = $.extend({
type: "POST",
dataType: "json",
xhrFields: {
withCredentials: true //跨域请求带上凭证,跨域必须true,因为我们很多跨域api请求,这里默认为true了
},
success: function (ret) {
index && Layer.close(index); //关闭loading图标
ret = Fast.events.onAjaxResponse(ret);
if (ret.code === 1) {
Fast.events.onAjaxSuccess(ret, success);
} else {
Fast.events.onAjaxError(ret, error);
}
},
error: function (xhr) {
index && Layer.close(index);
var ret = {code: xhr.status, msg: xhr.statusText, data: null};
Fast.events.onAjaxError(ret, error);
}
}, options);
return $.ajax(options);//最终把参数对象传给jquery的ajax并执行
},
```
FA还有另外3个函数用来一起处理ajax的结果,才能完成完成整个处理流程;
```
//这个方法是先将服务器返回的数据进行预先处理,组装成FA标准数据格式,
onAjaxResponse: function (response) {
try {
var ret = typeof response === 'object' ? response : JSON.parse(response);
if (!ret.hasOwnProperty('code')) {
$.extend(ret, {code: -2, msg: response, data: null});
}
} catch (e) {
var ret = {code: -1, msg: e.message, data: null};
}
return ret;
},
//请求成功的回调,由onAjaxResponse处理好的ret传过来,参数onAjaxSuccess是用户自己写的success函数
onAjaxSuccess: function (ret, onAjaxSuccess) {
var data = typeof ret.data !== 'undefined' ? ret.data : null;
//没有msg的话,就去获取系统默认的操作完成的提示语
var msg = typeof ret.msg !== 'undefined' && ret.msg ? ret.msg : \_\_('Operation completed');
if (typeof onAjaxSuccess === 'function') {//这里的onAjaxSuccess 就是用户自己编写的成功处理函数
var result = onAjaxSuccess.call(this, data, ret);//使用call方式调用用户自己编写的成功函数
//call的解释:https://xiaobei.blog.csdn.net/article/details/51480723
if (result === false)
return; //如果用户的处理函数返回false,则这边也不再继续进行任何操作了
}
Toastr.success(msg);//否则就弹出一个提示语
},
//请求错误的回调,代码流程与onAjaxSuccess基本一致
onAjaxError: function (ret, onAjaxError) {
var data = typeof ret.data !== 'undefined' ? ret.data : null;
//如果请求失败了,msg是xhr.statusText,由上面组合好了,这里不用进行判断是否存在
if (typeof onAjaxError === 'function') {
var result = onAjaxError.call(this, data, ret);
if (result === false) {
return;
}
}
Toastr.error(ret.msg);
},
```
例子1:
```
getData() {
let that = this;
Fast.api.ajax({
url: 'intellect/user/user/index',
loading: true,
type: 'GET',
data: {
searchWhere: that.searchKey,//搜索关键字
offset: that.offset,//分页数据:偏移量
limit: that.limit,//分页数据:每页多少行
},
}, function (data,ret) {//onAjaxSuccess.call传过来的两个参数,data是ret里面的data,ret是完整的返回数据
that.data = data.rows; //把数据赋值好
that.totalPage = data.total;
return false; //返回false,让页面不要弹出提示语,因为这里是查询数据,不需要再弹出提示'查询成功'
})
},
```
例子2:
```
let filter = {}
let op = {}
Fast.api.ajax({
url: 'shopro/order/order/index',
loading: true,
type: 'GET',
data: {
filter: JSON.stringify(filter),//也可以在之前就组装好filter数组,对于字段多的情况下,这样看起来没那么乱
op: JSON.stringify(op),
offset: that.offset,
limit: that.limit,
}
}, function (data, ret) {
that.orderList = data.rows;
that.totalPage = data.total;
return false;
})
```
### 后端PHP部分的使用介绍:
后端使用一个统一函数对查询条件进行处理
application\common\controller\Backend.php 的
`protected function buildparams($searchfields = null, $relationSearch = null)`
和 intellect插件基于FA的改进版本
application\admin\controller\intellect\Base.php 的
`protected function custombuildparams($searchfields = null, $nobuildfields = \[\], $relationSearch = null)`
这两个函数的差别:custombuildparams包含整个buildparams,然后添加了$nobuildfields 参数,
用于去除一些不需要被buildparams的搜索条件,然后额外再给每个控制器再自主组合搜索条件;
这样更灵活组合搜索;
### 如何在前端js中组装出符合buildparams要求的参数:
首先我们要彻底清楚buildparams函数,下面是代码详解:
```
/**
* 生成查询所需要的条件,排序方式
* @param mixed $searchfields 快速查询的字段 ,默认的查询字段
* @param boolean $relationSearch 是否关联查询 ,是:将会给字段前面带上'表名.',用来区分不同的表的字段
* @return array
*/
protected function buildparams($searchfields = null, $relationSearch = null)
{
//每个控制器可以自己设置$this->searchField的值,写成数组或者用逗号隔开每个字段名称,如果强制指定参数值,则以传值为准
$searchfields = is_null($searchfields) ? $this->searchFields : $searchfields;
//与快速查询的字段一样的操作
$relationSearch = is_null($relationSearch) ? $this->relationSearch : $relationSearch;
//要搜索的内容
$search = $this->request->get("search", '');
//要搜索的字段
$filter = $this->request->get("filter", '');
//对于搜索字段的判断符
$op = $this->request->get("op", '', 'trim');
//排序字段,获取顺序 1:传值,2当前模型的主键字段,3默认以id为排序字段名称
$sort = $this->request->get("sort", !empty($this->model) && $this->model->getPk() ? $this->model->getPk() : 'id');
//排序顺序,默认反序
$order = $this->request->get("order", "DESC");
//分页偏移量,默认不偏移
$offset = $this->request->get("offset/d", 0);
//每页数量量,默认等于不限制
$limit = $this->request->get("limit/d", 999999);
if($this->request->has("page")){
$page = $this->request->get("page/d", 1);
}else{
//自动计算页码
$page = $limit ? intval($offset / $limit) + 1 : 1;
//把分页值写入到request数组中,从配置中获取分页的变量名var_page
$this->request->get([config('paginate.var_page') => $page]);
}
//转为关联数组,索引就是字段名
$filter = (array)json_decode($filter, true);
$op = (array)json_decode($op, true);
$filter = $filter ? $filter : [];
$where = [];
$alias = [];//别名表,用于给模型联表查询时配置别名,结构:表名=>别名
$bind = [];
$name = '';
$aliasName = '';
//如果是关联查询,查出表格名, 最终别名为'表名.'
if (!empty($this->model) && $this->relationSearch) {
$name = $this->model->getTable();
$alias[$name] = Loader::parseName(basename(str_replace('\\', '/', get_class($this->model))));
$aliasName = $alias[$name] . '.';
}
$sortArr = explode(',', $sort);
//给排序字段加上表别名
foreach ($sortArr as $index => & $item) {
//字段不存在点符号,就认为没有指定表名,就要加上表名
$item = stripos($item, ".") === false ? $aliasName . trim($item) : $item;
}
unset($item);
//重新把排序字段转回字符串
$sort = implode(',', $sortArr);
//去查是否需要判断数据管理员id
$adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds)) {
//添加管理员id限制条件到where数组
$where[] = [$aliasName . $this->dataLimitField, 'in', $adminIds];
}
if ($search) {
$searcharr = is_array($searchfields) ? $searchfields : explode(',', $searchfields);
foreach ($searcharr as $k => &$v) {
//给快速搜索字段添加表别名
$v = stripos($v, ".") === false ? $aliasName . $v : $v;
}
unset($v);
//快速搜索字段重新转回字符串,并且使用LIKE判断符,添加到where数组
$where[] = [implode("|", $searcharr), "LIKE", "%{$search}%"];
}
$index = 0;
foreach ($filter as $k => $v) {
if (!preg_match('/^[a-zA-Z0-9_\-\.]+$/', $k)) {
//匹配:字母数字_-. 的字符串,不属于这些字符的就跳过, 也就是搜索字段要形如:a.user_id, us2Er-i4d
continue;
}
//获取判断符,从op数组中获取,默认是等号
$sym = isset($op[$k]) ? $op[$k] : '=';
if (stripos($k, ".") === false) {
//同时也要增加表别名
$k = $aliasName . $k;
}
//如果搜索字段的值不是数组,就去除两边的空白
$v = !is_array($v) ? trim($v) : $v;
//把判断符转为大写
$sym = strtoupper(isset($op[$k]) ? $op[$k] : $sym);
//null和空字符串特殊处理
if (!is_array($v)) {
if (in_array(strtoupper($v), ['NULL', 'NOT NULL'])) {
//如果值是NULL或者NOT NULL 则判断符与值变成一样.
$sym = strtoupper($v);
}
if (in_array($v, ['""', "''"])) {
//如果只是空字符,则认为是搜索值等于空字符
$v = '';
$sym = '=';
}
}
switch ($sym) {
case '=':
case '<>':
//等于和不等于,不等于不能写成'!='
$where[] = [$k, $sym, (string)$v];
break;
case 'LIKE':
case 'NOT LIKE':
case 'LIKE %...%':
case 'NOT LIKE %...%':
//模糊搜索,默认是前后都模糊匹配,不支持只匹配前或者只匹配后
$where[] = [$k, trim(str_replace('%...%', '', $sym)), "%{$v}%"];
break;
case '>':
case '>=':
case '<':
case '<=':
//对于大于小于,只支持判断 整数
$where[] = [$k, $sym, intval($v)];
break;
case 'FINDIN':
case 'FINDINSET':
case 'FIND_IN_SET':
//对于搜索FIND_IN_SET,要求参数值的格式需要是数组,否则是逗号链接的字符串(推荐写法),最后是空格链接的字符串(不推荐)
$v = is_array($v) ? $v : explode(',', str_replace(' ', ',', $v));
$findArr = array_values($v);
foreach ($findArr as $idx => $item) {
$bindName = "item_" . $index . "_" . $idx;
$bind[$bindName] = $item;
//bindName的作用:
$where[] = "FIND_IN_SET(:{$bindName}, `" . str_replace('.', '`.`', $k) . "`)"; //把. 变成`.`,这就组合出`表名`.`字段名`
}
break;
case 'IN':
case 'IN(...)':
case 'NOT IN':
case 'NOT IN(...)':
//搜索IN ,值要求的数组或者逗号链接的字符串
$where[] = [$k, str_replace('(...)', '', $sym), is_array($v) ? $v : explode(',', $v)];
break;
case 'BETWEEN':
case 'NOT BETWEEN':
//从索引0开始,抽2个元素出来,组成新数组,也就是只认前两个元素.作为判断区间的开始和结尾
$arr = array_slice(explode(',', $v), 0, 2);
//如果值里面没有逗号(无法判断哪个是开始哪个是结尾)或者值的数组全部是空值,这样是无法进行区间判断,也就是写法不对
if (stripos($v, ',') === false || !array_filter($arr, function($v){
//判断$arr里面的每一个值是否是空值,不是空值就返回,最终返回的数组里面所有值都是非空值
return $v != '' && $v !== false && $v !== null;
})) {
continue 2; //写法不对,无法继续组合出条件,跳出2层循环,第一层跳出switch,第二层跳出foreach
}
//当出现一边为空时改变操作符
if ($arr[0] === '') {
//如果开始边界是空值且BETWEEN,则转为判断小于等于结束边界即可
//如果是NOT BETWEEN,则认为是判断 大于 结束边界即可
$sym = $sym == 'BETWEEN' ? '<=' : '>';
$arr = $arr[1];
} elseif ($arr[1] === '') {
//如果没有结束边界,则反过来判断
$sym = $sym == 'BETWEEN' ? '>=' : '<';
$arr = $arr[0];
}
$where[] = [$k, $sym, $arr];
break;
case 'RANGE':
case 'NOT RANGE':
//RANG判断符TP是没有的,是FA自己创的,是专门用来判断时间区间的
$v = str_replace(' - ', ',', $v);
$arr = array_slice(explode(',', $v), 0, 2);
if (stripos($v, ',') === false || !array_filter($arr)) {
continue 2;
}
//当出现一边为空时改变操作符
if ($arr[0] === '') {
$sym = $sym == 'RANGE' ? '<=' : '>';
$arr = $arr[1];
} elseif ($arr[1] === '') {
$sym = $sym == 'RANGE' ? '>=' : '<';
$arr = $arr[0];
}
$tableArr = explode('.', $k);//把字段名称转为表名和字段名分开的数组
if (count($tableArr) > 1 && $tableArr[0] != $name && !in_array($tableArr[0], $alias) && !empty($this->model)) {
//如果是其他表的字段,就认为是关联查询,需要添加别名
//修复关联模型下时间无法搜索的BUG
$relation = Loader::parseName($tableArr[0], 1, false);
$alias[$this->model->$relation()->getTable()] = $tableArr[0];
}
$where[] = [$k, str_replace('RANGE', 'BETWEEN', $sym) . ' TIME', $arr];
break;
case 'NULL':
case 'IS NULL':
case 'NOT NULL':
case 'IS NOT NULL':
//把IS变成空,实际上不推荐加IS的写法,推荐直接写 NULL 和NOT NULL
$where[] = [$k, strtolower(str_replace('IS ', '', $sym))];
break;
default:
break;
}
$index++;
}
if (!empty($this->model)) {
$this->model->alias($alias);//配置各个表别名,结构:真实表名(小写且不带前缀的) => 别名
}
$model = $this->model;
//这里使用闭包函数的方式,是为了一次性完成多条复杂的where配置
$where = function ($query) use ($where, $alias, $bind, &$model) {
if (!empty($model)) {
$model->alias($alias);
$model->bind($bind);//进行属性绑定,把关联模型的属性直接附加到当前模型
}
//因为上面组合where数组时,有可能值也是数组,TP的where数组本身没有这种写法(参数类型不接受数组的),所以这里使用call_user_func_array的方式进行$query的where方法调用
foreach ($where as $k => $v) {
if (is_array($v)) {
call_user_func_array([$query, 'where'], $v);
} else {
$query->where($v);
}
}
};
return [$where, $sort, $order, $offset, $limit, $page, $alias, $bind];
}
```
具体的在JS组合判断条件的写法:
在js中,我们按需组合出下列参数,作为ajax的data传给服务端:
1:search:模糊搜索的内容,任意字符串即可,sql会两边模糊搜索, 例如: '123', sql: LIKE %123%;
2:sort:排序字段,用逗号链接的字符串,sql的排序顺序就是js里面的顺序,例如:'id,weight' ,sql: ORDER BY id,weight;
3:order:排序方向,后台默认是DESC , 值也可以是ASC,只能是这两个值. sql:ORDER BY id DESC LIMIT 0,10
*排序局限性:如果排序字段是逗号链接的字符串,那么排序方向将被忽略,sql:ORDER BY id,status LIMIT 0,10 . 直接就没有排序方向了.也就是正序ASC了.这是TP源码导致的问题,在thinkphp\library\think\db\Query.php 的 public function order($field, $order = null) *
4:offset:分页的偏移量,默认是0,也就是从第一个开始查;
5:limit:每页的行数;
6:page:页码,通常不用穿这个参数,因为可由offset和limit计算得到,如果有传,则以传的值为准,就不会再去计算得到;
7:filter:要搜索的字段和值,在js中要写成一个对象
例:要搜索status字段 值为 normal(正常) ,具体判断符是 等于 还是不等于 就由下面的op参数决定
```
filter: {
status: "normal",
},
```
8:op:判断符(option),在js中要写成一个对象
例:
```
op: {
status: "=",
user_id: "=",
nickname: "like",
mobile: "like",
},
```
判断符具体有以下:
| 符号 | 作用 | filter值写法 |
| --- | --- | --- |--- |
| = | 等于 | 字符串 |
| <> | 不等于 | 字符串 |
| LIKE | 模糊搜索包含 | 字符串 |
| LIKE %...% | 模糊搜索包含(不推荐) | 字符串 |
| NOT LIKE | 模糊搜索排除 | 字符串 |
| NOT LIKE %...% | 模糊搜索排除(不推荐) | 字符串 |
| > | 大于 | 数字,服务端会转为int|
| >= |大于等于 | 数字,服务端会转为int|
| < | 小于 | 数字,服务端会转为int|
| <= | 小于dengyu | 数字,服务端会转为int|
| FINDINSET | FIND IN SET 搜索 | 字符串或者用空格或者逗号链接的字符串|
| FINDIN | FIND IN SET 搜索(不推荐) |
| FIND\_IN\_SET | FIND IN SET 搜索 (不推荐) |
| IN | IN搜索 | 字符串或者逗号链接的字符串|
| IN(...) | IN搜索 (不推荐) | |
| NOT IN | NOT IN 搜索 | |
| NOT IN(...) | NOT IN 搜索 (不推荐) | |
| BETWEEN | 区间内判断 | 一个逗号链接的字符串|
| NOT BETWEEN | 区间外判断 | 一个逗号链接的字符串|
| RANGE | 专用于时间的区间内判断,最终还是转换成 BETWEEN | 用中划线-链接的字符串 |
| NOT RANGE | 专用于时间的区间外判断 | 用中划线-链接的字符串 |
| NULL | 是NULL判断 | 不需要传值,传了也会被忽略|
| IS NULL | 是NULL判断 (不推荐) | 不需要传值,传了也会被忽略|
| NOT NULL | 非NULL判断 | 不需要传值,传了也会被忽略|
| IS NOT NULL | 非NULL判断 (不推荐,反人类写法) | 不需要传值,传了也会被忽略|
个别判断符的值的写法的详解:
**FIND IN SET**: FIND IN SET的值,根据SQL的写法,我们应该要提供一个要搜索的值和一个字段名,让TP可以组合出符合SQL语法的 `WHERE FIND_IN_SET('值',字段名)` :搜索这个字段的值是否包含要搜索的内容;
那么filter的写法是:
```
filter: {
status: "1,2,3",
},
```
op的写法是:
```
op: {
status: "FINDINSET",
},
```
表示:这里要搜索user表的status字段里面包含1且2且3 ,得到sql:
```
WHERE (
( FIND_IN_SET('1', `user`.`status`) )
AND
( FIND_IN_SET('2', `user`.`status`) )
AND
( FIND_IN_SET('3', `user`.`status`) )
)
```
buildparams函数的写法导致如果要搜索多个值,关系一定是且不是或.
SQL只支持值以逗号连接的形式保存在数据库中,如果连接符不是逗号,则需要用SQL的REPLACE函数转换,
例如把'|'转为','后在进行findinset:where FIND\_IN\_SET('其他',REPLACE(type\_name,'|',','))
这已经超出FA的设计,所以我们必须在数据库字段的格式上就明确要求是用逗号连接.
**BETWEEN** :区间搜索,一般需要有2个值,表示两个边界.标准值的写法是:一个逗号在中间,前后是边界值;
```
filter={
money:'1,99'
}
op={
money:'BETWEEN'
}
```
表示搜索 价格在 1-99之间(含1和99,封闭区间)的商品;如果判断符是NOT BETWEEN,就表示搜索价格不是1-99之间(不含1和99,开放区间)的商品;
filter的值,必须用逗号连接;
如果filter的值有多个逗号,FA只认 第一个逗号和第一个逗号两边的值作为边界值,其他内容会被忽略;
如果出现逗号一边无值或者空值的情况,实际就会转化成大于小于的问题,左值认为是小值,右值是大值
①左空值的情况下:BETWEEN转为判断 : <= 右值,NOT BETWEEN转为判断: > 右值
②右空值的情况下:BETWEEN转为判断 : >= 左值,NOT BETWEEN转为判断: < 左值
**RANGE**:专门用于判断时间的,实际会转化为TP 的between time 和 notbetween time;
```
filter={
createtime:'1491635035-1491635036'
}
op={
createtime:'RANGE'
}
```
filter的值就是连字符(中划线)连接的两个时间戳;
空值情况与BETWEEN同样方式处理;
====================
在ajax中要转为json传给服务端
```
data: {
offset: that.poffset,
limit: that.plimit,
filter: JSON.stringify(filter),
op: JSON.stringify(op)
},
```
- FA的JS调用机制说明
- FA的JS之Fast.api逐个详解
- FA页面渲染时后端传递数据给前端的方式
- FA的ajax查询数据的前后台流程
- FA特有的函数解释
- FA的鉴权Auth类
- extend\fast\Auth.php详解
- application\admin\library\Auth.php详解
- application\common\library\Auth.php详解
- FA的Token机制
- FA管理员(后台)的权限机制
- FA用户(前台和API)的权限机制
- FA在前台模板文件中进行鉴权
- FA的登录页面
- TP类Hook:钩子机制
- TP类Lang:多语言机制
- TP类Config:参数配置机制
- TP类Request:请求类
- TP的模型关联详解
- think-queue队列组件
- Queue.php
- \queue\Connector.php
- \queue\connector\Redis.php
- \queue\Job.php
- queue\job\Redis.php
- PHP规则:正则表达式
- PHP规则:闭包与匿名函数
- 项目架构说明
- 代码架构
- TP数据库where条件的各种写法
