diff --git a/backend/dvadmin/system/views/menu_button.py b/backend/dvadmin/system/views/menu_button.py
index dc5130b..a14da74 100644
--- a/backend/dvadmin/system/views/menu_button.py
+++ b/backend/dvadmin/system/views/menu_button.py
@@ -87,12 +87,22 @@ class MenuButtonViewSet(CustomModelViewSet):
@action(methods=['post'], detail=False, permission_classes=[IsAuthenticated])
def batch_create(self, request, *args, **kwargs):
"""
- 批量创建菜单“增删改查查”权限
+ 批量创建菜单"增删改查查"权限
创建的数据来源于菜单,需要规范创建菜单参数
value:菜单的component_name:method
api:菜单的web_path增加'/api'前缀,并根据method增加{id}
"""
menu_obj = Menu.objects.filter(id=request.data['menu']).first()
+ if not menu_obj:
+ return DetailResponse(data={}, msg="菜单不存在", code=4000)
+ if not menu_obj.component_name:
+ return DetailResponse(data={}, msg="该菜单未设置组件名称(component_name),无法自动生成权限", code=4000)
+
+ # 获取已存在的 value,避免唯一约束冲突
+ existing_values = set(
+ MenuButton.objects.filter(menu=menu_obj.id).values_list('value', flat=True)
+ )
+
result_list = [
{'menu': menu_obj.id, 'name': '新增', 'name_en': 'Add', 'name_zh_tw': '新增', 'value': f'{menu_obj.component_name}:Create', 'api': f'/api/{menu_obj.component_name}/', 'method': 1},
{'menu': menu_obj.id, 'name': '删除', 'name_en': 'Delete', 'name_zh_tw': '刪除', 'value': f'{menu_obj.component_name}:Delete', 'api': f'/api/{menu_obj.component_name}/{{id}}/', 'method': 3},
@@ -101,11 +111,22 @@ class MenuButtonViewSet(CustomModelViewSet):
{'menu': menu_obj.id, 'name': '详情', 'name_en': 'Detail', 'name_zh_tw': '詳情', 'value': f'{menu_obj.component_name}:Retrieve', 'api': f'/api/{menu_obj.component_name}/{{id}}/', 'method': 0},
{'menu': menu_obj.id, 'name': '复制', 'name_en': 'Copy', 'name_zh_tw': '複製', 'value': f'{menu_obj.component_name}:Copy', 'api': f'/api/{menu_obj.component_name}/', 'method': 1},
{'menu': menu_obj.id, 'name': '导入', 'name_en': 'Import', 'name_zh_tw': '導入', 'value': f'{menu_obj.component_name}:Import', 'api': f'/api/{menu_obj.component_name}/import_data/', 'method': 1},
- {'menu': menu_obj.id, 'name': '导出', 'name_en': 'Export', 'name_zh_tw': '導出', 'value': f'{menu_obj.component_name}:Export', 'api': f'/api/{menu_obj.component_name}/export_data/', 'method': 1},]
- serializer = self.get_serializer(data=result_list, many=True)
- serializer.is_valid(raise_exception=True)
- serializer.save()
- return SuccessResponse(serializer.data, msg=_("Batch creation successful"))
+ {'menu': menu_obj.id, 'name': '导出', 'name_en': 'Export', 'name_zh_tw': '導出', 'value': f'{menu_obj.component_name}:Export', 'api': f'/api/{menu_obj.component_name}/export_data/', 'method': 1},
+ ]
+
+ # 只保留不存在的新记录
+ to_create = [r for r in result_list if r['value'] not in existing_values]
+ skipped = len(result_list) - len(to_create)
+
+ if to_create:
+ serializer = self.get_serializer(data=to_create, many=True)
+ serializer.is_valid(raise_exception=True)
+ MenuButton.objects.bulk_create(
+ [MenuButton(**item) for item in to_create],
+ ignore_conflicts=True
+ )
+
+ return SuccessResponse(data={'created': len(to_create), 'skipped': skipped}, msg=f"批量创建成功 {len(to_create)} 项,跳过 {skipped} 项(已存在)")
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def scan_get_apps(self, request):
diff --git a/docs/auto-permission-scan.md b/docs/auto-permission-scan.md
index 9986fa2..7c4c9d3 100644
--- a/docs/auto-permission-scan.md
+++ b/docs/auto-permission-scan.md
@@ -17,7 +17,18 @@
## 2. 菜单管理 - 自动扫描功能
+
+
+
+
+
+
+
+
+
+
### 流程
+
```
菜单管理页面 → 选中菜单 → 点击「自动扫描」→ 选择 App → 开始扫描
→ 预览分组表格(按 ViewSet 分组,可折叠)
diff --git a/web/src/views/system/menu/components/MenuButtonCom/crud.tsx b/web/src/views/system/menu/components/MenuButtonCom/crud.tsx
index 8bcf20a..65635de 100644
--- a/web/src/views/system/menu/components/MenuButtonCom/crud.tsx
+++ b/web/src/views/system/menu/components/MenuButtonCom/crud.tsx
@@ -3,8 +3,7 @@ import { useI18n } from 'vue-i18n';
import * as api from './api';
import {auth} from '/@/utils/authFunction'
import {request} from '/@/utils/service';
-import { successNotification } from '/@/utils/message';
-import { ElMessage } from 'element-plus';
+import { ElMessage, ElMessageBox } from 'element-plus';
import { nextTick, ref } from 'vue';
import XEUtils from 'xe-utils';
//此处为crudOptions配置
@@ -92,11 +91,61 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
}
const result = await api.BatchAdd({ menu: context!.selectOptions.value.id });
if (result.code == 2000) {
- successNotification(result.msg);
+ ElMessage.success(result.msg);
crudExpose.doRefresh();
+ } else {
+ ElMessage.error(result.msg);
}
},
},
+ batchDelete: {
+ show: auth('menu:DeleteButton'),
+ type: 'danger',
+ text: t('message.pages.menu.buttons.batchDelete'),
+ disabled: () => selectedRows.value.length === 0,
+ click: async () => {
+ if (selectedRows.value.length === 0) {
+ ElMessage.warning(t('message.pages.menu.messages.selectMenu'));
+ return;
+ }
+ await ElMessageBox.confirm(
+ t('message.pages.menu.messages.batchDeleteConfirm', { count: selectedRows.value.length }),
+ t('message.pages.menu.buttons.confirm'),
+ { type: 'warning', confirmButtonText: t('message.pages.menu.buttons.confirm'), cancelButtonText: t('message.pages.menu.buttons.cancel') }
+ );
+ await api.BatchDelete(XEUtils.pluck(selectedRows.value, 'id'));
+ ElMessage.success(t('message.pages.menu.messages.deleteSuccess'));
+ selectedRows.value = [];
+ crudExpose.doRefresh();
+ },
+ },
+ selectAll: {
+ show: true,
+ text: t('message.pages.menu.buttons.selectAll'),
+ disabled: () => !context!.selectOptions.value.id,
+ click: () => {
+ const tableRef = crudExpose.getBaseTableRef();
+ const tableData = crudExpose.getTableData();
+ nextTick(() => {
+ XEUtils.arrayEach(tableData, (row: any) => {
+ tableRef.toggleRowSelection(row, true);
+ });
+ });
+ },
+ },
+ selectNone: {
+ show: true,
+ text: t('message.pages.menu.buttons.selectNone'),
+ disabled: () => selectedRows.value.length === 0,
+ click: () => {
+ const tableRef = crudExpose.getBaseTableRef();
+ const tableData = crudExpose.getTableData();
+ XEUtils.arrayEach(tableData, (row: any) => {
+ tableRef.toggleRowSelection(row, false);
+ });
+ selectedRows.value = [];
+ },
+ },
},
},
rowHandle: {
diff --git a/web/src/views/system/menu/components/MenuButtonCom/index.vue b/web/src/views/system/menu/components/MenuButtonCom/index.vue
index ec55e35..6fb69d9 100644
--- a/web/src/views/system/menu/components/MenuButtonCom/index.vue
+++ b/web/src/views/system/menu/components/MenuButtonCom/index.vue
@@ -1,26 +1,23 @@
-
-
-
-
-
-
+
+
+