From b43048a589d61994039faa8de55c825c53767b1f Mon Sep 17 00:00:00 2001
From: RuoYi <yzz_ivy@163.com>
Date: Sat, 21 Mar 2026 22:54:21 +0800
Subject: [PATCH] 菜单管理支持批量保存排序

---
 ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java            |    7 ++
 ruoyi-ui/src/api/system/menu.js                                                                |    9 +++
 ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml                  |    4 +
 ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java         |    8 ++
 ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysMenuController.java    |   15 +++++
 ruoyi-ui/src/views/system/menu/index.vue                                                       |   56 ++++++++++++++++++
 ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java |   29 +++++++++
 7 files changed, 126 insertions(+), 2 deletions(-)

diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysMenuController.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysMenuController.java
index 620d2cb..44b15cb 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysMenuController.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/controller/SysMenuController.java
@@ -1,6 +1,7 @@
 package com.ruoyi.system.controller;
 
 import java.util.List;
+import java.util.Map;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.DeleteMapping;
@@ -134,6 +135,20 @@
     }
 
     /**
+     * 保存菜单排序
+     */
+    @RequiresPermissions("system:menu:edit")
+    @Log(title = "保存菜单排序", businessType = BusinessType.UPDATE)
+    @PutMapping("/updateSort")
+    public AjaxResult updateSort(@RequestBody Map<String, String> params)
+    {
+        String[] menuIds = params.get("menuIds").split(",");
+        String[] orderNums = params.get("orderNums").split(",");
+        menuService.updateMenuSort(menuIds, orderNums);
+        return success();
+    }
+
+    /**
      * 删除菜单
      */
     @RequiresPermissions("system:menu:remove")
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java
index de625f3..eb78bae 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java
@@ -107,6 +107,13 @@
     public int updateMenu(SysMenu menu);
 
     /**
+     * 保存菜单排序
+     * 
+     * @param menu 菜单信息
+     */
+    public void updateMenuSort(SysMenu menu);
+
+    /**
      * 删除菜单管理信息
      * 
      * @param menuId 菜单ID
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java
index 7393830..eabbd21 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java
@@ -127,6 +127,14 @@
     public int updateMenu(SysMenu menu);
 
     /**
+     * 保存菜单排序
+     * 
+     * @param menuIds 菜单ID
+     * @param orderNums 排序ID
+     */
+    public void updateMenuSort(String[] menuIds, String[] orderNums);
+
+    /**
      * 删除菜单管理信息
      * 
      * @param menuId 菜单ID
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java
index 4643957..f52c5ac 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java
@@ -12,8 +12,11 @@
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import com.ruoyi.common.core.constant.Constants;
 import com.ruoyi.common.core.constant.UserConstants;
+import com.ruoyi.common.core.exception.ServiceException;
+import com.ruoyi.common.core.text.Convert;
 import com.ruoyi.common.core.utils.StringUtils;
 import com.ruoyi.common.security.utils.SecurityUtils;
 import com.ruoyi.system.api.domain.SysRole;
@@ -322,6 +325,32 @@
     }
 
     /**
+     * 保存菜单排序
+     * 
+     * @param menuIds 菜单ID
+     * @param orderNums 排序ID
+     */
+    @Override
+    @Transactional
+    public void updateMenuSort(String[] menuIds, String[] orderNums)
+    {
+        try
+        {
+            for (int i = 0; i < menuIds.length; i++)
+            {
+                SysMenu menu = new SysMenu();
+                menu.setMenuId(Convert.toLong(menuIds[i]));
+                menu.setOrderNum(Convert.toInt(orderNums[i]));
+                menuMapper.updateMenuSort(menu);
+            }
+        }
+        catch (Exception e)
+        {
+            throw new ServiceException("保存排序异常,请联系管理员");
+        }
+    }
+
+    /**
      * 删除菜单管理信息
      * 
      * @param menuId 菜单ID
diff --git a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml
index bc80401..690f949 100644
--- a/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml
+++ b/ruoyi-modules/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml
@@ -204,6 +204,10 @@
 		)
 	</insert>
 	
+	<update id="updateMenuSort" parameterType="SysMenu">
+	    update sys_menu set order_num = #{orderNum} where menu_id = #{menuId}
+	</update>
+
 	<delete id="deleteMenuById" parameterType="Long">
 	    delete from sys_menu where menu_id = #{menuId}
 	</delete>
diff --git a/ruoyi-ui/src/api/system/menu.js b/ruoyi-ui/src/api/system/menu.js
index 97258ee..2fdeb21 100644
--- a/ruoyi-ui/src/api/system/menu.js
+++ b/ruoyi-ui/src/api/system/menu.js
@@ -51,6 +51,15 @@
   })
 }
 
+// 保存菜单排序
+export function updateMenuSort(data) {
+  return request({
+    url: '/system/menu/updateSort',
+    method: 'put',
+    data: data
+  })
+}
+
 // 删除菜单
 export function delMenu(menuId) {
   return request({
diff --git a/ruoyi-ui/src/views/system/menu/index.vue b/ruoyi-ui/src/views/system/menu/index.vue
index a6b2f9c..6510a8e 100644
--- a/ruoyi-ui/src/views/system/menu/index.vue
+++ b/ruoyi-ui/src/views/system/menu/index.vue
@@ -38,6 +38,16 @@
       </el-col>
       <el-col :span="1.5">
         <el-button
+          type="warning"
+          plain
+          icon="el-icon-check"
+          size="mini"
+          @click="handleSaveSort"
+          v-hasPermi="['system:menu:edit']"
+        >保存排序</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
           type="info"
           plain
           icon="el-icon-sort"
@@ -62,7 +72,11 @@
           <svg-icon :icon-class="scope.row.icon" />
         </template>
       </el-table-column>
-      <el-table-column prop="orderNum" label="排序" width="60"></el-table-column>
+      <el-table-column prop="orderNum" label="排序" width="200">
+        <template slot-scope="scope">
+          <el-input-number v-model="scope.row.orderNum" controls-position="right" :min="0" size="mini" style="width: 88px" />
+        </template>
+      </el-table-column>
       <el-table-column prop="perms" label="权限标识" :show-overflow-tooltip="true"></el-table-column>
       <el-table-column prop="component" label="组件路径" :show-overflow-tooltip="true"></el-table-column>
       <el-table-column prop="status" label="状态" width="80">
@@ -299,7 +313,7 @@
 </template>
 
 <script>
-import { listMenu, getMenu, delMenu, addMenu, updateMenu } from "@/api/system/menu"
+import { listMenu, getMenu, delMenu, addMenu, updateMenu, updateMenuSort } from "@/api/system/menu"
 import Treeselect from "@riophae/vue-treeselect"
 import "@riophae/vue-treeselect/dist/vue-treeselect.css"
 import IconSelect from "@/components/IconSelect"
@@ -326,6 +340,8 @@
       isExpandAll: false,
       // 重新渲染表格状态
       refreshTable: true,
+      // 记录原始排序,用于对比变更
+      originalOrders: {},
       // 查询参数
       queryParams: {
         menuName: undefined,
@@ -360,6 +376,8 @@
       this.loading = true
       listMenu(this.queryParams).then(response => {
         this.menuList = this.handleTree(response.data, "menuId")
+        // 记录原始排序值
+        this.recordOriginalOrders(this.menuList)
         this.loading = false
       })
     },
@@ -463,6 +481,40 @@
         }
       })
     },
+    /** 递归记录原始排序 */
+    recordOriginalOrders(list) {
+      list.forEach(item => {
+        this.originalOrders[item.menuId] = item.orderNum
+        if (item.children && item.children.length) {
+          this.recordOriginalOrders(item.children)
+        }
+      })
+    },
+    /** 保存排序 */
+    handleSaveSort() {
+      const changedMenuIds = []
+      const changedOrderNums = []
+      const collectChanged = (list) => {
+        list.forEach(item => {
+          if (String(this.originalOrders[item.menuId]) !== String(item.orderNum)) {
+            changedMenuIds.push(item.menuId)
+            changedOrderNums.push(item.orderNum)
+          }
+          if (item.children && item.children.length) {
+            collectChanged(item.children)
+          }
+        })
+      }
+      collectChanged(this.menuList)
+      if (changedMenuIds.length === 0) {
+        this.$modal.msgWarning("未检测到排序修改")
+        return
+      }
+      updateMenuSort({ menuIds: changedMenuIds.join(","), orderNums: changedOrderNums.join(",") }).then(() => {
+        this.$modal.msgSuccess("排序保存成功")
+        this.recordOriginalOrders(this.menuList)
+      })
+    },
     /** 删除按钮操作 */
     handleDelete(row) {
       this.$modal.confirm('是否确认删除名称为"' + row.menuName + '"的数据项?').then(function() {

--
Gitblit v1.9.3