From 943b68c17b27f3b0f9973533f832c48dd6afbb6f Mon Sep 17 00:00:00 2001
From: zzw <228544128@qq.com>
Date: 星期二, 06 十月 2026 15:01:53 +0800
Subject: [PATCH] feat: reorganize system menus and add error logs
---
emergency-repair-server/src/main/java/com/emergencyrepair/system/controller/SystemController.java | 17 +
emergency-repair-web/src/views/system/ErrorLogManage.vue | 151 ++++++++
emergency-repair-server/src/main/java/com/emergencyrepair/common/GlobalExceptionHandler.java | 10
emergency-repair-server/src/main/java/com/emergencyrepair/system/log/ErrorLogRecorder.java | 107 ++++++
emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DataInitializer.java | 203 ++++++++++-
emergency-repair-web/src/views/system/RoleManage.vue | 14
emergency-repair-web/src/router/index.js | 2
emergency-repair-web/src/views/system/MenuManage.vue | 2
HANDOFF.md | 28 +
/dev/null | 140 --------
emergency-repair-web/src/views/system/RolePermissionDialog.vue | 194 +++++++++++
emergency-repair-server/src/main/java/com/emergencyrepair/system/service/ButtonPermissionCatalog.java | 5
requirement/交接文件_2026.10.3.md | 43 ++
emergency-repair-server/src/main/java/com/emergencyrepair/system/mapper/SysErrorLogMapper.java | 7
emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DatabaseBootstrap.java | 5
emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java | 47 ++
emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysErrorLog.java | 31 +
17 files changed, 825 insertions(+), 181 deletions(-)
diff --git a/HANDOFF.md b/HANDOFF.md
index d7093f3..3c3b292 100644
--- a/HANDOFF.md
+++ b/HANDOFF.md
@@ -753,3 +753,31 @@
**鎺ユ墜娉ㄦ剰**
- 涓嶈鎻愪氦 `.codex-run`銆丱ffice 閿佹枃浠舵垨 `emergency-repair-web/public/鏂板缓 鏂囨湰鏂囨。.txt`銆�
- 鏈疆浠g爜涓庝氦鎺ヨ褰曚竴骞舵彁浜ゅ苟鎺ㄩ�佸叕鍙� Git 浠撳簱 `main` 鍒嗘敮銆�
+
+## 19. 鏀跺熬蹇収锛�2026-10-06 鍛ㄤ竴锛岄渶姹� 21-22 鑿滃崟鏉冮檺鍏ュ彛涓庨敊璇棩蹇楋級
+
+**鏈疆瀹屾垚鍐呭**
+- 闇�姹� 21锛氱Щ闄ょ嫭绔嬧�滆彍鍗曟潈闄愮鐞嗏�濊彍鍗曪紝鍦ㄨ鑹茬鐞嗘搷浣滃垪鏂板鈥滄潈闄愯缃�濇寜閽紝骞跺鐢ㄨ彍鍗�/鎸夐挳鏉冮檺鏍戝脊绐楋紱鏃� `/system/menu-permissions` 璺敱鍜岄〉闈㈠凡鍒犻櫎銆�
+- 闇�姹� 22锛氱郴缁熺鐞嗘柊澧炩�滅郴缁熼厤缃� / 鏉冮檺閰嶇疆 / 鏃ュ織绠$悊鈥濅笁涓簩绾х洰褰曪紱缁勭粐鏈烘瀯銆佹壙淇崟浣嶃�佷骇鍝佺粨鏋勬爲銆佽澶囧熀纭�搴撱�佸伐绋嬩笓涓氥�乀闃熶笓涓氥�佷笁鍛樻帶鍒跺綊鍏ョ郴缁熼厤缃紝鐢ㄦ埛绠$悊銆佽鑹茬鐞嗐�佽彍鍗曠鐞嗗綊鍏ユ潈闄愰厤缃紝鐧诲綍鏃ュ織銆佹搷浣滄棩蹇椼�侀敊璇棩蹇楀綊鍏ユ棩蹇楃鐞嗐��
+- 閿欒鏃ュ織涓烘柊澧炲姛鑳斤細`GlobalExceptionHandler` 鎹曡幏鏈鐞嗙殑闈炰笟鍔″紓甯稿苟寮傛鍐欏叆 `LX.T_SYS_ERROR_LOG`锛涗笟鍔℃牎楠屽紓甯镐笉璁板綍銆傚垪琛ㄦ敮鎸佹ā鍧椼�佸紓甯哥被鍨嬨�佹椂闂寸瓫閫夛紝璇︽儏灞曠ず瀹屾暣鍫嗘爤銆�
+- 鏂板簱鍒濆鍖栧拰鏃у簱杩佺Щ鍧囦細鍒犻櫎鏃ц彍鍗曞苟琛ラ綈浜岀骇鐩綍鐨勭埗瀛愯彍鍗曞叧绯伙紱鏉冮檺鏍戠户缁繚鐣欒彍鍗曡妭鐐瑰拰鎸夐挳鑺傜偣锛岃鑹插垏鎹㈠悗澶嶉�夋鍙弽鏄俱��
+
+**涓昏鏂囦欢**
+- 鍚庣锛歚emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysErrorLog.java`銆乣.../mapper/SysErrorLogMapper.java`銆乣.../log/ErrorLogRecorder.java`銆乣.../common/GlobalExceptionHandler.java`銆乣.../bootstrap/DataInitializer.java`銆乣.../bootstrap/DatabaseBootstrap.java`銆乣.../system/controller/SystemController.java`銆乣.../system/service/SystemService.java`銆乣.../system/service/ButtonPermissionCatalog.java`銆�
+- 鍓嶇锛歚emergency-repair-web/src/views/system/RoleManage.vue`銆乣RolePermissionDialog.vue`銆乣ErrorLogManage.vue`銆乣emergency-repair-web/src/router/index.js`銆乣MenuManage.vue`锛沗MenuPermission.vue` 宸插垹闄ゃ��
+
+**楠岃瘉缁撴灉**
+- 鍚庣 `mvn -B -DskipTests clean package` 閫氳繃锛涘墠绔� `npm run build` 閫氳繃锛屼粎鏈夋棦鏈夊寘浣撶Н鍛婅銆�
+- `node .codex-run/verify-req21-22.js` 閫氳繃锛氫笁涓簩绾х洰褰曞拰閿欒鏃ュ織鑿滃崟姝e父鏄剧ず锛屾棫鑿滃崟涓嶅瓨鍦紝瑙掕壊绠$悊鈥滄潈闄愯缃�濆脊绐楁寜閽妭鐐规暟涓� 78 涓斿弽鏄炬甯搞��
+- 浜轰负璁块棶闈炴硶閿欒鏃ュ織璇︽儏 ID 鍚庯紝璁板綍鑷姩钀藉簱骞跺彲鍦ㄩ敊璇棩蹇楅〉闈㈡煡鐪嬶紱楠岃瘉鐢ㄤ复鏃惰褰曞凡浠庢暟鎹簱娓呯悊銆�
+- 鎴浘锛歚.codex-run/role-permission-dialog-verified.png`銆乣.codex-run/error-log-verified.png`銆�
+
+**褰撳墠杩愯鐘舵�侊紙2026-10-06锛�**
+- 鍓嶇 `http://localhost:8082`锛涘悗绔� `http://127.0.0.1:8091`锛汥M8 `127.0.0.1:5236`锛汦dge CDP `9222`銆�
+- 鐧诲綍 `admin / admin123`锛涙暟鎹簱 `SYSDBA/SYSDBA`锛屼笟鍔� schema `LX`銆�
+- 鍚庣鏃ュ織锛歚.codex-run/backend-20261006-req21-22.out.log`銆乣.codex-run/backend-20261006-req21-22.err.log`銆�
+
+**鎺ユ墜娉ㄦ剰**
+- 閿欒鏃ュ織褰撳墠瑕嗙洊杩涘叆 Spring MVC 鍏ㄥ眬寮傚父澶勭悊鐨勫紓甯革紱鍚姩闃舵寮傚父銆佸紓姝ョ嚎绋嬩腑鏈繘鍏ヨ澶勭悊鍣ㄧ殑寮傚父鏆備笉浼氳嚜鍔ㄨ惤搴撱��
+- 涓嶈鎻愪氦 `.codex-run`銆丱ffice 閿佹枃浠舵垨 `emergency-repair-web/public/鏂板缓 鏂囨湰鏂囨。.txt`銆�
+- 鏈疆浠g爜涓庝氦鎺ヨ褰曚竴骞舵彁浜ゅ苟鎺ㄩ�佸叕鍙� Git 浠撳簱 `main` 鍒嗘敮銆�
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DataInitializer.java b/emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DataInitializer.java
index b186d98..4c5edb4 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DataInitializer.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DataInitializer.java
@@ -50,6 +50,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
@Component
@Order(2)
@@ -88,6 +89,7 @@
seedButtonMenus();
ensureAdminRoleAssignment();
ensureMajorConfigMenus();
+ ensureRoleMenuAncestors();
seedMajors();
seedContractors();
seedTrees();
@@ -130,16 +132,24 @@
createMenu("report-trend", reports, "鏁呴殰鏁�/澶囧搧澶囦欢娑堣�楀勾搴﹁秼鍔�", "/reports/trend", null, 1, 50);
Long system = createMenu("system", null, "绯荤粺绠$悊", null, "el-icon-setting", 0, 50);
- createMenu("organization", system, "缁勭粐鏈烘瀯", "/system/organization", null, 1, 5);
- createMenu("contractors", system, "鎵夸慨鍗曚綅绠$悊", "/system/contractors", null, 1, 10);
- createMenu("product-tree", system, "浜у搧缁撴瀯鏍戠鐞�", "/system/product-tree", null, 1, 20);
- createMenu("base-equipment", system, "璁惧鍩虹搴撶鐞�", "/system/base-equipment", null, 1, 30);
- createMenu("engineering-majors", system, "宸ョ▼涓撲笟閰嶇疆", "/system/engineering-majors", null, 1, 35);
- createMenu("team-majors", system, "rk_T闃熶笓涓氶厤缃�", "/system/team-majors", null, 1, 36);
- createMenu("roles", system, "瑙掕壊绠$悊", "/system/roles", null, 1, 40);
- createMenu("menus", system, "鑿滃崟绠$悊", "/system/menus", null, 1, 50);
- createMenu("menu-permissions", system, "鑿滃崟鏉冮檺绠$悊", "/system/menu-permissions", null, 1, 60);
- createMenu("three-member", system, "涓夊憳鎺у埗", "/system/three-member", null, 1, 70);
+ Long systemConfig = createMenu("system-config", system, "绯荤粺閰嶇疆", null, "el-icon-s-tools", 0, 10);
+ createMenu("organization", systemConfig, "缁勭粐鏈烘瀯", "/system/organization", null, 1, 10);
+ createMenu("contractors", systemConfig, "鎵夸慨鍗曚綅绠$悊", "/system/contractors", null, 1, 20);
+ createMenu("product-tree", systemConfig, "浜у搧缁撴瀯鏍戠鐞�", "/system/product-tree", null, 1, 30);
+ createMenu("base-equipment", systemConfig, "璁惧鍩虹搴撶鐞�", "/system/base-equipment", null, 1, 40);
+ createMenu("engineering-majors", systemConfig, "宸ョ▼涓撲笟閰嶇疆", "/system/engineering-majors", null, 1, 50);
+ createMenu("team-majors", systemConfig, "rk_T闃熶笓涓氶厤缃�", "/system/team-majors", null, 1, 60);
+ createMenu("three-member", systemConfig, "涓夊憳鎺у埗", "/system/three-member", null, 1, 70);
+
+ Long permissionConfig = createMenu("permission-config", system, "鏉冮檺閰嶇疆", null, "el-icon-lock", 0, 20);
+ createMenu("users", permissionConfig, "鐢ㄦ埛绠$悊", "/system/users", null, 1, 10);
+ createMenu("roles", permissionConfig, "瑙掕壊绠$悊", "/system/roles", null, 1, 20);
+ createMenu("menus", permissionConfig, "鑿滃崟绠$悊", "/system/menus", null, 1, 30);
+
+ Long logConfig = createMenu("log-config", system, "鏃ュ織绠$悊", null, "el-icon-document", 0, 30);
+ createMenu("login-logs", logConfig, "鐧诲綍鏃ュ織", "/system/login-logs", null, 1, 10);
+ createMenu("operation-logs", logConfig, "鎿嶄綔鏃ュ織", "/system/operation-logs", null, 1, 20);
+ createMenu("error-logs", logConfig, "閿欒鏃ュ織", "/system/error-logs", null, 1, 30);
} else {
for (SysMenu menu : menuMapper.selectList(null)) {
menuIds.put(menu.getUrl() == null ? "id-" + menu.getId() : menu.getUrl(), menu.getId());
@@ -178,36 +188,112 @@
SysMenu system = menuMapper.selectOne(Wrappers.<SysMenu>lambdaQuery()
.eq(SysMenu::getName, "绯荤粺绠$悊")
.last("LIMIT 1"));
- Long pid = system == null ? 0L : system.getId();
- ensureMenu(pid, "宸ョ▼涓撲笟閰嶇疆", "/system/engineering-majors", 35);
- ensureMenu(pid, "rk_T闃熶笓涓氶厤缃�", "/system/team-majors", 36);
+ SysMenu config = menuMapper.selectOne(Wrappers.<SysMenu>lambdaQuery()
+ .eq(SysMenu::getName, "绯荤粺閰嶇疆")
+ .eq(SysMenu::getMenuType, 0)
+ .last("LIMIT 1"));
+ Long pid = config == null ? (system == null ? 0L : system.getId()) : config.getId();
+ ensureMenu(pid, "宸ョ▼涓撲笟閰嶇疆", "/system/engineering-majors", 50);
+ ensureMenu(pid, "rk_T闃熶笓涓氶厤缃�", "/system/team-majors", 60);
}
- private void ensureMenu(Long pid, String name, String url, int sort) {
- if (menuMapper.selectCount(Wrappers.<SysMenu>lambdaQuery().eq(SysMenu::getUrl, url)) > 0) {
- return;
+ private Long ensureGroupMenu(Long pid, String name, String icon, int sort) {
+ SysMenu menu = menuMapper.selectOne(Wrappers.<SysMenu>lambdaQuery()
+ .eq(SysMenu::getPid, pid)
+ .eq(SysMenu::getName, name)
+ .eq(SysMenu::getMenuType, 0)
+ .last("LIMIT 1"));
+ LocalDateTime now = LocalDateTime.now();
+ if (menu == null) {
+ menu = new SysMenu();
+ menu.setPid(pid);
+ menu.setName(name);
+ menu.setIcon(icon);
+ menu.setMenuType(0);
+ menu.setSortNo(sort);
+ menu.setVisible(1);
+ menu.setCreateTime(now);
+ menu.setUpdateTime(now);
+ menuMapper.insert(menu);
+ return menu.getId();
}
- SysMenu menu = new SysMenu();
- menu.setPid(pid);
- menu.setName(name);
- menu.setUrl(url);
- menu.setMenuType(1);
- menu.setSortNo(sort);
- menu.setVisible(1);
- menu.setCreateTime(LocalDateTime.now());
- menu.setUpdateTime(LocalDateTime.now());
- menuMapper.insert(menu);
+ boolean changed = !java.util.Objects.equals(menu.getIcon(), icon)
+ || !java.util.Objects.equals(menu.getSortNo(), sort)
+ || !java.util.Objects.equals(menu.getVisible(), 1);
+ if (changed) {
+ menu.setIcon(icon);
+ menu.setSortNo(sort);
+ menu.setVisible(1);
+ menu.setUpdateTime(now);
+ menuMapper.updateById(menu);
+ }
+ return menu.getId();
+ }
+
+ private Long ensureMenu(Long pid, String name, String url, int sort) {
+ SysMenu menu = menuMapper.selectOne(Wrappers.<SysMenu>lambdaQuery()
+ .eq(SysMenu::getUrl, url).last("LIMIT 1"));
+ LocalDateTime now = LocalDateTime.now();
+ if (menu == null) {
+ menu = new SysMenu();
+ menu.setPid(pid);
+ menu.setName(name);
+ menu.setUrl(url);
+ menu.setMenuType(1);
+ menu.setSortNo(sort);
+ menu.setVisible(1);
+ menu.setCreateTime(now);
+ menu.setUpdateTime(now);
+ menuMapper.insert(menu);
+ return menu.getId();
+ }
+ boolean changed = !java.util.Objects.equals(menu.getPid(), pid)
+ || !java.util.Objects.equals(menu.getName(), name)
+ || !java.util.Objects.equals(menu.getSortNo(), sort)
+ || !java.util.Objects.equals(menu.getMenuType(), 1)
+ || !java.util.Objects.equals(menu.getVisible(), 1);
+ if (changed) {
+ menu.setPid(pid);
+ menu.setName(name);
+ menu.setMenuType(1);
+ menu.setSortNo(sort);
+ menu.setVisible(1);
+ menu.setUpdateTime(now);
+ menuMapper.updateById(menu);
+ }
+ return menu.getId();
}
private void ensureSystemMenus() {
SysMenu system = menuMapper.selectOne(Wrappers.<SysMenu>lambdaQuery()
.eq(SysMenu::getName, "\u7cfb\u7edf\u7ba1\u7406").last("LIMIT 1"));
Long pid = system == null ? 0L : system.getId();
- ensureMenu(pid, "缁勭粐鏈烘瀯", "/system/organization", 5);
- ensureMenu(pid, "\u7528\u6237\u7ba1\u7406", "/system/users", 37);
- ensureMenu(pid, "\u767b\u5f55\u65e5\u5fd7", "/system/login-logs", 65);
- ensureMenu(pid, "\u64cd\u4f5c\u65e5\u5fd7", "/system/operation-logs", 66);
- grantAdminMenus("/system/organization", "/system/users", "/system/login-logs", "/system/operation-logs");
+ Long systemConfig = ensureGroupMenu(pid, "绯荤粺閰嶇疆", "el-icon-s-tools", 10);
+ Long permissionConfig = ensureGroupMenu(pid, "鏉冮檺閰嶇疆", "el-icon-lock", 20);
+ Long logConfig = ensureGroupMenu(pid, "鏃ュ織绠$悊", "el-icon-document", 30);
+
+ ensureMenu(systemConfig, "缁勭粐鏈烘瀯", "/system/organization", 10);
+ ensureMenu(systemConfig, "鎵夸慨鍗曚綅绠$悊", "/system/contractors", 20);
+ ensureMenu(systemConfig, "浜у搧缁撴瀯鏍戠鐞�", "/system/product-tree", 30);
+ ensureMenu(systemConfig, "璁惧鍩虹搴撶鐞�", "/system/base-equipment", 40);
+ ensureMenu(systemConfig, "宸ョ▼涓撲笟閰嶇疆", "/system/engineering-majors", 50);
+ ensureMenu(systemConfig, "rk_T闃熶笓涓氶厤缃�", "/system/team-majors", 60);
+ ensureMenu(systemConfig, "涓夊憳鎺у埗", "/system/three-member", 70);
+
+ ensureMenu(permissionConfig, "鐢ㄦ埛绠$悊", "/system/users", 10);
+ ensureMenu(permissionConfig, "瑙掕壊绠$悊", "/system/roles", 20);
+ ensureMenu(permissionConfig, "鑿滃崟绠$悊", "/system/menus", 30);
+
+ ensureMenu(logConfig, "鐧诲綍鏃ュ織", "/system/login-logs", 10);
+ ensureMenu(logConfig, "鎿嶄綔鏃ュ織", "/system/operation-logs", 20);
+ ensureMenu(logConfig, "閿欒鏃ュ織", "/system/error-logs", 30);
+
+ removeMenuByUrl("/system/menu-permissions");
+ grantAdminMenuIds(systemConfig, permissionConfig, logConfig);
+ grantAdminMenus("/system/organization", "/system/contractors", "/system/product-tree",
+ "/system/base-equipment", "/system/engineering-majors", "/system/team-majors",
+ "/system/three-member", "/system/users", "/system/roles", "/system/menus",
+ "/system/login-logs", "/system/operation-logs", "/system/error-logs");
}
private void grantAdminMenus(String... urls) {
@@ -226,6 +312,61 @@
}
}
+ private void grantAdminMenuIds(Long... menuIds) {
+ SysRole adminRole = roleMapper.selectOne(Wrappers.<SysRole>lambdaQuery()
+ .eq(SysRole::getCode, "SYS_ADMIN").last("LIMIT 1"));
+ if (adminRole == null) {
+ return;
+ }
+ for (Long menuId : menuIds) {
+ ensureRoleMenu(adminRole.getId(), menuId);
+ ensureTenantMenu(1L, menuId);
+ }
+ }
+
+ private void removeMenuByUrl(String url) {
+ SysMenu menu = menuMapper.selectOne(Wrappers.<SysMenu>lambdaQuery()
+ .eq(SysMenu::getUrl, url).last("LIMIT 1"));
+ if (menu == null) {
+ return;
+ }
+ List<SysMenu> children = menuMapper.selectList(Wrappers.<SysMenu>lambdaQuery()
+ .eq(SysMenu::getPid, menu.getId()));
+ for (SysMenu child : children) {
+ removeMenuRelations(child.getId());
+ menuMapper.deleteById(child.getId());
+ }
+ removeMenuRelations(menu.getId());
+ menuMapper.deleteById(menu.getId());
+ }
+
+ private void removeMenuRelations(Long menuId) {
+ roleMenuMapper.delete(Wrappers.<SysRoleMenu>lambdaQuery().eq(SysRoleMenu::getMenuId, menuId));
+ tenantMenuMapper.delete(Wrappers.<SysTenantMenu>lambdaQuery().eq(SysTenantMenu::getMenuId, menuId));
+ }
+
+ private void ensureRoleMenuAncestors() {
+ Map<Long, SysMenu> menuById = menuMapper.selectList(null).stream()
+ .collect(java.util.stream.Collectors.toMap(SysMenu::getId, item -> item));
+ List<SysRoleMenu> relations = new ArrayList<>(roleMenuMapper.selectList(null));
+ Set<String> existing = relations.stream()
+ .map(item -> item.getRoleId() + "-" + item.getMenuId())
+ .collect(java.util.stream.Collectors.toSet());
+ for (SysRoleMenu relation : relations) {
+ SysMenu menu = menuById.get(relation.getMenuId());
+ Long cursor = menu == null ? null : menu.getPid();
+ int guard = 0;
+ while (cursor != null && cursor != 0L && guard++ < 32) {
+ String key = relation.getRoleId() + "-" + cursor;
+ if (existing.add(key)) {
+ ensureRoleMenu(relation.getRoleId(), cursor);
+ }
+ SysMenu parent = menuById.get(cursor);
+ cursor = parent == null ? null : parent.getPid();
+ }
+ }
+ }
+
/**
* Materializes the declarative button catalog as MENU_TYPE=2 rows.
* Existing rows are left untouched so manual menu edits are preserved.
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DatabaseBootstrap.java b/emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DatabaseBootstrap.java
index d1c6d10..4797f5e 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DatabaseBootstrap.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DatabaseBootstrap.java
@@ -17,7 +17,8 @@
"T_FAULT", "T_ENGINEERING_MAJOR", "T_TEAM_MAJOR", "T_CONTRACTOR",
"T_BASE_EQUIPMENT", "T_PRODUCT_TREE", "T_WORK_ORDER", "T_COMPLETION_INFO",
"T_ACCEPTANCE", "T_SYS_MENU", "T_SYS_ROLE", "T_SYS_ROLE_MENU",
- "T_SYS_TENANT_MENU", "T_SYS_ROLE_USER", "T_SYS_LOGIN_LOG", "T_SYS_OPERATION_LOG"
+ "T_SYS_TENANT_MENU", "T_SYS_ROLE_USER", "T_SYS_LOGIN_LOG", "T_SYS_OPERATION_LOG",
+ "T_SYS_ERROR_LOG"
};
private final JdbcTemplate jdbcTemplate;
@@ -40,6 +41,7 @@
ensureIndex("IX_T_COMPLETION_WORK_ORDER", "CREATE INDEX LX.IX_T_COMPLETION_WORK_ORDER ON LX.T_COMPLETION_INFO(WORK_ORDER_ID)");
ensureIndex("IX_T_SYS_LOGIN_LOG_TIME", "CREATE INDEX LX.IX_T_SYS_LOGIN_LOG_TIME ON LX.T_SYS_LOGIN_LOG(LOGIN_TIME)");
ensureIndex("IX_T_SYS_OPERATION_LOG_TIME", "CREATE INDEX LX.IX_T_SYS_OPERATION_LOG_TIME ON LX.T_SYS_OPERATION_LOG(CREATE_TIME)");
+ ensureIndex("IX_T_SYS_ERROR_LOG_TIME", "CREATE INDEX LX.IX_T_SYS_ERROR_LOG_TIME ON LX.T_SYS_ERROR_LOG(ERROR_TIME)");
ensureIndex("IX_T_SYS_ROLE_USER_USER", "CREATE INDEX LX.IX_T_SYS_ROLE_USER_USER ON LX.T_SYS_ROLE_USER(USER_ID)");
}
@@ -122,6 +124,7 @@
ddl.put("T_SYS_ROLE_USER", "CREATE TABLE LX.T_SYS_ROLE_USER (ID BIGINT NOT NULL PRIMARY KEY, ROLE_ID BIGINT, USER_ID BIGINT, CREATE_TIME TIMESTAMP)");
ddl.put("T_SYS_LOGIN_LOG", "CREATE TABLE LX.T_SYS_LOGIN_LOG (ID BIGINT NOT NULL PRIMARY KEY, USER_ID BIGINT, USER_NAME VARCHAR(120), NICK_NAME VARCHAR(120), DEPT_NAME VARCHAR(120), LOGIN_IP VARCHAR(200), STATUS VARCHAR(20), MESSAGE VARCHAR(500), LOGIN_TIME TIMESTAMP)");
ddl.put("T_SYS_OPERATION_LOG", "CREATE TABLE LX.T_SYS_OPERATION_LOG (ID BIGINT NOT NULL PRIMARY KEY, USER_ID BIGINT, USER_NAME VARCHAR(120), NICK_NAME VARCHAR(120), MODULE VARCHAR(100), OPERATION_TYPE VARCHAR(40), TARGET_TYPE VARCHAR(100), TARGET_ID VARCHAR(80), DESCRIPTION VARCHAR(500), REQUEST_METHOD VARCHAR(10), REQUEST_URI VARCHAR(300), RESULT VARCHAR(20), CREATE_TIME TIMESTAMP)");
+ ddl.put("T_SYS_ERROR_LOG", "CREATE TABLE LX.T_SYS_ERROR_LOG (ID BIGINT NOT NULL PRIMARY KEY, USER_ID BIGINT, USER_NAME VARCHAR(120), NICK_NAME VARCHAR(120), MODULE VARCHAR(100), REQUEST_METHOD VARCHAR(10), REQUEST_URI VARCHAR(500), EXCEPTION_TYPE VARCHAR(300), MESSAGE VARCHAR(2000), STACK_TRACE CLOB, ERROR_TIME TIMESTAMP)");
return ddl;
}
}
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/common/GlobalExceptionHandler.java b/emergency-repair-server/src/main/java/com/emergencyrepair/common/GlobalExceptionHandler.java
index 68c3eca..5bfff63 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/common/GlobalExceptionHandler.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/common/GlobalExceptionHandler.java
@@ -1,17 +1,25 @@
package com.emergencyrepair.common;
+import com.emergencyrepair.system.log.ErrorLogRecorder;
+import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
+import javax.servlet.http.HttpServletRequest;
+
@RestControllerAdvice
+@RequiredArgsConstructor
public class GlobalExceptionHandler {
+ private final ErrorLogRecorder errorLogRecorder;
+
@ExceptionHandler(BusinessException.class)
public Result<Void> handleBusiness(BusinessException exception) {
return Result.error(exception.getMessage());
}
@ExceptionHandler(Exception.class)
- public Result<Void> handleOther(Exception exception) {
+ public Result<Void> handleOther(Exception exception, HttpServletRequest request) {
+ errorLogRecorder.record(exception, request);
exception.printStackTrace();
return Result.error(exception.getMessage() == null ? "绯荤粺寮傚父" : exception.getMessage());
}
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/system/controller/SystemController.java b/emergency-repair-server/src/main/java/com/emergencyrepair/system/controller/SystemController.java
index c167be9..09f271c 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/system/controller/SystemController.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/system/controller/SystemController.java
@@ -8,6 +8,7 @@
import com.emergencyrepair.business.entity.TeamMajor;
import com.emergencyrepair.common.Result;
import com.emergencyrepair.system.entity.SysMenu;
+import com.emergencyrepair.system.entity.SysErrorLog;
import com.emergencyrepair.system.entity.SysLoginLog;
import com.emergencyrepair.system.entity.SysOperationLog;
import com.emergencyrepair.system.entity.SysRole;
@@ -111,6 +112,22 @@
return Result.ok(systemService.operationLogPage(current, size, keyword, module, operationType, startDate, endDate));
}
+ @GetMapping("/error-logs")
+ public Result<Page<SysErrorLog>> errorLogs(@RequestParam(defaultValue = "1") int current,
+ @RequestParam(defaultValue = "20") int size,
+ @RequestParam(required = false) String keyword,
+ @RequestParam(required = false) String module,
+ @RequestParam(required = false) String exceptionType,
+ @RequestParam(required = false) String startDate,
+ @RequestParam(required = false) String endDate) {
+ return Result.ok(systemService.errorLogPage(current, size, keyword, module, exceptionType, startDate, endDate));
+ }
+
+ @GetMapping("/error-logs/{id}")
+ public Result<SysErrorLog> errorLog(@PathVariable Long id) {
+ return Result.ok(systemService.errorLog(id));
+ }
+
@PostMapping("/menu")
public Result<SysMenu> createMenu(@RequestBody SysMenu menu) {
menu.setId(null);
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysErrorLog.java b/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysErrorLog.java
new file mode 100644
index 0000000..75dda2f
--- /dev/null
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysErrorLog.java
@@ -0,0 +1,31 @@
+package com.emergencyrepair.system.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.emergencyrepair.common.entity.BaseAuditEntity;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("LX.T_SYS_ERROR_LOG")
+public class SysErrorLog extends BaseAuditEntity {
+ @TableId(value = "ID", type = IdType.ASSIGN_ID)
+ @JsonSerialize(using = ToStringSerializer.class)
+ private Long id;
+ @JsonSerialize(using = ToStringSerializer.class)
+ @TableField("USER_ID") private Long userId;
+ @TableField("USER_NAME") private String userName;
+ @TableField("NICK_NAME") private String nickName;
+ @TableField("MODULE") private String module;
+ @TableField("REQUEST_METHOD") private String requestMethod;
+ @TableField("REQUEST_URI") private String requestUri;
+ @TableField("EXCEPTION_TYPE") private String exceptionType;
+ @TableField("MESSAGE") private String message;
+ @TableField("STACK_TRACE") private String stackTrace;
+ @TableField("ERROR_TIME") private LocalDateTime errorTime;
+}
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/system/log/ErrorLogRecorder.java b/emergency-repair-server/src/main/java/com/emergencyrepair/system/log/ErrorLogRecorder.java
new file mode 100644
index 0000000..6c17876
--- /dev/null
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/system/log/ErrorLogRecorder.java
@@ -0,0 +1,107 @@
+package com.emergencyrepair.system.log;
+
+import com.emergencyrepair.common.audit.CurrentUserContext;
+import com.emergencyrepair.system.entity.SysErrorLog;
+import com.emergencyrepair.system.mapper.SysErrorLogMapper;
+import lombok.RequiredArgsConstructor;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * Persists unhandled server exceptions so administrators can inspect runtime bugs.
+ */
+@Component
+@RequiredArgsConstructor
+public class ErrorLogRecorder {
+ private static final int MAX_MESSAGE_LENGTH = 2000;
+ private static final int MAX_STACK_LENGTH = 60000;
+
+ private final SysErrorLogMapper errorLogMapper;
+ private final JdbcTemplate jdbcTemplate;
+
+ public void record(Exception exception, HttpServletRequest request) {
+ if (exception == null) {
+ return;
+ }
+ try {
+ String username = CurrentUserContext.get();
+ Long userId = resolveUserId(username);
+ SysErrorLog log = new SysErrorLog();
+ log.setUserId(userId);
+ log.setUserName(username);
+ log.setNickName(resolveNickName(userId));
+ log.setModule(resolveModule(request));
+ log.setRequestMethod(request == null ? null : limit(request.getMethod(), 10));
+ log.setRequestUri(request == null ? null : limit(request.getRequestURI(), 500));
+ log.setExceptionType(limit(exception.getClass().getName(), 300));
+ log.setMessage(limit(resolveMessage(exception), MAX_MESSAGE_LENGTH));
+ log.setStackTrace(limit(stackTrace(exception), MAX_STACK_LENGTH));
+ log.setErrorTime(LocalDateTime.now());
+ errorLogMapper.insert(log);
+ } catch (Exception ignored) {
+ // Error logging must never interfere with the original error response.
+ }
+ }
+
+ private Long resolveUserId(String username) {
+ if (!StringUtils.hasText(username)) {
+ return null;
+ }
+ List<Long> ids = jdbcTemplate.query(
+ "SELECT USER_ID FROM CSICZB_ZB.SYS_USER WHERE USER_NAME = ? AND (DEL_FLAG = '0' OR DEL_FLAG IS NULL)",
+ new Object[]{username}, (rs, rowNum) -> rs.getLong(1));
+ return ids.isEmpty() ? null : ids.get(0);
+ }
+
+ private String resolveNickName(Long userId) {
+ if (userId == null) {
+ return null;
+ }
+ List<String> names = jdbcTemplate.query(
+ "SELECT NICK_NAME FROM CSICZB_ZB.SYS_USER WHERE USER_ID = ?",
+ new Object[]{userId}, (rs, rowNum) -> rs.getString(1));
+ return names.isEmpty() ? null : names.get(0);
+ }
+
+ private String resolveMessage(Exception exception) {
+ String message = exception.getMessage();
+ return StringUtils.hasText(message) ? message : exception.getClass().getSimpleName();
+ }
+
+ private String stackTrace(Exception exception) {
+ StringWriter writer = new StringWriter();
+ exception.printStackTrace(new PrintWriter(writer));
+ return writer.toString();
+ }
+
+ private String resolveModule(HttpServletRequest request) {
+ if (request == null || !StringUtils.hasText(request.getRequestURI())) {
+ return "鍏朵粬";
+ }
+ String uri = request.getRequestURI();
+ if (uri.startsWith("/api/faults") || uri.startsWith("/api/fault")) return "鏁呴殰绠$悊";
+ if (uri.startsWith("/api/work-orders") || uri.startsWith("/api/completion")) return "瀹屽伐绠$悊";
+ if (uri.startsWith("/api/acceptances")) return "瀹屽伐楠屾敹";
+ if (uri.startsWith("/api/reports")) return "缁熻鎶ヨ〃";
+ if (uri.startsWith("/api/system/users")) return "鐢ㄦ埛绠$悊";
+ if (uri.startsWith("/api/system/roles")) return "瑙掕壊绠$悊";
+ if (uri.startsWith("/api/system/menu")) return "鑿滃崟绠$悊";
+ if (uri.startsWith("/api/system/contractors")) return "鎵夸慨鍗曚綅绠$悊";
+ if (uri.startsWith("/api/system")) return "绯荤粺绠$悊";
+ return "鍏朵粬";
+ }
+
+ private String limit(String value, int maxLength) {
+ if (value == null || value.length() <= maxLength) {
+ return value;
+ }
+ return value.substring(0, maxLength);
+ }
+}
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/system/mapper/SysErrorLogMapper.java b/emergency-repair-server/src/main/java/com/emergencyrepair/system/mapper/SysErrorLogMapper.java
new file mode 100644
index 0000000..5343f90
--- /dev/null
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/system/mapper/SysErrorLogMapper.java
@@ -0,0 +1,7 @@
+package com.emergencyrepair.system.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.emergencyrepair.system.entity.SysErrorLog;
+
+public interface SysErrorLogMapper extends BaseMapper<SysErrorLog> {
+}
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/system/service/ButtonPermissionCatalog.java b/emergency-repair-server/src/main/java/com/emergencyrepair/system/service/ButtonPermissionCatalog.java
index d530fa1..982796d 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/system/service/ButtonPermissionCatalog.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/system/service/ButtonPermissionCatalog.java
@@ -86,12 +86,11 @@
put("/system/engineering-majors", def("system:major:add", "\u65b0\u589e"), def("system:major:edit", "\u7f16\u8f91"), def("system:major:delete", "\u5220\u9664"));
put("/system/team-majors", def("system:major:add", "\u65b0\u589e"), def("system:major:edit", "\u7f16\u8f91"), def("system:major:delete", "\u5220\u9664"));
put("/system/users", def("system:users:query", "\u67e5\u8be2"), def("system:users:assign", "\u89d2\u8272\u6388\u6743"));
- put("/system/roles", def("system:roles:add", "\u65b0\u589e"), def("system:roles:edit", "\u7f16\u8f91"), def("system:roles:delete", "\u5220\u9664"));
- put("/system/roles", def("system:roles:add", "\u65b0\u589e"), def("system:roles:edit", "\u7f16\u8f91"), def("system:roles:delete", "\u5220\u9664"), def("system:roles:assign", "\u7528\u6237\u8bbe\u7f6e"));
+ put("/system/roles", def("system:roles:add", "\u65b0\u589e"), def("system:roles:edit", "\u7f16\u8f91"), def("system:roles:delete", "\u5220\u9664"), def("system:roles:assign", "\u7528\u6237\u8bbe\u7f6e"), def("system:roles:permission", "\u6743\u9650\u8bbe\u7f6e"));
put("/system/menus", def("system:menus:add", "\u65b0\u589e"), def("system:menus:edit", "\u7f16\u8f91"), def("system:menus:delete", "\u5220\u9664"), def("system:menus:buttons", "\u83dc\u5355\u6309\u94ae\u540c\u6b65"));
- put("/system/menu-permissions", def("system:menu-permissions:save", "\u4fdd\u5b58\u6388\u6743"));
put("/system/login-logs", def("system:login-logs:query", "\u67e5\u8be2"), def("system:login-logs:export", "\u5bfc\u51fa"));
put("/system/operation-logs", def("system:operation-logs:query", "\u67e5\u8be2"), def("system:operation-logs:export", "\u5bfc\u51fa"));
+ put("/system/error-logs", def("system:error-logs:query", "\u67e5\u8be2"), def("system:error-logs:detail", "\u8be6\u60c5"));
}
private static void put(String url, ButtonDefinition... definitions) {
diff --git a/emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java b/emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java
index c2b4797..9f0cfd1 100644
--- a/emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java
+++ b/emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java
@@ -14,6 +14,7 @@
import com.emergencyrepair.business.mapper.TeamMajorMapper;
import com.emergencyrepair.common.BusinessException;
import com.emergencyrepair.system.entity.SysMenu;
+import com.emergencyrepair.system.entity.SysErrorLog;
import com.emergencyrepair.system.entity.SysLoginLog;
import com.emergencyrepair.system.entity.SysOperationLog;
import com.emergencyrepair.system.entity.SysRole;
@@ -21,6 +22,7 @@
import com.emergencyrepair.system.entity.SysRoleUser;
import com.emergencyrepair.system.entity.SysTenantMenu;
import com.emergencyrepair.system.mapper.SysMenuMapper;
+import com.emergencyrepair.system.mapper.SysErrorLogMapper;
import com.emergencyrepair.system.mapper.SysLoginLogMapper;
import com.emergencyrepair.system.mapper.SysOperationLogMapper;
import com.emergencyrepair.system.mapper.SysRoleMapper;
@@ -59,6 +61,7 @@
private final SysRoleUserMapper roleUserMapper;
private final SysLoginLogMapper loginLogMapper;
private final SysOperationLogMapper operationLogMapper;
+ private final SysErrorLogMapper errorLogMapper;
private final ButtonPermissionCatalog buttonPermissionCatalog;
private final JdbcTemplate jdbcTemplate;
private final EngineeringMajorMapper engineeringMajorMapper;
@@ -887,9 +890,10 @@
}
}
final Set<Long> allowedIds = allowed;
+ Set<Long> visibleIds = new LinkedHashSet<>(expandWithAncestors(new ArrayList<>(allowed)));
List<SysMenu> navigable = listMenus().stream()
.filter(menu -> menu.getMenuType() == null || menu.getMenuType() != 2)
- .filter(menu -> allowedIds.contains(menu.getId()))
+ .filter(menu -> visibleIds.contains(menu.getId()))
.collect(Collectors.toList());
return buildTree(navigable);
}
@@ -1038,6 +1042,47 @@
return operationLogMapper.selectPage(page, wrapper);
}
+ public Page<SysErrorLog> errorLogPage(int current, int size, String keyword, String module, String exceptionType, String startDate, String endDate) {
+ Page<SysErrorLog> page = new Page<>(current <= 0 ? 1 : current, size <= 0 ? 20 : size);
+ LambdaQueryWrapper<SysErrorLog> wrapper = Wrappers.lambdaQuery();
+ wrapper.select(SysErrorLog::getId, SysErrorLog::getUserId, SysErrorLog::getUserName,
+ SysErrorLog::getNickName, SysErrorLog::getModule, SysErrorLog::getRequestMethod,
+ SysErrorLog::getRequestUri, SysErrorLog::getExceptionType, SysErrorLog::getMessage,
+ SysErrorLog::getErrorTime);
+ if (StringUtils.hasText(keyword)) {
+ String value = keyword.trim();
+ wrapper.and(item -> item.like(SysErrorLog::getUserName, value)
+ .or().like(SysErrorLog::getNickName, value)
+ .or().like(SysErrorLog::getRequestUri, value)
+ .or().like(SysErrorLog::getExceptionType, value)
+ .or().like(SysErrorLog::getMessage, value));
+ }
+ if (StringUtils.hasText(module)) {
+ wrapper.eq(SysErrorLog::getModule, module.trim());
+ }
+ if (StringUtils.hasText(exceptionType)) {
+ wrapper.like(SysErrorLog::getExceptionType, exceptionType.trim());
+ }
+ LocalDateTime start = parseStart(startDate);
+ LocalDateTime end = parseEnd(endDate);
+ if (start != null) {
+ wrapper.ge(SysErrorLog::getErrorTime, start);
+ }
+ if (end != null) {
+ wrapper.le(SysErrorLog::getErrorTime, end);
+ }
+ wrapper.orderByDesc(SysErrorLog::getErrorTime);
+ return errorLogMapper.selectPage(page, wrapper);
+ }
+
+ public SysErrorLog errorLog(Long id) {
+ SysErrorLog log = errorLogMapper.selectById(id);
+ if (log == null) {
+ throw new BusinessException("閿欒鏃ュ織涓嶅瓨鍦�");
+ }
+ return log;
+ }
+
public Long resolveUserId(String username) {
if (!StringUtils.hasText(username)) {
return null;
diff --git a/emergency-repair-web/src/router/index.js b/emergency-repair-web/src/router/index.js
index 3339885..5ba2af1 100644
--- a/emergency-repair-web/src/router/index.js
+++ b/emergency-repair-web/src/router/index.js
@@ -36,8 +36,8 @@
{ path: 'system/roles', component: () => import('@/views/system/RoleManage.vue'), meta: { title: '瑙掕壊绠$悊', cacheName: 'RoleManage' } },
{ path: 'system/login-logs', component: () => import('@/views/system/LoginLogManage.vue'), meta: { title: '鐧诲綍鏃ュ織', cacheName: 'LoginLogManage' } },
{ path: 'system/operation-logs', component: () => import('@/views/system/OperationLogManage.vue'), meta: { title: '鎿嶄綔鏃ュ織', cacheName: 'OperationLogManage' } },
+ { path: 'system/error-logs', component: () => import('@/views/system/ErrorLogManage.vue'), meta: { title: '閿欒鏃ュ織', cacheName: 'ErrorLogManage' } },
{ path: 'system/menus', component: () => import('@/views/system/MenuManage.vue'), meta: { title: '鑿滃崟绠$悊', cacheName: 'MenuManage' } },
- { path: 'system/menu-permissions', component: () => import('@/views/system/MenuPermission.vue'), meta: { title: '鑿滃崟鏉冮檺', cacheName: 'MenuPermission' } },
{ path: 'system/three-member', component: () => import('@/views/system/ThreeMember.vue'), meta: { title: '涓夊憳绠$悊', cacheName: 'ThreeMember' } }
]
}
diff --git a/emergency-repair-web/src/views/system/ErrorLogManage.vue b/emergency-repair-web/src/views/system/ErrorLogManage.vue
new file mode 100644
index 0000000..2df088a
--- /dev/null
+++ b/emergency-repair-web/src/views/system/ErrorLogManage.vue
@@ -0,0 +1,151 @@
+<template>
+ <div class="page">
+ <div class="page-title">
+ <div><h2>閿欒鏃ュ織</h2><p>璁板綍绋嬪簭杩愯涓殑鏈鐞嗗紓甯革紝渚夸簬瀹氫綅鍜屼慨澶嶇己闄�</p></div>
+ </div>
+ <div class="table-panel">
+ <div class="toolbar">
+ <el-input v-model="filters.keyword" clearable placeholder="璐︽埛鍚� / 濮撳悕 / 璇锋眰鍦板潃 / 寮傚父淇℃伅" style="width:280px" @keyup.enter.native="search"></el-input>
+ <el-select v-model="filters.module" clearable placeholder="妯″潡" style="width:150px">
+ <el-option v-for="item in modules" :key="item" :label="item" :value="item"></el-option>
+ </el-select>
+ <el-input v-model="filters.exceptionType" clearable placeholder="寮傚父绫诲瀷" style="width:220px" @keyup.enter.native="search"></el-input>
+ <el-date-picker
+ v-model="filters.dates"
+ type="daterange"
+ value-format="yyyy-MM-dd"
+ range-separator="鑷�"
+ start-placeholder="寮�濮嬫棩鏈�"
+ end-placeholder="缁撴潫鏃ユ湡"
+ style="width:260px"
+ ></el-date-picker>
+ <el-button type="primary" icon="el-icon-search" v-permission="'system:error-logs:query'" @click="search">鏌ヨ</el-button>
+ <el-button icon="el-icon-refresh-left" @click="reset">閲嶇疆</el-button>
+ </div>
+ <el-table v-loading="loading" :data="rows" border stripe height="calc(100vh - 250px)">
+ <el-table-column prop="errorTime" label="鍙戠敓鏃堕棿" width="180"></el-table-column>
+ <el-table-column prop="userName" label="璐︽埛鍚�" width="120"></el-table-column>
+ <el-table-column prop="nickName" label="濮撳悕" width="110"></el-table-column>
+ <el-table-column prop="module" label="妯″潡" width="120"></el-table-column>
+ <el-table-column prop="requestMethod" label="鏂瑰紡" width="75" align="center"></el-table-column>
+ <el-table-column prop="requestUri" label="璇锋眰鍦板潃" min-width="230" show-overflow-tooltip></el-table-column>
+ <el-table-column prop="exceptionType" label="寮傚父绫诲瀷" min-width="220" show-overflow-tooltip></el-table-column>
+ <el-table-column prop="message" label="寮傚父淇℃伅" min-width="280" show-overflow-tooltip></el-table-column>
+ <el-table-column label="鎿嶄綔" width="80" fixed="right">
+ <template slot-scope="{ row }">
+ <el-button type="text" v-permission="'system:error-logs:detail'" @click="openDetail(row)">璇︽儏</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ <el-pagination
+ class="pager"
+ background
+ layout="total, sizes, prev, pager, next"
+ :total="total"
+ :current-page="current"
+ :page-size="size"
+ :page-sizes="[20, 50, 100]"
+ @current-change="handleCurrentChange"
+ @size-change="handleSizeChange"
+ ></el-pagination>
+ </div>
+
+ <el-dialog title="閿欒璇︽儏" :visible.sync="detailVisible" width="900px" append-to-body>
+ <div v-loading="detailLoading">
+ <div class="detail-meta">
+ <span><b>鍙戠敓鏃堕棿锛�</b>{{ detail.errorTime }}</span>
+ <span><b>鎿嶄綔浜猴細</b>{{ detail.userName || '-' }}{{ detail.nickName ? ' / ' + detail.nickName : '' }}</span>
+ <span><b>妯″潡锛�</b>{{ detail.module || '-' }}</span>
+ </div>
+ <div class="detail-meta">
+ <span><b>璇锋眰锛�</b>{{ (detail.requestMethod || '') + ' ' + (detail.requestUri || '') }}</span>
+ </div>
+ <div class="detail-meta">
+ <span><b>寮傚父绫诲瀷锛�</b>{{ detail.exceptionType }}</span>
+ </div>
+ <div class="detail-meta">
+ <span><b>寮傚父淇℃伅锛�</b>{{ detail.message }}</span>
+ </div>
+ <pre class="stack-trace">{{ detail.stackTrace || '鏃犲爢鏍堜俊鎭�' }}</pre>
+ </div>
+ <template slot="footer"><el-button @click="detailVisible = false">鍏抽棴</el-button></template>
+ </el-dialog>
+ </div>
+</template>
+
+<script>
+import api from '@/api'
+export default {
+ name: 'ErrorLogManage',
+ data() {
+ return {
+ rows: [],
+ total: 0,
+ current: 1,
+ size: 20,
+ loading: false,
+ detailVisible: false,
+ detailLoading: false,
+ detail: {},
+ modules: ['鏁呴殰绠$悊', '瀹屽伐绠$悊', '瀹屽伐楠屾敹', '缁熻鎶ヨ〃', '鐢ㄦ埛绠$悊', '瑙掕壊绠$悊', '鑿滃崟绠$悊', '鎵夸慨鍗曚綅绠$悊', '绯荤粺绠$悊', '鍏朵粬'],
+ filters: { keyword: '', module: '', exceptionType: '', dates: [] }
+ }
+ },
+ created() { this.load() },
+ methods: {
+ load() {
+ this.loading = true
+ const dates = this.filters.dates || []
+ api.get('/system/error-logs', {
+ params: {
+ keyword: this.filters.keyword || undefined,
+ module: this.filters.module || undefined,
+ exceptionType: this.filters.exceptionType || undefined,
+ startDate: dates[0] || undefined,
+ endDate: dates[1] || undefined,
+ current: this.current,
+ size: this.size
+ }
+ }).then(res => {
+ const data = res.data || {}
+ this.rows = data.records || []
+ this.total = data.total || 0
+ }).finally(() => { this.loading = false })
+ },
+ search() { this.current = 1; this.load() },
+ reset() { this.filters = { keyword: '', module: '', exceptionType: '', dates: [] }; this.search() },
+ openDetail(row) {
+ this.detailLoading = true
+ this.detail = {}
+ this.detailVisible = true
+ api.get('/system/error-logs/' + row.id).then(res => {
+ this.detail = res.data || {}
+ }).finally(() => { this.detailLoading = false })
+ },
+ handleCurrentChange(page) { this.current = page; this.load() },
+ handleSizeChange(size) { this.size = size; this.current = 1; this.load() }
+ }
+}
+</script>
+
+<style scoped>
+.toolbar { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
+.pager { margin-top: 12px; text-align: right; }
+.detail-meta { margin-bottom: 8px; color: #42566b; line-height: 22px; word-break: break-all; }
+.detail-meta b { color: #26394d; }
+.stack-trace {
+ max-height: 520px;
+ margin: 12px 0 0;
+ padding: 12px;
+ border: 1px solid #dce4ec;
+ border-radius: 4px;
+ color: #30445a;
+ background: #f7f9fb;
+ font-family: Consolas, "Courier New", monospace;
+ font-size: 12px;
+ line-height: 18px;
+ white-space: pre-wrap;
+ word-break: break-all;
+ overflow: auto;
+}
+</style>
diff --git a/emergency-repair-web/src/views/system/MenuManage.vue b/emergency-repair-web/src/views/system/MenuManage.vue
index 9d1f4af..095bfda 100644
--- a/emergency-repair-web/src/views/system/MenuManage.vue
+++ b/emergency-repair-web/src/views/system/MenuManage.vue
@@ -63,7 +63,7 @@
</el-dialog>
<el-dialog :title="'椤甸潰鎸夐挳閰嶇疆 - ' + (buttonMenu.name || '')" :visible.sync="buttonDialogVisible" width="620px" append-to-body>
- <p class="dialog-tip">涓嬪垪鎸夐挳鏉ヨ嚜鍚庣鎸夐挳鐩綍锛屽嬀閫夊悗鑷姩鐢熸垚鎸夐挳绫诲瀷鑿滃崟锛屽彲鍦ㄨ彍鍗曟潈闄愮鐞嗕腑鎺堟潈缁欒鑹层��</p>
+ <p class="dialog-tip">涓嬪垪鎸夐挳鏉ヨ嚜鍚庣鎸夐挳鐩綍锛屽嬀閫夊悗鑷姩鐢熸垚鎸夐挳绫诲瀷鑿滃崟锛屽彲鍦ㄨ鑹茬鐞嗙殑鏉冮檺璁剧疆涓巿鏉冪粰瑙掕壊銆�</p>
<el-checkbox-group v-model="buttonSelected" class="button-list">
<el-checkbox v-for="item in buttonAvailable" :key="item.code" :label="item.code" class="button-item">
<span class="button-name">{{ item.name }}</span>
diff --git a/emergency-repair-web/src/views/system/MenuPermission.vue b/emergency-repair-web/src/views/system/MenuPermission.vue
deleted file mode 100644
index e69de15..0000000
--- a/emergency-repair-web/src/views/system/MenuPermission.vue
+++ /dev/null
@@ -1,140 +0,0 @@
-<template>
- <div class="page">
- <div class="page-title">
- <div><h2>鑿滃崟鏉冮檺绠$悊</h2><p>鎸夎鑹茶缃彍鍗曞拰鎸夐挳鏉冮檺锛屼繚瀛樺悗鍒囨崲瑙掕壊浼氳嚜鍔ㄥ弽鏄俱��</p></div>
- </div>
- <div class="toolbar">
- <span>瑙掕壊</span>
- <el-select v-model="roleId" style="width:260px" @change="loadPermissions"><el-option v-for="item in roles" :key="item.id" :label="item.name" :value="item.id"></el-option></el-select>
- <div class="spacer"></div>
- <el-tag type="success">宸查�夋潈闄愶細{{ selectedRoleIds.length }} 涓�</el-tag>
- <el-button type="primary" :loading="saving" @click="save">淇濆瓨瑙掕壊鏉冮檺</el-button>
- </div>
- <section class="permission-panel">
- <div class="panel-head"><h3>瑙掕壊鑿滃崟鍙婃寜閽潈闄�</h3><el-checkbox v-model="roleCheckAll" @change="checkAll">鍏ㄩ��</el-checkbox></div>
- <el-tree ref="roleTree" :data="menus" node-key="id" show-checkbox default-expand-all :render-content="renderContent" :props="{ label: 'name', children: 'children' }" @check="syncCheckAllState"></el-tree>
- </section>
- </div>
-</template>
-<script>
-import api from '@/api'
-export default {
- name: 'MenuPermission',
- data() {
- return {
- menus: [],
- flatMenus: [],
- roles: [],
- roleId: null,
- saving: false,
- roleCheckAll: false,
- selectedRoleIds: []
- }
- },
- computed: {
- allMenuIds() { return this.flatMenus.map(item => String(item.id)) }
- },
- created() { this.loadBase() },
- methods: {
- loadBase() {
- Promise.all([api.get('/system/menu/tree'), api.get('/system/menu/list'), api.get('/system/roles')]).then(([tree, flat, roles]) => {
- this.menus = tree.data || []
- this.flatMenus = flat.data || []
- this.roles = roles.data || []
- if (!this.roleId && this.roles.length) this.roleId = this.roles[0].id
- this.$nextTick(this.loadPermissions)
- })
- },
- loadPermissions() {
- if (!this.roleId) {
- this.selectedRoleIds = []
- this.roleCheckAll = false
- if (this.$refs.roleTree) this.$refs.roleTree.setCheckedKeys([])
- return
- }
- api.get('/system/menu-permissions', { params: { roleId: this.roleId } }).then(res => {
- const data = res.data || {}
- this.selectedRoleIds = (data.roleMenuIds || []).map(String)
- this.$nextTick(() => {
- this.$refs.roleTree.setCheckedKeys(this.displayCheckedKeys(this.selectedRoleIds))
- this.syncCheckAllState()
- })
- })
- },
- renderContent(h, { data }) {
- const label = data.menuType === 2 ? data.name + ' [鎸夐挳 ' + (data.permissions || '') + ']' : data.name
- return h('span', { class: { 'perm-button-node': data.menuType === 2 } }, label)
- },
- displayCheckedKeys(ids) {
- const selected = new Set((ids || []).map(String))
- const children = {}
- const menuIds = new Set(this.flatMenus.map(item => String(item.id)))
- this.flatMenus.forEach(item => {
- const id = String(item.id)
- const pid = item.pid == null ? '0' : String(item.pid)
- if (!children[pid]) children[pid] = []
- children[pid].push(id)
- })
- const result = []
- const collectFullySelected = id => {
- if (!selected.has(id)) return false
- const childIds = children[id] || []
- if (!childIds.length) {
- result.push(id)
- return true
- }
- const allChildrenSelected = childIds.map(collectFullySelected).every(Boolean)
- if (allChildrenSelected) result.push(id)
- return allChildrenSelected
- }
- this.flatMenus.filter(item => item.pid == null || String(item.pid) === '0' || !menuIds.has(String(item.pid))).forEach(item => collectFullySelected(String(item.id)))
- return result
- },
- checkedMenuIds(ref) { return [...new Set([...(ref.getCheckedKeys(false) || []), ...(ref.getHalfCheckedKeys() || [])].map(String))] },
- syncCheckAllState() {
- const ref = this.$refs.roleTree
- if (!ref) return
- this.roleCheckAll = this.checkedMenuIds(ref).length === this.allMenuIds.length
- },
- checkAll() {
- this.$refs.roleTree.setCheckedKeys(this.roleCheckAll ? this.allMenuIds : [])
- this.$nextTick(this.syncCheckAllState)
- },
- save() {
- if (!this.roleId) {
- this.$message.warning('璇峰厛閫夋嫨瑙掕壊')
- return
- }
- const roleMenuIds = this.checkedMenuIds(this.$refs.roleTree)
- this.saving = true
- api.post('/system/menu-permissions', { roleId: this.roleId, roleMenuIds }).then(() => {
- this.selectedRoleIds = roleMenuIds
- this.$message.success('瑙掕壊鑿滃崟鏉冮檺宸蹭繚瀛�')
- }).finally(() => { this.saving = false })
- }
- }
-}
-</script>
-<style scoped>
-::v-deep .perm-button-node { color: #b8791f; }
-
-.permission-panel {
- min-height: calc(100vh - 220px);
- padding: 14px;
- border: 1px solid #dce4ec;
- border-radius: 4px;
- background: #fff;
- overflow: auto;
-}
-.panel-head {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 12px;
-}
-.panel-head h3 {
- margin: 0;
- color: #31465d;
- font-size: 15px;
-}
-</style>
diff --git a/emergency-repair-web/src/views/system/RoleManage.vue b/emergency-repair-web/src/views/system/RoleManage.vue
index 16dd95f..a711de8 100644
--- a/emergency-repair-web/src/views/system/RoleManage.vue
+++ b/emergency-repair-web/src/views/system/RoleManage.vue
@@ -1,7 +1,7 @@
<template>
<div class="page">
<div class="page-title">
- <div><h2>瑙掕壊绠$悊</h2><p>缁存姢瑙掕壊淇℃伅锛屽苟鍙寜瑙掕壊鎵归噺璁剧疆鎵�灞炵敤鎴�</p></div>
+ <div><h2>瑙掕壊绠$悊</h2><p>缁存姢瑙掕壊淇℃伅锛屽苟鍙寜瑙掕壊璁剧疆鎵�灞炵敤鎴峰拰鑿滃崟鎸夐挳鏉冮檺</p></div>
<el-button type="primary" icon="el-icon-plus" v-permission="'system:roles:add'" @click="openCreate">鏂板瑙掕壊</el-button>
</div>
<div class="table-panel">
@@ -11,9 +11,10 @@
<el-table-column prop="code" label="瑙掕壊缂栫爜" width="170"></el-table-column>
<el-table-column prop="roleType" label="瑙掕壊绫诲瀷" width="170"></el-table-column>
<el-table-column prop="remark" label="澶囨敞" min-width="220" show-overflow-tooltip></el-table-column>
- <el-table-column label="鎿嶄綔" width="220" fixed="right">
+ <el-table-column label="鎿嶄綔" width="300" fixed="right">
<template slot-scope="{ row }">
<el-button type="text" v-permission="'system:roles:assign'" @click="openUserSetting(row)">鐢ㄦ埛璁剧疆</el-button>
+ <el-button type="text" v-permission="'system:roles:permission'" @click="openPermissionSetting(row)">鏉冮檺璁剧疆</el-button>
<el-button type="text" v-permission="'system:roles:edit'" @click="openEdit(row)">缂栬緫</el-button>
<el-button type="text" class="danger-link" v-permission="'system:roles:delete'" @click="remove(row)">鍒犻櫎</el-button>
</template>
@@ -80,16 +81,21 @@
<el-button type="primary" :loading="userSaving" v-permission="'system:roles:assign'" @click="saveRoleUsers">淇濆瓨</el-button>
</template>
</el-dialog>
+
+ <role-permission-dialog :visible.sync="permissionDialogVisible" :role="currentRole"></role-permission-dialog>
</div>
</template>
<script>
import api from '@/api'
+import RolePermissionDialog from './RolePermissionDialog.vue'
export default {
name: 'RoleManage',
+ components: { RolePermissionDialog },
data() {
return {
rows: [],
dialogVisible: false,
+ permissionDialogVisible: false,
saving: false,
userDialogVisible: false,
userLoading: false,
@@ -164,6 +170,10 @@
this.$nextTick(this.restoreUserSelection)
}).finally(() => { this.userLoading = false })
},
+ openPermissionSetting(row) {
+ this.currentRole = { ...row }
+ this.permissionDialogVisible = true
+ },
restoreUserSelection() {
const table = this.$refs.roleUserTable
if (!table) return
diff --git a/emergency-repair-web/src/views/system/RolePermissionDialog.vue b/emergency-repair-web/src/views/system/RolePermissionDialog.vue
new file mode 100644
index 0000000..7271f40
--- /dev/null
+++ b/emergency-repair-web/src/views/system/RolePermissionDialog.vue
@@ -0,0 +1,194 @@
+<template>
+ <el-dialog
+ title="瑙掕壊鏉冮檺璁剧疆"
+ :visible.sync="dialogVisible"
+ width="860px"
+ append-to-body
+ :close-on-click-modal="false"
+ >
+ <div class="permission-head">
+ <div>
+ <span class="role-label">褰撳墠瑙掕壊</span>
+ <strong>{{ role.name }}</strong>
+ <span class="role-code">{{ role.code }}</span>
+ </div>
+ <span class="selected-total">宸查�夋潈闄� {{ selectedRoleIds.length }} 涓�</span>
+ </div>
+ <div class="permission-toolbar">
+ <el-checkbox v-model="checkAll" @change="toggleAll">鍏ㄩ��</el-checkbox>
+ <span class="permission-tip">鑿滃崟鍜屾寜閽潈闄愬潎鍙嬀閫夛紝淇濆瓨鍚庡垏鎹㈣鑹蹭細鑷姩鍙嶆樉銆�</span>
+ </div>
+ <div v-loading="loading" class="permission-tree">
+ <el-tree
+ ref="permissionTree"
+ :data="menus"
+ node-key="id"
+ show-checkbox
+ default-expand-all
+ :render-content="renderContent"
+ :props="{ label: 'name', children: 'children' }"
+ @check="syncCheckAllState"
+ ></el-tree>
+ </div>
+ <template slot="footer">
+ <el-button @click="dialogVisible = false">鍙栨秷</el-button>
+ <el-button type="primary" :loading="saving" v-permission="'system:roles:permission'" @click="save">淇濆瓨</el-button>
+ </template>
+ </el-dialog>
+</template>
+
+<script>
+import api from '@/api'
+export default {
+ name: 'RolePermissionDialog',
+ props: {
+ visible: { type: Boolean, default: false },
+ role: { type: Object, default: () => ({}) }
+ },
+ data() {
+ return {
+ menus: [],
+ flatMenus: [],
+ loading: false,
+ saving: false,
+ checkAll: false,
+ selectedRoleIds: []
+ }
+ },
+ computed: {
+ dialogVisible: {
+ get() { return this.visible },
+ set(value) { this.$emit('update:visible', value) }
+ },
+ allMenuIds() {
+ return this.flatMenus.map(item => String(item.id))
+ }
+ },
+ watch: {
+ visible(value) {
+ if (value) this.open()
+ },
+ },
+ methods: {
+ open() {
+ if (!this.role || !this.role.id) return
+ this.loading = true
+ this.checkAll = false
+ this.selectedRoleIds = []
+ Promise.all([
+ api.get('/system/menu/tree'),
+ api.get('/system/menu/list'),
+ api.get('/system/menu-permissions', { params: { roleId: this.role.id } })
+ ]).then(([tree, flat, permissions]) => {
+ this.menus = tree.data || []
+ this.flatMenus = flat.data || []
+ const data = permissions.data || {}
+ this.selectedRoleIds = (data.roleMenuIds || []).map(String)
+ this.$nextTick(() => {
+ if (!this.$refs.permissionTree) return
+ this.$refs.permissionTree.setCheckedKeys(this.displayCheckedKeys(this.selectedRoleIds))
+ this.syncCheckAllState()
+ })
+ }).finally(() => { this.loading = false })
+ },
+ renderContent(h, { data }) {
+ const label = data.menuType === 2 ? data.name + ' [鎸夐挳 ' + (data.permissions || '') + ']' : data.name
+ return h('span', { class: { 'perm-button-node': data.menuType === 2 } }, label)
+ },
+ displayCheckedKeys(ids) {
+ const selected = new Set((ids || []).map(String))
+ const children = {}
+ const menuIds = new Set(this.flatMenus.map(item => String(item.id)))
+ this.flatMenus.forEach(item => {
+ const id = String(item.id)
+ const pid = item.pid == null ? '0' : String(item.pid)
+ if (!children[pid]) children[pid] = []
+ children[pid].push(id)
+ })
+ const result = []
+ const collectFullySelected = id => {
+ if (!selected.has(id)) return false
+ const childIds = children[id] || []
+ if (!childIds.length) {
+ result.push(id)
+ return true
+ }
+ const allChildrenSelected = childIds.map(collectFullySelected).every(Boolean)
+ if (allChildrenSelected) result.push(id)
+ return allChildrenSelected
+ }
+ this.flatMenus
+ .filter(item => item.pid == null || String(item.pid) === '0' || !menuIds.has(String(item.pid)))
+ .forEach(item => collectFullySelected(String(item.id)))
+ return result
+ },
+ checkedMenuIds() {
+ const tree = this.$refs.permissionTree
+ if (!tree) return []
+ return [...new Set([...(tree.getCheckedKeys(false) || []), ...(tree.getHalfCheckedKeys() || [])].map(String))]
+ },
+ syncCheckAllState() {
+ this.checkAll = this.checkedMenuIds().length === this.allMenuIds.length
+ },
+ toggleAll() {
+ if (!this.$refs.permissionTree) return
+ this.$refs.permissionTree.setCheckedKeys(this.checkAll ? this.allMenuIds : [])
+ this.$nextTick(this.syncCheckAllState)
+ },
+ save() {
+ const roleMenuIds = this.checkedMenuIds()
+ this.saving = true
+ api.post('/system/menu-permissions', { roleId: this.role.id, roleMenuIds }).then(() => {
+ this.selectedRoleIds = roleMenuIds
+ this.$message.success('瑙掕壊鏉冮檺宸蹭繚瀛�')
+ this.$emit('saved')
+ this.dialogVisible = false
+ }).finally(() => { this.saving = false })
+ }
+ }
+}
+</script>
+
+<style scoped>
+.permission-head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 12px;
+ color: #31465d;
+}
+.role-label {
+ margin-right: 10px;
+ color: #7b8b9c;
+ font-size: 13px;
+}
+.role-code {
+ margin-left: 8px;
+ color: #93a2b1;
+ font-size: 12px;
+}
+.selected-total {
+ color: #2f658f;
+ font-size: 13px;
+}
+.permission-toolbar {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 10px;
+}
+.permission-tip {
+ color: #7b8a9a;
+ font-size: 12px;
+}
+.permission-tree {
+ height: 460px;
+ padding: 8px 10px;
+ border: 1px solid #dce4ec;
+ border-radius: 4px;
+ overflow: auto;
+}
+::v-deep .perm-button-node {
+ color: #b8791f;
+}
+</style>
diff --git "a/requirement/\344\272\244\346\216\245\346\226\207\344\273\266_2026.10.3.md" "b/requirement/\344\272\244\346\216\245\346\226\207\344\273\266_2026.10.3.md"
index 443f293..b1b34d0 100644
--- "a/requirement/\344\272\244\346\216\245\346\226\207\344\273\266_2026.10.3.md"
+++ "b/requirement/\344\272\244\346\216\245\346\226\207\344\273\266_2026.10.3.md"
@@ -534,3 +534,46 @@
- 鏈疆浠g爜涓庝氦鎺ヨ褰曚竴骞舵彁浜ゅ苟鎺ㄩ�佸叕鍙� Git 浠撳簱 `main` 鍒嗘敮銆�
- `.codex-run`銆丱ffice 閿佹枃浠跺強 `emergency-repair-web/public/鏂板缓 鏂囨湰鏂囨。.txt` 浠嶄负鏈湴涓存椂鏂囦欢锛屼笉搴旀彁浜ゃ��
+
+## 鍗佷笁銆�2026-10-06 鑱旇皟鏀跺熬锛堥渶姹� 21-22锛�
+
+| 搴忓彿 | 闇�姹� | 鐘舵�� | 瀹炵幇涓庨獙璇� |
+| --- | --- | --- | --- |
+| 21 | 鑿滃崟鏉冮檺绠$悊鏀逛负瑙掕壊绠$悊鐨勨�滄潈闄愯缃�濆叆鍙� | 宸插畬鎴� | 鍒犻櫎鐙珛鑿滃崟銆佽矾鐢卞拰椤甸潰锛涜鑹插垪琛ㄦ柊澧炩�滄潈闄愯缃�濓紝寮圭獥淇濈暀鑿滃崟鍙婃寜閽妭鐐瑰苟姝e父鍙嶆樉 |
+| 22 | 绯荤粺绠$悊鏂板浜岀骇鑿滃崟缁勶紝鏂板閿欒鏃ュ織 | 宸插畬鎴� | 鏂板鈥滅郴缁熼厤缃� / 鏉冮檺閰嶇疆 / 鏃ュ織绠$悊鈥濈洰褰曞苟杩佺Щ鑿滃崟锛涢敊璇棩蹇楄褰曢潪涓氬姟寮傚父锛屾敮鎸佺瓫閫夈�佸垎椤靛拰鍫嗘爤璇︽儏 |
+
+### 13.1 涓昏鍙樻洿鏂囦欢
+
+鍚庣锛�
+
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/entity/SysErrorLog.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/mapper/SysErrorLogMapper.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/log/ErrorLogRecorder.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/common/GlobalExceptionHandler.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DataInitializer.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/bootstrap/DatabaseBootstrap.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/controller/SystemController.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/service/SystemService.java`
+- `emergency-repair-server/src/main/java/com/emergencyrepair/system/service/ButtonPermissionCatalog.java`
+
+鍓嶇锛�
+
+- `emergency-repair-web/src/views/system/RoleManage.vue`
+- `emergency-repair-web/src/views/system/RolePermissionDialog.vue`
+- `emergency-repair-web/src/views/system/ErrorLogManage.vue`
+- `emergency-repair-web/src/views/system/MenuManage.vue`
+- `emergency-repair-web/src/router/index.js`
+- 鍒犻櫎 `emergency-repair-web/src/views/system/MenuPermission.vue`
+
+### 13.2 楠岃瘉缁撴灉
+
+- 鍚庣 `mvn -B -DskipTests clean package` 閫氳繃锛涘墠绔� `npm run build` 閫氳繃锛屼粎鏈夋棦鏈夊寘浣撶Н鍛婅銆�
+- `node .codex-run/verify-req21-22.js` 閫氳繃锛氫簩绾х洰褰曞拰閿欒鏃ュ織鑿滃崟鏄剧ず姝e父锛屾棫鈥滆彍鍗曟潈闄愮鐞嗏�濅笉瀛樺湪锛涜鑹叉潈闄愬脊绐楁寜閽妭鐐规暟 78锛屾潈闄愬弽鏄炬甯搞��
+- 閿欒鏃ュ織鍒楄〃銆佸紓甯哥被鍨嬬瓫閫夊拰鍫嗘爤璇︽儏椤甸潰楠岃瘉閫氳繃锛涗汉涓鸿Е鍙戦潪娉曟帴鍙e弬鏁板悗鎴愬姛钀藉簱锛屼复鏃堕獙璇佽褰曞凡娓呯悊銆�
+- 鎴浘锛歚.codex-run/role-permission-dialog-verified.png`銆乣.codex-run/error-log-verified.png`銆�
+
+### 13.3 鎺ユ墜娉ㄦ剰
+
+- 閿欒鏃ュ織鐩墠瑕嗙洊杩涘叆 Spring MVC 鍏ㄥ眬寮傚父澶勭悊鐨勫紓甯革紱鍚姩闃舵寮傚父銆佸紓姝ョ嚎绋嬪紓甯歌嫢缁曡繃璇ュ鐞嗗櫒锛屾殏涓嶄細鑷姩璁板綍銆�
+- 鏈疆浠g爜涓庝氦鎺ヨ褰曚竴骞舵彁浜ゅ苟鎺ㄩ�佸叕鍙� Git 浠撳簱 `main` 鍒嗘敮銆�
+- `.codex-run`銆丱ffice 閿佹枃浠跺強 `emergency-repair-web/public/鏂板缓 鏂囨湰鏂囨。.txt` 浠嶄负鏈湴涓存椂鏂囦欢锛屼笉搴旀彁浜ゃ��
--
Gitblit v1.9.1