137 lines
8.2 KiB
HTML
137 lines
8.2 KiB
HTML
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="zh-CN">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<title>甲辰管理后台</title>
|
||
|
|
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
|
||
|
|
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
|
||
|
|
<script src="https://unpkg.com/axios@1/dist/axios.min.js"></script>
|
||
|
|
<style>
|
||
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
|
|
html, body, #root { min-height: 100vh; width: 100%; }
|
||
|
|
body { font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', sans-serif; background: #1a1a2e; color: #fff; }
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<div id="root"></div>
|
||
|
|
<script>
|
||
|
|
const { useState, useEffect } = React;
|
||
|
|
|
||
|
|
function Login({ onLogin }) {
|
||
|
|
const [username, setUsername] = useState('admin');
|
||
|
|
const [password, setPassword] = useState('admin123');
|
||
|
|
const [error, setError] = useState('');
|
||
|
|
const [loading, setLoading] = useState(false);
|
||
|
|
|
||
|
|
const handleLogin = async () => {
|
||
|
|
if (!username || !password) { setError('请输入用户名和密码'); return; }
|
||
|
|
setLoading(true); setError('');
|
||
|
|
try {
|
||
|
|
const res = await axios.post('/api/auth/login', new URLSearchParams({ username, password }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } });
|
||
|
|
if (res.data.access_token) {
|
||
|
|
localStorage.setItem('adminToken', res.data.access_token);
|
||
|
|
onLogin({ username: username });
|
||
|
|
} else if (res.data.code === 0) {
|
||
|
|
localStorage.setItem('adminToken', res.data.data.token);
|
||
|
|
onLogin(res.data.data.user || { username: username });
|
||
|
|
} else { setError(res.data.message || '登录失败'); }
|
||
|
|
} catch (e) { setError('网络错误: ' + e.message); }
|
||
|
|
setLoading(false);
|
||
|
|
}
|
||
|
|
|
||
|
|
return React.createElement('div', { style: { minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#1a1a2e' } },
|
||
|
|
React.createElement('div', { style: { width: '320px', padding: '30px', background: '#16213e', borderRadius: '12px' } },
|
||
|
|
React.createElement('h2', { style: { textAlign: 'center', marginBottom: '20px' } }, '甲辰管理后台'),
|
||
|
|
React.createElement('input', { type: 'text', value: username, onChange: e => setUsername(e.target.value), placeholder: '账号', style: { width: '100%', padding: '12px', marginBottom: '12px', background: '#0f172a', border: '1px solid #334155', color: '#fff', borderRadius: '6px' } }),
|
||
|
|
React.createElement('input', { type: 'password', value: password, onChange: e => setPassword(e.target.value), placeholder: '密码', style: { width: '100%', padding: '12px', marginBottom: '12px', background: '#0f172a', border: '1px solid #334155', color: '#fff', borderRadius: '6px' } }),
|
||
|
|
error && React.createElement('div', { style: { color: '#f87171', marginBottom: '12px', fontSize: '14px' } }, error),
|
||
|
|
React.createElement('button', { onClick: handleLogin, disabled: loading, style: { width: '100%', padding: '12px', background: '#f59e0b', border: 'none', borderRadius: '6px', color: '#000', cursor: 'pointer' } }, loading ? '登录中...' : '登录')
|
||
|
|
)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
function UserManager() {
|
||
|
|
const [users, setUsers] = useState([]);
|
||
|
|
const [loading, setLoading] = useState(true);
|
||
|
|
const [search, setSearch] = useState('');
|
||
|
|
|
||
|
|
useEffect(() => { loadUsers(); }, []);
|
||
|
|
|
||
|
|
const loadUsers = async () => {
|
||
|
|
setLoading(true);
|
||
|
|
try {
|
||
|
|
const token = localStorage.getItem('adminToken');
|
||
|
|
const res = await axios.get('/api/admin/users', { headers: { Authorization: 'Bearer ' + token } });
|
||
|
|
if (res.data) setUsers(res.data);
|
||
|
|
} catch (e) { console.error(e); }
|
||
|
|
setLoading(false);
|
||
|
|
};
|
||
|
|
|
||
|
|
const filtered = users.filter(u => (u.username || '').includes(search) || (u.phone || '').includes(search));
|
||
|
|
|
||
|
|
return React.createElement('div', { padding: '20px' },
|
||
|
|
React.createElement('h2', { marginBottom: '16px' }, '用户列表'),
|
||
|
|
React.createElement('input', { type: 'text', value: search, onChange: e => setSearch(e.target.value), placeholder: '搜索...', style: { width: '100%', padding: '10px', marginBottom: '16px', background: '#0f172a', border: '1px solid #334155', color: '#fff', borderRadius: '6px' } }),
|
||
|
|
loading ? React.createElement('div', { textAlign: 'center', padding: '40px' }, '加载中...') :
|
||
|
|
React.createElement('table', { width: '100%', borderCollapse: 'collapse' },
|
||
|
|
React.createElement('thead', null,
|
||
|
|
React.createElement('tr', null,
|
||
|
|
React.createElement('th', { style: { padding: '10px', textAlign: 'left' } }, '用户名'),
|
||
|
|
React.createElement('th', { style: { padding: '10px', textAlign: 'left' } }, '编码'),
|
||
|
|
React.createElement('th', { style: { padding: '10px', textAlign: 'left' } }, '手机'),
|
||
|
|
React.createElement('th', { style: { padding: '10px', textAlign: 'left' } }, '角色'),
|
||
|
|
React.createElement('th', { style: { padding: '10px', textAlign: 'right' } }, '余额'),
|
||
|
|
React.createElement('th', { style: { padding: '10px', textAlign: 'right' } }, '积分')
|
||
|
|
)
|
||
|
|
),
|
||
|
|
React.createElement('tbody', null,
|
||
|
|
filtered.map(u => React.createElement('tr', { key: u.id, style: { borderBottom: '1px solid #334155' } },
|
||
|
|
React.createElement('td', { style: { padding: '10px' } }, u.username || '-'),
|
||
|
|
React.createElement('td', { style: { padding: '10px', color: '#f59e0b' } }, u.user_code || '-'),
|
||
|
|
React.createElement('td', { style: { padding: '10px' } }, u.phone || '-'),
|
||
|
|
React.createElement('td', { style: { padding: '10px' } }, u.role === 'admin' ? '管理员' : '用户'),
|
||
|
|
React.createElement('td', { style: { padding: '10px', textAlign: 'right' } }, '¥' + (u.balance || 0)),
|
||
|
|
React.createElement('td', { style: { padding: '10px', textAlign: 'right' } }, u.points || 0)
|
||
|
|
))
|
||
|
|
)
|
||
|
|
)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
function App() {
|
||
|
|
const [user, setUser] = useState(null);
|
||
|
|
const [activeTab, setActiveTab] = useState('users');
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const token = localStorage.getItem('adminToken');
|
||
|
|
const userStr = localStorage.getItem('adminUser');
|
||
|
|
if (token && userStr) {
|
||
|
|
try { setUser(JSON.parse(userStr)); } catch (e) {}
|
||
|
|
}
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
if (!user) return React.createElement(Login, { onLogin: (u) => { localStorage.setItem('adminUser', JSON.stringify(u)); setUser(u); } });
|
||
|
|
|
||
|
|
return React.createElement('div', { minHeight: '100vh', background: '#1a1a2e' },
|
||
|
|
React.createElement('div', { padding: '16px', background: '#16213e', display: 'flex', justifyContent: 'space-between', borderBottom: '1px solid #334155' },
|
||
|
|
React.createElement('h1', { fontSize: '18px' }, '管理后台'),
|
||
|
|
React.createElement('button', { onClick: () => { localStorage.clear(); setUser(null); }, style: { background: 'transparent', border: '1px solid #334155', color: '#94a3b8', padding: '6px 12px', borderRadius: '4px', cursor: 'pointer' } }, '退出')
|
||
|
|
),
|
||
|
|
React.createElement('div', { display: 'flex', borderBottom: '1px solid #334155' },
|
||
|
|
React.createElement('div', { onClick: () => setActiveTab('users'), style: { padding: '12px 20px', cursor: 'pointer', borderBottom: activeTab === 'users' ? '2px solid #f59e0b' : '2px solid transparent', color: activeTab === 'users' ? '#fff' : '#94a3b8' } }, '用户'),
|
||
|
|
React.createElement('div', { onClick: () => setActiveTab('stats'), style: { padding: '12px 20px', cursor: 'pointer', borderBottom: activeTab === 'stats' ? '2px solid #f59e0b' : '2px solid transparent', color: activeTab === 'stats' ? '#fff' : '#94a3b8' } }, '统计')
|
||
|
|
),
|
||
|
|
React.createElement('div', null,
|
||
|
|
activeTab === 'users' && React.createElement(UserManager),
|
||
|
|
activeTab === 'stats' && React.createElement('div', { padding: '20px', color: '#94a3b8' }, '统计功能开发中...')
|
||
|
|
)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||
|
|
root.render(React.createElement(App));
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|