优化后台拖拽排序逻辑

pull/515/head
Karson 2025-06-03 11:19:01 +08:00
parent fad64c36f0
commit e2901f3b5e
9 changed files with 173 additions and 71 deletions

View File

@ -50,10 +50,9 @@ class Ajax extends Backend
$controllername = $this->request->get('controllername'); $controllername = $this->request->get('controllername');
$lang = $this->request->get('lang'); $lang = $this->request->get('lang');
if (!$lang || !in_array($lang, config('allow_lang_list')) || !$controllername || !preg_match("/^[a-z0-9_\.]+$/i", $controllername)) { if (!$lang || !in_array($lang, config('allow_lang_list')) || !$controllername || !preg_match("/^[a-z0-9_\.]+$/i", $controllername)) {
return jsonp(['errmsg' => '参数错误'], 200, [], ['json_encode_param' => JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE]); return jsonp(['errmsg' => __('Invalid parameters')], 200, [], ['json_encode_param' => JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE]);
} }
$controllername = input("controllername");
$className = Loader::parseClass($this->request->module(), 'controller', $controllername, false); $className = Loader::parseClass($this->request->module(), 'controller', $controllername, false);
//存在对应的类才加载 //存在对应的类才加载
@ -135,66 +134,26 @@ class Ajax extends Backend
*/ */
public function weigh() public function weigh()
{ {
//排序的数组 if (!$this->request->isPost()) {
$ids = $this->request->post("ids"); $this->error(__('Invalid request'));
//拖动的记录ID
$changeid = $this->request->post("changeid");
//操作字段
$field = $this->request->post("field");
//操作的数据表
$table = $this->request->post("table");
if (!Validate::is($table, "alphaDash")) {
$this->error();
} }
//主键 $controllername = $this->request->post('controllername');
$pk = $this->request->post("pk"); if (!$controllername || !preg_match("/^[a-z0-9_\.]+$/i", $controllername)) {
//排序的方式 $this->error(__('Invalid parameters'));
$orderway = strtolower($this->request->post("orderway", "")); }
$orderway = $orderway == 'asc' ? 'ASC' : 'DESC'; if (!$this->auth->check(str_replace('.', '/', $controllername) . '/dragsort')) {
$sour = $weighdata = []; $this->error(__('You have no permission'));
$ids = explode(',', $ids);
$prikey = $pk && preg_match("/^[a-z0-9\-_]+$/i", $pk) ? $pk : (Db::name($table)->getPk() ?: 'id');
$pid = $this->request->post("pid", "");
//限制更新的字段
$field = in_array($field, ['weigh']) ? $field : 'weigh';
// 如果设定了pid的值,此时只匹配满足条件的ID,其它忽略
if ($pid !== '') {
$hasids = [];
$list = Db::name($table)->where($prikey, 'in', $ids)->where('pid', 'in', $pid)->field("{$prikey},pid")->select();
foreach ($list as $k => $v) {
$hasids[] = $v[$prikey];
}
$ids = array_values(array_intersect($ids, $hasids));
} }
$list = Db::name($table)->field("$prikey,$field")->where($prikey, 'in', $ids)->order($field, $orderway)->select(); $className = Loader::parseClass($this->request->module(), 'controller', $controllername, false);
foreach ($list as $k => $v) {
$sour[] = $v[$prikey]; if (!class_exists($className)) {
$weighdata[$v[$prikey]] = $v[$field]; $this->error(__('Invalid parameters'));
} }
$position = array_search($changeid, $ids);
$desc_id = $sour[$position] ?? end($sour); //移动到目标的ID值,取出所处改变前位置的值 // 重设action方法中获取控制器名称
$sour_id = $changeid; $this->request->controller($controllername);
$weighids = []; return action('admin/' . $controllername . '/dragsort');
$temp = array_values(array_diff_assoc($ids, $sour));
foreach ($temp as $m => $n) {
if ($n == $sour_id) {
$offset = $desc_id;
} else {
if ($sour_id == $temp[0]) {
$offset = $temp[$m + 1] ?? $sour_id;
} else {
$offset = $temp[$m - 1] ?? $sour_id;
}
}
if (!isset($weighdata[$offset])) {
continue;
}
$weighids[$n] = $weighdata[$offset];
Db::name($table)->where($prikey, $n)->update([$field => $weighdata[$offset]]);
}
$this->success();
} }
/** /**
@ -213,21 +172,21 @@ class Ajax extends Backend
if ($type == 'content') { if ($type == 'content') {
break; break;
} }
// no break // no break
case 'template': case 'template':
// 模板缓存 // 模板缓存
rmdirs(TEMP_PATH, false); rmdirs(TEMP_PATH, false);
if ($type == 'template') { if ($type == 'template') {
break; break;
} }
// no break // no break
case 'addons': case 'addons':
// 插件缓存 // 插件缓存
Service::refresh(); Service::refresh();
if ($type == 'addons') { if ($type == 'addons') {
break; break;
} }
// no break // no break
case 'browser': case 'browser':
// 浏览器缓存 // 浏览器缓存
// 只有生产环境下才修改 // 只有生产环境下才修改
@ -288,8 +247,8 @@ class Ajax extends Backend
{ {
$params = $this->request->get("row/a"); $params = $this->request->get("row/a");
if (!empty($params)) { if (!empty($params)) {
$province = isset($params['province']) ? $params['province'] : null; $province = $params['province'] ?? null;
$city = isset($params['city']) ? $params['city'] : null; $city = $params['city'] ?? null;
} else { } else {
$province = $this->request->get('province'); $province = $this->request->get('province');
$city = $this->request->get('city'); $city = $this->request->get('city');

View File

@ -6,6 +6,8 @@ use app\admin\model\AuthRule;
use app\common\controller\Backend; use app\common\controller\Backend;
use fast\Tree; use fast\Tree;
use think\Cache; use think\Cache;
use think\db\Query;
use think\exception\HttpResponseException;
/** /**
* 规则管理 * 规则管理
@ -31,7 +33,7 @@ class Rule extends Backend
} }
$this->model = model('AuthRule'); $this->model = model('AuthRule');
// 必须将结果集转换为数组 // 必须将结果集转换为数组
$ruleList = \think\Db::name("auth_rule")->field('type,condition,remark,createtime,updatetime', true)->order('weigh DESC,id ASC')->select(); $ruleList = \think\Db::name("auth_rule")->field('type,condition,remark,menutype,extend,pinyin,py,createtime,updatetime', true)->order('weigh DESC,id ASC')->select();
foreach ($ruleList as $k => &$v) { foreach ($ruleList as $k => &$v) {
$v['title'] = __($v['title']); $v['title'] = __($v['title']);
} }
@ -154,4 +156,18 @@ class Rule extends Backend
} }
$this->error(); $this->error();
} }
/**
* 排序
*/
public function dragsort()
{
// 注册Hook
\think\Hook::add('admin_dragsort_after', function ($model) {
Cache::rm('__menu__');
});
parent::dragsort();
}
} }

