前言:layui 框架本身不支持table的拖拽排序,不过,在它红极一时的时候,有过很多优秀的第三方扩展插件,借助于这些扩展插件,我们就能轻松的完成拖拽排序这个操作啦
简单看一下效果图 动动手指就能轻松完成排序了,排序效果直观可见
拖拽功能本身因为有第三方的插件,是比较好实现的,重点在于后端的数据更新
通常更新数据的方案有3种
一、全量更新,直接获取更新后的列表数据,将所有的ID 按照顺序 发送给后端,然后后端进行全部更新(优点,足够简单无脑的,缺点就是,如果数据量比较多,这样更新会性能开销比较大)
二、取中值法,存储排序字段,不再是紧凑连续的,而是每个数据直接会间隔一段距离。
例如表的第一条数据sort是1000,第二条则是2000 ,第三条3000,以此类推,每个数据排序字段间隔1000的差值
然后将数据的排序字段通过对拖拽后,获取到最新的位置前后的数据ID,
例如,将第三条数据 拖拽到1和2直接,则将1和2 发送给后端,后端查询表后,得知他们的排序 分别是 1000,2000
此时 只需要将3的排序值改成(1000+2000)/2 = 1500 即可完成更新
这种更新的方式优点显而易见,只会针对有变动的数据进行更新,缺点也很明显,如果多次更新的话,数值之间没有足够的空间进行二分,就会导致更新失败
这时,我们就需要针对全表进行一次大的排序重置,将数据间的间距重新恢复到初始状态才行,所以该方案适合更新频率不高的场景,或者数据量比较小的也可以(另一个缺点则是新增数据的时候,也需要动态去计算排序值了,无法直接使用一个默认的排序值)
三、单表单列 ,这个我没看懂,有点难复杂,我就不班门弄斧了,原帖在此,有兴趣的可以去看看 拖拽排序后台设计与实现
在这里,我使用的是第二条方案,并且会在无法继续取中间值时,自动触发全表重置,使用起来 还是挺安逸的
我这边是使用的 webmen-admin 框架,理论上,只要前端是用的layui 都可以使用这个教程的
有请本次友情出场的插件:layui-soul-table
先将插件代码下载到本地,插件文件很多,因为这个插件的功能确实很多,像什么拖拽排序,表头筛选,表头拖拽排序等等... 不过我们先不需要管其他的,先将核心的文件复制一下,也就是 ext 的文件
进入我们自己的项目,在 plugin/admin/public/component/pear/module 目录下 新建一个文件夹 soulTable,将ext 文件夹中的文件拷贝过来
这儿的 module 文件夹 也就是 layui的模块文件夹, 如果有第三方插件 都可以放在这儿
新建一个JS文件,也可以直接 在plugin/admin/public/admin/js/common.js 中追加下方的代码
plugin/admin/public/admin/js/soulTable.js
// 使用 extend 将 刚刚下载的插件 加载进来
layui.extend({
soulTable:'soulTable/soulTable.slim',// 模块
})
/**
* 拖拽表格 进行排序
* @param obj 当前拖拽对象
* @param tableId 表的主键ID
* @param updateUrl 更新数据的接口
* @param weightField 排序字段
*/
function rowDragDoneFunc(obj,tableId,updateUrl,weightField){
weightField = weightField || 'weight';
console.log(obj.row,'--obj.row')
// 获取最新位置 前后数据的id
var beforId = afterId = 0;
if(obj.newIndex > 0){
beforId = obj.cache[obj.newIndex-1][tableId]
}
if(obj.newIndex < obj.cache.length-1){
afterId = obj.cache[obj.newIndex+1][tableId]
}
var data = {
dragDone:1,// 增加数据标识 方便后台接口进行判断
id:obj.row[tableId],
field:weightField,
beforId,afterId
}
// 提交数据进行排序更新
layui.$.post(updateUrl,data,function(res){
if(res.code){
layui.layer.msg(res.msg,{icon:5});
}else{
obj.row[weightField] =res.data;
refreshTable();
}
})
}
在业务代码中开始使用
app/admin/view/app/demo/index.html
注意:如果上面是新建的 soulTable.js 文件 则需要在 index.html 中 引入这个js文件方可
table.render({
elem: "#data-table",
url: SELECT_API,
page: true,
cols: [cols],
skin: "row",// line 行边框风格 row 列边框风格 nob 无边框
even:true,// 隔行换色
size: "lg",
toolbar: "#table-toolbar",
autoSort: false,
where:{field:'weight',order:'desc'},// 默认使用的weight 倒序
defaultToolbar: [{
title: "刷新",
layEvent: "refresh",
icon: "layui-icon-refresh",
}, "filter", "print", "exports"],
// 关键代码 拖拽方法回调 rowDragDoneFunc 就是上面创建的方法
rowDrag:{
done:(obj)=>{rowDragDoneFunc(obj,PRIMARY_KEY,UPDATE_API,'weight')},
},
done: function () {
layer.photos({photos: 'div[lay-id="data-table"]', anim: 5});
// 关键代码 拖拽方法绑定
soulTable.render(this);
}})
至此,前端的代码已经添加完毕了,下面需要对后台代码进行一点点小小的改动
app/admin/app/servicers/DragdoneUpService.php
<?php
namespace app\admin\app\servicers;
use support\Db;
use support\exception\ApiException;
use support\facade\Logger;
use support\Request;
use support\Response;
class DragdoneUpService extends BaseService
{
const sortSpace = 1000;// 排序默认间距
/**
* 拖拽排序 后台处理逻辑
* @param Request $request 接收的参数
* @param \support\Model $model 被排序的表
* @return true
* @throws ApiException
*/
static function dragDoneUpData(Request $request,\support\Model $model){
$primary_key = $model->getKeyName();
// 获取前一条数据的 排序数值
$params = $request->post();
$field = $params['field'];
$selfId = $params[$primary_key];
$afterWeight = $beforWeight = 0;
if($params['beforId']){
$beforWeight = $model->where($primary_key,$params['beforId'])->value($field);
}else{
// 不存在 则查询 后一个位置的前一条记录 先查询 后者的排序值
$afterWeight = $model->where($primary_key,$params['afterId'])->value($field);
$info = $model->where([[$field,'>',$afterWeight],[$primary_key,'<>',$selfId]])->orderBy($field,'desc')->select($primary_key,$field)->first();
if($info){
$beforWeight = $info[$field];
$params['beforId'] = $info[$primary_key];
}else{
$beforWeight = -1 ;// 没有记录
}
}
// Logger::debug(['$beforWeight',$beforWeight]);
if(!$afterWeight){
if($params['afterId']){
$afterWeight = $model->where($primary_key,$params['afterId'])->value($field);
}else{
$info = $model->where([[$field,'<',$beforWeight],[$primary_key,'<>',$selfId]])->orderBy($field,'desc')->select($primary_key,$field)->first();
if($info){
$afterWeight = $info[$field];
$params['afterId'] = $info[$primary_key];
}else{
$afterWeight = -1 ;// 没有记录
}
}
}
// 间隔值太小 触发全表重排
if(abs($afterWeight - $beforWeight)<10 ||
($beforWeight<0 && $afterWeight<10) ||
($beforWeight<10 && $afterWeight<0)
){
self::dragDoneUpDataDefault($model,$field);
// 再次获取新的 排序数值
$beforWeight = $model->where($primary_key,$params['beforId'])->value($field)?:-1;
$afterWeight = $model->where($primary_key,$params['afterId'])->value($field)?:-1;
}
// 如果当前位置末尾没有数据了
if($afterWeight == -1) $afterWeight = 0;
if($beforWeight == -1){
// 如果当前位置前面没有数据了 则更新的排序值 = 最大的排序值+间隔值
$newWeight = $afterWeight + self::sortSpace;
}else{
// 取中间值 则为当前数据的值
$newWeight = ceil(($beforWeight + $afterWeight) / 2);
}
// Logger::debug(['取中间值',$newWeight,$beforWeight,$afterWeight,$params]);
if($newWeight<2){
// throw new ApiException('抱歉,排序失败,取值错误');
}
// 更新排序
$model->where($primary_key,$selfId)->update([$field=>$newWeight]);
return $newWeight;
}
/**
* 全表重排排序
* @param \support\Model $model
* @param $field
* @param $sortSpace
* @return void
*/
static protected function dragDoneUpDataDefault(\support\Model $model,$field){
$tableName = $model->getTable();
$primary_key = $model->getKeyName();
$sortSpace = self::sortSpace;
DB::statement("SET @row_number = 0");
$sql = " UPDATE $tableName t1
JOIN (
SELECT id, (@row_number := @row_number + 1) AS new_weight
FROM (
SELECT id, $field
FROM $tableName
ORDER BY $field asc,$primary_key asc
) AS sorted_table
) AS t2 ON t1.id = t2.id
SET t1.$field = t2.new_weight * $sortSpace";
return Db::statement($sql);
}
}
最后,前往我们的业务控制器中, 调用我们上方声明的方法即可
app/admin/app/controller/DemoController.php
/**
* 更新
* @param Request $request
* @return Response
* @throws BusinessException
*/
public function update(Request $request): Response
{
if ($request->method() === 'POST') {
// 通过前端我们安排的间谍 dragDone 来判断本次请求是否是拖拽排序
// 拖拽排序更新
if($request->post('dragDone')==1 && DragdoneUpService::dragDoneUpData($request,$this->model)){
return $this->json(0,'ok');
}
return parent::update($request);
}
return view('app/demo/update');
}
好了,到此为止,相信各位看官都能够轻松完成表格的拖拽排序啦~~~
(小插曲,最开始是想给一键菜单里面加个拖拽排序。毕竟 字段不能拖拖 着实难受,不过 在那边试了下 遇到了点挫折,明天我会继续努力的,如果成功了,我再来更新 ^_^)
正好头疼这个排序问题,每次建表创建时间排前面看着难受,下午试试看
一键菜单配置中的 拖拽排序 我倒是已经改好 提交了PR ,老哥你可以参考看看 https://github.com/zxc112233/webman-admin/commit/dbfacaab63d4648841750f9aeb28e8d07e0541ac
可以看看这个 https://github.com/webman-php/admin/pull/95/files
dragDoneUpDataDefault
函数里面的SQL
,能否PHP
化这个sql 我只会原生的写法。 用 orm 不知道怎么写 QAQ
这,好吧。。。。