增加后台管理项目;
This commit is contained in:
32
backstage/eslint.config.js
Normal file
32
backstage/eslint.config.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
|
||||
export default [
|
||||
{ ignores: ['dist'] },
|
||||
{
|
||||
files: ['**/*.{js,jsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2022,
|
||||
globals: globals.browser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
ecmaFeatures: { jsx: true },
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...js.configs.recommended.rules,
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
13
backstage/index.html
Normal file
13
backstage/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>后台管理系统 - 会话存档管理</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
32
backstage/package.json
Normal file
32
backstage/package.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "backstage",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"axios": "^1.13.2",
|
||||
"daisyui": "^5.5.5",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-router-dom": "^7.3.0",
|
||||
"tailwindcss": "^4.1.18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@types/react": "^19.2.5",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.24",
|
||||
"globals": "^16.5.0",
|
||||
"vite": "^7.2.4"
|
||||
}
|
||||
}
|
||||
2380
backstage/pnpm-lock.yaml
generated
Normal file
2380
backstage/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
backstage/src/App.jsx
Normal file
26
backstage/src/App.jsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'
|
||||
import Layout from './components/layout/Layout'
|
||||
import Dashboard from './pages/Dashboard'
|
||||
import Messages from './pages/Messages'
|
||||
import Settings from './pages/Settings'
|
||||
import Login from './pages/Login'
|
||||
|
||||
function App() {
|
||||
// 简单的认证检查(实际项目中应该使用更完善的认证方案)
|
||||
const isAuthenticated = true // 暂时设为 true 以便测试
|
||||
|
||||
return (
|
||||
<Router basename="/chat_bm">
|
||||
<Routes>
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/" element={isAuthenticated ? <Layout /> : <Navigate to="/login" />}>
|
||||
<Route index element={<Dashboard />} />
|
||||
<Route path="messages" element={<Messages />} />
|
||||
<Route path="settings" element={<Settings />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Router>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
115
backstage/src/components/layout/Layout.jsx
Normal file
115
backstage/src/components/layout/Layout.jsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import { Outlet, Link, useLocation } from 'react-router-dom'
|
||||
import { useState } from 'react'
|
||||
|
||||
const Layout = () => {
|
||||
const location = useLocation()
|
||||
const [sidebarOpen, setSidebarOpen] = useState(true)
|
||||
|
||||
const navigation = [
|
||||
{ name: '仪表板', href: '/', icon: '🏠', current: location.pathname === '/' },
|
||||
{ name: '消息管理', href: '/messages', icon: '💬', current: location.pathname === '/messages' },
|
||||
{ name: '系统设置', href: '/settings', icon: '⚙️', current: location.pathname === '/settings' },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-base-100">
|
||||
{/* 侧边栏 */}
|
||||
<div className={`fixed inset-y-0 left-0 z-50 w-64 bg-base-200 shadow-xl transition-transform duration-300 ${sidebarOpen ? 'translate-x-0' : '-translate-x-full'}`}>
|
||||
<div className="flex h-16 items-center justify-between border-b border-base-300 px-4">
|
||||
<div className="flex items-center">
|
||||
<div className="text-2xl font-bold text-primary">后台管理</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
className="btn btn-ghost btn-sm"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav className="flex-1 space-y-1 p-4">
|
||||
{navigation.map((item) => (
|
||||
<Link
|
||||
key={item.name}
|
||||
to={item.href}
|
||||
className={`flex items-center rounded-lg px-3 py-2 text-sm font-medium transition-colors ${
|
||||
item.current
|
||||
? 'bg-primary text-primary-content'
|
||||
: 'text-base-content hover:bg-base-300'
|
||||
}`}
|
||||
>
|
||||
<span className="mr-3 text-lg">{item.icon}</span>
|
||||
{item.name}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<div className="border-t border-base-300 p-4">
|
||||
<div className="flex items-center">
|
||||
<div className="avatar placeholder">
|
||||
<div className="bg-neutral text-neutral-content w-8 rounded-full">
|
||||
<span className="text-xs">AD</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<p className="text-sm font-medium">管理员</p>
|
||||
<p className="text-xs text-base-content/70">admin@example.com</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 主内容区 */}
|
||||
<div className={`transition-all duration-300 ${sidebarOpen ? 'ml-64' : 'ml-0'}`}>
|
||||
{/* 顶部导航栏 */}
|
||||
<header className="sticky top-0 z-40 flex h-16 items-center justify-between border-b border-base-300 bg-base-100 px-6 shadow-sm">
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
onClick={() => setSidebarOpen(!sidebarOpen)}
|
||||
className="btn btn-ghost btn-sm mr-4"
|
||||
>
|
||||
{sidebarOpen ? '◀' : '▶'}
|
||||
</button>
|
||||
<h1 className="text-lg font-semibold">
|
||||
{navigation.find(item => item.current)?.name || '后台管理系统'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
<button className="btn btn-ghost btn-sm">
|
||||
🔔
|
||||
<span className="badge badge-xs badge-primary absolute -top-1 -right-1">3</span>
|
||||
</button>
|
||||
<div className="dropdown dropdown-end">
|
||||
<div tabIndex={0} role="button" className="btn btn-ghost btn-sm">
|
||||
<div className="avatar placeholder">
|
||||
<div className="bg-neutral text-neutral-content w-8 rounded-full">
|
||||
<span className="text-xs">AD</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul tabIndex={0} className="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2 shadow">
|
||||
<li><a>个人资料</a></li>
|
||||
<li><a>修改密码</a></li>
|
||||
<li className="divider my-1"></li>
|
||||
<li><a className="text-error">退出登录</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* 页面内容 */}
|
||||
<main className="p-6">
|
||||
<Outlet />
|
||||
</main>
|
||||
|
||||
{/* 页脚 */}
|
||||
<footer className="border-t border-base-300 p-4 text-center text-sm text-base-content/70">
|
||||
<p>© 2025 后台管理系统 - 会话存档管理 | 版本 1.0.0</p>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Layout
|
||||
35
backstage/src/components/ui/DocumentationViewer.jsx
Normal file
35
backstage/src/components/ui/DocumentationViewer.jsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
const DocumentationViewer = () => {
|
||||
const [library, setLibrary] = useState('react')
|
||||
const [mode, setMode] = useState('code')
|
||||
const [topic, setTopic] = useState('')
|
||||
const [page, setPage] = useState(1)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [docs, setDocs] = useState(null)
|
||||
const [error, setError] = useState('')
|
||||
|
||||
const libraries = [
|
||||
{ id: 'react', name: 'React', description: '用于构建用户界面的 JavaScript 库' },
|
||||
{ id: 'tailwindcss', name: 'Tailwind CSS', description: '实用优先的 CSS 框架' },
|
||||
{ id: 'daisyui', name: 'DaisyUI', description: 'Tailwind CSS 组件库' },
|
||||
{ id: 'vite', name: 'Vite', description: '下一代前端工具' },
|
||||
{ id: 'axios', name: 'Axios', description: '基于 Promise 的 HTTP 客户端' },
|
||||
]
|
||||
|
||||
const modes = [
|
||||
{ id: 'code', name: '代码文档', description: 'API 参考和代码示例' },
|
||||
{ id: 'info', name: '概念指南', description: '概念性指南和架构信息' },
|
||||
]
|
||||
|
||||
const handleFetchDocs = async () => {
|
||||
setLoading(true)
|
||||
setError('')
|
||||
setDocs(null)
|
||||
|
||||
try {
|
||||
// 这里应该调用 Context7 MCP 工具
|
||||
// 由于我们无法直接在前端调用 MCP 工具,这里模拟 API 调用
|
||||
// 实际项目中应该通过后端代理调用 MCP 工具
|
||||
|
||||
// 模拟延迟
|
||||
39
backstage/src/index.css
Normal file
39
backstage/src/index.css
Normal file
@@ -0,0 +1,39 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
/* DaisyUI 主题配置 */
|
||||
@plugin "daisyui" {
|
||||
themes: light --default, dark --prefersdark;
|
||||
}
|
||||
|
||||
/* 自定义样式 */
|
||||
:root {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
10
backstage/src/main.jsx
Normal file
10
backstage/src/main.jsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.jsx'
|
||||
import './index.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
)
|
||||
167
backstage/src/pages/Dashboard.jsx
Normal file
167
backstage/src/pages/Dashboard.jsx
Normal file
@@ -0,0 +1,167 @@
|
||||
const Dashboard = () => {
|
||||
const stats = [
|
||||
{ title: '总消息数', value: '1,234', change: '+12%', icon: '💬', color: 'bg-primary' },
|
||||
{ title: '今日新增', value: '56', change: '+5%', icon: '📈', color: 'bg-success' },
|
||||
{ title: '用户数', value: '89', change: '+8%', icon: '👥', color: 'bg-info' },
|
||||
{ title: '加载成功率', value: '98%', change: '+2%', icon: '✅', color: 'bg-warning' },
|
||||
]
|
||||
|
||||
const recentActivities = [
|
||||
{ id: 1, user: '张三', action: '加载了会话存档', time: '10分钟前', type: 'success' },
|
||||
{ id: 2, user: '李四', action: '查看了消息详情', time: '30分钟前', type: 'info' },
|
||||
{ id: 3, user: '王五', action: '更新了系统设置', time: '1小时前', type: 'warning' },
|
||||
{ id: 4, user: '赵六', action: '导出了消息数据', time: '2小时前', type: 'success' },
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 欢迎标题 */}
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">欢迎回来,管理员</h1>
|
||||
<p className="text-base-content/70 mt-2">这里是会话存档管理系统的控制面板</p>
|
||||
</div>
|
||||
|
||||
{/* 统计卡片 */}
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
|
||||
{stats.map((stat, index) => (
|
||||
<div key={index} className="card bg-base-100 shadow">
|
||||
<div className="card-body">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-base-content/70">{stat.title}</p>
|
||||
<p className="text-2xl font-bold mt-1">{stat.value}</p>
|
||||
<p className="text-xs mt-1">
|
||||
<span className="text-success">{stat.change}</span> 较昨日
|
||||
</p>
|
||||
</div>
|
||||
<div className={`${stat.color} rounded-lg p-3`}>
|
||||
<span className="text-2xl">{stat.icon}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 快速操作 */}
|
||||
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
{/* 最近活动 */}
|
||||
<div className="card bg-base-100 shadow">
|
||||
<div className="card-body">
|
||||
<h2 className="card-title">最近活动</h2>
|
||||
<div className="space-y-4">
|
||||
{recentActivities.map((activity) => (
|
||||
<div key={activity.id} className="flex items-center justify-between border-b border-base-300 pb-3 last:border-0">
|
||||
<div className="flex items-center">
|
||||
<div className={`avatar placeholder ${activity.type === 'success' ? 'bg-success' : activity.type === 'warning' ? 'bg-warning' : 'bg-info'}`}>
|
||||
<div className="text-white w-8 rounded-full">
|
||||
<span className="text-xs">{activity.user.charAt(0)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<p className="font-medium">{activity.user}</p>
|
||||
<p className="text-sm text-base-content/70">{activity.action}</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-sm text-base-content/70">{activity.time}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 快速操作卡片 */}
|
||||
<div className="card bg-base-100 shadow">
|
||||
<div className="card-body">
|
||||
<h2 className="card-title">快速操作</h2>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<button className="btn btn-primary">
|
||||
<span className="mr-2">📥</span>
|
||||
加载存档
|
||||
</button>
|
||||
<button className="btn btn-outline">
|
||||
<span className="mr-2">📊</span>
|
||||
查看统计
|
||||
</button>
|
||||
<button className="btn btn-outline">
|
||||
<span className="mr-2">🔍</span>
|
||||
搜索消息
|
||||
</button>
|
||||
<button className="btn btn-outline">
|
||||
<span className="mr-2">⚙️</span>
|
||||
系统设置
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="divider my-4"></div>
|
||||
|
||||
<div>
|
||||
<h3 className="font-medium mb-2">系统状态</h3>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span>后端服务</span>
|
||||
<span className="badge badge-success">运行中</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>数据库连接</span>
|
||||
<span className="badge badge-success">正常</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>API 可用性</span>
|
||||
<span className="badge badge-success">100%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 消息类型分布 */}
|
||||
<div className="card bg-base-100 shadow">
|
||||
<div className="card-body">
|
||||
<h2 className="card-title">消息类型分布</h2>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>消息类型</th>
|
||||
<th>数量</th>
|
||||
<th>占比</th>
|
||||
<th>趋势</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>文本消息</td>
|
||||
<td>856</td>
|
||||
<td>69%</td>
|
||||
<td className="text-success">↗ 增长</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>图片消息</td>
|
||||
<td>234</td>
|
||||
<td>19%</td>
|
||||
<td className="text-warning">→ 稳定</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>文件消息</td>
|
||||
<td>98</td>
|
||||
<td>8%</td>
|
||||
<td className="text-success">↗ 增长</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>语音消息</td>
|
||||
<td>46</td>
|
||||
<td>4%</td>
|
||||
<td className="text-error">↘ 下降</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Dashboard
|
||||
143
backstage/src/pages/Login.jsx
Normal file
143
backstage/src/pages/Login.jsx
Normal file
@@ -0,0 +1,143 @@
|
||||
import { useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
const Login = () => {
|
||||
const [username, setUsername] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const navigate = useNavigate()
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault()
|
||||
setError('')
|
||||
|
||||
if (!username || !password) {
|
||||
setError('请输入用户名和密码')
|
||||
return
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
|
||||
// 模拟登录 API 调用
|
||||
setTimeout(() => {
|
||||
setLoading(false)
|
||||
if (username === 'admin' && password === 'admin123') {
|
||||
// 登录成功,跳转到首页
|
||||
navigate('/')
|
||||
} else {
|
||||
setError('用户名或密码错误')
|
||||
}
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-primary/10 to-secondary/10 flex items-center justify-center p-4">
|
||||
<div className="card bg-base-100 shadow-2xl w-full max-w-md">
|
||||
<div className="card-body">
|
||||
{/* Logo 和标题 */}
|
||||
<div className="text-center mb-8">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-primary text-primary-content text-2xl font-bold mb-4">
|
||||
BS
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold">后台管理系统</h1>
|
||||
<p className="text-base-content/70 mt-2">会话存档管理平台</p>
|
||||
</div>
|
||||
|
||||
{/* 登录表单 */}
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{error && (
|
||||
<div className="alert alert-error">
|
||||
<span>{error}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">用户名</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="input input-bordered"
|
||||
placeholder="请输入用户名"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">密码</span>
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
className="input input-bordered"
|
||||
placeholder="请输入密码"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
disabled={loading}
|
||||
/>
|
||||
<label className="label">
|
||||
<a href="#" className="label-text-alt link link-hover">忘记密码?</a>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label cursor-pointer justify-start gap-3">
|
||||
<input type="checkbox" className="checkbox checkbox-primary" defaultChecked />
|
||||
<span className="label-text">记住我</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-control mt-6">
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-primary"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<span className="loading loading-spinner loading-sm"></span>
|
||||
登录中...
|
||||
</>
|
||||
) : (
|
||||
'登录'
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{/* 分隔线 */}
|
||||
<div className="divider my-6">或</div>
|
||||
|
||||
{/* 其他登录方式 */}
|
||||
<div className="space-y-3">
|
||||
<button className="btn btn-outline btn-block">
|
||||
<span className="mr-2">👤</span>
|
||||
使用访客账户登录
|
||||
</button>
|
||||
<button className="btn btn-outline btn-block">
|
||||
<span className="mr-2">🔑</span>
|
||||
使用 API Token 登录
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 底部信息 */}
|
||||
<div className="mt-8 text-center text-sm text-base-content/70">
|
||||
<p>默认账户:admin / admin123</p>
|
||||
<p className="mt-2">© 2025 后台管理系统 | 版本 1.0.0</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 背景装饰 */}
|
||||
<div className="absolute inset-0 -z-10 overflow-hidden">
|
||||
<div className="absolute -top-40 -right-40 w-80 h-80 bg-primary/20 rounded-full blur-3xl"></div>
|
||||
<div className="absolute -bottom-40 -left-40 w-80 h-80 bg-secondary/20 rounded-full blur-3xl"></div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Login
|
||||
265
backstage/src/pages/Messages.jsx
Normal file
265
backstage/src/pages/Messages.jsx
Normal file
@@ -0,0 +1,265 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
const Messages = () => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [messages, setMessages] = useState([
|
||||
{ id: 1, from: '张三', content: '你好,我想了解一下产品详情', time: '2025-12-12 10:30', type: 'text', room: '单聊' },
|
||||
{ id: 2, from: '李四', content: '这是会议纪要文件', time: '2025-12-12 09:15', type: 'file', room: '群聊-项目组' },
|
||||
{ id: 3, from: '王五', content: '产品图片已发送', time: '2025-12-11 16:45', type: 'image', room: '单聊' },
|
||||
{ id: 4, from: '赵六', content: '关于下周会议安排', time: '2025-12-11 14:20', type: 'text', room: '群聊-管理组' },
|
||||
{ id: 5, from: '钱七', content: '语音消息', time: '2025-12-10 11:10', type: 'voice', room: '单聊' },
|
||||
])
|
||||
|
||||
const handleLoadArchive = async () => {
|
||||
setLoading(true)
|
||||
// 模拟 API 调用
|
||||
setTimeout(() => {
|
||||
setLoading(false)
|
||||
alert('会话存档加载成功!')
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
const handleSearch = (e) => {
|
||||
e.preventDefault()
|
||||
// 搜索逻辑
|
||||
alert('搜索功能开发中...')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 页面标题和操作 */}
|
||||
<div className="flex flex-col justify-between gap-4 sm:flex-row sm:items-center">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">消息管理</h1>
|
||||
<p className="text-base-content/70 mt-2">管理企业微信会话存档消息</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
onClick={handleLoadArchive}
|
||||
disabled={loading}
|
||||
className="btn btn-primary"
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<span className="loading loading-spinner loading-sm"></span>
|
||||
加载中...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="mr-2">📥</span>
|
||||
加载会话存档
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<button className="btn btn-outline">
|
||||
<span className="mr-2">📊</span>
|
||||
查看统计
|
||||
</button>
|
||||
<button className="btn btn-outline">
|
||||
<span className="mr-2">📤</span>
|
||||
导出数据
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 搜索和过滤 */}
|
||||
<div className="card bg-base-100 shadow">
|
||||
<div className="card-body">
|
||||
<form onSubmit={handleSearch} className="flex flex-col gap-4 md:flex-row">
|
||||
<div className="flex-1">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="搜索消息内容、发送者..."
|
||||
className="input input-bordered w-full"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<select className="select select-bordered">
|
||||
<option value="">所有消息类型</option>
|
||||
<option value="text">文本消息</option>
|
||||
<option value="image">图片消息</option>
|
||||
<option value="file">文件消息</option>
|
||||
<option value="voice">语音消息</option>
|
||||
</select>
|
||||
<select className="select select-bordered">
|
||||
<option value="">所有聊天类型</option>
|
||||
<option value="single">单聊</option>
|
||||
<option value="group">群聊</option>
|
||||
</select>
|
||||
<button type="submit" className="btn btn-primary">
|
||||
<span className="mr-2">🔍</span>
|
||||
搜索
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 消息列表 */}
|
||||
<div className="card bg-base-100 shadow">
|
||||
<div className="card-body">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<label>
|
||||
<input type="checkbox" className="checkbox" />
|
||||
</label>
|
||||
</th>
|
||||
<th>发送者</th>
|
||||
<th>消息内容</th>
|
||||
<th>消息类型</th>
|
||||
<th>聊天类型</th>
|
||||
<th>时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{messages.map((message) => (
|
||||
<tr key={message.id}>
|
||||
<td>
|
||||
<label>
|
||||
<input type="checkbox" className="checkbox" />
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="avatar placeholder">
|
||||
<div className="bg-neutral text-neutral-content w-8 rounded-full">
|
||||
<span className="text-xs">{message.from.charAt(0)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-bold">{message.from}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div className="max-w-xs truncate" title={message.content}>
|
||||
{message.content}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span className={`badge ${message.type === 'text' ? 'badge-info' : message.type === 'image' ? 'badge-success' : message.type === 'file' ? 'badge-warning' : 'badge-error'}`}>
|
||||
{message.type === 'text' ? '文本' : message.type === 'image' ? '图片' : message.type === 'file' ? '文件' : '语音'}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span className={`badge ${message.room.includes('群聊') ? 'badge-primary' : 'badge-secondary'}`}>
|
||||
{message.room}
|
||||
</span>
|
||||
</td>
|
||||
<td>{message.time}</td>
|
||||
<td>
|
||||
<div className="flex gap-2">
|
||||
<button className="btn btn-ghost btn-xs">
|
||||
<span className="mr-1">👁️</span>
|
||||
查看
|
||||
</button>
|
||||
<button className="btn btn-ghost btn-xs">
|
||||
<span className="mr-1">📋</span>
|
||||
复制
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* 分页 */}
|
||||
<div className="flex items-center justify-between pt-4">
|
||||
<div className="text-sm text-base-content/70">
|
||||
显示 1-5 条,共 1234 条消息
|
||||
</div>
|
||||
<div className="join">
|
||||
<button className="join-item btn btn-sm">«</button>
|
||||
<button className="join-item btn btn-sm btn-active">1</button>
|
||||
<button className="join-item btn btn-sm">2</button>
|
||||
<button className="join-item btn btn-sm">3</button>
|
||||
<button className="join-item btn btn-sm">4</button>
|
||||
<button className="join-item btn btn-sm">»</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 统计信息 */}
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-3">
|
||||
<div className="card bg-base-100 shadow">
|
||||
<div className="card-body">
|
||||
<h3 className="card-title">今日消息统计</h3>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span>文本消息</span>
|
||||
<span className="font-bold">45 条</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>图片消息</span>
|
||||
<span className="font-bold">12 条</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>文件消息</span>
|
||||
<span className="font-bold">8 条</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>语音消息</span>
|
||||
<span className="font-bold">3 条</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card bg-base-100 shadow">
|
||||
<div className="card-body">
|
||||
<h3 className="card-title">加载状态</h3>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<div className="flex justify-between mb-1">
|
||||
<span>上次加载时间</span>
|
||||
<span className="font-bold">2025-12-12 10:30</span>
|
||||
</div>
|
||||
<progress className="progress progress-primary w-full" value="100" max="100"></progress>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between mb-1">
|
||||
<span>加载成功率</span>
|
||||
<span className="font-bold">98.5%</span>
|
||||
</div>
|
||||
<progress className="progress progress-success w-full" value="98.5" max="100"></progress>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card bg-base-100 shadow">
|
||||
<div className="card-body">
|
||||
<h3 className="card-title">快速操作</h3>
|
||||
<div className="space-y-2">
|
||||
<button className="btn btn-outline btn-block justify-start">
|
||||
<span className="mr-2">🔄</span>
|
||||
刷新消息列表
|
||||
</button>
|
||||
<button className="btn btn-outline btn-block justify-start">
|
||||
<span className="mr-2">🧹</span>
|
||||
清理旧数据
|
||||
</button>
|
||||
<button className="btn btn-outline btn-block justify-start">
|
||||
<span className="mr-2">📋</span>
|
||||
批量操作
|
||||
</button>
|
||||
<button className="btn btn-outline btn-block justify-start">
|
||||
<span className="mr-2">⚙️</span>
|
||||
消息设置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Messages
|
||||
270
backstage/src/pages/Settings.jsx
Normal file
270
backstage/src/pages/Settings.jsx
Normal file
@@ -0,0 +1,270 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
const Settings = () => {
|
||||
const [apiConfig, setApiConfig] = useState({
|
||||
baseUrl: 'http://localhost:8000/api',
|
||||
timeout: 10000,
|
||||
retryCount: 3,
|
||||
})
|
||||
|
||||
const [systemSettings, setSystemSettings] = useState({
|
||||
autoLoad: true,
|
||||
loadInterval: 30,
|
||||
keepHistory: 90,
|
||||
theme: 'light',
|
||||
})
|
||||
|
||||
const handleSaveApiConfig = () => {
|
||||
alert('API 配置已保存!')
|
||||
}
|
||||
|
||||
const handleSaveSystemSettings = () => {
|
||||
alert('系统设置已保存!')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 页面标题 */}
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">系统设置</h1>
|
||||
<p className="text-base-content/70 mt-2">配置系统参数和 API 设置</p>
|
||||
</div>
|
||||
|
||||
{/* API 配置 */}
|
||||
<div className="card bg-base-100 shadow">
|
||||
<div className="card-body">
|
||||
<h2 className="card-title">API 配置</h2>
|
||||
<p className="text-base-content/70">配置后端 API 连接参数</p>
|
||||
|
||||
<div className="divider my-4"></div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">API 基础地址</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="input input-bordered"
|
||||
value={apiConfig.baseUrl}
|
||||
onChange={(e) => setApiConfig({...apiConfig, baseUrl: e.target.value})}
|
||||
placeholder="http://localhost:8000/api"
|
||||
/>
|
||||
<label className="label">
|
||||
<span className="label-text-alt">后端 API 服务的基础地址</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">请求超时时间(毫秒)</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
className="input input-bordered"
|
||||
value={apiConfig.timeout}
|
||||
onChange={(e) => setApiConfig({...apiConfig, timeout: parseInt(e.target.value)})}
|
||||
min="1000"
|
||||
max="30000"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">重试次数</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
className="input input-bordered"
|
||||
value={apiConfig.retryCount}
|
||||
onChange={(e) => setApiConfig({...apiConfig, retryCount: parseInt(e.target.value)})}
|
||||
min="0"
|
||||
max="10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label cursor-pointer justify-start gap-3">
|
||||
<input type="checkbox" className="toggle toggle-primary" defaultChecked />
|
||||
<span className="label-text">启用请求日志</span>
|
||||
</label>
|
||||
<label className="label cursor-pointer justify-start gap-3">
|
||||
<input type="checkbox" className="toggle toggle-primary" defaultChecked />
|
||||
<span className="label-text">启用错误重试</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<button onClick={handleSaveApiConfig} className="btn btn-primary">
|
||||
<span className="mr-2">💾</span>
|
||||
保存 API 配置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 系统设置 */}
|
||||
<div className="card bg-base-100 shadow">
|
||||
<div className="card-body">
|
||||
<h2 className="card-title">系统设置</h2>
|
||||
<p className="text-base-content/70">配置系统行为和外观</p>
|
||||
|
||||
<div className="divider my-4"></div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">自动加载间隔(分钟)</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
className="input input-bordered"
|
||||
value={systemSettings.loadInterval}
|
||||
onChange={(e) => setSystemSettings({...systemSettings, loadInterval: parseInt(e.target.value)})}
|
||||
min="1"
|
||||
max="1440"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">历史数据保留天数</span>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
className="input input-bordered"
|
||||
value={systemSettings.keepHistory}
|
||||
onChange={(e) => setSystemSettings({...systemSettings, keepHistory: parseInt(e.target.value)})}
|
||||
min="1"
|
||||
max="365"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">主题设置</span>
|
||||
</label>
|
||||
<div className="flex gap-4">
|
||||
<label className="flex cursor-pointer items-center gap-2">
|
||||
<input
|
||||
type="radio"
|
||||
name="theme"
|
||||
className="radio radio-primary"
|
||||
checked={systemSettings.theme === 'light'}
|
||||
onChange={() => setSystemSettings({...systemSettings, theme: 'light'})}
|
||||
/>
|
||||
<span>浅色主题</span>
|
||||
</label>
|
||||
<label className="flex cursor-pointer items-center gap-2">
|
||||
<input
|
||||
type="radio"
|
||||
name="theme"
|
||||
className="radio radio-primary"
|
||||
checked={systemSettings.theme === 'dark'}
|
||||
onChange={() => setSystemSettings({...systemSettings, theme: 'dark'})}
|
||||
/>
|
||||
<span>深色主题</span>
|
||||
</label>
|
||||
<label className="flex cursor-pointer items-center gap-2">
|
||||
<input
|
||||
type="radio"
|
||||
name="theme"
|
||||
className="radio radio-primary"
|
||||
checked={systemSettings.theme === 'auto'}
|
||||
onChange={() => setSystemSettings({...systemSettings, theme: 'auto'})}
|
||||
/>
|
||||
<span>自动切换</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-control">
|
||||
<label className="label cursor-pointer justify-start gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="toggle toggle-primary"
|
||||
checked={systemSettings.autoLoad}
|
||||
onChange={(e) => setSystemSettings({...systemSettings, autoLoad: e.target.checked})}
|
||||
/>
|
||||
<span className="label-text">启用自动加载会话存档</span>
|
||||
</label>
|
||||
<label className="label cursor-pointer justify-start gap-3">
|
||||
<input type="checkbox" className="toggle toggle-primary" defaultChecked />
|
||||
<span className="label-text">启用消息通知</span>
|
||||
</label>
|
||||
<label className="label cursor-pointer justify-start gap-3">
|
||||
<input type="checkbox" className="toggle toggle-primary" defaultChecked />
|
||||
<span className="label-text">启用数据备份</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<button onClick={handleSaveSystemSettings} className="btn btn-primary">
|
||||
<span className="mr-2">💾</span>
|
||||
保存系统设置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 系统信息 */}
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<div className="card bg-base-100 shadow">
|
||||
<div className="card-body">
|
||||
<h2 className="card-title">系统信息</h2>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span>前端版本</span>
|
||||
<span className="font-mono">1.0.0</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>React 版本</span>
|
||||
<span className="font-mono">19.2.0</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>构建时间</span>
|
||||
<span className="font-mono">2025-12-12</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>运行环境</span>
|
||||
<span className="font-mono">Production</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card bg-base-100 shadow">
|
||||
<div className="card-body">
|
||||
<h2 className="card-title">危险操作</h2>
|
||||
<p className="text-base-content/70 mb-4">请谨慎操作以下功能</p>
|
||||
<div className="space-y-2">
|
||||
<button className="btn btn-outline btn-error btn-block justify-start">
|
||||
<span className="mr-2">🧹</span>
|
||||
清理所有缓存
|
||||
</button>
|
||||
<button className="btn btn-outline btn-error btn-block justify-start">
|
||||
<span className="mr-2">🔄</span>
|
||||
重置所有设置
|
||||
</button>
|
||||
<button className="btn btn-outline btn-error btn-block justify-start">
|
||||
<span className="mr-2">🗑️</span>
|
||||
删除所有数据
|
||||
</button>
|
||||
</div>
|
||||
<div className="mt-4 text-xs text-error">
|
||||
⚠️ 注意:这些操作不可逆,请确认后再执行
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Settings
|
||||
123
backstage/src/services/api.js
Normal file
123
backstage/src/services/api.js
Normal file
@@ -0,0 +1,123 @@
|
||||
import axios from 'axios'
|
||||
|
||||
// API 基础配置
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000/api'
|
||||
|
||||
// 创建 axios 实例
|
||||
const apiClient = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
// 请求拦截器
|
||||
apiClient.interceptors.request.use(
|
||||
(config) => {
|
||||
// 可以在这里添加认证 token
|
||||
const token = localStorage.getItem('auth_token')
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
return config
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 响应拦截器
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => {
|
||||
return response.data
|
||||
},
|
||||
(error) => {
|
||||
console.error('API 请求错误:', error)
|
||||
|
||||
if (error.response) {
|
||||
// 服务器返回错误状态码
|
||||
const { status, data } = error.response
|
||||
|
||||
switch (status) {
|
||||
case 401:
|
||||
// 未授权,跳转到登录页
|
||||
localStorage.removeItem('auth_token')
|
||||
window.location.href = '/login'
|
||||
break
|
||||
case 403:
|
||||
console.error('权限不足:', data)
|
||||
break
|
||||
case 404:
|
||||
console.error('资源不存在:', data)
|
||||
break
|
||||
case 500:
|
||||
console.error('服务器内部错误:', data)
|
||||
break
|
||||
default:
|
||||
console.error(`HTTP 错误 ${status}:`, data)
|
||||
}
|
||||
} else if (error.request) {
|
||||
// 请求已发出但没有收到响应
|
||||
console.error('网络错误,请检查网络连接')
|
||||
} else {
|
||||
// 请求配置错误
|
||||
console.error('请求配置错误:', error.message)
|
||||
}
|
||||
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 消息相关 API
|
||||
export const messageApi = {
|
||||
// 加载会话存档
|
||||
loadMessages: (seq = 0, limit = 100) =>
|
||||
apiClient.post('/messages/load', null, {
|
||||
params: { seq, limit }
|
||||
}),
|
||||
|
||||
// 获取消息列表
|
||||
getMessages: (params = {}) =>
|
||||
apiClient.get('/messages', { params }),
|
||||
|
||||
// 获取消息统计
|
||||
getMessageStats: () =>
|
||||
apiClient.get('/messages/stats'),
|
||||
|
||||
// 获取单个消息详情
|
||||
getMessageById: (msgid) =>
|
||||
apiClient.get(`/messages/${msgid}`),
|
||||
}
|
||||
|
||||
// 认证相关 API
|
||||
export const authApi = {
|
||||
// 登录
|
||||
login: (username, password) =>
|
||||
apiClient.post('/auth/login', { username, password }),
|
||||
|
||||
// 获取用户信息
|
||||
getUserInfo: () =>
|
||||
apiClient.get('/auth/user'),
|
||||
|
||||
// 刷新 token
|
||||
refreshToken: () =>
|
||||
apiClient.post('/auth/refresh'),
|
||||
}
|
||||
|
||||
// 系统相关 API
|
||||
export const systemApi = {
|
||||
// 获取系统状态
|
||||
getSystemStatus: () =>
|
||||
apiClient.get('/system/status'),
|
||||
|
||||
// 获取系统配置
|
||||
getSystemConfig: () =>
|
||||
apiClient.get('/system/config'),
|
||||
|
||||
// 更新系统配置
|
||||
updateSystemConfig: (config) =>
|
||||
apiClient.put('/system/config', config),
|
||||
}
|
||||
|
||||
export default apiClient
|
||||
17
backstage/vite.config.js
Normal file
17
backstage/vite.config.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import tailwindcss from '@tailwindcss/vite'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
tailwindcss(),
|
||||
react(),
|
||||
],
|
||||
base: '/chat_bm',
|
||||
server: {
|
||||
port: 3020,
|
||||
host: '0.0.0.0',
|
||||
allowedHosts: ['mp.yizhezhe.com']
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user