完成性能日志和事务日志功能

Change-Id: Iec30043bc5b954a29fa0d8d18a84c1feed2a1696
diff --git a/Merge/front/package.json b/Merge/front/package.json
index 31c1d3e..54a8783 100644
--- a/Merge/front/package.json
+++ b/Merge/front/package.json
@@ -14,7 +14,8 @@
     "web-vitals": "^2.1.4",
     "lucide-react": "^0.468.0",
     "antd": "^4.24.0",
-    "crypto-js": "^4.2.0"
+    "crypto-js": "^4.2.0",
+    "recharts": "^2.1.9"
   },
   "scripts": {
     "start": "react-scripts start",
diff --git a/Merge/front/src/App.jsx b/Merge/front/src/App.jsx
index 770bc0a..3ab9fca 100644
--- a/Merge/front/src/App.jsx
+++ b/Merge/front/src/App.jsx
@@ -1,22 +1,19 @@
-import React from 'react'
-import { BrowserRouter as Router } from 'react-router-dom'
-import Header from './components/Header'
-import Sidebar from './components/Sidebar'
-import AppRoutes from './router/App'
-import './App.css'
+import React from 'react';
+import Header from './components/Header';
+import Sidebar from './components/Sidebar';
+import AppRoutes from './router/App';
+import './App.css';
 
 export default function App() {
   return (
-    <Router>
-      <div className="app">
-        <Header />
-        <Sidebar />
-        <main className="main-content">
-          <div className="content-wrapper">
-            <AppRoutes />
-          </div>
-        </main>
-      </div>
-    </Router>
-  )
-}
+    <div className="app">
+      <Header />
+      <Sidebar />
+      <main className="main-content">
+        <div className="content-wrapper">
+          <AppRoutes />
+        </div>
+      </main>
+    </div>
+  );
+}
\ No newline at end of file
diff --git a/Merge/front/src/api/posts.js b/Merge/front/src/api/posts_trm.js
similarity index 68%
rename from Merge/front/src/api/posts.js
rename to Merge/front/src/api/posts_trm.js
index 37acf43..f28ec19 100644
--- a/Merge/front/src/api/posts.js
+++ b/Merge/front/src/api/posts_trm.js
@@ -128,4 +128,64 @@
   })
   if (!res.ok) throw new Error(`giveUser: ${res.status}`)
   return res.json()
+}
+
+/**
+ * 获取事务日志
+ * POST /getrecordlog
+ * @param {number|string} userId 平台管理员的用户 ID
+ * @returns Promise<[ {id, user_id, type, content, ip, created_at}, … ]>
+ */
+export async function fetchRecordLog(userId) {
+  const res = await fetch(`${BASE}/getrecordlog`, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify({ userid: userId })
+  })
+  if (!res.ok) throw new Error(`fetchRecordLog: ${res.status}`)
+  const json = await res.json()
+  console.log('fetchRecordLog response:', json)
+  if (json.status === 'error' && json.message === 'Unauthorized') {
+    throw new Error('Unauthorized')
+  }
+  let list
+  if (Array.isArray(json)) {
+    list = json
+  } else if (Array.isArray(json.data)) {
+    list = json.data
+  } else {
+    list = []
+  }
+  console.log('Normalized record log list:', list)
+  return list
+}
+
+/**
+ * 获取系统性能消耗数据
+ * POST /getsyscost
+ * @param {number|string} userId 平台管理员的用户 ID
+ * @returns Promise<[ {id, record_time, endpoint, elapsed_time, cpu_user, cpu_system, memory_rss}, … ]>
+ */
+export async function fetchSysCost(userId) {
+  const res = await fetch(`${BASE}/getsyscost`, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify({ userid: userId })
+  })
+  if (!res.ok) throw new Error(`fetchSysCost: ${res.status}`)
+  const json = await res.json()
+  console.log('fetchSysCost response:', json)
+  if (json.status === 'error' && json.message === 'Unauthorized') {
+    throw new Error('Unauthorized')
+  }
+  let list
+  if (Array.isArray(json)) {
+    list = json
+  } else if (Array.isArray(json.data)) {
+    list = json.data
+  } else {
+    list = []
+  }
+  console.log('Normalized sys cost list:', list)
+  return list
 }
\ No newline at end of file
diff --git a/Merge/front/src/components/Admin.js b/Merge/front/src/components/Admin.js
index da11100..2d97495 100644
--- a/Merge/front/src/components/Admin.js
+++ b/Merge/front/src/components/Admin.js
@@ -2,7 +2,7 @@
 import React, { useState, useEffect, useMemo, useCallback } from 'react';
 import { Layout, Tabs, Input, List, Card, Button, Tag, Spin, Typography, Divider } from 'antd';
 import '../style/Admin.css';