View File

@ -15,7 +15,9 @@ use think\db\exception\ModelNotFoundException;
use think\exception\DbException; use think\exception\DbException;
use think\exception\PDOException; use think\exception\PDOException;
use think\exception\ValidateException; use think\exception\ValidateException;
use think\Hook;
use think\response\Json; use think\response\Json;
use think\Validate;
trait Backend trait Backend
{ {
@ -32,7 +34,7 @@ trait Backend
unset($params[$field]); unset($params[$field]);
} }
} }
} else if (array_key_exists($this->excludeFields, $params)) { } elseif (array_key_exists($this->excludeFields, $params)) {
unset($params[$this->excludeFields]); unset($params[$this->excludeFields]);
} }
return $params; return $params;
@ -341,6 +343,128 @@ trait Backend
$this->error(__('No rows were updated')); $this->error(__('No rows were updated'));
} }
/**
* 排序
*
* @return void
*/
public function dragsort()
{
$idsString = $this->request->post("ids");
$changeid = $this->request->post("changeid");
$field = $this->request->post("field", "weigh");
$pk = $this->request->post("pk");
$pid = $this->request->post("pid", "");
$orderway = $this->request->post("orderway", "desc");
// 检测参数是否有效
if (empty($idsString) || empty($changeid)) {
$this->error(__('Invalid parameters'));
}
// 验证排序方式
$orderway = strtolower($orderway);
if (!in_array($orderway, ['asc', 'desc'])) {
$orderway = 'desc';
}
// 获取模型表名
$table = $this->model->getTable();
$ids = explode(',', $idsString);
// 验证changeid是否在ids中
if (!in_array($changeid, $ids)) {
$this->error(__('Invalid parameters'));
}
// 允许的排序字段白名单
$allowedFields = explode(',', $this->dragsortFields);
$field = in_array($field, $allowedFields) ? $field : '';
if (!$field) {
$this->error(__('You have no permission'));
}
// 获取主键
$prikey = $this->model->getPk();
// 开始事务
Db::startTrans();
try {
// 处理pid筛选
if ($pid !== '') {
$pidValues = explode(',', $pid); // 支持多个pid值
$validRecords = Db::table($table)->where($prikey, 'in', $ids)->where('pid', 'in', $pidValues)->column($prikey);
// 只保留有效的ID
$ids = array_values(array_intersect($ids, $validRecords));
if (empty($ids)) {
Db::rollback();
exception(__('No rows were updated'));
}
}
// 获取当前记录的权重值
$currentWeights = Db::table($table)->where($prikey, 'in', $ids)->column($field, $prikey);
// 直接从$currentWeights获取最大和最小权重值
$minWeight = !empty($currentWeights) ? min($currentWeights) : 1;
$maxWeight = !empty($currentWeights) ? max($currentWeights) : count($ids);
// 计算权重步长
$step = 1;
// 准备批量更新数据 - 直接根据新顺序分配权重
$updateData = [];
// 根据排序方式设置初始权重和步长方向
if ($orderway == 'asc') {
// 升序:从小到大排列
$newWeight = $minWeight;
$step = abs($step);
} else {
// 降序:从大到小排列
$newWeight = $maxWeight;
$step = -abs($step);
}
// 根据新的顺序分配权重值
foreach ($ids as $index => $id) {
// 计算该记录应有的新权重值
$expectedWeight = $newWeight;
$newWeight += $step;
// 如果当前权重与期望权重不同,才需要更新
if (!isset($currentWeights[$id]) || $currentWeights[$id] != $expectedWeight) {
$updateData[] = [$prikey => $id, $field => $expectedWeight];
}
}
// 批量更新
if (!empty($updateData)) {
foreach ($updateData as $data) {
Db::table($table)->where($prikey, $data[$prikey])->update([$field => $data[$field]]);
}
}
// 提交事务
Db::commit();
} catch (\Exception $e) {
// 发生异常时回滚事务
Db::rollback();
$this->error();
}
Hook::listen("admin_dragsort_after", $this->model);
$this->success();
}
/** /**
* 导入 * 导入
* *
@ -391,7 +515,7 @@ trait Backend
} }
//导入文件首行类型,默认是注释,如果需要使用字段名称请使用name //导入文件首行类型,默认是注释,如果需要使用字段名称请使用name
$importHeadType = isset($this->importHeadType) ? $this->importHeadType : 'comment'; $importHeadType = $this->importHeadType ?? 'comment';
$table = $this->model->getQuery()->getTable(); $table = $this->model->getQuery()->getTable();
$database = \think\Config::get('database.database'); $database = \think\Config::get('database.database');

View File

@ -25,6 +25,7 @@
<table id="table" class="table table-bordered table-hover" <table id="table" class="table table-bordered table-hover"
data-operate-edit="{:$auth->check('auth/rule/edit')}" data-operate-edit="{:$auth->check('auth/rule/edit')}"
data-operate-del="{:$auth->check('auth/rule/del')}" data-operate-del="{:$auth->check('auth/rule/del')}"
data-operate-dragsort="{:$auth->check('auth/rule/dragsort')}"
width="100%"> width="100%">
</table> </table>
</div> </div>

View File

@ -26,6 +26,7 @@
<table id="table" class="table table-striped table-bordered table-hover table-nowrap" <table id="table" class="table table-striped table-bordered table-hover table-nowrap"
data-operate-edit="{:$auth->check('category/edit')}" data-operate-edit="{:$auth->check('category/edit')}"
data-operate-del="{:$auth->check('category/del')}" data-operate-del="{:$auth->check('category/del')}"
data-operate-dragsort="{:$auth->check('category/dragsort')}"
width="100%"> width="100%">
</table> </table>
</div> </div>

View File

@ -19,6 +19,7 @@
<table id="table" class="table table-striped table-bordered table-hover table-nowrap" <table id="table" class="table table-striped table-bordered table-hover table-nowrap"
data-operate-edit="{:$auth->check('user/rule/edit')}" data-operate-edit="{:$auth->check('user/rule/edit')}"
data-operate-del="{:$auth->check('user/rule/del')}" data-operate-del="{:$auth->check('user/rule/del')}"
data-operate-dragsort="{:$auth->check('user/rule/dragsort')}"
width="100%"> width="100%">
</table> </table>
</div> </div>

View File

@ -38,7 +38,6 @@ class Ajax extends Frontend
return jsonp(['errmsg' => '参数错误'], 200, [], ['json_encode_param' => JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE]); return jsonp(['errmsg' => '参数错误'], 200, [], ['json_encode_param' => JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE]);
} }
$controllername = input("controllername");
$className = Loader::parseClass($this->request->module(), 'controller', $controllername, false); $className = Loader::parseClass($this->request->module(), 'controller', $controllername, false);
//存在对应的类才加载 //存在对应的类才加载

View File

@ -10,7 +10,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
edit_url: 'category/edit', edit_url: 'category/edit',
del_url: 'category/del', del_url: 'category/del',
multi_url: 'category/multi', multi_url: 'category/multi',
dragsort_url: 'ajax/weigh', dragsort_url: 'category/dragsort',
table: 'category', table: 'category',
} }
}); });

View File

@ -335,7 +335,7 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
if ($(".export .exporttips").length === 0) { if ($(".export .exporttips").length === 0) {
$(".export .dropdown-menu").prepend("<li class='exporttips alert alert-warning-light mb-0 no-border p-2'></li>") $(".export .dropdown-menu").prepend("<li class='exporttips alert alert-warning-light mb-0 no-border p-2'></li>")
} }
$(".export .exporttips").html("导出记录:" + (selectedIds.length > 0 ? "选中" : "全部")); $(".export .exporttips").html("导出记录:" + (selectedIds.length > 0 ? "选中" : "全部"));
} }
$(Table.config.disabledbtn, toolbar).toggleClass('disabled', !options.selectedIds.length); $(Table.config.disabledbtn, toolbar).toggleClass('disabled', !options.selectedIds.length);
@ -503,8 +503,9 @@ define(['jquery', 'bootstrap', 'moment', 'moment/locale/zh-cn', 'bootstrap-table
pid: pid, pid: pid,
field: Table.config.dragsortfield, field: Table.config.dragsortfield,
orderway: options.sortOrder, orderway: options.sortOrder,
table: options.extend.table, table: options.extend.table || '',
pk: options.pk pk: options.pk,
controllername: Config.controllername || ''
} }
}; };
Fast.api.ajax(params, function (data, ret) { Fast.api.ajax(params, function (data, ret) {