From 7a2d8e4664877a4b43a354b5494e4c3cb78f410e Mon Sep 17 00:00:00 2001 From: iven Date: Sat, 25 Apr 2026 10:53:58 +0800 Subject: [PATCH] =?UTF-8?q?fix(web):=20=E5=89=8D=E7=AB=AF=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E9=AA=8C=E8=AF=81=E4=BF=AE=E5=A4=8D=20=E2=80=94=20?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E7=A1=AC=E7=BC=96=E7=A0=81=E5=81=87=E6=95=B0?= =?UTF-8?q?=E6=8D=AE/=E4=BF=AE=E6=AD=A3=E7=B3=BB=E7=BB=9F=E4=BF=A1?= =?UTF-8?q?=E6=81=AF/=E4=BF=AE=E5=A4=8Ddev.ps1=E7=8E=AF=E5=A2=83=E5=8F=98?= =?UTF-8?q?=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - refactor(Home): 待办任务改为从工作流API获取真实数据 - refactor(Home): 最近动态改为从审计日志API获取真实操作记录 - refactor(Home): 移除硬编码的sparkline趋势图和假统计数据 - fix(Home): 系统信息 PostgreSQL 16→18,模块数量 5→6 - fix(Login): 移除硬编码版本号 v0.1.0 - fix(MainLayout): Footer 更新为 "HMS 健康管理平台" - fix(dev.ps1): 添加缺失的 WECHAT/HEALTH 环境变量 --- apps/web/src/layouts/MainLayout.tsx | 2 +- apps/web/src/pages/Home.tsx | 268 +++++++++++++++------------- apps/web/src/pages/Login.tsx | 2 +- dev.ps1 | 12 ++ 4 files changed, 155 insertions(+), 129 deletions(-) diff --git a/apps/web/src/layouts/MainLayout.tsx b/apps/web/src/layouts/MainLayout.tsx index 2beea29..fff5e24 100644 --- a/apps/web/src/layouts/MainLayout.tsx +++ b/apps/web/src/layouts/MainLayout.tsx @@ -397,7 +397,7 @@ export default function MainLayout({ children }: { children: React.ReactNode }) {/* 底部 */} diff --git a/apps/web/src/pages/Home.tsx b/apps/web/src/pages/Home.tsx index 2ada2a5..df7f6fc 100644 --- a/apps/web/src/pages/Home.tsx +++ b/apps/web/src/pages/Home.tsx @@ -1,5 +1,5 @@ import { useEffect, useState, useCallback, useRef } from 'react'; -import { Row, Col, Spin } from 'antd'; +import { Row, Col, Spin, Empty } from 'antd'; import { UserOutlined, SafetyCertificateOutlined, @@ -11,16 +11,14 @@ import { ClockCircleOutlined, ApartmentOutlined, CheckCircleOutlined, - TeamOutlined, - FileProtectOutlined, - RiseOutlined, - FallOutlined, RightOutlined, } from '@ant-design/icons'; import { useNavigate } from 'react-router-dom'; import client from '../api/client'; import { useThemeMode } from '../hooks/useThemeMode'; import { useMessageStore } from '../stores/message'; +import { listAuditLogs, type AuditLogItem } from '../api/auditLogs'; +import { listPendingTasks, type TaskInfo } from '../api/workflowTasks'; interface DashboardStats { userCount: number; @@ -29,12 +27,6 @@ interface DashboardStats { unreadMessages: number; } -interface TrendData { - value: string; - direction: 'up' | 'down' | 'neutral'; - label: string; -} - interface StatCardConfig { key: string; title: string; @@ -43,29 +35,9 @@ interface StatCardConfig { gradient: string; iconBg: string; delay: string; - trend: TrendData; - sparkline: number[]; onClick?: () => void; } -interface TaskItem { - id: string; - title: string; - priority: 'high' | 'medium' | 'low'; - assignee: string; - dueText: string; - color: string; - icon: React.ReactNode; - path: string; -} - -interface ActivityItem { - id: string; - text: string; - time: string; - icon: React.ReactNode; -} - function useCountUp(end: number, duration = 800) { const [count, setCount] = useState(0); const prevEnd = useRef(end); @@ -99,6 +71,54 @@ function StatValue({ value, loading }: { value: number; loading: boolean }) { return {animatedValue.toLocaleString()}; } +const ACTION_LABELS: Record = { + create: '创建', update: '更新', delete: '删除', + login: '登录', user_login: '登录', 'user.login': '登录', + 'user.create': '创建', 'user.update': '更新', 'user.delete': '删除', + 'role.create': '创建', 'role.update': '更新', 'role.delete': '删除', + 'patient.create': '创建', 'patient.update': '更新', + 'appointment.create': '创建', 'appointment.update': '更新', +}; +const RESOURCE_LABELS: Record = { + user: '用户', role: '角色', permission: '权限', + organization: '组织', department: '部门', position: '岗位', + dictionary: '字典', menu: '菜单', setting: '设置', + process_definition: '流程定义', process_instance: '流程实例', + message: '消息', plugin: '插件', + patient: '患者', doctor: '医护', appointment: '预约', + follow_up_task: '随访', consultation_session: '咨询', + auth: '认证', +}; +const RESOURCE_ICONS: Record = { + user: , role: , + organization: , process_definition: , + process_instance: , message: , + patient: , doctor: , +}; + +function formatTimeAgo(dateStr: string): string { + const diff = Date.now() - new Date(dateStr).getTime(); + const minutes = Math.floor(diff / 60000); + if (minutes < 1) return '刚刚'; + if (minutes < 60) return `${minutes} 分钟前`; + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours} 小时前`; + const days = Math.floor(hours / 24); + return `${days} 天前`; +} + +function formatActionLabel(action: string): string { + if (ACTION_LABELS[action]) return ACTION_LABELS[action]; + const lastPart = action.split('.').pop() || action; + return ACTION_LABELS[lastPart] || lastPart; +} + +function formatResourceLabel(resource: string): string { + if (RESOURCE_LABELS[resource]) return RESOURCE_LABELS[resource]; + const lastPart = resource.split('.').pop() || resource; + return RESOURCE_LABELS[lastPart] || lastPart; +} + export default function Home() { const [stats, setStats] = useState({ userCount: 0, @@ -107,6 +127,9 @@ export default function Home() { unreadMessages: 0, }); const [loading, setLoading] = useState(true); + const [pendingTasks, setPendingTasks] = useState([]); + const [recentActivities, setRecentActivities] = useState([]); + const [activitiesLoading, setActivitiesLoading] = useState(true); const unreadCount = useMessageStore((s) => s.unreadCount); const fetchUnreadCount = useMessageStore((s) => s.fetchUnreadCount); const navigate = useNavigate(); @@ -150,8 +173,31 @@ export default function Home() { } } + async function loadTasks() { + try { + const result = await listPendingTasks(1, 5); + if (!cancelled) setPendingTasks(result.data); + } catch { + // 静默处理 + } + } + + async function loadActivities() { + setActivitiesLoading(true); + try { + const result = await listAuditLogs({ page: 1, page_size: 5 }); + if (!cancelled) setRecentActivities(result.data); + } catch { + // 静默处理 + } finally { + if (!cancelled) setActivitiesLoading(false); + } + } + fetchUnreadCount(); loadStats(); + loadTasks(); + loadActivities(); return () => { cancelled = true; }; // eslint-disable-next-line react-hooks/exhaustive-deps @@ -170,8 +216,6 @@ export default function Home() { gradient: 'linear-gradient(135deg, #2563eb, #60a5fa)', iconBg: 'rgba(79, 70, 229, 0.12)', delay: 'erp-fade-in erp-fade-in-delay-1', - trend: { value: '+2', direction: 'up', label: '较上周' }, - sparkline: [30, 45, 35, 50, 40, 55, 60, 50, 65, 70], onClick: () => handleNavigate('/users'), }, { @@ -182,8 +226,6 @@ export default function Home() { gradient: 'linear-gradient(135deg, #059669, #10B981)', iconBg: 'rgba(5, 150, 105, 0.12)', delay: 'erp-fade-in erp-fade-in-delay-2', - trend: { value: '+1', direction: 'up', label: '较上月' }, - sparkline: [20, 25, 30, 28, 35, 40, 38, 42, 45, 50], onClick: () => handleNavigate('/roles'), }, { @@ -194,8 +236,6 @@ export default function Home() { gradient: 'linear-gradient(135deg, #d97706, #F59E0B)', iconBg: 'rgba(217, 119, 6, 0.12)', delay: 'erp-fade-in erp-fade-in-delay-3', - trend: { value: '0', direction: 'neutral', label: '较昨日' }, - sparkline: [10, 15, 12, 20, 18, 25, 22, 28, 24, 20], onClick: () => handleNavigate('/workflow'), }, { @@ -206,8 +246,6 @@ export default function Home() { gradient: 'linear-gradient(135deg, #E11D48, #F43F5E)', iconBg: 'rgba(225, 29, 72, 0.12)', delay: 'erp-fade-in erp-fade-in-delay-4', - trend: { value: '0', direction: 'neutral', label: '全部已读' }, - sparkline: [5, 8, 3, 10, 6, 12, 8, 4, 7, 5], onClick: () => handleNavigate('/messages'), }, ]; @@ -221,21 +259,6 @@ export default function Home() { { icon: , label: '系统设置', path: '/settings', color: '#475569' }, ]; - const pendingTasks: TaskItem[] = [ - { id: '1', title: '审核新用户注册申请', priority: 'high', assignee: '系统', dueText: '待处理', color: '#dc2626', icon: , path: '/users' }, - { id: '2', title: '配置工作流审批节点', priority: 'medium', assignee: '管理员', dueText: '进行中', color: '#d97706', icon: , path: '/workflow' }, - { id: '3', title: '更新角色权限策略', priority: 'low', assignee: '管理员', dueText: '计划中', color: '#059669', icon: , path: '/roles' }, - ]; - - const recentActivities: ActivityItem[] = [ - { id: '1', text: '系统管理员 创建了 管理员角色', time: '刚刚', icon: }, - { id: '2', text: '系统管理员 配置了 工作流模板', time: '5 分钟前', icon: }, - { id: '3', text: '系统管理员 更新了 组织架构', time: '10 分钟前', icon: }, - { id: '4', text: '系统管理员 设置了 消息通知偏好', time: '30 分钟前', icon: }, - ]; - - const priorityLabel: Record = { high: '紧急', medium: '一般', low: '低' }; - return (
{/* 欢迎语 */} @@ -256,50 +279,29 @@ export default function Home() { {/* 统计卡片行 */} - {statCards.map((card) => { - const maxSpark = Math.max(...card.sparkline, 1); - return ( - -
{ if (e.key === 'Enter') card.onClick?.(); }} - > -
-
-
-
{card.title}
-
- -
-
- {card.trend.direction === 'up' && } - {card.trend.direction === 'down' && } - {card.trend.value} - {card.trend.label} -
+ {statCards.map((card) => ( + +
{ if (e.key === 'Enter') card.onClick?.(); }} + > +
+
+
+
{card.title}
+
+
-
{card.icon}
-
-
- {card.sparkline.map((v, i) => ( -
- ))}
+
{card.icon}
- - ); - })} +
+ + ))} {/* 待办任务 + 最近活动 */} @@ -319,30 +321,32 @@ export default function Home() {
- {pendingTasks.map((task) => ( -
handleNavigate(task.path)} - role="button" - tabIndex={0} - onKeyDown={(e) => { if (e.key === 'Enter') handleNavigate(task.path); }} - > -
{task.icon}
-
-
{task.title}
-
- {task.assignee} - {task.dueText} + {pendingTasks.length === 0 ? ( + + ) : ( + pendingTasks.map((task) => ( +
handleNavigate('/workflow')} + role="button" + tabIndex={0} + onKeyDown={(e) => { if (e.key === 'Enter') handleNavigate('/workflow'); }} + > +
+
+
{task.node_name || task.definition_name || '流程任务'}
+
+ {task.definition_name || '工作流'} + {task.status === 'pending' ? '待处理' : task.status} +
+ 一般 +
- - {priorityLabel[task.priority]} - - -
- ))} + )) + )}
@@ -355,15 +359,25 @@ export default function Home() { 最近动态
- {recentActivities.map((activity) => ( -
-
{activity.icon}
-
-
{activity.text}
-
{activity.time}
+ {activitiesLoading ? ( +
+ ) : recentActivities.length === 0 ? ( + + ) : ( + recentActivities.map((log) => ( +
+
+ {RESOURCE_ICONS[log.resource_type] || } +
+
+
+ {formatActionLabel(log.action)}了{formatResourceLabel(log.resource_type)} +
+
{formatTimeAgo(log.created_at)}
+
-
- ))} + )) + )}
@@ -407,10 +421,10 @@ export default function Home() { {[ { label: '系统版本', value: 'v0.1.0' }, { label: '后端框架', value: 'Axum 0.8 + Tokio' }, - { label: '数据库', value: 'PostgreSQL 16' }, + { label: '数据库', value: 'PostgreSQL 18' }, { label: '缓存', value: 'Redis 7' }, { label: '前端框架', value: 'React 19 + Ant Design 6' }, - { label: '模块数量', value: '5 个业务模块' }, + { label: '模块数量', value: '6 个业务模块' }, ].map((item) => (
{item.label} diff --git a/apps/web/src/pages/Login.tsx b/apps/web/src/pages/Login.tsx index af215cb..ee17fbd 100644 --- a/apps/web/src/pages/Login.tsx +++ b/apps/web/src/pages/Login.tsx @@ -198,7 +198,7 @@ export default function Login() {

- ERP Platform v0.1.0 · Powered by Rust + React + ERP Platform · Powered by Rust + React

diff --git a/dev.ps1 b/dev.ps1 index 9ff7c5b..73b6ed9 100644 --- a/dev.ps1 +++ b/dev.ps1 @@ -19,6 +19,16 @@ $BackendPort = 3000 $FrontendPort = 5174 $LogDir = ".logs" +# --- environment variables --- +$env:ERP__DATABASE__URL = "postgres://postgres:123123@localhost:5432/erp" +$env:ERP__JWT__SECRET = "dev-secret-key-change-in-prod" +$env:ERP__AUTH__SUPER_ADMIN_PASSWORD = "Admin@2026" +$env:ERP__REDIS__URL = "redis://:redis_KBCYJk@129.204.154.246:6379" +$env:ERP__WECHAT__APPID = "wx20f4ef9cc2ec66c5" +$env:ERP__WECHAT__SECRET = "placeholder_wechat_secret" +$env:ERP__HEALTH__AES_KEY = "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2" +$env:ERP__HEALTH__HMAC_KEY = "f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5d4c3b2a1f6e5" + # --- find PID using port --- function Find-PortPid([int]$Port) { try { @@ -135,7 +145,9 @@ function Start-Services { $backendLog = Join-Path $LogDir "backend.log" $backendErr = Join-Path $LogDir "backend.err" + $backendDir = Join-Path $PSScriptRoot "crates\erp-server" $proc = Start-Process -FilePath "cargo" -ArgumentList "run","-p","erp-server" ` + -WorkingDirectory $backendDir ` -RedirectStandardOutput $backendLog -RedirectStandardError $backendErr ` -WindowStyle Hidden -PassThru