-import { fetchPosts, approvePost, rejectPost } from '../api/posts';
+import { fetchPosts, approvePost, rejectPost } from '../api/posts_trm';
 
 export default function Admin() {
   const ADMIN_USER_ID = 2;
diff --git a/Merge/front/src/components/LogsDashboard.js b/Merge/front/src/components/LogsDashboard.js
index 1bd6cb7..22047e2 100644
--- a/Merge/front/src/components/LogsDashboard.js
+++ b/Merge/front/src/components/LogsDashboard.js
@@ -1,4 +1,5 @@
 import React, { useEffect, useState } from 'react';
+import { NavLink, Outlet } from 'react-router-dom';
 import '../style/Admin.css';
 
 function LogsDashboard() {
@@ -19,25 +20,17 @@
   return (
     <div className="admin-container">
       <h2>运行日志 & 性能 Dashboard</h2>
-      <section className="dashboard-stats">
-        <pre>{JSON.stringify(stats, null, 2)}</pre>
-      </section>
-      <section className="dashboard-logs">
-        <table className="admin-table">
-          <thead>
-            <tr><th>时间</th><th>级别</th><th>消息</th></tr>
-          </thead>
-          <tbody>
-            {logs.map((log, i) => (
-              <tr key={i}>
-                <td>{new Date(log.time).toLocaleString()}</td>
-                <td>{log.level}</td>
-                <td>{log.message}</td>
-              </tr>
-            ))}
-          </tbody>
-        </table>
-      </section>
+      <nav className="dashboard-nav">
+        <NavLink to="transactions" className={({ isActive }) => isActive ? 'active' : ''}>
+          事务日志
+        </NavLink>
+        <NavLink to="performance" className={({ isActive }) => isActive ? 'active' : ''}>
+          性能日志
+        </NavLink>
+      </nav>
+
+      {/* nested routes will render here */}
+      <Outlet />
     </div>
   );
 }
diff --git a/Merge/front/src/components/PerformanceLogs.js b/Merge/front/src/components/PerformanceLogs.js
new file mode 100644
index 0000000..bb1c244
--- /dev/null
+++ b/Merge/front/src/components/PerformanceLogs.js
@@ -0,0 +1,44 @@
+import React, { useState, useEffect } from 'react';
+import {
+  ResponsiveContainer, LineChart, Line,
+  XAxis, YAxis, Tooltip, CartesianGrid, Legend
+} from 'recharts';
+import { fetchSysCost } from '../api/posts_trm';
+
+function PerformanceLogs({ userId }) {
+  const [data, setData] = useState([]);
+
+  useEffect(() => {
+    fetchSysCost(userId)
+      .then(list => setData(list))
+      .catch(err => console.error('fetchSysCost error:', err));
+  }, [userId]);
+
+  return (
+    <section className="dashboard-performance">
+      <ResponsiveContainer width="100%" height={400}>
+        <LineChart
+          data={data}
+          margin={{ top: 20, right: 30, left: 20, bottom: 5 }}
+        >
+          <CartesianGrid strokeDasharray="3 3" />
+          <XAxis dataKey="record_time" />
+          <YAxis
+            domain={[0, 'auto']}
+            label={{ value: '时间 (s)', angle: -90, position: 'insideLeft' }}
+            tickFormatter={val => (val / 1000).toFixed(2)}
+          />
+          <Tooltip formatter={val => (typeof val === 'number' ? (val/1000).toFixed(2) + 's' : val)} />
+          <Legend />
+          <Line type="monotone" dataKey="elapsed_time" stroke="#8884d8" name="响应时间" />
+          <Line type="monotone" dataKey="cpu_user" stroke="#82ca9d" name="CPU 用户时间" />
+          <Line type="monotone" dataKey="cpu_system" stroke="#ffc658" name="CPU 系统时间" />
+          <Line type="monotone" dataKey="memory_rss" stroke="#ff7300" name="内存 RSS" />
+          {/* 如需更多指标,可再添其他 <Line /> */}
+        </LineChart>
+      </ResponsiveContainer>
+    </section>
+  );
+}
+
+export default PerformanceLogs;
diff --git a/Merge/front/src/components/SuperAdmin.js b/Merge/front/src/components/SuperAdmin.js
index 817b708..f24e5d4 100644
--- a/Merge/front/src/components/SuperAdmin.js
+++ b/Merge/front/src/components/SuperAdmin.js
@@ -1,7 +1,7 @@
 import React, { useState, useEffect } from 'react';
 import { NavLink, Outlet } from 'react-router-dom';
 import { Spin } from 'antd';
-import { fetchUserList } from '../api/posts';
+import { fetchUserList } from '../api/posts_trm';
 import '../style/SuperAdmin.css';
 
 export default function SuperAdmin() {
diff --git a/Merge/front/src/components/TransactionLogs.js b/Merge/front/src/components/TransactionLogs.js
new file mode 100644
index 0000000..9df8f67
--- /dev/null
+++ b/Merge/front/src/components/TransactionLogs.js
@@ -0,0 +1,50 @@
+import React, { useState, useEffect } from 'react';
+import { fetchRecordLog } from '../api/posts_trm';
+
+function TransactionLogs({ userId }) {
+  const [records, setRecords] = useState([]);
+
+  useEffect(() => {
+    fetchRecordLog(userId)
+      .then(data => setRecords(data))
+      .catch(err => console.error('fetchRecordLog error:', err));
+  }, [userId]);
+
+  return (
+    <section className="dashboard-logs">
+      <table className="admin-table">
+        <thead>
+          <tr>
+            <th>ID</th>
+            <th>用户ID</th>
+            <th>类型</th>
+            <th>内容</th>
+            <th>IP</th>
+            <th>创建时间</th>
+          </tr>
+        </thead>
+        <tbody>
+          {records.length > 0
+            ? records.map((r, i) => (
+                <tr key={i}>
+                  <td>{r.id}</td>
+                  <td>{r.user_id}</td>
+                  <td>{r.type}</td>
+                  <td>{r.content}</td>
+                  <td>{r.ip}</td>
+                  <td>{new Date(r.created_at).toLocaleString()}</td>
+                </tr>
+              ))
+            : (
+              <tr>
+                <td colSpan="6" style={{ textAlign: 'center' }}>暂无数据</td>
+              </tr>
+            )
+          }
+        </tbody>
+      </table>
+    </section>
+  );
+}
+
+export default TransactionLogs;
diff --git a/Merge/front/src/components/UserManagement.js b/Merge/front/src/components/UserManagement.js
index 4bd05c5..a48f8cf 100644
--- a/Merge/front/src/components/UserManagement.js
+++ b/Merge/front/src/components/UserManagement.js
@@ -1,7 +1,7 @@
 import React, { useState, useEffect } from 'react';
 import '../style/Admin.css';
 import { Select, message, Table } from 'antd';
-import { fetchUserList, giveUser, giveAdmin, giveSuperAdmin } from '../api/posts';
+import { fetchUserList, giveUser, giveAdmin, giveSuperAdmin } from '../api/posts_trm';
 
 const { Option } = Select;
 const ROLE_LIST = ['用户', '管理员', '超级管理员'];
diff --git a/Merge/front/src/index.js b/Merge/front/src/index.js
index 1ce450d..56520f8 100644
--- a/Merge/front/src/index.js
+++ b/Merge/front/src/index.js
@@ -8,8 +8,8 @@
 const root = ReactDOM.createRoot(document.getElementById('root'));
 root.render(
   <React.StrictMode>
-   
-    <App />
-    
+    <BrowserRouter>
+      <App />
+    </BrowserRouter>
   </React.StrictMode>
 );
\ No newline at end of file
diff --git a/Merge/front/src/router/App.js b/Merge/front/src/router/App.js
index e1b9454..4d606f9 100644
--- a/Merge/front/src/router/App.js
+++ b/Merge/front/src/router/App.js
@@ -20,6 +20,9 @@
 import ForgotPasswordPage from '../pages/ForgotPasswordPage/ForgotPasswordPage';
 import TestDashboard from '../pages/TestDashboard/TestDashboard';
 
+import TransactionLogs from '../components/TransactionLogs';
+import PerformanceLogs from '../components/PerformanceLogs';
+
 export default function AppRoutes() {
   return (
     <Routes>
@@ -44,9 +47,6 @@
       <Route path="/forgot-password" element={<ForgotPasswordPage />} />
       <Route path="/test-dashboard" element={<TestDashboard />} />
 
-      {/* 最后一个兜底 */}
-      <Route path="*" element={<PlaceholderPage pageId="home" />} />
-
       {/* 普通管理员,无 header */}
       <Route path="admin" element={<AdminPage />} />
 
@@ -54,8 +54,17 @@
       <Route path="superadmin" element={<SuperAdmin />}>
         <Route index element={<Navigate to="users" replace />} />
         <Route path="users" element={<UserManagement superAdminId={3} />} />
-        <Route path="dashboard" element={<LogsDashboard />} />
+
+        {/* dashboard as layout */}
+        <Route path="dashboard" element={<LogsDashboard />}>
+          <Route index element={<Navigate to="transactions" replace />} />
+          <Route path="transactions" element={<TransactionLogs userId={1} />} />
+          <Route path="performance" element={<PerformanceLogs userId={1} />} />
+        </Route>
       </Route>
+
+      {/* 最后一个兜底,放在最末尾 */}
+      <Route path="*" element={<PlaceholderPage pageId="home" />} />
     </Routes>
   );
 }
\ No newline at end of file
diff --git a/Merge/front/src/style/Admin.css b/Merge/front/src/style/Admin.css
index 4a5bcb7..90e244d 100644
--- a/Merge/front/src/style/Admin.css
+++ b/Merge/front/src/style/Admin.css
@@ -386,4 +386,9 @@
 .ant-layout-sider .ant-menu-item:nth-child(1),
 .ant-layout-sider .ant-menu-item:nth-child(2) {
   color: var(--xiaohongshu-red) !important;
+}
+
+.dashboard-nav {
+  display: flex;
+  gap: 1rem;
 }
\ No newline at end of file