From 9afc6808330dcca79b54d319129c50e49ed07a07 Mon Sep 17 00:00:00 2001 From: peng Date: Tue, 21 Oct 2025 18:04:39 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=9F=E4=BA=A7=E7=AE=A1=E7=90=86=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E5=89=8D=E7=AB=AF=20=E5=BC=80=E5=8F=91=E4=B8=AD?= =?UTF-8?q?=E5=BF=83=E9=85=8D=E7=BD=AE=E7=B3=BB=E7=BB=9F=20=E6=89=80?= =?UTF-8?q?=E6=9C=89=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/app/(app)/central-config/layout.tsx | 48 +- .../central-config/message-center/layout.tsx | 22 - .../message-center/message-log/page.tsx | 8 - .../message-center/message-send/page.tsx | 146 ------ .../message-center/message-template/page.tsx | 8 - .../central-config/message-center/page.tsx | 111 ---- .../log/components/MessageLogFilter.tsx | 64 +++ .../log/components/MessageLogStats.tsx | 44 ++ .../log/components/MessageLogTable.tsx | 152 ++++++ .../message/log/mock/mockData.ts | 115 ++++ .../(app)/central-config/message/log/page.tsx | 139 +++++ .../app/(app)/central-config/message/page.tsx | 26 + .../send/components/MessageInstructions.tsx | 17 + .../send/components/MessagePreviewDialog.tsx | 122 +++++ .../send/components/MessageSendTable.tsx | 154 ++++++ .../send/components/MessageStatsCards.tsx | 42 ++ .../send/components/SendMessageDialog.tsx | 258 +++++++++ .../message/send/components/index.ts | 5 + .../central-config/message/send/page.tsx | 493 ++++++++++++++++++ .../central-config/message/send/types.ts | 12 + .../components/MessageTemplateDialog.tsx | 179 +++++++ .../components/MessageTemplateHeader.tsx | 30 ++ .../components/MessageTemplateInfo.tsx | 22 + .../components/MessageTemplateList.tsx | 128 +++++ .../components/MessageTemplateSearch.tsx | 48 ++ .../components/MessageTemplateTestDialog.tsx | 121 +++++ .../message/template/components/index.ts | 6 + .../central-config/message/template/page.tsx | 269 ++++++++++ .../central-config/message/template/types.ts | 14 + .../login-log/components/LoginLogFilters.tsx | 59 +++ .../login-log/components/LoginLogInfo.tsx | 20 + .../login-log/components/LoginLogStats.tsx | 50 ++ .../login-log/components/LoginLogTable.tsx | 87 ++++ .../monitor/login-log/components/index.ts | 4 + .../central-config/monitor/login-log/page.tsx | 202 +++++++ .../components/NetworkLogDetailDialog.tsx | 191 +++++++ .../components/NetworkLogFilters.tsx | 62 +++ .../network-log/components/NetworkLogInfo.tsx | 20 + .../components/NetworkLogService.ts | 338 ++++++++++++ .../components/NetworkLogStats.tsx | 51 ++ .../components/NetworkLogTable.tsx | 133 +++++ .../monitor/network-log/components/index.ts | 8 + .../monitor/network-log/page.tsx | 139 +++++ .../components/OperationLogDetailDialog.tsx | 183 +++++++ .../components/OperationLogFilters.tsx | 81 +++ .../components/OperationLogInfo.tsx | 20 + .../components/OperationLogService.ts | 314 +++++++++++ .../components/OperationLogStats.tsx | 51 ++ .../components/OperationLogTable.tsx | 144 +++++ .../monitor/operation-log/components/index.ts | 9 + .../monitor/operation-log/page.tsx | 152 ++++++ .../app/(app)/central-config/monitor/page.tsx | 30 ++ .../performance/components/CpuMetricCard.tsx | 40 ++ .../performance/components/DiskMetricCard.tsx | 51 ++ .../performance/components/JvmInfoCard.tsx | 53 ++ .../components/MemoryMetricCard.tsx | 47 ++ .../components/PerformanceInstructions.tsx | 20 + .../components/PerformanceTrendChart.tsx | 42 ++ .../performance/components/TomcatInfoCard.tsx | 48 ++ .../monitor/performance/components/index.ts | 7 + .../monitor/performance/page.tsx | 274 ++++++++++ .../central-config/system-monitor/layout.tsx | 11 - .../system-monitor/login-log/page.tsx | 8 - .../system-monitor/network-log/page.tsx | 8 - .../system-monitor/operation-log/page.tsx | 8 - .../central-config/system-monitor/page.tsx | 8 - .../performance-monitor/page.tsx | 8 - .../exception-handling/page.tsx | 185 ------- .../system-monitoring/layout.tsx | 22 - .../system-monitoring/log-management/page.tsx | 160 ------ .../central-config/system-monitoring/page.tsx | 103 ---- .../performance-monitoring/page.tsx | 119 ----- .../basic-configuration/page.tsx | 223 -------- .../business-rule-settings/page.tsx | 243 --------- .../category-dictionary/page.tsx | 8 - .../data-dictionary/page.tsx | 8 - .../interface-configuration/page.tsx | 299 ----------- .../system-parameters/layout.tsx | 22 - .../central-config/system-parameters/page.tsx | 103 ---- .../system-settings/page.tsx | 8 - .../central-config/system/category/page.tsx | 14 + .../central-config/system/dictionary/page.tsx | 14 + .../app/(app)/central-config/system/page.tsx | 26 + .../settings/components/CopyrightInfoCard.tsx | 35 ++ .../settings/components/FeatureToggleCard.tsx | 39 ++ .../components/PasswordPolicyCard.tsx | 67 +++ .../settings/components/PlatformInfoCard.tsx | 60 +++ .../components/RegionalSettingsCard.tsx | 69 +++ .../components/SessionManagementCard.tsx | 45 ++ .../settings/components/SettingsInfoCard.tsx | 20 + .../components/SystemAnnouncementCard.tsx | 22 + .../system/settings/components/index.ts | 8 + .../central-config/system/settings/page.tsx | 168 ++++++ .../tenant-management/audit-history/page.tsx | 8 - .../components/EnterpriseBankInfo.tsx | 65 --- .../components/EnterpriseBasicInfo.tsx | 43 -- .../components/EnterpriseDetailDialog.tsx | 192 ------- .../components/EnterpriseLegalInfo.tsx | 62 --- .../components/EnterpriseOtherInfo.tsx | 75 --- .../enterprise-audit/components/index.ts | 8 - .../enterprise-info/page.tsx | 8 - .../tenant-management/layout.tsx | 22 - .../central-config/tenant-management/page.tsx | 163 ------ .../platform-user-management/page.tsx | 8 - .../tenant-authorization/page.tsx | 243 --------- .../tenant-configuration/page.tsx | 287 ---------- .../tenant-creation/page.tsx | 221 -------- .../components/AuditHistoryDetailDialog.tsx | 352 +++++++++++++ .../components/AuditHistoryFilters.tsx | 84 +++ .../components/AuditHistoryInstructions.tsx | 23 + .../components/AuditHistoryList.tsx | 93 ++++ .../components/AuditHistoryStatsCards.tsx | 49 ++ .../tenant/audit-history/page.tsx | 211 ++++++++ .../tenant/audit-history/types.ts | 79 +++ .../components/AuditStatsCards.tsx} | 21 +- .../components/EnterpriseDetailDialog.tsx | 318 +++++++++++ .../components/EnterpriseList.tsx} | 37 +- .../components/SearchFilters.tsx} | 35 +- .../enterprise-audit/page.tsx | 60 +-- .../tenant/enterprise-audit/types.ts | 71 +++ .../components/AuditStatusAlert.tsx | 57 ++ .../components/BankInfoForm.tsx | 132 +++++ .../components/BasicInfoForm.tsx | 129 +++++ .../components/EnterpriseInfoHeader.tsx | 45 ++ .../components/LegalInfoForm.tsx | 122 +++++ .../components/OperationTips.tsx | 23 + .../components/OtherInfoForm.tsx | 178 +++++++ .../enterprise-info/components/SystemInfo.tsx | 44 ++ .../tenant/enterprise-info/page.tsx | 221 ++++++++ .../tenant/enterprise-info/types.ts | 55 ++ .../app/(app)/central-config/tenant/page.tsx | 30 ++ .../components/UserDetailDialog.tsx | 118 +++++ .../components/UserFormDialog.tsx | 138 +++++ .../user-management/components/UserList.tsx | 140 +++++ .../components/UserManagementFilters.tsx | 61 +++ .../components/UserManagementHeader.tsx | 31 ++ .../components/UserManagementStatsCards.tsx | 49 ++ .../tenant/user-management/page.tsx | 272 ++++++++++ .../tenant/user-management/types.ts | 54 ++ .../employee-management/page.tsx | 8 - .../central-config/user-management/layout.tsx | 22 - .../user-management/menu-management/page.tsx | 8 - .../central-config/user-management/page.tsx | 178 ------- .../permission-config/page.tsx | 8 - .../user-management/role-management/page.tsx | 8 - .../role-permission-management/page.tsx | 245 --------- .../user-account-management/page.tsx | 283 ---------- .../user-behavior-tracking/page.tsx | 337 ------------ .../components/EmployeeDetailDialog.tsx | 119 +++++ .../components/EmployeeFormDialog.tsx | 155 ++++++ .../user/employee/components/EmployeeList.tsx | 127 +++++ .../components/EmployeeManagementFilters.tsx | 53 ++ .../components/EmployeeManagementHeader.tsx | 24 + .../EmployeeManagementStatsCards.tsx | 43 ++ .../central-config/user/employee/page.tsx | 259 +++++++++ .../central-config/user/employee/types.ts | 65 +++ .../user/menu/components/MenuFormDialog.tsx | 160 ++++++ .../menu/components/MenuManagementHeader.tsx | 25 + .../components/MenuManagementInstructions.tsx | 21 + .../components/MenuManagementStatsCards.tsx | 57 ++ .../user/menu/components/MenuTree.tsx | 213 ++++++++ .../(app)/central-config/user/menu/page.tsx | 463 ++++++++++++++++ .../(app)/central-config/user/menu/types.ts | 16 + .../app/(app)/central-config/user/page.tsx | 30 ++ .../components/PermissionManagementHeader.tsx | 24 + .../central-config/user/permission/page.tsx | 414 +++++++++++++++ .../central-config/user/permission/types.ts | 37 ++ .../user/role/components/RoleDetailDialog.tsx | 110 ++++ .../user/role/components/RoleFormDialog.tsx | 235 +++++++++ .../user/role/components/RoleList.tsx | 113 ++++ .../role/components/RoleManagementHeader.tsx | 24 + .../components/RoleManagementInstructions.tsx | 19 + .../components/RoleManagementStatsCards.tsx | 43 ++ .../user/role/components/RoleSearch.tsx | 27 + .../(app)/central-config/user/role/page.tsx | 219 ++++++++ .../(app)/central-config/user/role/types.ts | 358 +++++++++++++ .../src/app/(app)/central-config/映射关系.md | 309 +++++++++++ crop-x/src/components/layouts/Navbar.tsx | 42 +- crop-x/src/services/api/messageLogApi.ts | 267 ++++++++++ crop-x/src/types/auth.ts | 81 +++ crop-x/src/types/message.ts | 61 +++ crop-x/src/types/monitor.ts | 112 ++++ crop-x/src/types/system-params.ts | 77 +++ ...目完整技术架构方案-Next.js-AppRouter版本.md | 34 +- src/types/message.ts | 20 + 185 files changed, 13677 insertions(+), 4487 deletions(-) delete mode 100644 crop-x/src/app/(app)/central-config/message-center/layout.tsx delete mode 100644 crop-x/src/app/(app)/central-config/message-center/message-log/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/message-center/message-send/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/message-center/message-template/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/message-center/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/message/log/components/MessageLogFilter.tsx create mode 100644 crop-x/src/app/(app)/central-config/message/log/components/MessageLogStats.tsx create mode 100644 crop-x/src/app/(app)/central-config/message/log/components/MessageLogTable.tsx create mode 100644 crop-x/src/app/(app)/central-config/message/log/mock/mockData.ts create mode 100644 crop-x/src/app/(app)/central-config/message/log/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/message/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/message/send/components/MessageInstructions.tsx create mode 100644 crop-x/src/app/(app)/central-config/message/send/components/MessagePreviewDialog.tsx create mode 100644 crop-x/src/app/(app)/central-config/message/send/components/MessageSendTable.tsx create mode 100644 crop-x/src/app/(app)/central-config/message/send/components/MessageStatsCards.tsx create mode 100644 crop-x/src/app/(app)/central-config/message/send/components/SendMessageDialog.tsx create mode 100644 crop-x/src/app/(app)/central-config/message/send/components/index.ts create mode 100644 crop-x/src/app/(app)/central-config/message/send/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/message/send/types.ts create mode 100644 crop-x/src/app/(app)/central-config/message/template/components/MessageTemplateDialog.tsx create mode 100644 crop-x/src/app/(app)/central-config/message/template/components/MessageTemplateHeader.tsx create mode 100644 crop-x/src/app/(app)/central-config/message/template/components/MessageTemplateInfo.tsx create mode 100644 crop-x/src/app/(app)/central-config/message/template/components/MessageTemplateList.tsx create mode 100644 crop-x/src/app/(app)/central-config/message/template/components/MessageTemplateSearch.tsx create mode 100644 crop-x/src/app/(app)/central-config/message/template/components/MessageTemplateTestDialog.tsx create mode 100644 crop-x/src/app/(app)/central-config/message/template/components/index.ts create mode 100644 crop-x/src/app/(app)/central-config/message/template/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/message/template/types.ts create mode 100644 crop-x/src/app/(app)/central-config/monitor/login-log/components/LoginLogFilters.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/login-log/components/LoginLogInfo.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/login-log/components/LoginLogStats.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/login-log/components/LoginLogTable.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/login-log/components/index.ts create mode 100644 crop-x/src/app/(app)/central-config/monitor/login-log/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/network-log/components/NetworkLogDetailDialog.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/network-log/components/NetworkLogFilters.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/network-log/components/NetworkLogInfo.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/network-log/components/NetworkLogService.ts create mode 100644 crop-x/src/app/(app)/central-config/monitor/network-log/components/NetworkLogStats.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/network-log/components/NetworkLogTable.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/network-log/components/index.ts create mode 100644 crop-x/src/app/(app)/central-config/monitor/network-log/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/operation-log/components/OperationLogDetailDialog.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/operation-log/components/OperationLogFilters.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/operation-log/components/OperationLogInfo.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/operation-log/components/OperationLogService.ts create mode 100644 crop-x/src/app/(app)/central-config/monitor/operation-log/components/OperationLogStats.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/operation-log/components/OperationLogTable.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/operation-log/components/index.ts create mode 100644 crop-x/src/app/(app)/central-config/monitor/operation-log/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/performance/components/CpuMetricCard.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/performance/components/DiskMetricCard.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/performance/components/JvmInfoCard.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/performance/components/MemoryMetricCard.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/performance/components/PerformanceInstructions.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/performance/components/PerformanceTrendChart.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/performance/components/TomcatInfoCard.tsx create mode 100644 crop-x/src/app/(app)/central-config/monitor/performance/components/index.ts create mode 100644 crop-x/src/app/(app)/central-config/monitor/performance/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/system-monitor/layout.tsx delete mode 100644 crop-x/src/app/(app)/central-config/system-monitor/login-log/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/system-monitor/network-log/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/system-monitor/operation-log/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/system-monitor/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/system-monitor/performance-monitor/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/system-monitoring/exception-handling/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/system-monitoring/layout.tsx delete mode 100644 crop-x/src/app/(app)/central-config/system-monitoring/log-management/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/system-monitoring/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/system-monitoring/performance-monitoring/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/system-parameters/basic-configuration/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/system-parameters/business-rule-settings/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/system-parameters/category-dictionary/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/system-parameters/data-dictionary/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/system-parameters/interface-configuration/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/system-parameters/layout.tsx delete mode 100644 crop-x/src/app/(app)/central-config/system-parameters/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/system-parameters/system-settings/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/system/category/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/system/dictionary/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/system/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/system/settings/components/CopyrightInfoCard.tsx create mode 100644 crop-x/src/app/(app)/central-config/system/settings/components/FeatureToggleCard.tsx create mode 100644 crop-x/src/app/(app)/central-config/system/settings/components/PasswordPolicyCard.tsx create mode 100644 crop-x/src/app/(app)/central-config/system/settings/components/PlatformInfoCard.tsx create mode 100644 crop-x/src/app/(app)/central-config/system/settings/components/RegionalSettingsCard.tsx create mode 100644 crop-x/src/app/(app)/central-config/system/settings/components/SessionManagementCard.tsx create mode 100644 crop-x/src/app/(app)/central-config/system/settings/components/SettingsInfoCard.tsx create mode 100644 crop-x/src/app/(app)/central-config/system/settings/components/SystemAnnouncementCard.tsx create mode 100644 crop-x/src/app/(app)/central-config/system/settings/components/index.ts create mode 100644 crop-x/src/app/(app)/central-config/system/settings/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/tenant-management/audit-history/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/tenant-management/enterprise-audit/components/EnterpriseBankInfo.tsx delete mode 100644 crop-x/src/app/(app)/central-config/tenant-management/enterprise-audit/components/EnterpriseBasicInfo.tsx delete mode 100644 crop-x/src/app/(app)/central-config/tenant-management/enterprise-audit/components/EnterpriseDetailDialog.tsx delete mode 100644 crop-x/src/app/(app)/central-config/tenant-management/enterprise-audit/components/EnterpriseLegalInfo.tsx delete mode 100644 crop-x/src/app/(app)/central-config/tenant-management/enterprise-audit/components/EnterpriseOtherInfo.tsx delete mode 100644 crop-x/src/app/(app)/central-config/tenant-management/enterprise-audit/components/index.ts delete mode 100644 crop-x/src/app/(app)/central-config/tenant-management/enterprise-info/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/tenant-management/layout.tsx delete mode 100644 crop-x/src/app/(app)/central-config/tenant-management/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/tenant-management/platform-user-management/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/tenant-management/tenant-authorization/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/tenant-management/tenant-configuration/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/tenant-management/tenant-creation/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryDetailDialog.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryFilters.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryInstructions.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryList.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryStatsCards.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/audit-history/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/audit-history/types.ts rename crop-x/src/app/(app)/central-config/{tenant-management/enterprise-audit/components/AuditStatsCard.tsx => tenant/enterprise-audit/components/AuditStatsCards.tsx} (69%) create mode 100644 crop-x/src/app/(app)/central-config/tenant/enterprise-audit/components/EnterpriseDetailDialog.tsx rename crop-x/src/app/(app)/central-config/{tenant-management/enterprise-audit/components/EnterpriseTable.tsx => tenant/enterprise-audit/components/EnterpriseList.tsx} (77%) rename crop-x/src/app/(app)/central-config/{tenant-management/enterprise-audit/components/SearchAndFilter.tsx => tenant/enterprise-audit/components/SearchFilters.tsx} (62%) rename crop-x/src/app/(app)/central-config/{tenant-management => tenant}/enterprise-audit/page.tsx (88%) create mode 100644 crop-x/src/app/(app)/central-config/tenant/enterprise-audit/types.ts create mode 100644 crop-x/src/app/(app)/central-config/tenant/enterprise-info/components/AuditStatusAlert.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/enterprise-info/components/BankInfoForm.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/enterprise-info/components/BasicInfoForm.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/enterprise-info/components/EnterpriseInfoHeader.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/enterprise-info/components/LegalInfoForm.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/enterprise-info/components/OperationTips.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/enterprise-info/components/OtherInfoForm.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/enterprise-info/components/SystemInfo.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/enterprise-info/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/enterprise-info/types.ts create mode 100644 crop-x/src/app/(app)/central-config/tenant/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/user-management/components/UserDetailDialog.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/user-management/components/UserFormDialog.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/user-management/components/UserList.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/user-management/components/UserManagementFilters.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/user-management/components/UserManagementHeader.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/user-management/components/UserManagementStatsCards.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/user-management/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/tenant/user-management/types.ts delete mode 100644 crop-x/src/app/(app)/central-config/user-management/employee-management/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/user-management/layout.tsx delete mode 100644 crop-x/src/app/(app)/central-config/user-management/menu-management/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/user-management/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/user-management/permission-config/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/user-management/role-management/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/user-management/role-permission-management/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/user-management/user-account-management/page.tsx delete mode 100644 crop-x/src/app/(app)/central-config/user-management/user-behavior-tracking/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/employee/components/EmployeeDetailDialog.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/employee/components/EmployeeFormDialog.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/employee/components/EmployeeList.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/employee/components/EmployeeManagementFilters.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/employee/components/EmployeeManagementHeader.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/employee/components/EmployeeManagementStatsCards.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/employee/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/employee/types.ts create mode 100644 crop-x/src/app/(app)/central-config/user/menu/components/MenuFormDialog.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/menu/components/MenuManagementHeader.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/menu/components/MenuManagementInstructions.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/menu/components/MenuManagementStatsCards.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/menu/components/MenuTree.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/menu/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/menu/types.ts create mode 100644 crop-x/src/app/(app)/central-config/user/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/permission/components/PermissionManagementHeader.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/permission/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/permission/types.ts create mode 100644 crop-x/src/app/(app)/central-config/user/role/components/RoleDetailDialog.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/role/components/RoleFormDialog.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/role/components/RoleList.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/role/components/RoleManagementHeader.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/role/components/RoleManagementInstructions.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/role/components/RoleManagementStatsCards.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/role/components/RoleSearch.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/role/page.tsx create mode 100644 crop-x/src/app/(app)/central-config/user/role/types.ts create mode 100644 crop-x/src/app/(app)/central-config/映射关系.md create mode 100644 crop-x/src/services/api/messageLogApi.ts create mode 100644 crop-x/src/types/auth.ts create mode 100644 crop-x/src/types/message.ts create mode 100644 crop-x/src/types/monitor.ts create mode 100644 crop-x/src/types/system-params.ts diff --git a/crop-x/src/app/(app)/central-config/layout.tsx b/crop-x/src/app/(app)/central-config/layout.tsx index 38f40b3..91ffe44 100644 --- a/crop-x/src/app/(app)/central-config/layout.tsx +++ b/crop-x/src/app/(app)/central-config/layout.tsx @@ -8,125 +8,125 @@ const centralConfigData = { navMain: [ { title: "租户管理", - url: "/central-config/tenant-management", + url: "/central-config/tenant", icon: "🏢", items: [ { title: "企业审核", - url: "/central-config/tenant-management/enterprise-audit", + url: "/central-config/tenant/enterprise-audit", isActive: false }, { title: "审核历史", - url: "/central-config/tenant-management/audit-history", + url: "/central-config/tenant/audit-history", isActive: false }, { title: "企业信息", - url: "/central-config/tenant-management/enterprise-info", + url: "/central-config/tenant/enterprise-info", isActive: false }, { - title: "平台用户管理", - url: "/central-config/tenant-management/platform-user-management", + title: "用户管理", + url: "/central-config/tenant/user-management", isActive: false } ] }, { title: "用户管理", - url: "/central-config/user-management", + url: "/central-config/user", icon: "👥", items: [ { title: "员工管理", - url: "/central-config/user-management/employee-management", + url: "/central-config/user/employee", isActive: false }, { title: "角色管理", - url: "/central-config/user-management/role-management", + url: "/central-config/user/role", isActive: false }, { title: "菜单管理", - url: "/central-config/user-management/menu-management", + url: "/central-config/user/menu", isActive: false }, { title: "权限配置管理", - url: "/central-config/user-management/permission-config", + url: "/central-config/user/permission", isActive: false } ] }, { title: "系统参数", - url: "/central-config/system-parameters", + url: "/central-config/system", icon: "🔧", items: [ { title: "系统设置", - url: "/central-config/system-parameters/system-settings", + url: "/central-config/system/settings", isActive: false }, { title: "分类字典", - url: "/central-config/system-parameters/category-dictionary", + url: "/central-config/system/category", isActive: false }, { title: "数据字典", - url: "/central-config/system-parameters/data-dictionary", + url: "/central-config/system/dictionary", isActive: false } ] }, { title: "系统监控", - url: "/central-config/system-monitor", + url: "/central-config/monitor", icon: "📈", items: [ { title: "登录日志", - url: "/central-config/system-monitor/login-log", + url: "/central-config/monitor/login-log", isActive: false }, { title: "操作日志", - url: "/central-config/system-monitor/operation-log", + url: "/central-config/monitor/operation-log", isActive: false }, { title: "性能监控", - url: "/central-config/system-monitor/performance-monitor", + url: "/central-config/monitor/performance", isActive: false }, { title: "网络日志", - url: "/central-config/system-monitor/network-log", + url: "/central-config/monitor/network-log", isActive: false } ] }, { title: "消息中心", - url: "/central-config/message-center", + url: "/central-config/message", icon: "📨", items: [ { title: "消息发送", - url: "/central-config/message-center/message-send", + url: "/central-config/message/send", isActive: false }, { title: "消息模版", - url: "/central-config/message-center/message-template", + url: "/central-config/message/template", isActive: false }, { title: "消息日志", - url: "/central-config/message-center/message-log", + url: "/central-config/message/log", isActive: false } ] diff --git a/crop-x/src/app/(app)/central-config/message-center/layout.tsx b/crop-x/src/app/(app)/central-config/message-center/layout.tsx deleted file mode 100644 index 1980cba..0000000 --- a/crop-x/src/app/(app)/central-config/message-center/layout.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { ReactNode } from 'react' - -export default function MessageCenterLayout({ - children, -}: { - children: ReactNode -}) { - return ( -
-
-
-

- 📨 消息中心管理 -

-
-
-
- {children} -
-
- ) -} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/message-center/message-log/page.tsx b/crop-x/src/app/(app)/central-config/message-center/message-log/page.tsx deleted file mode 100644 index febf53f..0000000 --- a/crop-x/src/app/(app)/central-config/message-center/message-log/page.tsx +++ /dev/null @@ -1,8 +0,0 @@ -export default function MessageLogPage() { - return ( -
-

消息日志

-

消息日志管理页面

-
- ) -} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/message-center/message-send/page.tsx b/crop-x/src/app/(app)/central-config/message-center/message-send/page.tsx deleted file mode 100644 index c34efcd..0000000 --- a/crop-x/src/app/(app)/central-config/message-center/message-send/page.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import { Metadata } from 'next' - -export const metadata: Metadata = { - title: '消息发送 - Crop-X 智慧农业管理系统', - description: '消息推送管理页面', -} - -export default function MessageSendPage() { - return ( -
-
-

- 📤 消息发送管理 -

- -
-
-

- 创建新消息 -

-
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
- - - -
-
- -
-
- -
-

- 最近发送记录 -

-
- {[ - { id: 'MSG001', title: '系统维护通知', recipients: '全体用户', time: '2024-10-20 14:30', status: '已发送' }, - { id: 'MSG002', title: '农机任务分配', recipients: '农机操作员', time: '2024-10-20 12:15', status: '已发送' }, - { id: 'MSG003', title: '天气预警', recipients: '农场管理员', time: '2024-10-20 09:45', status: '已发送' }, - { id: 'MSG004', title: '数据备份提醒', recipients: '系统管理员', time: '2024-10-19 23:00', status: '已发送' }, - ].map((message) => ( -
-
-
-

{message.title}

-

接收人: {message.recipients}

-

{message.time}

-
- - {message.status} - -
-
- ))} -
-
-
- -
-

- 📊 消息统计 -

-
-
-
156
-
今日发送
-
-
-
98.5%
-
发送成功率
-
-
-
1,234
-
总发送量
-
-
-
8
-
待发送
-
-
-
-
-
- ) -} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/message-center/message-template/page.tsx b/crop-x/src/app/(app)/central-config/message-center/message-template/page.tsx deleted file mode 100644 index 82524c0..0000000 --- a/crop-x/src/app/(app)/central-config/message-center/message-template/page.tsx +++ /dev/null @@ -1,8 +0,0 @@ -export default function MessageTemplatePage() { - return ( -
-

消息模版

-

消息模版管理页面

-
- ) -} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/message-center/page.tsx b/crop-x/src/app/(app)/central-config/message-center/page.tsx deleted file mode 100644 index d650ea4..0000000 --- a/crop-x/src/app/(app)/central-config/message-center/page.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import Link from 'next/link' -import { Metadata } from 'next' - -export const metadata: Metadata = { - title: '消息中心 - Crop-X 智慧农业管理系统', - description: '消息推送管理页面', -} - -export default function MessageCenterPage() { - return ( -
-
-

- 消息中心管理 -

-

- 管理消息推送、通知设置和反馈处理 -

- -
- -

- 📤 消息推送管理 -

-

- 创建和发送各类消息通知 -

- - - -

- 📨 消息发送 -

-

- 快速发送消息给指定用户 -

- - - -

- ⚙️ 通知设置管理 -

-

- 配置系统通知规则和模板 -

- - - -

- 💬 反馈管理 -

-

- 处理用户反馈和建议 -

- -
-
- -
-
-

- 📊 消息统计概览 -

-
-
- 今日发送消息 - 156 条 -
-
- 待处理反馈 - 23 条 -
-
- 活跃通知规则 - 8 个 -
-
-
- -
-

- 🔧 快速操作 -

-
- - - -
-
-
-
- ) -} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/message/log/components/MessageLogFilter.tsx b/crop-x/src/app/(app)/central-config/message/log/components/MessageLogFilter.tsx new file mode 100644 index 0000000..eef4dc3 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/message/log/components/MessageLogFilter.tsx @@ -0,0 +1,64 @@ +'use client'; + +import { Card } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Search } from 'lucide-react'; + +interface MessageLogFilterProps { + searchKeyword: string; + typeFilter: string; + statusFilter: string; + onSearchChange: (value: string) => void; + onTypeChange: (value: string) => void; + onStatusChange: (value: string) => void; +} + +export function MessageLogFilter({ + searchKeyword, + typeFilter, + statusFilter, + onSearchChange, + onTypeChange, + onStatusChange, +}: MessageLogFilterProps) { + return ( + +
+
+ + onSearchChange(e.target.value)} + className="pl-10" + /> +
+ + +
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/message/log/components/MessageLogStats.tsx b/crop-x/src/app/(app)/central-config/message/log/components/MessageLogStats.tsx new file mode 100644 index 0000000..db6c6d2 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/message/log/components/MessageLogStats.tsx @@ -0,0 +1,44 @@ +'use client'; + +import { Card } from '@/components/ui/card'; +import { MessageLog } from '@/types/message'; + +interface MessageLogStatsProps { + logs: MessageLog[]; +} + +export function MessageLogStats({ logs }: MessageLogStatsProps) { + const stats = [ + { + label: '总消息数', + value: logs.length, + color: 'text-blue-600', + }, + { + label: '已发送', + value: logs.filter(l => l.status === 'sent' || l.status === 'read').length, + color: 'text-green-600', + }, + { + label: '已读', + value: logs.filter(l => l.status === 'read').length, + color: 'text-purple-600', + }, + { + label: '发送失败', + value: logs.filter(l => l.status === 'failed').length, + color: 'text-red-600', + }, + ]; + + return ( +
+ {stats.map((stat, index) => ( + +
{stat.label}
+
{stat.value}
+
+ ))} +
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/message/log/components/MessageLogTable.tsx b/crop-x/src/app/(app)/central-config/message/log/components/MessageLogTable.tsx new file mode 100644 index 0000000..089d261 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/message/log/components/MessageLogTable.tsx @@ -0,0 +1,152 @@ +'use client'; + +import { Card } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; +import { MessageLog } from '@/types/message'; +import { + Mail, + MessageSquare, + Smartphone, + Bell, + CheckCircle, + XCircle, + Clock +} from 'lucide-react'; + +interface MessageLogTableProps { + logs: MessageLog[]; +} + +export function MessageLogTable({ logs }: MessageLogTableProps) { + const getTypeIcon = (type: string) => { + switch (type) { + case 'sms': return ; + case 'email': return ; + case 'internal': return ; + case 'push': return ; + default: return ; + } + }; + + const getTypeLabel = (type: string) => { + const labels: Record = { + sms: '短信', + email: '邮件', + internal: '站内信', + push: '推送', + }; + return labels[type] || type; + }; + + const getTypeBadge = (type: string) => { + const colors: Record = { + sms: 'bg-blue-100 text-blue-700', + email: 'bg-purple-100 text-purple-700', + internal: 'bg-green-100 text-green-700', + push: 'bg-orange-100 text-orange-700', + }; + return colors[type] || 'bg-gray-100 text-gray-700'; + }; + + const getStatusBadge = (status: string) => { + switch (status) { + case 'sent': + return { icon: , label: '已发送', className: 'bg-green-100 text-green-700' }; + case 'read': + return { icon: , label: '已读', className: 'bg-blue-100 text-blue-700' }; + case 'failed': + return { icon: , label: '失败', className: 'bg-red-100 text-red-700' }; + case 'pending': + return { icon: , label: '待发送', className: 'bg-yellow-100 text-yellow-700' }; + default: + return { icon: , label: status, className: 'bg-gray-100 text-gray-700' }; + } + }; + + const formatDateTime = (dateTime: string | undefined) => { + if (!dateTime) return '-'; + return dateTime; + }; + + return ( + + + + + 发送时间 + 类型 + 接收人 + 主题/内容 + 状态 + 重试次数 + + + + {logs.length === 0 ? ( + + + 暂无消息日志 + + + ) : ( + logs.map((log) => { + const statusBadge = getStatusBadge(log.status); + // 调试信息 + console.log('Log data:', log); + console.log('Type:', log.type, 'Badge color:', getTypeBadge(log.type)); + return ( + + + {formatDateTime(log.sentTime)} + + +
+ {getTypeIcon(log.type)} + {getTypeLabel(log.type)} +
+
+ +
+
{log.recipientName || '-'}
+
{log.recipient}
+
+
+ + {log.subject && ( +
{log.subject}
+ )} +
+ {log.content} +
+
+ +
+
+ {statusBadge.icon} + {statusBadge.label} +
+ {log.status === 'read' && log.readTime && ( +

+ {formatDateTime(log.readTime)} +

+ )} + {log.status === 'failed' && log.failReason && ( +

{log.failReason}

+ )} +
+
+ +
+ {log.retryCount} +
+
+
+ ); + }) + )} +
+
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/message/log/mock/mockData.ts b/crop-x/src/app/(app)/central-config/message/log/mock/mockData.ts new file mode 100644 index 0000000..5da6994 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/message/log/mock/mockData.ts @@ -0,0 +1,115 @@ +import { MessageLog } from '@/types/message'; + +export const mockMessageLogs: MessageLog[] = [ + { + id: 'msg-1', + templateId: 'tpl-1', + templateName: '任务分配通知', + type: 'internal', + recipient: 'user-2', + recipientName: '张三', + subject: '新任务分配', + content: '您好,张三!您有新的作业任务:小麦播种作业,计划执行时间:2024-10-15 08:00。请及时查看并准备。', + status: 'sent', + sentTime: '2024-10-14 09:30:00', + readTime: '2024-10-14 10:15:00', + retryCount: 0, + variables: { + username: '张三', + taskName: '小麦播种作业', + executeTime: '2024-10-15 08:00', + }, + }, + { + id: 'msg-2', + templateId: 'tpl-2', + templateName: '设备预警通知', + type: 'sms', + recipient: '13800138000', + recipientName: '李四', + content: '【智慧农业】设备预警:约翰迪尔拖拉机检测到异常,发动机温度过高,请及时处理。', + status: 'sent', + sentTime: '2024-10-14 09:30:00', + retryCount: 0, + }, + { + id: 'msg-3', + templateId: 'tpl-3', + templateName: '保养提醒', + type: 'email', + recipient: 'wangwu@example.com', + recipientName: '王五', + subject: '设备保养提醒', + content: '尊敬的用户:\n\n您的设备约翰迪尔拖拉机(编号:JD-001)已使用500小时,建议进行保养维护...', + status: 'sent', + sentTime: '2024-10-14 09:30:00', + retryCount: 0, + }, + { + id: 'msg-4', + templateId: 'tpl-4', + templateName: '任务完成通知', + type: 'push', + recipient: 'user-2', + recipientName: '张三', + subject: '任务完成', + content: '作业任务小麦播种作业已完成,作业面积:50亩,耗时:3小时。', + status: 'read', + sentTime: '2024-10-14 09:30:00', + readTime: '2024-10-14 10:15:00', + retryCount: 0, + }, + { + id: 'msg-5', + templateId: 'tpl-5', + templateName: '验证码', + type: 'sms', + recipient: '13900139000', + recipientName: '赵六', + content: '【智慧农业】验证码:123456,有效期5分钟。', + status: 'failed', + sentTime: '2024-10-14 09:30:00', + failReason: '手机号码格式错误', + retryCount: 2, + }, + { + id: 'msg-6', + templateId: 'tpl-6', + templateName: '系统维护通知', + type: 'internal', + recipient: 'user-3', + recipientName: '钱七', + subject: '系统维护通知', + content: '系统将于今晚22:00-24:00进行维护,期间可能影响部分功能使用。', + status: 'pending', + sentTime: '2024-10-15 14:20:00', + retryCount: 0, + }, + { + id: 'msg-7', + templateId: 'tpl-7', + templateName: '天气预警', + type: 'push', + recipient: 'user-4', + recipientName: '孙八', + subject: '天气预警', + content: '未来24小时将有暴雨,请注意防范,做好农田排水工作。', + status: 'sent', + sentTime: '2024-10-15 14:20:00', + retryCount: 0, + }, + { + id: 'msg-8', + templateId: 'tpl-8', + templateName: '作业报告', + type: 'email', + recipient: 'manager@example.com', + recipientName: '管理员', + subject: '每日作业报告', + content: '今日完成播种作业100亩,施肥作业50亩,灌溉作业200亩。', + status: 'read', + sentTime: '2024-10-15 14:20:00', + readTime: '2024-10-15 15:30:00', + retryCount: 0, + } +]; \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/message/log/page.tsx b/crop-x/src/app/(app)/central-config/message/log/page.tsx new file mode 100644 index 0000000..5af17b8 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/message/log/page.tsx @@ -0,0 +1,139 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { toast } from 'sonner'; +import { Button } from '@/components/ui/button'; +import { Card } from '@/components/ui/card'; +import { Download, MessageSquare, RefreshCw } from 'lucide-react'; +import { MessageLogStats } from './components/MessageLogStats'; +import { MessageLogFilter } from './components/MessageLogFilter'; +import { MessageLogTable } from './components/MessageLogTable'; +import { MessageLog } from '@/types/message'; +import { mockMessageLogs } from './mock/mockData'; + +export default function MessageLogPage() { + const [logs, setLogs] = useState([]); + const [filteredLogs, setFilteredLogs] = useState([]); + const [searchKeyword, setSearchKeyword] = useState(''); + const [typeFilter, setTypeFilter] = useState('all'); + const [statusFilter, setStatusFilter] = useState('all'); + const [loading, setLoading] = useState(false); + + useEffect(() => { + loadLogs(); + }, []); + + useEffect(() => { + applyFilters(); + }, [logs, searchKeyword, typeFilter, statusFilter]); + + const loadLogs = async () => { + setLoading(true); + try { + // 模拟API延迟 + await new Promise(resolve => setTimeout(resolve, 500)); + setLogs(mockMessageLogs); + } catch (error) { + console.error('Failed to load message logs:', error); + toast.error('加载消息日志失败'); + } finally { + setLoading(false); + } + }; + + const applyFilters = () => { + let filtered = logs; + + // 调试信息 + console.log('All logs:', logs); + console.log('Filters:', { searchKeyword, typeFilter, statusFilter }); + + if (searchKeyword) { + filtered = filtered.filter(log => + (log.recipientName && log.recipientName.includes(searchKeyword)) || + log.recipient.includes(searchKeyword) || + log.content.includes(searchKeyword) + ); + } + + if (typeFilter !== 'all') { + filtered = filtered.filter(log => log.type === typeFilter); + } + + if (statusFilter !== 'all') { + filtered = filtered.filter(log => log.status === statusFilter); + } + + filtered.sort((a, b) => b.sentTime.localeCompare(a.sentTime)); + + setFilteredLogs(filtered); + }; + + const handleExport = async () => { + try { + const dataStr = JSON.stringify(filteredLogs, null, 2); + const dataBlob = new Blob([dataStr], { type: 'application/json' }); + const url = URL.createObjectURL(dataBlob); + const link = document.createElement('a'); + link.href = url; + link.download = `message_logs_${new Date().getTime()}.json`; + link.click(); + toast.success('导出成功'); + } catch (error) { + console.error('Failed to export logs:', error); + toast.error('导出失败'); + } + }; + + const handleRefresh = () => { + loadLogs(); + }; + + return ( +
+
+
+

消息日志

+

完整记录所有通过系统发送的消息流水

+
+
+ + +
+
+ + + + + + + + +

+ + 消息日志说明 +

+
    +
  • • 完整记录所有通过系统发送的消息,包括短信、邮件、站内信和推送
  • +
  • • 支持按条件搜索历史消息,便于跟踪消息触达情况
  • +
  • • 失败消息会记录失败原因,便于排查发送异常
  • +
  • • 站内信和推送消息会记录阅读时间
  • +
  • • 重试次数用于标识消息的发送尝试次数
  • +
+
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/message/page.tsx b/crop-x/src/app/(app)/central-config/message/page.tsx new file mode 100644 index 0000000..3f85955 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/message/page.tsx @@ -0,0 +1,26 @@ +'use client'; + +import React from 'react'; +import Link from 'next/link'; + +export default function MessagePage() { + return ( +
+

消息中心

+
+ +

消息发送

+

发送系统消息

+ + +

消息模版

+

管理消息模版

+ + +

消息日志

+

查看消息发送记录

+ +
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/message/send/components/MessageInstructions.tsx b/crop-x/src/app/(app)/central-config/message/send/components/MessageInstructions.tsx new file mode 100644 index 0000000..3798cc4 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/message/send/components/MessageInstructions.tsx @@ -0,0 +1,17 @@ +import { Card } from '@/components/ui/card'; + +export function MessageInstructions() { + return ( + +

消息发送说明

+
    +
  • • 支持发送短信、邮件、站内信、推送四种类型的消息
  • +
  • • 实时发送:消息立即发送给接收人
  • +
  • • 定时发送:设定未来的日期和时间,系统到时自动发送
  • +
  • • 可以使用消息模版,自动填充变量生成个性化内容
  • +
  • • 支持批量发送,一次可向多个接收人发送相同消息
  • +
  • • 定时消息在未发送前可以取消,已发送的消息可以删除记录
  • +
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/message/send/components/MessagePreviewDialog.tsx b/crop-x/src/app/(app)/central-config/message/send/components/MessagePreviewDialog.tsx new file mode 100644 index 0000000..589f969 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/message/send/components/MessagePreviewDialog.tsx @@ -0,0 +1,122 @@ +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Label } from '@/components/ui/label'; +import { Card } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { format } from 'date-fns'; +import { zhCN } from 'date-fns/locale'; +import { MessageSendRecord } from '@/types/message'; + +interface MessagePreviewDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + record: MessageSendRecord | null; + getTypeIcon: (type: string) => JSX.Element; + getTypeLabel: (type: string) => string; + getTypeBadge: (type: string) => string; + getStatusBadge: (status: string) => JSX.Element; +} + +export function MessagePreviewDialog({ + open, + onOpenChange, + record, + getTypeIcon, + getTypeLabel, + getTypeBadge, + getStatusBadge +}: MessagePreviewDialogProps) { + if (!record) return null; + + return ( + + + + 消息详情 + + 查看消息发送详情 + + +
+
+
+ +
{record.templateName}
+
+
+ +
+ + + {getTypeIcon(record.type)} + {getTypeLabel(record.type)} + + +
+
+
+ +
+ {record.sendType === 'immediate' ? '实时发送' : '定时发送'} +
+
+
+ +
+ {getStatusBadge(record.status)} +
+
+ {record.scheduledTime && ( +
+ +
+ {format(new Date(record.scheduledTime), 'yyyy-MM-dd HH:mm', { locale: zhCN })} +
+
+ )} +
+ +
+ {format(new Date(record.createdAt), 'yyyy-MM-dd HH:mm', { locale: zhCN })} +
+
+
+ + {record.subject && ( +
+ +
{record.subject}
+
+ )} + +
+ + +
+ {record.recipients.map((recipient, index) => ( + + {recipient} + + ))} +
+
+
+ +
+ + +
+                {record.content}
+              
+
+
+
+ + + +
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/message/send/components/MessageSendTable.tsx b/crop-x/src/app/(app)/central-config/message/send/components/MessageSendTable.tsx new file mode 100644 index 0000000..3599d97 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/message/send/components/MessageSendTable.tsx @@ -0,0 +1,154 @@ +import { Card } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; +import { + Send, + Clock, + Users, + Eye, + Trash2, + CheckCircle2, + XCircle, + Timer +} from 'lucide-react'; +import { format } from 'date-fns'; +import { zhCN } from 'date-fns/locale'; +import { MessageSendRecord } from '@/types/message'; + +interface MessageSendTableProps { + sendRecords: MessageSendRecord[]; + onPreview: (record: MessageSendRecord) => void; + onCancel: (id: string) => void; + onDelete: (id: string) => void; + getTypeIcon: (type: string) => JSX.Element; + getTypeLabel: (type: string) => string; + getTypeBadge: (type: string) => string; + getStatusBadge: (status: string) => JSX.Element; +} + +export function MessageSendTable({ + sendRecords, + onPreview, + onCancel, + onDelete, + getTypeIcon, + getTypeLabel, + getTypeBadge, + getStatusBadge +}: MessageSendTableProps) { + return ( + + + + + 消息模版 + 类型 + 接收人数 + 发送方式 + 状态 + 创建时间 + 操作 + + + + {sendRecords.length === 0 ? ( + + + 暂无发送记录 + + + ) : ( + sendRecords.map((record) => ( + + +
{record.templateName}
+ {record.subject && ( +

{record.subject}

+ )} +
+ + + + {getTypeIcon(record.type)} + {getTypeLabel(record.type)} + + + + +
+ + {record.recipientCount} +
+
+ + {record.sendType === 'immediate' ? ( + + + 实时发送 + + ) : ( +
+ + + 定时发送 + + {record.scheduledTime && ( +

+ {format(new Date(record.scheduledTime), 'MM-dd HH:mm', { locale: zhCN })} +

+ )} +
+ )} +
+ + {getStatusBadge(record.status)} + {record.status === 'sent' && ( +

+ 成功 {record.sentCount}/{record.recipientCount} +

+ )} +
+ + {format(new Date(record.createdAt), 'MM-dd HH:mm', { locale: zhCN })} + + +
+ + {record.status === 'pending' && ( + + )} + {(record.status === 'sent' || record.status === 'cancelled') && ( + + )} +
+
+
+ )) + )} +
+
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/message/send/components/MessageStatsCards.tsx b/crop-x/src/app/(app)/central-config/message/send/components/MessageStatsCards.tsx new file mode 100644 index 0000000..f8783b7 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/message/send/components/MessageStatsCards.tsx @@ -0,0 +1,42 @@ +import { Card } from '@/components/ui/card'; +import { MessageSendRecord } from '@/types/message'; + +interface MessageStatsCardsProps { + sendRecords: MessageSendRecord[]; +} + +export function MessageStatsCards({ sendRecords }: MessageStatsCardsProps) { + const stats = [ + { + label: '总发送数', + value: sendRecords.length, + color: 'text-blue-600', + }, + { + label: '已发送', + value: sendRecords.filter(r => r.status === 'sent').length, + color: 'text-green-600', + }, + { + label: '待发送', + value: sendRecords.filter(r => r.status === 'pending').length, + color: 'text-yellow-600', + }, + { + label: '已取消', + value: sendRecords.filter(r => r.status === 'cancelled').length, + color: 'text-gray-600', + }, + ]; + + return ( +
+ {stats.map((stat, index) => ( + +
{stat.label}
+
{stat.value}
+
+ ))} +
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/message/send/components/SendMessageDialog.tsx b/crop-x/src/app/(app)/central-config/message/send/components/SendMessageDialog.tsx new file mode 100644 index 0000000..d2195ca --- /dev/null +++ b/crop-x/src/app/(app)/central-config/message/send/components/SendMessageDialog.tsx @@ -0,0 +1,258 @@ +import { useState } from 'react'; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Card } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Calendar } from '@/components/ui/calendar'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; +import { Send, Clock, CalendarIcon } from 'lucide-react'; +import { format } from 'date-fns'; +import { zhCN } from 'date-fns/locale'; +import { MessageTemplate } from '@/types/message'; +import { MessageSendFormData } from '../types'; + +interface SendMessageDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + templates: MessageTemplate[]; + formData: MessageSendFormData; + onFormDataChange: (data: MessageSendFormData) => void; + onSend: () => void; + getTypeIcon: (type: string) => JSX.Element; + getTypeLabel: (type: string) => string; +} + +export function SendMessageDialog({ + open, + onOpenChange, + templates, + formData, + onFormDataChange, + onSend, + getTypeIcon, + getTypeLabel +}: SendMessageDialogProps) { + const replaceVariables = (content: string, variables: Record): string => { + let result = content; + Object.entries(variables).forEach(([key, value]) => { + result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value || `{{${key}}}`); + }); + return result; + }; + + const handleTemplateChange = (templateId: string) => { + const template = templates.find(t => t.id === templateId); + if (template) { + // 初始化变量 + const vars: Record = {}; + template.variables.forEach(v => { + vars[v] = ''; + }); + + onFormDataChange({ + ...formData, + templateId, + type: template.type, + subject: template.subject || '', + content: template.content, + variables: vars, + }); + } + }; + + const selectedTemplate = templates.find(t => t.id === formData.templateId); + + return ( + + + + +
+ + 发送消息 +
+
+ + 选择消息模版并发送消息 + +
+
+ {/* 选择模版 */} +
+ + +
+ + {/* 发送方式 */} +
+ + +
+ + {/* 定时发送设置 */} + {formData.sendType === 'scheduled' && ( +
+
+ + + + + + + onFormDataChange({ ...formData, scheduledDate: date })} + locale={zhCN} + disabled={(date) => date < new Date(new Date().setHours(0, 0, 0, 0))} + /> + + +
+
+ + onFormDataChange({ ...formData, scheduledTime: e.target.value })} + /> +
+
+ )} + + {/* 接收人 */} +
+ + -
-
- - -
-

- 📊 权限统计 -

-
-
- 已启用模块 - 4 / 7 -
-
- 功能权限 - 18 / 28 -
-
- API权限 - 已授权 -
-
- 最后更新 - 2024-10-20 15:30 -
-
-
- -
-

- ⚡ 快速操作 -

-
- - - -
-
- - - -
-

- 📋 权限变更记录 -

-
- - - - - - - - - - - {[ - { time: '2024-10-20 15:30', type: '模块授权', content: '启用智能农机管理模块', operator: 'admin' }, - { time: '2024-10-20 14:15', type: '权限调整', content: '关闭农业资产管理模块', operator: 'admin' }, - { time: '2024-10-20 10:45', type: 'API配置', content: '重置API密钥', operator: 'admin' }, - { time: '2024-10-19 16:20', type: '权限调整', content: '启用水肥控制模块', operator: 'admin' }, - ].map((record, index) => ( - - - - - - - ))} - -
- 时间 - - 操作类型 - - 操作内容 - - 操作人 -
- {record.time} - - - {record.type} - - - {record.content} - - {record.operator} -
-
-
- -
- - -
- - - ) -} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/tenant-management/tenant-configuration/page.tsx b/crop-x/src/app/(app)/central-config/tenant-management/tenant-configuration/page.tsx deleted file mode 100644 index 5600fdf..0000000 --- a/crop-x/src/app/(app)/central-config/tenant-management/tenant-configuration/page.tsx +++ /dev/null @@ -1,287 +0,0 @@ -import { Metadata } from 'next' - -export const metadata: Metadata = { - title: '租户配置 - Crop-X 智慧农业管理系统', - description: '租户配置管理页面', -} - -export default function TenantConfigurationPage() { - return ( -
-
-

- ⚙️ 租户配置管理 -

- -
-
- - -
-
- -
-
-
-

- 基本信息配置 -

-
-
- - -
-
- -
-
- Logo -
- - -
-
-
- - -
-
- - -
-
- - -
-
-
- -
-

- 业务配置 -

-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
- -
-
-

- 系统配置 -

-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- -
-

- 通知配置 -

-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
-
- -
- - -
-
-
- ) -} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/tenant-management/tenant-creation/page.tsx b/crop-x/src/app/(app)/central-config/tenant-management/tenant-creation/page.tsx deleted file mode 100644 index bcde4b3..0000000 --- a/crop-x/src/app/(app)/central-config/tenant-management/tenant-creation/page.tsx +++ /dev/null @@ -1,221 +0,0 @@ -import { Metadata } from 'next' - -export const metadata: Metadata = { - title: '租户创建 - Crop-X 智慧农业管理系统', - description: '租户创建管理页面', -} - -export default function TenantCreationPage() { - return ( -
-
-

- ➕ 租户创建管理 -

- -
-
-

- 创建新租户 -

-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
- -
-
-

- 📋 创建说明 -

-
-
-
-

租户名称必须为中文,长度2-50个字符

-
-
-
-

租户代码必须为英文,长度3-20个字符,只能包含字母和数字

-
-
-
-

联系电话必须是有效的手机号码或座机号码

-
-
-
-

电子邮箱必须是有效的邮箱地址格式

-
-
-
-

创建成功后,系统将自动向管理员邮箱发送登录信息

-
-
-
- -
-

- ⚡ 快速操作 -

-
- - - -
-
- -
-

- 📈 今日统计 -

-
-
- 今日创建租户 - 3 个 -
-
- 待审核租户 - 2 个 -
-
- 已通过租户 - 1 个 -
-
- 创建成功率 - 100% -
-
-
-
-
-
-
- ) -} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryDetailDialog.tsx b/crop-x/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryDetailDialog.tsx new file mode 100644 index 0000000..6a683be --- /dev/null +++ b/crop-x/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryDetailDialog.tsx @@ -0,0 +1,352 @@ +'use client'; + +import React from 'react'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Label } from '@/components/ui/label'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { Card } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { FileText, Building, CreditCard, User } from 'lucide-react'; +import { AuditRecord, Enterprise, AuditStatus } from '../types'; + +interface AuditHistoryDetailDialogProps { + record: AuditRecord | null; + enterprise: Enterprise | null; + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export function AuditHistoryDetailDialog({ + record, + enterprise, + open, + onOpenChange +}: AuditHistoryDetailDialogProps) { + const getResultBadge = (result: AuditStatus) => { + switch (result) { + case 'pending': + return 待审核; + case 'approved': + return 已通过; + case 'rejected': + return 已驳回; + default: + return {result}; + } + }; + + const getTypeBadge = (type: string) => { + switch (type) { + case 'register': + return 注册审核; + case 'update': + return 变更审核; + default: + return {type}; + } + }; + + if (!record || !enterprise) return null; + + return ( + + + + +
+
+ + 审核记录详情 +
+
+ {getResultBadge(record.result)} +
+
+
+ + 查看企业审核的历史记录详情 + +
+ + +
+ {/* 企业信息标签页 */} + + + + + 基本信息 + + + + 其他信息 + + + + 开户信息 + + + + 法人信息 + + + + {/* 基本信息 */} + +
+
+ +
{enterprise.name}
+
+
+ +
{enterprise.type}
+
+
+ +
+ {enterprise.province} {enterprise.city} {enterprise.district} +
+
+
+ +
{enterprise.address}
+
+
+ +
{enterprise.registrant}
+
+
+ +
{enterprise.contactPhone}
+
+
+
+ + {/* 其他信息 */} + +
+
+ +
{enterprise.companySize || '-'}
+
+
+ +
{enterprise.registeredCapital || '-'}
+
+
+ +
{enterprise.establishmentDate || '-'}
+
+
+ +
{enterprise.invoiceType || '-'}
+
+
+ +
+ + {enterprise.socialCreditCode} + +
+
+
+ +
{enterprise.businessScope || '-'}
+
+
+ +
+ {enterprise.businessLicense ? ( + 营业执照 + ) : ( + 未上传 + )} +
+
+
+
+ + {/* 开户信息 */} + +
+
+ +
+ {enterprise.bankAccount ? ( + + {enterprise.bankAccount} + + ) : '-'} +
+
+
+ +
{enterprise.bankName || '-'}
+
+
+ +
{enterprise.bankFullName || '-'}
+
+
+ +
{enterprise.bankAddress || '-'}
+
+
+ +
+ {enterprise.bankLicense ? ( + 开户许可证 + ) : ( + 未上传 + )} +
+
+
+
+ + {/* 法人信息 */} + +
+
+ +
{enterprise.legalPerson || '-'}
+
+
+ +
+ {enterprise.idCardFront ? ( + 身份证正面 + ) : ( + 未上传 + )} +
+
+
+ +
+ {enterprise.idCardBack ? ( + 身份证反面 + ) : ( + 未上传 + )} +
+
+
+
+
+ + {/* 审核信息 */} +
+

审核信息

+ +
+
+ +
+ {getTypeBadge(record.auditType)} +
+
+
+ +
+ {new Date(record.submitTime).toLocaleString('zh-CN')} +
+
+ {record.auditTime && ( +
+ +
+ {new Date(record.auditTime).toLocaleString('zh-CN')} +
+
+ )} + {record.auditor && ( +
+ +
+ {record.auditor} +
+
+ )} + {record.reason && ( +
+ +
+ {record.reason} +
+
+ )} + {record.remarks && ( +
+ +
+ {record.remarks} +
+
+ )} +
+
+
+ + {/* 审核流程时间线 */} +
+

审核流程

+
+
+
+
+
提交审核
+
+ {new Date(record.submitTime).toLocaleString('zh-CN')} +
+
+
+ {record.auditTime && ( +
+
+
+
+ {record.result === 'approved' ? '审核通过' : '审核驳回'} +
+
+ {new Date(record.auditTime).toLocaleString('zh-CN')} + {record.auditor && ` · ${record.auditor}`} +
+
+
+ )} +
+
+
+ + + + + + +
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryFilters.tsx b/crop-x/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryFilters.tsx new file mode 100644 index 0000000..277f554 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryFilters.tsx @@ -0,0 +1,84 @@ +'use client'; + +import React from 'react'; +import { Card } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Search } from 'lucide-react'; +import { FilterOptions } from '../types'; + +interface AuditHistoryFiltersProps { + filters: FilterOptions; + onFiltersChange: (filters: FilterOptions) => void; +} + +export function AuditHistoryFilters({ filters, onFiltersChange }: AuditHistoryFiltersProps) { + const updateFilter = (key: keyof FilterOptions, value: string) => { + onFiltersChange({ + ...filters, + [key]: value + }); + }; + + return ( + +
+
+
+ + updateFilter('searchKeyword', e.target.value)} + className="pl-10" + /> +
+
+ + +
+
+ +
+ {[ + { value: 'all', label: '全部' }, + { value: 'today', label: '今天' }, + { value: 'week', label: '近7天' }, + { value: 'month', label: '近30天' }, + { value: 'quarter', label: '近90天' }, + ].map((option) => ( + + ))} +
+
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryInstructions.tsx b/crop-x/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryInstructions.tsx new file mode 100644 index 0000000..0f9d20a --- /dev/null +++ b/crop-x/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryInstructions.tsx @@ -0,0 +1,23 @@ +'use client'; + +import React from 'react'; +import { Card } from '@/components/ui/card'; +import { Calendar } from 'lucide-react'; + +export function AuditHistoryInstructions() { + return ( + +

+ + 审核历史说明 +

+
    +
  • • 完整记录所有企业的注册和变更审核历史
  • +
  • • 支持按企业名称、审核时间、审核结果等多维度筛选
  • +
  • • 包含审核人员、审核时间、审核结果及备注等完整信息
  • +
  • • 提供审核流程时间线,清晰展示审核进度
  • +
  • • 支持导出审核记录,满足审计和稽查需求
  • +
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryList.tsx b/crop-x/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryList.tsx new file mode 100644 index 0000000..9d96356 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryList.tsx @@ -0,0 +1,93 @@ +'use client'; + +import React from 'react'; +import { Card } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; +import { Eye } from 'lucide-react'; +import { AuditRecord, AuditStatus } from '../types'; + +interface AuditHistoryListProps { + records: AuditRecord[]; + onViewDetail: (record: AuditRecord) => void; +} + +export function AuditHistoryList({ records, onViewDetail }: AuditHistoryListProps) { + const getResultBadge = (result: AuditStatus) => { + switch (result) { + case 'pending': + return 待审核; + case 'approved': + return 已通过; + case 'rejected': + return 已驳回; + default: + return {result}; + } + }; + + const getTypeBadge = (type: string) => { + switch (type) { + case 'register': + return 注册审核; + case 'update': + return 变更审核; + default: + return {type}; + } + }; + + return ( + + + + + 企业名称 + 审核类型 + 提交时间 + 审核时间 + 审核人 + 审核结果 + 操作 + + + + {records.length === 0 ? ( + + + 暂无审核记录 + + + ) : ( + records.map((record) => ( + + {record.enterpriseName} + {getTypeBadge(record.auditType)} + + {new Date(record.submitTime).toLocaleString('zh-CN')} + + + {record.auditTime + ? new Date(record.auditTime).toLocaleString('zh-CN') + : '-'} + + {record.auditor || '-'} + {getResultBadge(record.result)} + + + + + )) + )} + +
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryStatsCards.tsx b/crop-x/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryStatsCards.tsx new file mode 100644 index 0000000..2bad767 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/tenant/audit-history/components/AuditHistoryStatsCards.tsx @@ -0,0 +1,49 @@ +'use client'; + +import React from 'react'; +import { Card } from '@/components/ui/card'; +import { AuditHistoryStats, AuditRecord } from '../types'; + +interface AuditHistoryStatsCardsProps { + records: AuditRecord[]; +} + +export function AuditHistoryStatsCards({ records }: AuditHistoryStatsCardsProps) { + const stats: AuditHistoryStats[] = [ + { + label: '总审核数', + value: records.length, + color: 'text-blue-600', + bg: 'bg-blue-100', + }, + { + label: '已通过', + value: records.filter(r => r.result === 'approved').length, + color: 'text-green-600', + bg: 'bg-green-100', + }, + { + label: '已驳回', + value: records.filter(r => r.result === 'rejected').length, + color: 'text-red-600', + bg: 'bg-red-100', + }, + { + label: '待审核', + value: records.filter(r => r.result === 'pending').length, + color: 'text-yellow-600', + bg: 'bg-yellow-100', + }, + ]; + + return ( +
+ {stats.map((stat, index) => ( + +
{stat.label}
+
{stat.value}
+
+ ))} +
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/tenant/audit-history/page.tsx b/crop-x/src/app/(app)/central-config/tenant/audit-history/page.tsx new file mode 100644 index 0000000..193e8a2 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/tenant/audit-history/page.tsx @@ -0,0 +1,211 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { toast } from 'sonner'; +import { Button } from '@/components/ui/button'; +import { Download } from 'lucide-react'; +import { AuditHistoryStatsCards } from './components/AuditHistoryStatsCards'; +import { AuditHistoryFilters } from './components/AuditHistoryFilters'; +import { AuditHistoryList } from './components/AuditHistoryList'; +import { AuditHistoryDetailDialog } from './components/AuditHistoryDetailDialog'; +import { AuditHistoryInstructions } from './components/AuditHistoryInstructions'; +import { AuditRecord, Enterprise, FilterOptions } from './types'; + +export default function AuditHistoryPage() { + const [records, setRecords] = useState([]); + const [enterprises, setEnterprises] = useState([]); + const [showDetailDialog, setShowDetailDialog] = useState(false); + const [selectedRecord, setSelectedRecord] = useState(null); + const [selectedEnterprise, setSelectedEnterprise] = useState(null); + + const [filters, setFilters] = useState({ + searchKeyword: '', + resultFilter: 'all', + typeFilter: 'all', + dateRange: 'all' + }); + + useEffect(() => { + loadEnterprises(); + loadAuditHistory(); + }, []); + + const loadEnterprises = () => { + const data = localStorage.getItem('smart_agriculture_enterprises'); + if (data) { + setEnterprises(JSON.parse(data)); + } + }; + + const loadAuditHistory = () => { + const data = localStorage.getItem('smart_agriculture_audit_records'); + if (data) { + setRecords(JSON.parse(data)); + } else { + // 初始化审核历史数据 + const mockRecords: AuditRecord[] = [ + { + id: 'audit-1', + enterpriseId: 'ent-2', + enterpriseName: '丰收现代农业集团', + auditType: 'register', + submitTime: '2024-10-05T10:00:00', + auditTime: '2024-10-08T14:30:00', + auditor: '系统管理员', + result: 'approved', + remarks: '企业资质完整,审核通过', + }, + { + id: 'audit-2', + enterpriseId: 'ent-3', + enterpriseName: '金穗农机服务中心', + auditType: 'register', + submitTime: '2024-10-06T09:00:00', + auditTime: '2024-10-09T16:00:00', + auditor: '系统管理员', + result: 'rejected', + reason: '资质材料不完整,请补充营业执照副本', + remarks: '缺少必要的资质证明文件', + }, + { + id: 'audit-3', + enterpriseId: 'ent-1', + enterpriseName: '绿野农业科技有限公司', + auditType: 'register', + submitTime: '2024-10-10T08:00:00', + result: 'pending', + }, + { + id: 'audit-4', + enterpriseId: 'ent-2', + enterpriseName: '丰收现代农业集团', + auditType: 'update', + submitTime: '2024-10-12T15:30:00', + auditTime: '2024-10-13T10:00:00', + auditor: '系统管理员', + result: 'approved', + remarks: '企业地址变更审核通过', + }, + { + id: 'audit-5', + enterpriseId: 'ent-4', + enterpriseName: '智慧农田科技公司', + auditType: 'register', + submitTime: '2024-09-28T11:00:00', + auditTime: '2024-09-30T09:30:00', + auditor: '系统管理员', + result: 'approved', + remarks: '优质企业,快速审核通过', + }, + { + id: 'audit-6', + enterpriseId: 'ent-5', + enterpriseName: '农业机械租赁中心', + auditType: 'register', + submitTime: '2024-10-03T14:20:00', + auditTime: '2024-10-05T11:00:00', + auditor: '系统管理员', + result: 'rejected', + reason: '企业经营范围与平台业务不符', + remarks: '建议企业完善相关资质后重新申请', + }, + ]; + localStorage.setItem('smart_agriculture_audit_records', JSON.stringify(mockRecords)); + setRecords(mockRecords); + } + }; + + const filteredRecords = records.filter(record => { + const matchKeyword = !filters.searchKeyword || + record.enterpriseName.includes(filters.searchKeyword) || + (record.auditor && record.auditor.includes(filters.searchKeyword)); + + const matchResult = filters.resultFilter === 'all' || record.result === filters.resultFilter; + const matchType = filters.typeFilter === 'all' || record.auditType === filters.typeFilter; + + // 日期筛选 + let matchDate = true; + if (filters.dateRange !== 'all' && record.auditTime) { + const auditDate = new Date(record.auditTime); + const now = new Date(); + const diffDays = Math.floor((now.getTime() - auditDate.getTime()) / (1000 * 60 * 60 * 24)); + + switch (filters.dateRange) { + case 'today': + matchDate = diffDays === 0; + break; + case 'week': + matchDate = diffDays <= 7; + break; + case 'month': + matchDate = diffDays <= 30; + break; + case 'quarter': + matchDate = diffDays <= 90; + break; + } + } + + return matchKeyword && matchResult && matchType && matchDate; + }); + + const handleViewDetail = (record: AuditRecord) => { + setSelectedRecord(record); + // 查找对应的企业信息 + const enterprise = enterprises.find(e => e.id === record.enterpriseId); + setSelectedEnterprise(enterprise || null); + setShowDetailDialog(true); + }; + + const handleExport = () => { + const dataStr = JSON.stringify(filteredRecords, null, 2); + const dataBlob = new Blob([dataStr], { type: 'application/json' }); + const url = URL.createObjectURL(dataBlob); + const link = document.createElement('a'); + link.href = url; + link.download = `audit_history_${new Date().getTime()}.json`; + link.click(); + toast.success('审核历史数据导出成功'); + }; + + return ( +
+
+
+

审核历史

+

追溯查询全部企业的历史审核记录

+
+ +
+ + {/* 统计卡片 */} + + + {/* 搜索和筛选 */} + + + {/* 审核历史列表 */} + + + {/* 详情对话框 */} + + + {/* 使用说明 */} + +
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/tenant/audit-history/types.ts b/crop-x/src/app/(app)/central-config/tenant/audit-history/types.ts new file mode 100644 index 0000000..98b709e --- /dev/null +++ b/crop-x/src/app/(app)/central-config/tenant/audit-history/types.ts @@ -0,0 +1,79 @@ +// 审核历史相关类型定义 + +export interface Enterprise { + id: string; + // 企业基本信息 + name: string; + type: string; // 企业类型 + province: string; + city: string; + district?: string; + + // 其他信息 + companySize?: string; // 公司规模 + registeredCapital?: string; // 注册资本 + establishmentDate?: string; // 成立时间 + invoiceType?: string; // 发票类型 + socialCreditCode: string; // 社会信用代码 + businessScope?: string; // 经营范围 + businessLicense?: string; // 营业执照(图片URL) + + // 开户信息 + bankAccount?: string; // 银行账号 + bankName?: string; // 开户行 + bankFullName?: string; // 开户行全称 + bankAddress?: string; // 开户行地址 + bankLicense?: string; // 开户许可证(图片URL) + + // 法人信息 + legalPerson?: string; // 法人名称 + idCardFront?: string; // 身份证正面(图片URL) + idCardBack?: string; // 身份证反面(图片URL) + + // 联系信息 + registrant: string; + contactPhone: string; + address: string; + + // 系统信息 + status: EnterpriseStatus; + auditStatus: AuditStatus; + auditReason?: string; + auditTime?: string; + auditor?: string; + createdAt: string; + updatedAt: string; +} + +export type EnterpriseStatus = 'active' | 'inactive' | 'suspended'; +export type AuditStatus = 'pending' | 'approved' | 'rejected'; + +// 审核记录 +export interface AuditRecord { + id: string; + enterpriseId: string; + enterpriseName: string; + auditType: 'register' | 'update'; + submitTime: string; + auditTime?: string; + auditor?: string; + result: AuditStatus; + reason?: string; + remarks?: string; +} + +// 统计数据 +export interface AuditHistoryStats { + label: string; + value: number; + color: string; + bg: string; +} + +// 筛选条件 +export interface FilterOptions { + searchKeyword: string; + resultFilter: string; + typeFilter: string; + dateRange: string; +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/tenant-management/enterprise-audit/components/AuditStatsCard.tsx b/crop-x/src/app/(app)/central-config/tenant/enterprise-audit/components/AuditStatsCards.tsx similarity index 69% rename from crop-x/src/app/(app)/central-config/tenant-management/enterprise-audit/components/AuditStatsCard.tsx rename to crop-x/src/app/(app)/central-config/tenant/enterprise-audit/components/AuditStatsCards.tsx index b64a2d6..17114d9 100644 --- a/crop-x/src/app/(app)/central-config/tenant-management/enterprise-audit/components/AuditStatsCard.tsx +++ b/crop-x/src/app/(app)/central-config/tenant/enterprise-audit/components/AuditStatsCards.tsx @@ -1,14 +1,15 @@ -'use client' +'use client'; -import { Card } from '@/components/ui/card' -import { Enterprise, AuditStatus } from '@/types/user-management' +import React from 'react'; +import { Card } from '@/components/ui/card'; +import { AuditStats, Enterprise } from '../types'; -interface AuditStatsCardProps { - enterprises: Enterprise[] +interface AuditStatsCardsProps { + enterprises: Enterprise[]; } -export function AuditStatsCard({ enterprises }: AuditStatsCardProps) { - const stats = [ +export function AuditStatsCards({ enterprises }: AuditStatsCardsProps) { + const stats: AuditStats[] = [ { label: '待审核', value: enterprises.filter(e => e.auditStatus === 'pending').length, @@ -33,16 +34,16 @@ export function AuditStatsCard({ enterprises }: AuditStatsCardProps) { color: 'text-blue-600', bg: 'bg-blue-100', }, - ] + ]; return (
{stats.map((stat, index) => (
{stat.label}
-
{stat.value}
+
{stat.value}
))}
- ) + ); } \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/tenant/enterprise-audit/components/EnterpriseDetailDialog.tsx b/crop-x/src/app/(app)/central-config/tenant/enterprise-audit/components/EnterpriseDetailDialog.tsx new file mode 100644 index 0000000..c265b34 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/tenant/enterprise-audit/components/EnterpriseDetailDialog.tsx @@ -0,0 +1,318 @@ +'use client'; + +import React from 'react'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { Card } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Building, FileText, CreditCard, User, CheckCircle, XCircle, Image as ImageIcon } from 'lucide-react'; +import { Enterprise, AuditStatus } from '../types'; + +interface EnterpriseDetailDialogProps { + enterprise: Enterprise | null; + open: boolean; + onOpenChange: (open: boolean) => void; + auditReason: string; + onAuditReasonChange: (reason: string) => void; + onApprove: () => void; + onReject: () => void; +} + +export function EnterpriseDetailDialog({ + enterprise, + open, + onOpenChange, + auditReason, + onAuditReasonChange, + onApprove, + onReject +}: EnterpriseDetailDialogProps) { + const getAuditStatusBadge = (status: AuditStatus) => { + switch (status) { + case 'pending': + return 待审核; + case 'approved': + return 已通过; + case 'rejected': + return 已驳回; + default: + return {status}; + } + }; + + if (!enterprise) return null; + + return ( + + + +
+ 企业详情审核 + {getAuditStatusBadge(enterprise.auditStatus)} +
+ + 查看企业的详细信息和审核状态 + +
+ + + + + + + 基本信息 + + + + 其他信息 + + + + 开户信息 + + + + 法人信息 + + + + {/* 企业基本信息 */} + +
+
+ +
{enterprise.name}
+
+
+ +
{enterprise.type}
+
+
+ +
+ {enterprise.province} {enterprise.city} {enterprise.district} +
+
+
+ +
{enterprise.address}
+
+
+ +
{enterprise.registrant}
+
+
+ +
{enterprise.contactPhone}
+
+
+
+ + {/* 其他信息 */} + +
+
+ +
{enterprise.companySize || '-'}
+
+
+ +
{enterprise.registeredCapital || '-'}
+
+
+ +
{enterprise.establishmentDate || '-'}
+
+
+ +
{enterprise.invoiceType || '-'}
+
+
+ +
+ + {enterprise.socialCreditCode} + +
+
+
+ +
{enterprise.businessScope || '-'}
+
+
+ +
+ {enterprise.businessLicense ? ( + 营业执照 + ) : ( + 未上传 + )} +
+
+
+
+ + {/* 开户信息 */} + +
+
+ +
+ {enterprise.bankAccount ? ( + + {enterprise.bankAccount} + + ) : '-'} +
+
+
+ +
{enterprise.bankName || '-'}
+
+
+ +
{enterprise.bankFullName || '-'}
+
+
+ +
{enterprise.bankAddress || '-'}
+
+
+ +
+ {enterprise.bankLicense ? ( + 开户许可证 + ) : ( + 未上传 + )} +
+
+
+
+ + {/* 法人信息 */} + +
+
+ +
{enterprise.legalPerson || '-'}
+
+
+ +
+ {enterprise.idCardFront ? ( + 身份证正面 + ) : ( + 未上传 + )} +
+
+
+ +
+ {enterprise.idCardBack ? ( + 身份证反面 + ) : ( + 未上传 + )} +
+
+
+
+
+ + {/* 审核信息 */} +
+

审核信息

+ +
+
+ +
+ {new Date(enterprise.createdAt).toLocaleString('zh-CN')} +
+
+ {enterprise.auditTime && ( +
+ +
+ {new Date(enterprise.auditTime).toLocaleString('zh-CN')} +
+
+ )} + {enterprise.auditor && ( +
+ +
+ {enterprise.auditor} +
+
+ )} + {enterprise.auditReason && ( +
+ +
+ {enterprise.auditReason} +
+
+ )} +
+
+ + {/* 审核操作区 - 仅待审核状态显示 */} + {enterprise.auditStatus === 'pending' && ( +
+ + -
- -
- - - -
-
-

- 🔑 权限矩阵配置 -

-
- -
-
- {[ - { - module: '智能农机管理', - permissions: ['查看', '创建', '编辑', '删除', '导出'] - }, - { - module: '地块信息管理', - permissions: ['查看', '创建', '编辑', '删除', '导出'] - }, - { - module: '农事操作管理', - permissions: ['查看', '创建', '编辑', '删除', '导出'] - }, - { - module: '农业资产管理', - permissions: ['查看', '创建', '编辑', '删除', '导出'] - }, - { - module: 'AI作物模型', - permissions: ['查看', '创建', '编辑', '删除', '导出'] - }, - { - module: '水肥控制', - permissions: ['查看', '创建', '编辑', '删除', '导出'] - }, - { - module: '系统配置', - permissions: ['查看', '创建', '编辑', '删除', '导出'] - }, - ].map((module, index) => ( -
-
-

{module.module}

- -
-
- {module.permissions.map((permission, permIndex) => ( - - ))} -
-
- ))} -
- -
- -
-

- 👥 用户角色分配 -

-
-
- - -
-
- -
- {['超级管理员', '农场管理员', '农机操作员', '技术员', '观察员'].map((role, index) => ( - - ))} -
-
- -
-
-
- - -
-

- 📊 权限使用统计 -

-
-
-
5
-
总角色数
-
-
-
35
-
总权限数
-
-
-
18
-
今日角色变更
-
-
-
42
-
今日权限调整
-
-
-
- - - ) -} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user-management/user-account-management/page.tsx b/crop-x/src/app/(app)/central-config/user-management/user-account-management/page.tsx deleted file mode 100644 index 2163421..0000000 --- a/crop-x/src/app/(app)/central-config/user-management/user-account-management/page.tsx +++ /dev/null @@ -1,283 +0,0 @@ -import { Metadata } from 'next' - -export const metadata: Metadata = { - title: '用户账号管理 - Crop-X 智慧农业管理系统', - description: '用户账号管理页面', -} - -export default function UserAccountManagementPage() { - return ( -
-
-

- 👤 用户账号管理 -

- -
-
- - - - -
- -
- -
-
-
-

- 用户列表 -

-
- - - - - - - - - - - - - - {[ - { - username: 'admin', - name: '系统管理员', - email: 'admin@crop-x.com', - avatar: '👨‍💼', - role: '超级管理员', - tenant: '系统', - status: 'active', - createTime: '2024-01-01' - }, - { - username: 'zhangsan', - name: '张三', - email: 'zhangsan@green-agri.com', - avatar: '👨‍🌾', - role: '农场管理员', - tenant: '绿色农业合作社', - status: 'active', - createTime: '2024-03-15' - }, - { - username: 'lisi', - name: '李四', - email: 'lisi@harvest.com', - avatar: '👩‍🌾', - role: '农机操作员', - tenant: '丰收农场', - status: 'active', - createTime: '2024-05-20' - }, - { - username: 'wangwu', - name: '王五', - email: 'wangwu@smart-agri.com', - avatar: '👨‍🔧', - role: '技术员', - tenant: '智慧农业科技', - status: 'inactive', - createTime: '2024-07-10' - }, - { - username: 'zhaoliu', - name: '赵六', - email: 'zhaoliu@modern-agri.com', - avatar: '👩‍🔬', - role: '观察员', - tenant: '现代农业示范园', - status: 'pending', - createTime: '2024-09-05' - }, - ].map((user, index) => ( - - - - - - - - - - ))} - -
- - - 用户信息 - - 角色 - - 租户 - - 状态 - - 创建时间 - - 操作 -
- - -
-
{user.avatar}
-
-
{user.name}
-
@{user.username}
-
{user.email}
-
-
-
- - {user.role} - - - {user.tenant} - - - {user.status === 'active' ? '活跃' : user.status === 'inactive' ? '未激活' : '待审核'} - - - {user.createTime} - - - - -
-
- -
-
- 显示 1-5 条,共 248 条用户 -
-
- - - - - - -
-
-
-
- -
-
-

- 📊 用户统计 -

-
-
- 总用户数 - 248 -
-
- 活跃用户 - 186 -
-
- 待审核 - 5 -
-
- 已禁用 - 12 -
-
-
- -
-

- 🎭 角色分布 -

-
-
- 超级管理员 - 3 -
-
- 农场管理员 - 45 -
-
- 农机操作员 - 89 -
-
- 技术员 - 67 -
-
- 观察员 - 44 -
-
-
- -
-

- ⚡ 批量操作 -

-
- - - - -
-
-
-
-
-
- ) -} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user-management/user-behavior-tracking/page.tsx b/crop-x/src/app/(app)/central-config/user-management/user-behavior-tracking/page.tsx deleted file mode 100644 index fea1ba5..0000000 --- a/crop-x/src/app/(app)/central-config/user-management/user-behavior-tracking/page.tsx +++ /dev/null @@ -1,337 +0,0 @@ -import { Metadata } from 'next' - -export const metadata: Metadata = { - title: '用户行为跟踪 - Crop-X 智慧农业管理系统', - description: '用户行为跟踪管理页面', -} - -export default function UserBehaviorTrackingPage() { - return ( -
-
-

- 📊 用户行为跟踪 -

- -
- - - - - -
- -
-
-
-

- 📋 行为记录 -

-
- - - - - - - - - - - - - - {[ - { - time: '2024-10-20 15:30:25', - user: '张三', - type: '登录', - module: '系统认证', - description: '用户登录系统', - ip: '192.168.1.100', - status: 'success' - }, - { - time: '2024-10-20 15:28:15', - user: '李四', - type: '查看页面', - module: '智能农机管理', - description: '查看农机列表页面', - ip: '192.168.1.101', - status: 'success' - }, - { - time: '2024-10-20 15:25:42', - user: '王五', - type: '创建数据', - module: '地块信息管理', - description: '创建新的地块信息', - ip: '192.168.1.102', - status: 'success' - }, - { - time: '2024-10-20 15:22:18', - user: '赵六', - type: '编辑数据', - module: '农事操作管理', - description: '编辑农事任务信息', - ip: '192.168.1.103', - status: 'success' - }, - { - time: '2024-10-20 15:20:05', - user: '张三', - type: '导出数据', - module: '系统配置', - description: '导出用户列表', - ip: '192.168.1.100', - status: 'success' - }, - { - time: '2024-10-20 15:18:30', - user: '李四', - type: '删除数据', - module: '农业资产管理', - description: '删除资产记录', - ip: '192.168.1.101', - status: 'warning' - }, - { - time: '2024-10-20 15:15:45', - user: '王五', - type: '登录', - module: '系统认证', - description: '用户登录失败', - ip: '192.168.1.102', - status: 'error' - }, - ].map((record, index) => ( - - - - - - - - - - ))} - -
- 时间 - - 用户 - - 行为类型 - - 模块 - - 描述 - - IP地址 - - 状态 -
- {record.time} - - {record.user} - - - {record.type} - - - {record.module} - - {record.description} - - {record.ip} - - - {record.status === 'success' ? '成功' : record.status === 'warning' ? '警告' : '失败'} - -
-
- -
-
- 显示 1-7 条,共 1,456 条记录 -
-
- - - - - -
-
-
-
- -
-
-

- 📈 今日统计 -

-
-
- 总操作数 - 1,456 -
-
- 成功操作 - 1,423 -
-
- 失败操作 - 25 -
-
- 活跃用户 - 186 -
-
-
- -
-

- 🎯 热门模块 -

-
-
- 智能农机管理 - 342 次 -
-
- 地块信息管理 - 298 次 -
-
- 农事操作管理 - 256 次 -
-
- 系统配置 - 189 次 -
-
-
- -
-

- 👥 活跃用户TOP5 -

-
-
- 张三 - 89 次 -
-
- 李四 - 76 次 -
-
- 王五 - 65 次 -
-
- 赵六 - 54 次 -
-
- 钱七 - 43 次 -
-
-
- -
-

- ⚠️ 异常行为监控 -

-
-
-
- 频繁登录失败 - -
-

用户: test@example.com - 5次失败

-
-
-
- 异常时间段操作 - -
-

用户: 李四 - 凌晨3点操作

-
-
-
- 大量数据导出 - -
-

用户: 王五 - 单日导出10次

-
-
-
-
-
- -
-

- 🔍 高级分析 -

-
- - - -
-
-
-
- ) -} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeDetailDialog.tsx b/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeDetailDialog.tsx new file mode 100644 index 0000000..e4e5c1c --- /dev/null +++ b/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeDetailDialog.tsx @@ -0,0 +1,119 @@ +'use client'; + +import React from 'react'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Label } from '@/components/ui/label'; +import { Badge } from '@/components/ui/badge'; +import { Employee, UserStatus } from '../types'; + +interface EmployeeDetailDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + selectedEmployee: Employee | null; +} + +export function EmployeeDetailDialog({ + open, + onOpenChange, + selectedEmployee +}: EmployeeDetailDialogProps) { + const getStatusBadge = (status: UserStatus) => { + switch (status) { + case 'active': + return 正常; + case 'frozen': + return 已冻结; + case 'inactive': + return 停用; + default: + return {status}; + } + }; + + if (!selectedEmployee) return null; + + return ( + + + + 员工详情 + + 查看员工的详细信息 + + +
+
+
+ +
{selectedEmployee.name}
+
+
+ +
{selectedEmployee.username}
+
+
+ +
{selectedEmployee.phone}
+
+
+ +
{selectedEmployee.email || '-'}
+
+
+ +
{selectedEmployee.department || '-'}
+
+
+ +
{selectedEmployee.position || '-'}
+
+
+ +
{getStatusBadge(selectedEmployee.status)}
+
+
+ +
+ {selectedEmployee.roles && selectedEmployee.roles.length > 0 ? ( + selectedEmployee.roles.map((role, index) => ( + + {role} + + )) + ) : ( + 未分配角色 + )} +
+
+ {selectedEmployee.lastLoginTime && ( +
+ +
+ {new Date(selectedEmployee.lastLoginTime).toLocaleString('zh-CN')} +
+
+ )} +
+ +
+ {new Date(selectedEmployee.createdAt).toLocaleString('zh-CN')} +
+
+
+ +
+ {new Date(selectedEmployee.updatedAt).toLocaleString('zh-CN')} +
+
+
+
+ + + +
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeFormDialog.tsx b/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeFormDialog.tsx new file mode 100644 index 0000000..5c21e41 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeFormDialog.tsx @@ -0,0 +1,155 @@ +'use client'; + +import React from 'react'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Card } from '@/components/ui/card'; +import { Checkbox } from '@/components/ui/checkbox'; +import { Employee, Role, EmployeeFormData } from '../types'; + +interface EmployeeFormDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + editingEmployee: Employee | null; + formData: EmployeeFormData; + onFormDataChange: (data: EmployeeFormData) => void; + onSave: () => void; + roles: Role[]; +} + +export function EmployeeFormDialog({ + open, + onOpenChange, + editingEmployee, + formData, + onFormDataChange, + onSave, + roles +}: EmployeeFormDialogProps) { + return ( + + + + {editingEmployee ? '编辑员工' : '添加员工'} + + {editingEmployee ? '编辑员工信息' : '添加新员工'} + + +
+
+
+ + onFormDataChange({ ...formData, username: e.target.value })} + placeholder="登录用户名" + /> +
+
+ + onFormDataChange({ ...formData, name: e.target.value })} + placeholder="真实姓名" + /> +
+
+ + onFormDataChange({ ...formData, phone: e.target.value })} + placeholder="手机号码" + /> +
+
+ + onFormDataChange({ ...formData, email: e.target.value })} + placeholder="电子邮箱" + /> +
+
+ + onFormDataChange({ ...formData, department: e.target.value })} + placeholder="所属部门" + /> +
+
+ + onFormDataChange({ ...formData, position: e.target.value })} + placeholder="职位名称" + /> +
+
+ + {/* 角色选择 */} +
+ + +
+ {roles + .filter(role => role.status === 'active') + .map((role) => ( +
+ { + if (checked) { + onFormDataChange({ + ...formData, + roleIds: [...(formData.roleIds || []), role.id], + }); + } else { + onFormDataChange({ + ...formData, + roleIds: (formData.roleIds || []).filter(id => id !== role.id), + }); + } + }} + /> +
+ + {role.description && ( +

+ {role.description} +

+ )} +
+
+ ))} +
+ {roles.filter(r => r.status === 'active').length === 0 && ( +

+ 暂无可用角色,请先在角色管理中创建角色 +

+ )} +
+
+
+ + + + +
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeList.tsx b/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeList.tsx new file mode 100644 index 0000000..7a061e9 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeList.tsx @@ -0,0 +1,127 @@ +'use client'; + +import React from 'react'; +import { Card } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; +import { Eye, Edit, Lock, Trash2, UserX, UserCheck } from 'lucide-react'; +import { Employee, UserStatus } from '../types'; + +interface EmployeeListProps { + employees: Employee[]; + onViewDetail: (employee: Employee) => void; + onEdit: (employee: Employee) => void; + onResetPassword: (employee: Employee) => void; + onToggleStatus: (employee: Employee) => void; + onDelete: (id: string) => void; +} + +export function EmployeeList({ + employees, + onViewDetail, + onEdit, + onResetPassword, + onToggleStatus, + onDelete +}: EmployeeListProps) { + const getStatusBadge = (status: UserStatus) => { + switch (status) { + case 'active': + return 正常; + case 'frozen': + return 已冻结; + case 'inactive': + return 停用; + default: + return {status}; + } + }; + + return ( + + + + + 姓名 + 用户名 + 电话 + 部门 + 职位 + 角色 + 状态 + 操作 + + + + {employees.length === 0 ? ( + + + 暂无数据 + + + ) : ( + employees.map((employee) => ( + + {employee.name} + {employee.username} + {employee.phone} + {employee.department || '-'} + {employee.position || '-'} + + {employee.roles && employee.roles.length > 0 + ? employee.roles.join(', ') + : '-'} + + {getStatusBadge(employee.status)} + +
+ + + + + +
+
+
+ )) + )} +
+
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeManagementFilters.tsx b/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeManagementFilters.tsx new file mode 100644 index 0000000..4526438 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeManagementFilters.tsx @@ -0,0 +1,53 @@ +'use client'; + +import React from 'react'; +import { Card } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Search } from 'lucide-react'; +import { EmployeeFilters } from '../types'; + +interface EmployeeManagementFiltersProps { + filters: EmployeeFilters; + onFiltersChange: (filters: EmployeeFilters) => void; +} + +export function EmployeeManagementFilters({ + filters, + onFiltersChange +}: EmployeeManagementFiltersProps) { + const updateFilter = (key: keyof EmployeeFilters, value: string) => { + onFiltersChange({ + ...filters, + [key]: value + }); + }; + + return ( + +
+
+
+ + updateFilter('searchKeyword', e.target.value)} + className="pl-10" + /> +
+
+ +
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeManagementHeader.tsx b/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeManagementHeader.tsx new file mode 100644 index 0000000..6190f64 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeManagementHeader.tsx @@ -0,0 +1,24 @@ +'use client'; + +import React from 'react'; +import { Button } from '@/components/ui/button'; +import { Plus } from 'lucide-react'; + +interface EmployeeManagementHeaderProps { + onAddEmployee: () => void; +} + +export function EmployeeManagementHeader({ onAddEmployee }: EmployeeManagementHeaderProps) { + return ( +
+
+

员工管理

+

管理本企业员工账户体系

+
+ +
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeManagementStatsCards.tsx b/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeManagementStatsCards.tsx new file mode 100644 index 0000000..378ae6f --- /dev/null +++ b/crop-x/src/app/(app)/central-config/user/employee/components/EmployeeManagementStatsCards.tsx @@ -0,0 +1,43 @@ +'use client'; + +import React from 'react'; +import { Card } from '@/components/ui/card'; +import { EmployeeManagementStats, Employee } from '../types'; + +interface EmployeeManagementStatsCardsProps { + employees: Employee[]; +} + +export function EmployeeManagementStatsCards({ employees }: EmployeeManagementStatsCardsProps) { + const stats: EmployeeManagementStats[] = [ + { + label: '总员工数', + value: employees.length, + color: 'text-blue-600', + bg: 'bg-blue-100', + }, + { + label: '正常员工', + value: employees.filter(e => e.status === 'active').length, + color: 'text-green-600', + bg: 'bg-green-100', + }, + { + label: '冻结员工', + value: employees.filter(e => e.status === 'frozen').length, + color: 'text-gray-600', + bg: 'bg-gray-100', + }, + ]; + + return ( +
+ {stats.map((stat, index) => ( + +
{stat.label}
+
{stat.value}
+
+ ))} +
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/employee/page.tsx b/crop-x/src/app/(app)/central-config/user/employee/page.tsx new file mode 100644 index 0000000..2cec50b --- /dev/null +++ b/crop-x/src/app/(app)/central-config/user/employee/page.tsx @@ -0,0 +1,259 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { toast } from 'sonner'; + +import { EmployeeManagementHeader } from './components/EmployeeManagementHeader'; +import { EmployeeManagementStatsCards } from './components/EmployeeManagementStatsCards'; +import { EmployeeManagementFilters } from './components/EmployeeManagementFilters'; +import { EmployeeList } from './components/EmployeeList'; +import { EmployeeFormDialog } from './components/EmployeeFormDialog'; +import { EmployeeDetailDialog } from './components/EmployeeDetailDialog'; +import { Employee, Role, EmployeeFilters, EmployeeFormData } from './types'; + +export default function EmployeeManagementPage() { + const [employees, setEmployees] = useState([]); + const [roles, setRoles] = useState([]); + const [filters, setFilters] = useState({ + searchKeyword: '', + statusFilter: 'all' + }); + const [showForm, setShowForm] = useState(false); + const [showDetailDialog, setShowDetailDialog] = useState(false); + const [editingEmployee, setEditingEmployee] = useState(null); + const [selectedEmployee, setSelectedEmployee] = useState(null); + const [formData, setFormData] = useState({ + enterpriseId: 'ent-2', + enterpriseName: '丰收现代农业集团', + status: 'active', + roleIds: [], + }); + + useEffect(() => { + loadEmployees(); + loadRoles(); + }, []); + + const loadRoles = () => { + const data = localStorage.getItem('smart_agriculture_roles'); + if (data) { + setRoles(JSON.parse(data)); + } + }; + + const loadEmployees = () => { + const data = localStorage.getItem('smart_agriculture_employees'); + if (data) { + setEmployees(JSON.parse(data)); + } else { + // 初始化示例数据 + const mockEmployees: Employee[] = [ + { + id: 'emp-1', + enterpriseId: 'ent-2', + enterpriseName: '丰收现代农业集团', + username: 'zhangsan', + name: '张三', + phone: '13800138001', + email: 'zhangsan@example.com', + department: '技术部', + position: '农机操作员', + roleIds: ['role-3'], + roles: ['操作员'], + status: 'active', + createdAt: '2024-10-01T08:00:00', + updatedAt: '2024-10-01T08:00:00', + lastLoginTime: '2024-10-14T09:30:00', + }, + { + id: 'emp-2', + enterpriseId: 'ent-2', + enterpriseName: '丰收现代农业集团', + username: 'lisi', + name: '李四', + phone: '13900139002', + email: 'lisi@example.com', + department: '管理部', + position: '部门主管', + roleIds: ['role-2'], + roles: ['企业管理员'], + status: 'active', + createdAt: '2024-10-02T10:00:00', + updatedAt: '2024-10-02T10:00:00', + lastLoginTime: '2024-10-14T08:15:00', + }, + { + id: 'emp-3', + enterpriseId: 'ent-2', + enterpriseName: '丰收现代农业集团', + username: 'wangwu', + name: '王五', + phone: '13700137003', + department: '维修部', + position: '维修技师', + roleIds: ['role-3'], + roles: ['操作员'], + status: 'frozen', + createdAt: '2024-09-28T14:00:00', + updatedAt: '2024-10-10T16:00:00', + }, + ]; + localStorage.setItem('smart_agriculture_employees', JSON.stringify(mockEmployees)); + setEmployees(mockEmployees); + } + }; + + const filteredEmployees = employees.filter(emp => { + const matchKeyword = !filters.searchKeyword || + emp.name.includes(filters.searchKeyword) || + emp.username.includes(filters.searchKeyword) || + emp.phone.includes(filters.searchKeyword) || + (emp.department && emp.department.includes(filters.searchKeyword)); + + const matchStatus = filters.statusFilter === 'all' || emp.status === filters.statusFilter; + + return matchKeyword && matchStatus; + }); + + const handleAddEmployee = () => { + setEditingEmployee(null); + setFormData({ + enterpriseId: 'ent-2', + enterpriseName: '丰收现代农业集团', + status: 'active', + roleIds: [], + }); + setShowForm(true); + }; + + const handleEdit = (employee: Employee) => { + setEditingEmployee(employee); + setFormData(employee); + setShowForm(true); + }; + + const handleSave = () => { + if (!formData.username || !formData.name || !formData.phone) { + toast.error('请填写必填项'); + return; + } + + // 验证角色选择 + if (!formData.roleIds || formData.roleIds.length === 0) { + toast.error('请至少选择一个角色'); + return; + } + + // 根据角色ID设置角色名称 + const selectedRoles = roles.filter(r => formData.roleIds?.includes(r.id)); + const roleNames = selectedRoles.map(r => r.name); + + if (editingEmployee) { + // 更新 + const updated = employees.map(emp => + emp.id === editingEmployee.id + ? { + ...emp, + ...formData, + roles: roleNames, + updatedAt: new Date().toISOString(), + } + : emp + ); + setEmployees(updated); + localStorage.setItem('smart_agriculture_employees', JSON.stringify(updated)); + toast.success('员工信息更新成功'); + } else { + // 新增 + const newEmployee: Employee = { + id: `emp-${Date.now()}`, + ...formData as Employee, + roles: roleNames, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + const updated = [...employees, newEmployee]; + setEmployees(updated); + localStorage.setItem('smart_agriculture_employees', JSON.stringify(updated)); + toast.success('员工添加成功'); + } + + setShowForm(false); + }; + + const handleDelete = (id: string) => { + if (!confirm('确定要删除该员工吗?')) return; + + const updated = employees.filter(emp => emp.id !== id); + setEmployees(updated); + localStorage.setItem('smart_agriculture_employees', JSON.stringify(updated)); + toast.success('员工删除成功'); + }; + + const handleToggleStatus = (employee: Employee) => { + const newStatus = employee.status === 'active' ? 'frozen' : 'active'; + const updated = employees.map(emp => + emp.id === employee.id + ? { ...emp, status: newStatus, updatedAt: new Date().toISOString() } + : emp + ); + setEmployees(updated); + localStorage.setItem('smart_agriculture_employees', JSON.stringify(updated)); + toast.success(newStatus === 'active' ? '账户已激活' : '账户已冻结'); + }; + + const handleResetPassword = (employee: Employee) => { + if (!confirm(`确定要重置 ${employee.name} 的密码吗?`)) return; + toast.success('密码已重置为:123456'); + }; + + const handleViewDetail = (employee: Employee) => { + setSelectedEmployee(employee); + setShowDetailDialog(true); + }; + + return ( +
+ + + {/* 统计卡片 */} + + + {/* 搜索和筛选 */} + + + {/* 员工列表 */} + + + {/* 添加/编辑表单 */} + + + {/* 详情对话框 */} + +
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/employee/types.ts b/crop-x/src/app/(app)/central-config/user/employee/types.ts new file mode 100644 index 0000000..181de6f --- /dev/null +++ b/crop-x/src/app/(app)/central-config/user/employee/types.ts @@ -0,0 +1,65 @@ +// 员工管理相关类型定义 + +export interface Employee { + id: string; + enterpriseId: string; + enterpriseName: string; + username: string; + name: string; + phone: string; + email?: string; + department?: string; + position?: string; + roleIds: string[]; + roles?: string[]; + status: UserStatus; + createdAt: string; + updatedAt: string; + lastLoginTime?: string; +} + +export type UserStatus = 'active' | 'inactive' | 'frozen'; + +export interface Role { + id: string; + name: string; + code: string; + description?: string; + type: RoleType; + menuIds: string[]; + permissionIds: string[]; + defaultHomePage?: string; + status: 'active' | 'inactive'; + createdAt: string; + updatedAt: string; +} + +export type RoleType = 'system' | 'custom'; + +// 统计数据 +export interface EmployeeManagementStats { + label: string; + value: number; + color: string; + bg: string; +} + +// 筛选条件 +export interface EmployeeFilters { + searchKeyword: string; + statusFilter: string; +} + +// 表单数据 +export interface EmployeeFormData { + username?: string; + name?: string; + phone?: string; + email?: string; + department?: string; + position?: string; + enterpriseId?: string; + enterpriseName?: string; + status?: UserStatus; + roleIds?: string[]; +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/menu/components/MenuFormDialog.tsx b/crop-x/src/app/(app)/central-config/user/menu/components/MenuFormDialog.tsx new file mode 100644 index 0000000..b16bee1 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/user/menu/components/MenuFormDialog.tsx @@ -0,0 +1,160 @@ +'use client'; + +import React from 'react'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Switch } from '@/components/ui/switch'; +import { Menu, MenuType } from '../types'; + +interface MenuFormDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + editingMenu: Menu | null; + parentMenu: Menu | null; + formData: Partial; + onFormDataChange: (data: Partial) => void; + onSave: () => void; +} + +export function MenuFormDialog({ + open, + onOpenChange, + editingMenu, + parentMenu, + formData, + onFormDataChange, + onSave +}: MenuFormDialogProps) { + return ( + + + + + {editingMenu ? '编辑菜单' : parentMenu ? `添加子菜单(父级:${parentMenu.name})` : '添加一级菜单'} + + + {editingMenu ? '编辑菜单信息' : '添加新菜单'} + + +
+
+
+ + onFormDataChange({ ...formData, name: e.target.value })} + placeholder="请输入菜单名称" + /> +
+
+ + onFormDataChange({ ...formData, code: e.target.value })} + placeholder="请输入菜单编码,如:system:menu:add" + /> +
+
+ + +

+ 目录:可包含子菜单的分组 / 菜单:具体页面 / 按钮:页面内的操作权限 +

+
+
+ + onFormDataChange({ ...formData, icon: e.target.value })} + placeholder="如:Settings, Users" + /> +
+
+ + onFormDataChange({ ...formData, path: e.target.value })} + placeholder="/config/user/menu" + /> + {formData.type === 'menu' && ( +

菜单类型必须填写路由路径

+ )} +
+
+ + onFormDataChange({ ...formData, component: e.target.value })} + placeholder="@/views/config/MenuManagement" + /> +
+
+ + onFormDataChange({ ...formData, sort: parseInt(e.target.value) || 0 })} + placeholder="数字越小越靠前" + /> +
+
+ + +
+
+ +
+
+ +

控制菜单是否在导航中显示

+
+ onFormDataChange({ ...formData, visible: checked })} + /> +
+
+ + + + +
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/menu/components/MenuManagementHeader.tsx b/crop-x/src/app/(app)/central-config/user/menu/components/MenuManagementHeader.tsx new file mode 100644 index 0000000..b558fb8 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/user/menu/components/MenuManagementHeader.tsx @@ -0,0 +1,25 @@ +'use client'; + +import React from 'react'; +import { Button } from '@/components/ui/button'; +import { Plus } from 'lucide-react'; +import { Menu } from '../types'; + +interface MenuManagementHeaderProps { + onAddMenu: () => void; +} + +export function MenuManagementHeader({ onAddMenu }: MenuManagementHeaderProps) { + return ( +
+
+

菜单管理

+

树形结构管理系统菜单体系(最多支持三级菜单)

+
+ +
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/menu/components/MenuManagementInstructions.tsx b/crop-x/src/app/(app)/central-config/user/menu/components/MenuManagementInstructions.tsx new file mode 100644 index 0000000..a2902a5 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/user/menu/components/MenuManagementInstructions.tsx @@ -0,0 +1,21 @@ +'use client'; + +import React from 'react'; +import { Card } from '@/components/ui/card'; + +export function MenuManagementInstructions() { + return ( + +

菜单管理功能说明

+
    +
  • • 支持三级菜单树形结构管理(一级目录 → 二级目录 → 三级菜单)
  • +
  • • 目录类型可以包含子菜单,菜单类型为具体页面,按钮类型为页面内的操作权限
  • +
  • • 菜单编码用于权限控制,建议使用冒号分隔的层级结构,如:system:user:add
  • +
  • • 路由路径对应前端路由配置,菜单类型必须填写路径
  • +
  • • 排序号越小越靠前,可以通过调整排序号改变菜单顺序
  • +
  • • 菜单与角色权限关联,实现灵活的访问控制
  • +
  • • 删除菜单前请先删除其所有子菜单
  • +
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/menu/components/MenuManagementStatsCards.tsx b/crop-x/src/app/(app)/central-config/user/menu/components/MenuManagementStatsCards.tsx new file mode 100644 index 0000000..dae11b3 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/user/menu/components/MenuManagementStatsCards.tsx @@ -0,0 +1,57 @@ +'use client'; + +import React from 'react'; +import { Card } from '@/components/ui/card'; +import { Menu } from '../types'; + +interface MenuManagementStatsCardsProps { + menus: Menu[]; +} + +export function MenuManagementStatsCards({ menus }: MenuManagementStatsCardsProps) { + // 统计菜单数量 + const countMenus = (menus: Menu[]): { level1: number; level2: number; level3: number } => { + let level1 = 0; + let level2 = 0; + let level3 = 0; + + menus.forEach(menu => { + if (!menu.parentId) { + level1++; + if (menu.children) { + menu.children.forEach(child => { + level2++; + if (child.children) { + level3 += child.children.length; + } + }); + } + } + }); + + return { level1, level2, level3 }; + }; + + const stats = countMenus(menus); + + return ( +
+ +
一级菜单
+
{stats.level1}
+
+ +
二级菜单
+
{stats.level2}
+
+ +
三级菜单
+
{stats.level3}
+
+ +
菜单总数
+
{stats.level1 + stats.level2 + stats.level3}
+
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/menu/components/MenuTree.tsx b/crop-x/src/app/(app)/central-config/user/menu/components/MenuTree.tsx new file mode 100644 index 0000000..006cd6f --- /dev/null +++ b/crop-x/src/app/(app)/central-config/user/menu/components/MenuTree.tsx @@ -0,0 +1,213 @@ +'use client'; + +import React from 'react'; +import { Card } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { + Menu as MenuIcon, + Folder, + FileCode, + FileText, + Plus, + Edit, + Trash2, + ChevronRight, + ChevronDown, + Eye, + EyeOff +} from 'lucide-react'; +import { Menu, MenuType } from '../types'; + +interface MenuTreeProps { + menus: Menu[]; + expandedIds: Set; + onToggleExpand: (id: string) => void; + onExpandAll: () => void; + onCollapseAll: () => void; + onAdd: (parent?: Menu) => void; + onEdit: (menu: Menu) => void; + onDelete: (menu: Menu) => void; +} + +export function MenuTree({ + menus, + expandedIds, + onToggleExpand, + onExpandAll, + onCollapseAll, + onAdd, + onEdit, + onDelete +}: MenuTreeProps) { + const getMenuIcon = (type: MenuType) => { + switch (type) { + case 'directory': + return Folder; + case 'menu': + return FileCode; + case 'button': + return FileText; + default: + return MenuIcon; + } + }; + + const getTypeBadge = (type: MenuType) => { + switch (type) { + case 'directory': + return 目录; + case 'menu': + return 菜单; + case 'button': + return 按钮; + default: + return 未知; + } + }; + + const getStatusBadge = (status: string) => { + return status === 'active' ? ( + 启用 + ) : ( + 停用 + ); + }; + + // 渲染菜单树 + const renderMenuTree = (items: Menu[], level: number = 0) => { + return items.map((menu) => { + const isExpanded = expandedIds.has(menu.id); + const hasChildren = menu.children && menu.children.length > 0; + const Icon = getMenuIcon(menu.type); + const indent = level * 24; + + return ( +
+
+
+ {/* 展开/收起图标 */} +
+ {hasChildren ? ( + + ) : ( +
+ )} +
+ + {/* 菜单图标 */} + + + {/* 菜单信息 */} +
+
+ {menu.name} + ({menu.code}) +
+ {menu.path && ( +
{menu.path}
+ )} +
+ + {/* 状态标签 */} +
+ {getTypeBadge(menu.type)} + {getStatusBadge(menu.status)} + {menu.visible ? ( + + ) : ( + + )} + 排序: {menu.sort} +
+
+ + {/* 操作按钮 */} +
+ {/* 只有目录类型可以添加子菜单,且限制最多三级 */} + {menu.type === 'directory' && level < 2 && ( + + )} + + +
+
+ + {/* 递归渲染子菜单 */} + {hasChildren && isExpanded && ( +
+ {renderMenuTree(menu.children!, level + 1)} +
+ )} +
+ ); + }); + }; + + return ( + +
+

菜单结构

+
+ + +
+
+ +
+ {menus.length > 0 ? ( + renderMenuTree(menus) + ) : ( +
+ 暂无菜单数据,点击上方按钮添加一级菜单 +
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/menu/page.tsx b/crop-x/src/app/(app)/central-config/user/menu/page.tsx new file mode 100644 index 0000000..e64a678 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/user/menu/page.tsx @@ -0,0 +1,463 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { toast } from 'sonner'; + +import { MenuManagementHeader } from './components/MenuManagementHeader'; +import { MenuManagementStatsCards } from './components/MenuManagementStatsCards'; +import { MenuTree } from './components/MenuTree'; +import { MenuFormDialog } from './components/MenuFormDialog'; +import { MenuManagementInstructions } from './components/MenuManagementInstructions'; +import { Menu, MenuType } from './types'; + +/** + * 菜单管理页面组件 + * 提供菜单的增删改查、展开折叠等功能 + */ +export default function MenuManagementPage() { + // 菜单列表状态 + const [menus, setMenus] = useState([]); + // 展开的菜单ID集合 + const [expandedIds, setExpandedIds] = useState>(new Set()); + // 控制表单显示状态 + const [showForm, setShowForm] = useState(false); + // 当前编辑的菜单 + const [editingMenu, setEditingMenu] = useState(null); + // 父级菜单 + const [parentMenu, setParentMenu] = useState(null); + // 表单数据 + const [formData, setFormData] = useState>({ + type: 'menu', + visible: true, + status: 'active', + sort: 0, + }); + + // 组件挂载时加载菜单数据 + useEffect(() => { + loadMenus(); + }, []); + + /** + * 加载菜单数据 + * 优先从localStorage读取,若不存在则使用示例数据 + */ + const loadMenus = () => { + const data = localStorage.getItem('smart_agriculture_menus'); + if (data) { + setMenus(JSON.parse(data)); + } else { + // 初始化示例数据 - 三级菜单结构 + const mockMenus: Menu[] = [ + { + id: 'menu-1', + name: '智能农机管理系统', + code: 'machinery', + icon: 'Tractor', + type: 'directory', + sort: 1, + visible: true, + status: 'active', + children: [ + { + id: 'menu-1-1', + parentId: 'menu-1', + name: '农机档案', + code: 'machinery:archive', + icon: 'Archive', + type: 'directory', + sort: 1, + visible: true, + status: 'active', + children: [ + { + id: 'menu-1-1-1', + parentId: 'menu-1-1', + name: '农机录入', + code: 'machinery:archive:entry', + path: '/machinery/archive/entry', + type: 'menu', + sort: 1, + visible: true, + status: 'active', + }, + { + id: 'menu-1-1-2', + parentId: 'menu-1-1', + name: '农机分类', + code: 'machinery:archive:classification', + path: '/machinery/archive/classification', + type: 'menu', + sort: 2, + visible: true, + status: 'active', + }, + { + id: 'menu-1-1-3', + parentId: 'menu-1-1', + name: '二维码管理', + code: 'machinery:archive:qrcode', + path: '/machinery/archive/qrcode', + type: 'menu', + sort: 3, + visible: true, + status: 'active', + }, + ], + }, + { + id: 'menu-1-2', + parentId: 'menu-1', + name: '驾驶员管理', + code: 'machinery:driver', + icon: 'User', + type: 'directory', + sort: 2, + visible: true, + status: 'active', + children: [ + { + id: 'menu-1-2-1', + parentId: 'menu-1-2', + name: '驾驶员信息', + code: 'machinery:driver:info', + path: '/machinery/driver/info', + type: 'menu', + sort: 1, + visible: true, + status: 'active', + }, + { + id: 'menu-1-2-2', + parentId: 'menu-1-2', + name: '任务管理', + code: 'machinery:driver:task', + path: '/machinery/driver/task', + type: 'menu', + sort: 2, + visible: true, + status: 'active', + }, + ], + }, + ], + }, + { + id: 'menu-2', + name: '中心配置管理系统', + code: 'config', + icon: 'Settings', + type: 'directory', + sort: 7, + visible: true, + status: 'active', + children: [ + { + id: 'menu-2-1', + parentId: 'menu-2', + name: '租户管理', + code: 'config:tenant', + icon: 'Building2', + type: 'directory', + sort: 1, + visible: true, + status: 'active', + children: [ + { + id: 'menu-2-1-1', + parentId: 'menu-2-1', + name: '企业信息', + code: 'config:tenant:enterprise', + path: '/config/tenant/enterprise', + type: 'menu', + sort: 1, + visible: true, + status: 'active', + }, + { + id: 'menu-2-1-2', + parentId: 'menu-2-1', + name: '企业审核', + code: 'config:tenant:audit', + path: '/config/tenant/audit', + type: 'menu', + sort: 2, + visible: true, + status: 'active', + }, + ], + }, + { + id: 'menu-2-2', + parentId: 'menu-2', + name: '用户管理', + code: 'config:user', + icon: 'Users', + type: 'directory', + sort: 2, + visible: true, + status: 'active', + children: [ + { + id: 'menu-2-2-1', + parentId: 'menu-2-2', + name: '员工管理', + code: 'config:user:employee', + path: '/config/user/employee', + type: 'menu', + sort: 1, + visible: true, + status: 'active', + }, + { + id: 'menu-2-2-2', + parentId: 'menu-2-2', + name: '角色管理', + code: 'config:user:role', + path: '/config/user/role', + type: 'menu', + sort: 2, + visible: true, + status: 'active', + }, + { + id: 'menu-2-2-3', + parentId: 'menu-2-2', + name: '菜单管理', + code: 'config:user:menu', + path: '/config/user/menu', + type: 'menu', + sort: 3, + visible: true, + status: 'active', + }, + { + id: 'menu-2-2-4', + parentId: 'menu-2-2', + name: '权限管理', + code: 'config:user:permission', + path: '/config/user/permission', + type: 'menu', + sort: 4, + visible: true, + status: 'active', + }, + ], + }, + ], + }, + ]; + localStorage.setItem('smart_agriculture_menus', JSON.stringify(mockMenus)); + setMenus(mockMenus); + // 默认展开所有一级菜单 + setExpandedIds(new Set(mockMenus.map(m => m.id))); + } + }; + + const toggleExpand = (id: string) => { + const newExpanded = new Set(expandedIds); + if (newExpanded.has(id)) { + newExpanded.delete(id); + } else { + newExpanded.add(id); + } + setExpandedIds(newExpanded); + }; + + const expandAll = () => { + const getAllMenuIds = (menus: Menu[]): string[] => { + let ids: string[] = []; + menus.forEach(menu => { + if (menu.children && menu.children.length > 0) { + ids.push(menu.id); + ids.push(...getAllMenuIds(menu.children)); + } + }); + return ids; + }; + setExpandedIds(new Set(getAllMenuIds(menus))); + }; + + const collapseAll = () => { + setExpandedIds(new Set()); + }; + + const handleAdd = (parent?: Menu) => { + setEditingMenu(null); + setParentMenu(parent || null); + + // 根据父菜单决定默认类型 + let defaultType: MenuType = 'menu'; + if (!parent) { + defaultType = 'directory'; // 一级菜单默认为目录 + } else if (parent.type === 'directory' && !parent.parentId) { + defaultType = 'directory'; // 二级菜单默认为目录 + } else { + defaultType = 'menu'; // 三级菜单默认为菜单 + } + + setFormData({ + parentId: parent?.id, + type: defaultType, + visible: true, + status: 'active', + sort: 0, + }); + setShowForm(true); + }; + + const handleEdit = (menu: Menu) => { + setEditingMenu(menu); + setParentMenu(null); + setFormData({ + ...menu, + children: undefined, // 不包含子菜单 + }); + setShowForm(true); + }; + + const handleSave = () => { + if (!formData.name || !formData.code) { + toast.error('请填写必填项'); + return; + } + + // 验证路径:三级菜单必须填写路径 + if (formData.type === 'menu' && !formData.path) { + toast.error('菜单类型必须填写路径'); + return; + } + + if (editingMenu) { + // 更新菜单 + const updateMenuInTree = (items: Menu[]): Menu[] => { + return items.map(item => { + if (item.id === editingMenu.id) { + return { + ...item, + ...formData, + children: item.children, // 保留子菜单 + } as Menu; + } + if (item.children) { + return { + ...item, + children: updateMenuInTree(item.children), + }; + } + return item; + }); + }; + + const updated = updateMenuInTree(menus); + setMenus(updated); + localStorage.setItem('smart_agriculture_menus', JSON.stringify(updated)); + toast.success('菜单更新成功'); + } else { + // 新增菜单 + const newMenu: Menu = { + id: `menu-${Date.now()}`, + ...formData as Menu, + }; + + if (parentMenu) { + // 添加到父菜单下 + const addToParent = (items: Menu[]): Menu[] => { + return items.map(item => { + if (item.id === parentMenu.id) { + return { + ...item, + children: [...(item.children || []), newMenu], + }; + } + if (item.children) { + return { + ...item, + children: addToParent(item.children), + }; + } + return item; + }); + }; + + const updated = addToParent(menus); + setMenus(updated); + localStorage.setItem('smart_agriculture_menus', JSON.stringify(updated)); + // 自动展开父菜单 + setExpandedIds(prev => new Set([...prev, parentMenu.id])); + } else { + // 添加为一级菜单 + const updated = [...menus, newMenu]; + setMenus(updated); + localStorage.setItem('smart_agriculture_menus', JSON.stringify(updated)); + } + + toast.success('菜单添加成功'); + } + + setShowForm(false); + }; + + const handleDelete = (menu: Menu) => { + if (menu.children && menu.children.length > 0) { + toast.error('请先删除该菜单下的子菜单'); + return; + } + + if (!confirm(`确定要删除菜单"${menu.name}"吗?`)) return; + + const deleteFromTree = (items: Menu[]): Menu[] => { + return items + .filter(item => item.id !== menu.id) + .map(item => { + if (item.children) { + return { + ...item, + children: deleteFromTree(item.children), + }; + } + return item; + }); + }; + + const updated = deleteFromTree(menus); + setMenus(updated); + localStorage.setItem('smart_agriculture_menus', JSON.stringify(updated)); + toast.success('菜单删除成功'); + }; + + return ( +
+ handleAdd()} /> + + {/* 统计卡片 */} + + + {/* 菜单树 */} + + + {/* 添加/编辑表单 */} + + + {/* 功能说明 */} + +
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/menu/types.ts b/crop-x/src/app/(app)/central-config/user/menu/types.ts new file mode 100644 index 0000000..f0e557d --- /dev/null +++ b/crop-x/src/app/(app)/central-config/user/menu/types.ts @@ -0,0 +1,16 @@ +export interface Menu { + id: string; + parentId?: string; + name: string; + code: string; + icon?: string; + path?: string; + component?: string; + type: MenuType; + sort: number; + visible: boolean; + status: 'active' | 'inactive'; + children?: Menu[]; +} + +export type MenuType = 'directory' | 'menu' | 'button'; \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/page.tsx b/crop-x/src/app/(app)/central-config/user/page.tsx new file mode 100644 index 0000000..ca5096f --- /dev/null +++ b/crop-x/src/app/(app)/central-config/user/page.tsx @@ -0,0 +1,30 @@ +'use client'; + +import React from 'react'; +import Link from 'next/link'; + +export default function UserPage() { + return ( +
+

用户管理

+
+ +

员工管理

+

管理员工信息

+ + +

角色管理

+

管理系统角色

+ + +

菜单管理

+

管理系统菜单

+ + +

权限配置

+

配置系统权限

+ +
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/permission/components/PermissionManagementHeader.tsx b/crop-x/src/app/(app)/central-config/user/permission/components/PermissionManagementHeader.tsx new file mode 100644 index 0000000..990b974 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/user/permission/components/PermissionManagementHeader.tsx @@ -0,0 +1,24 @@ +'use client'; + +import React from 'react'; +import { Button } from '@/components/ui/button'; +import { Plus } from 'lucide-react'; + +interface PermissionManagementHeaderProps { + onAddPermission: () => void; +} + +export function PermissionManagementHeader({ onAddPermission }: PermissionManagementHeaderProps) { + return ( +
+
+

权限配置管理

+

精细化管理系统功能权限

+
+ +
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/permission/page.tsx b/crop-x/src/app/(app)/central-config/user/permission/page.tsx new file mode 100644 index 0000000..05db5e2 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/user/permission/page.tsx @@ -0,0 +1,414 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { toast } from 'sonner'; +import { Card } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Badge } from '@/components/ui/badge'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogFooter, +} from '@/components/ui/dialog'; +import { Label } from '@/components/ui/label'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table'; +import { + Search, + Plus, + Edit, + Trash2, + Shield, + ChevronRight, +} from 'lucide-react'; + +import { PermissionManagementHeader } from './components/PermissionManagementHeader'; +import { Permission, PermissionType, Menu } from './types'; + +export default function PermissionConfigPage() { + const [permissions, setPermissions] = useState([]); + const [menus, setMenus] = useState([]); + const [searchKeyword, setSearchKeyword] = useState(''); + const [typeFilter, setTypeFilter] = useState('all'); + const [showForm, setShowForm] = useState(false); + const [editingPermission, setEditingPermission] = useState(null); + const [formData, setFormData] = useState>({ + type: 'view', + status: 'active', + }); + + useEffect(() => { + loadPermissions(); + loadMenus(); + }, []); + + const loadMenus = () => { + const data = localStorage.getItem('smart_agriculture_menus'); + if (data) { + setMenus(JSON.parse(data)); + } + }; + + const loadPermissions = () => { + const data = localStorage.getItem('smart_agriculture_permissions'); + if (data) { + setPermissions(JSON.parse(data)); + } else { + const mockPermissions: Permission[] = [ + { + id: 'perm-1', + name: '查看农机', + code: 'machinery:view', + type: 'view', + description: '查看农机档案和基本信息', + menuId: 'menu-1-1-1', + menuPath: '智能农机管理系统 > 农机档案 > 农机录入', + status: 'active', + createdAt: '2024-01-01T00:00:00', + updatedAt: '2024-01-01T00:00:00', + }, + { + id: 'perm-2', + name: '添加农机', + code: 'machinery:add', + type: 'add', + description: '添加新的农机设备', + menuId: 'menu-1-1-1', + menuPath: '智能农机管理系统 > 农机档案 > 农机录入', + status: 'active', + createdAt: '2024-01-01T00:00:00', + updatedAt: '2024-01-01T00:00:00', + }, + ]; + localStorage.setItem('smart_agriculture_permissions', JSON.stringify(mockPermissions)); + setPermissions(mockPermissions); + } + }; + + const filteredPermissions = permissions.filter((perm) => { + const matchKeyword = + !searchKeyword || + perm.name.includes(searchKeyword) || + perm.code.includes(searchKeyword) || + (perm.menuPath && perm.menuPath.includes(searchKeyword)); + + const matchType = typeFilter === 'all' || perm.type === typeFilter; + + return matchKeyword && matchType; + }); + + const getPermissionTypeBadge = (type: PermissionType) => { + const config: Record = { + view: { label: '查看', className: 'bg-blue-100 text-blue-700' }, + add: { label: '新增', className: 'bg-green-100 text-green-700' }, + edit: { label: '编辑', className: 'bg-yellow-100 text-yellow-700' }, + delete: { label: '删除', className: 'bg-red-100 text-red-700' }, + export: { label: '导出', className: 'bg-purple-100 text-purple-700' }, + import: { label: '导入', className: 'bg-indigo-100 text-indigo-700' }, + approve: { label: '审核', className: 'bg-orange-100 text-orange-700' }, + control: { label: '控制', className: 'bg-pink-100 text-pink-700' }, + }; + + const { label, className } = config[type] || { label: type, className: '' }; + return {label}; + }; + + const stats = [ + { + label: '总权限数', + value: permissions.length, + color: 'text-blue-600', + }, + { + label: '查看权限', + value: permissions.filter((p) => p.type === 'view').length, + color: 'text-green-600', + }, + { + label: '操作权限', + value: permissions.filter((p) => ['add', 'edit', 'delete'].includes(p.type)).length, + color: 'text-orange-600', + }, + { + label: '特殊权限', + value: permissions.filter((p) => ['control', 'approve', 'export', 'import'].includes(p.type)).length, + color: 'text-purple-600', + }, + ]; + + const handleAdd = () => { + setEditingPermission(null); + setFormData({ + type: 'view', + status: 'active', + }); + setShowForm(true); + }; + + const handleEdit = (permission: Permission) => { + setEditingPermission(permission); + setFormData(permission); + setShowForm(true); + }; + + const handleSave = () => { + if (!formData.name || !formData.code) { + toast.error('请填写必填项'); + return; + } + + if (editingPermission) { + const updated = permissions.map((perm) => + perm.id === editingPermission.id + ? { + ...perm, + ...formData, + updatedAt: new Date().toISOString(), + } + : perm, + ); + setPermissions(updated); + localStorage.setItem('smart_agriculture_permissions', JSON.stringify(updated)); + toast.success('权限更新成功'); + } else { + const newPermission: Permission = { + id: `perm-${Date.now()}`, + ...formData as Permission, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + const updated = [...permissions, newPermission]; + setPermissions(updated); + localStorage.setItem('smart_agriculture_permissions', JSON.stringify(updated)); + toast.success('权限添加成功'); + } + + setShowForm(false); + }; + + const handleDelete = (id: string) => { + if (!confirm('确定要删除该权限吗?')) return; + + const updated = permissions.filter((perm) => perm.id !== id); + setPermissions(updated); + localStorage.setItem('smart_agriculture_permissions', JSON.stringify(updated)); + toast.success('权限删除成功'); + }; + + return ( +
+ + + {/* 统计卡片 */} +
+ {stats.map((stat, index) => ( + +
{stat.label}
+
{stat.value}
+
+ ))} +
+ + {/* 搜索和筛选 */} + +
+
+
+ + setSearchKeyword(e.target.value)} + className="pl-10" + /> +
+
+ +
+
+ + {/* 权限列表 */} + + + + + 权限名称 + 权限代码 + 类型 + 关联菜单 + 描述 + 操作 + + + + {filteredPermissions.length === 0 ? ( + + + 暂无数据 + + + ) : ( + filteredPermissions.map((permission) => ( + + +
+ + {permission.name} +
+
+ {permission.code} + {getPermissionTypeBadge(permission.type)} + + {permission.menuPath ? ( +
+ {permission.menuPath.split(' > ').map((part, index, arr) => ( + + + {part} + + {index < arr.length - 1 && ( + + )} + + ))} +
+ ) : ( + '-' + )} +
+ + {permission.description || '-'} + + +
+ + +
+
+
+ )) + )} +
+
+
+ + {/* 添加/编辑表单 */} + + + + {editingPermission ? '编辑权限' : '添加权限'} + + {editingPermission ? '编辑权限信息' : '添加新权限'} + + +
+
+
+ + setFormData({ ...formData, name: e.target.value })} + placeholder="如:查看农机" + /> +
+
+ + setFormData({ ...formData, code: e.target.value })} + placeholder="如:machinery:view" + /> +
+
+ + +
+
+
+ + setFormData({ ...formData, description: e.target.value })} + placeholder="描述该权限的作用..." + /> +
+
+ + + + +
+
+ + {/* 使用说明 */} + +

权限管理说明

+
    +
  • • 权限是系统功能的最小控制单元,精确到按钮级别
  • +
  • • 权限代码格式建议:模块:操作,如 machinery:view
  • +
  • • 每个权限可以关联到具体的菜单(支持一级、二级、三级菜单)
  • +
  • • 权限通过角色分配给用户,实现灵活的访问控制
  • +
  • • 支持8种权限类型:查看、新增、编辑、删除、导出、导入、审核、控制
  • +
  • • 合理规划权限体系,确保系统安全性和易用性
  • +
+
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/permission/types.ts b/crop-x/src/app/(app)/central-config/user/permission/types.ts new file mode 100644 index 0000000..4b74aae --- /dev/null +++ b/crop-x/src/app/(app)/central-config/user/permission/types.ts @@ -0,0 +1,37 @@ +export interface Permission { + id: string; + name: string; + code: string; + type: PermissionType; + description?: string; + menuId?: string; + menuPath?: string; + status: 'active' | 'inactive'; + createdAt: string; + updatedAt: string; +} + +export type PermissionType = + | 'view' + | 'add' + | 'edit' + | 'delete' + | 'export' + | 'import' + | 'approve' + | 'control'; + +export interface Menu { + id: string; + parentId?: string; + name: string; + code: string; + icon?: string; + path?: string; + component?: string; + type: 'directory' | 'menu' | 'button'; + sort: number; + visible: boolean; + status: 'active' | 'inactive'; + children?: Menu[]; +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/role/components/RoleDetailDialog.tsx b/crop-x/src/app/(app)/central-config/user/role/components/RoleDetailDialog.tsx new file mode 100644 index 0000000..9fcbe57 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/user/role/components/RoleDetailDialog.tsx @@ -0,0 +1,110 @@ +'use client'; + +import React from 'react'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Label } from '@/components/ui/label'; +import { Badge } from '@/components/ui/badge'; +import { Role, RoleType } from '../types'; + +interface RoleDetailDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + selectedRole: Role | null; +} + +export function RoleDetailDialog({ + open, + onOpenChange, + selectedRole +}: RoleDetailDialogProps) { + const getRoleTypeBadge = (type: RoleType) => { + return type === 'system' ? ( + 系统角色 + ) : ( + 自定义 + ); + }; + + const getStatusBadge = (status: string) => { + return status === 'active' ? ( + 启用 + ) : ( + 禁用 + ); + }; + + if (!selectedRole) return null; + + return ( + + + + 角色详情 + + 查看角色的详细信息和权限 + + +
+
+
+ +
{selectedRole.name}
+
+
+ +
{selectedRole.code}
+
+
+ +
{selectedRole.description || '-'}
+
+
+ +
{getRoleTypeBadge(selectedRole.type)}
+
+
+ +
{getStatusBadge(selectedRole.status)}
+
+ {selectedRole.defaultHomePage && ( +
+ +
{selectedRole.defaultHomePage}
+
+ )} +
+ +
+ {selectedRole.menuIds.includes('*') ? '全部' : selectedRole.menuIds.length} +
+
+
+ +
+ {selectedRole.permissionIds.includes('*') ? '全部' : selectedRole.permissionIds.length} +
+
+
+ +
+ {new Date(selectedRole.createdAt).toLocaleString('zh-CN')} +
+
+
+ +
+ {new Date(selectedRole.updatedAt).toLocaleString('zh-CN')} +
+
+
+
+ + + +
+
+ ); +} \ No newline at end of file diff --git a/crop-x/src/app/(app)/central-config/user/role/components/RoleFormDialog.tsx b/crop-x/src/app/(app)/central-config/user/role/components/RoleFormDialog.tsx new file mode 100644 index 0000000..61a0ad7 --- /dev/null +++ b/crop-x/src/app/(app)/central-config/user/role/components/RoleFormDialog.tsx @@ -0,0 +1,235 @@ +'use client'; + +import React from 'react'; +import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; +import { Checkbox } from '@/components/ui/checkbox'; +import { Card } from '@/components/ui/card'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { ChevronRight } from 'lucide-react'; +import { Role, RoleFormData, allSystemMenus } from '../types'; + +interface RoleFormDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + editingRole: Role | null; + formData: RoleFormData; + onFormDataChange: (data: RoleFormData) => void; + onSave: () => void; +} + +export function RoleFormDialog({ + open, + onOpenChange, + editingRole, + formData, + onFormDataChange, + onSave +}: RoleFormDialogProps) { + return ( + + + + {editingRole ? '编辑角色' : '添加角色'} + + {editingRole ? '编辑角色信息' : '添加新角色'} + + + +
+ {/* 基本信息 */} +
+

基本信息

+
+
+ + onFormDataChange({ ...formData, name: e.target.value })} + placeholder="如:数据分析员" + /> +
+
+ + onFormDataChange({ ...formData, code: e.target.value })} + placeholder="如:data_analyst" + /> +
+
+
+ +