NIUCLOUD是一款SaaS管理后台框架多应用插件+云编译。上千名开发者、服务商正在积极拥抱开发者生态。欢迎开发者们免费入驻。一起助力发展! 广告
### 前端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)  }, ```