拖拽排序(平均值法)
原先有讨论过一篇有关拖拽排序的问题,求教列表排序的解决方案(拖拽排序),在社区大佬们的帮助下,了解了很多的实现方法。下面介绍一个我本人比较认可的一个方法:平均值法(自己起的名字😂)。
此方法借鉴下面两位大佬的回复:
问答:求教列表排序的解决方案(拖拽排序)
问答:求教列表排序的解决方案(拖拽排序)
表与模型
CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL DEFAULT '' COMMENT '名称',
`rank` decimal(60,30) NOT NULL DEFAULT '0.000000000000000000000000000000' COMMENT '权重,用于排序,越小越靠前'
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8;
迁移
创建迁移文件
$ php artisan make:migration create_sort_table
完整的迁移文件内容
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('sort', function (Blueprint $table) {
$table->id();
$table->string('name', 50)->default('');
$table->decimal('rank', 60, 30)->default(0);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('sort');
}
};
运行迁移
$ php artisan migrate
模型
$ php artisan make:model "App\\Models\\Sort"
完整的模型文件内容
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Sort extends Model
{
use HasFactory;
/**
* 指示模型是否主动维护时间戳。
*
* @var bool
*/
public $timestamps = false;
/**
* 字段黑名单
*
* @var array
*/
protected $guarded = [];
/**
* The attributes that should be cast.
* @var array
*/
protected $casts = [
'rank' => 'string'
];
}
逻辑代码
创建控制器
$ php artisan make:controller "SortController"
控制器内容
<?php
namespace App\Http\Controllers;
use App\Models\Sort;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Throwable;
class SortController extends Controller
{
/**
* 列表
* @return JsonResponse
*/
public function index(): JsonResponse
{
$sorts = Sort::orderBy('rank')->get();
return response()->json($sorts);
}
/**
* 添加
*
* @param Request $request
* @return JsonResponse
* @throws Throwable
*/
public function store(Request $request): JsonResponse
{
$name = $request->input('name');
DB::beginTransaction();
try {
// 创建
$sort = Sort::create([
'name' => $name
]);
// 修改排序值为 当前 id * 10 的 20 次方,保留30为小数,相同位置最多可拖拽 20 + 30 次
$sort->rank = bcmul($sort->id, bcpow(10, 20, 30), 30);
$sort->save();
DB::commit();
} catch (Throwable $e) {
DB::rollBack();
throw $e;
}
return response()->json([
'id' => $sort->id
]);
}
/**
* 拖拽排序
*
* @param Request $request
* @param int $id
* @return JsonResponse
* @throws Throwable
*/
public function updateRank(Request $request, int $id): JsonResponse
{
$params = $request->only(['before_id', 'after_id']);
// 获取当前需要移动的 sort
$current = Sort::query()->find($id);
if (!$current) {
throw new \Exception();
}
// 前一个 rank 值
$before = Sort::query()->find($params['before_id']);
$beforeRank = $before->rank ?? 0;
// 后一个 rank 值
$after = Sort::query()->find($params['after_id']);
$afterRank = $after->rank ?? bcadd($before->rank, 2, 30); // 如果后一个不存在则 值=前一个rank + 2
// 保存 rank
$current->rank = bcdiv(bcadd($beforeRank, $afterRank, 30), 2, 30);
$current->save();
// 判断是否需要重置数据,当右边第三个数已被使用后则重置,最多可拖拽 50 -3 次,便需要重置
if ((int) substr($current->rank, -3) > 0) {
DB::beginTransaction();
try {
// 这里将循环遍历重置rank
$sorts = Sort::query()->orderBy('rank')->get();
foreach ($sorts as $index => $sort) {
$sort->rank = bcmul($index + 1, bcpow(10, 20, 30), 30);
$sort->save();
}
DB::commit();
} catch (Throwable $e) {
DB::rollBack();
throw $e;
}
}
return response()->json();
}
}
rank
字段总长度为60为,其中30位为小数点,添加数据的时候rank
是从 1020 开始的,因为 id 是 int(11) 类型的,这样正好保证了 rank 字段不过超过公式bcmul($sort->id, bcpow(10, 20, 30), 30)
的大小。为了防止拖动过快(小并发)保留 3 位作为并发空间,固此代码相同位置最多可以拖拽 47 次后,就需要重置rank
字段。
这是目前想出来比较合理的拖拽排序的解决方案了,大家有更好的吗?一起交流下
本作品采用《CC 协议》,转载必须注明作者和本文链接
我粗暴地直接用update实现了排序
我假设一下啊,是不是用链表的思维方式会不会更好一点