前言:layui 框架本身不支持table的拖拽排序,不過,在它紅極一時的時候,有過很多優(yōu)秀的第三方擴展插件,借助于這些擴展插件,我們就能輕松的完成拖拽排序這個操作啦
簡單看一下效果圖 動動手指就能輕松完成排序了,排序效果直觀可見
拖拽功能本身因為有第三方的插件,是比較好實現(xiàn)的,重點在于后端的數(shù)據(jù)更新
通常更新數(shù)據(jù)的方案有3種
一、全量更新,直接獲取更新后的列表數(shù)據(jù),將所有的ID 按照順序 發(fā)送給后端,然后后端進行全部更新(優(yōu)點,足夠簡單無腦的,缺點就是,如果數(shù)據(jù)量比較多,這樣更新會性能開銷比較大)
二、取中值法,存儲排序字段,不再是緊湊連續(xù)的,而是每個數(shù)據(jù)直接會間隔一段距離。
例如表的第一條數(shù)據(jù)sort是1000,第二條則是2000 ,第三條3000,以此類推,每個數(shù)據(jù)排序字段間隔1000的差值
然后將數(shù)據(jù)的排序字段通過對拖拽后,獲取到最新的位置前后的數(shù)據(jù)ID,
例如,將第三條數(shù)據(jù) 拖拽到1和2直接,則將1和2 發(fā)送給后端,后端查詢表后,得知他們的排序 分別是 1000,2000
此時 只需要將3的排序值改成(1000+2000)/2 = 1500 即可完成更新
這種更新的方式優(yōu)點顯而易見,只會針對有變動的數(shù)據(jù)進行更新,缺點也很明顯,如果多次更新的話,數(shù)值之間沒有足夠的空間進行二分,就會導致更新失敗
這時,我們就需要針對全表進行一次大的排序重置,將數(shù)據(jù)間的間距重新恢復到初始狀態(tài)才行,所以該方案適合更新頻率不高的場景,或者數(shù)據(jù)量比較小的也可以(另一個缺點則是新增數(shù)據(jù)的時候,也需要動態(tài)去計算排序值了,無法直接使用一個默認的排序值)
三、單表單列 ,這個我沒看懂,有點難復雜,我就不班門弄斧了,原帖在此,有興趣的可以去看看 拖拽排序后臺設計與實現(xiàn)
在這里,我使用的是第二條方案,并且會在無法繼續(xù)取中間值時,自動觸發(fā)全表重置,使用起來 還是挺安逸的
我這邊是使用的 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 更新數(shù)據(jù)的接口
* @param weightField 排序字段
*/
function rowDragDoneFunc(obj,tableId,updateUrl,weightField){
weightField = weightField || 'weight';
console.log(obj.row,'--obj.row')
// 獲取最新位置 前后數(shù)據(jù)的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,// 增加數(shù)據(jù)標識 方便后臺接口進行判斷
id:obj.row[tableId],
field:weightField,
beforId,afterId
}
// 提交數(shù)據(jù)進行排序更新
layui.$.post(updateUrl,data,function(res){
if(res.code){
layui.layer.msg(res.msg,{icon:5});
}else{
obj.row[weightField] =res.data;
refreshTable();
}
})
}
在業(yè)務代碼中開始使用
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"],
// 關(guān)鍵代碼 拖拽方法回調(diào) rowDragDoneFunc 就是上面創(chuàng)建的方法
rowDrag:{
done:(obj)=>{rowDragDoneFunc(obj,PRIMARY_KEY,UPDATE_API,'weight')},
},
done: function () {
layer.photos({photos: 'div[lay-id="data-table"]', anim: 5});
// 關(guān)鍵代碼 拖拽方法綁定
soulTable.render(this);
}})
至此,前端的代碼已經(jīng)添加完畢了,下面需要對后臺代碼進行一點點小小的改動
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 接收的參數(shù)
* @param \support\Model $model 被排序的表
* @return true
* @throws ApiException
*/
static function dragDoneUpData(Request $request,\support\Model $model){
$primary_key = $model->getKeyName();
// 獲取前一條數(shù)據(jù)的 排序數(shù)值
$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 ;// 沒有記錄
}
}
}
// 間隔值太小 觸發(fā)全表重排
if(abs($afterWeight - $beforWeight)<10 ||
($beforWeight<0 && $afterWeight<10) ||
($beforWeight<10 && $afterWeight<0)
){
self::dragDoneUpDataDefault($model,$field);
// 再次獲取新的 排序數(shù)值
$beforWeight = $model->where($primary_key,$params['beforId'])->value($field)?:-1;
$afterWeight = $model->where($primary_key,$params['afterId'])->value($field)?:-1;
}
// 如果當前位置末尾沒有數(shù)據(jù)了
if($afterWeight == -1) $afterWeight = 0;
if($beforWeight == -1){
// 如果當前位置前面沒有數(shù)據(jù)了 則更新的排序值 = 最大的排序值+間隔值
$newWeight = $afterWeight + self::sortSpace;
}else{
// 取中間值 則為當前數(shù)據(jù)的值
$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);
}
}
最后,前往我們的業(yè)務控制器中, 調(diào)用我們上方聲明的方法即可
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');
}
好了,到此為止,相信各位看官都能夠輕松完成表格的拖拽排序啦~~~
(小插曲,最開始是想給一鍵菜單里面加個拖拽排序。畢竟 字段不能拖拖 著實難受,不過 在那邊試了下 遇到了點挫折,明天我會繼續(xù)努力的,如果成功了,我再來更新 ^_^)
一鍵菜單配置中的 拖拽排序 我倒是已經(jīng)改好 提交了PR ,老哥你可以參考看看 https://github.com/zxc112233/webman-admin/commit/dbfacaab63d4648841750f9aeb28e8d07e0541ac
正好頭疼這個排序問題,每次建表創(chuàng)建時間排前面看著難受,下午試試看