Files
rainblogweb/frontend/src/pages/Setup.jsx
T
miaomiao 20deb09dbd P6: 集成上线 - dist 切换/后台路由/Setup+Embed/v1 退役/文档更新
- server.js: serveIndex 改读构建产物 dist/index.html(占位符替换保留);新增 /admin* → dist/admin.html;/forum/manage/:id 改 302 重定向
- Setup 初始化向导三步迁移;Embed 面板嵌入迁移(proxy 代理)
- v1 前端全部退役(12 html + public/js/ 12 文件 + deploy 脚本)
- README/AGENTS.md 更新为 v2 架构(React+Vite、better-sqlite3、构建流程)
2026-08-06 23:16:18 +08:00

167 lines
7.1 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useEffect, useState } from 'react';
import { request } from '../api/client.js';
import { showSnackbar } from '../lib/utils.js';
const THEMES = [
{ color: '#6750a4', name: 'default', label: '默认' },
{ color: '#0288d1', name: 'ocean', label: '海洋' },
{ color: '#2e7d32', name: 'nature', label: '自然' },
{ color: '#e65100', name: 'sunset', label: '日落' },
];
/**
* 初始化向导(迁移自 setup.html + routes/setup.js 三步流程):
* 1. 管理员密码(≥6 位、两次一致、不能等于 admin123)→ 2. 网站名称 + 主题色 → 3. 确认并完成。
* 提交 POST /api/setup/complete;已完成初始化时提示并跳首页。
*/
export default function Setup() {
const [status, setStatus] = useState(null); // null=检查中
const [step, setStep] = useState(1);
const [adminPw, setAdminPw] = useState('');
const [adminPwConfirm, setAdminPwConfirm] = useState('');
const [siteName, setSiteName] = useState('RainWeb');
const [theme, setTheme] = useState({ color: '#6750a4', name: 'default', label: '默认' });
const [error, setError] = useState('');
const [busy, setBusy] = useState(false);
useEffect(() => {
request('/setup/status')
.then((s) => setStatus(s || {}))
.catch(() => setStatus({ setup_complete: true })); // 接口异常不阻塞,视为已完成
}, []);
const next = (n) => {
setError('');
if (n === 2) {
if (adminPw.length < 6) { setError('密码至少6位'); return; }
if (adminPw !== adminPwConfirm) { setError('两次密码不一致'); return; }
if (adminPw === 'admin123') { setError('新密码不能与默认密码相同'); return; }
}
setStep(n);
};
const complete = async () => {
setBusy(true);
try {
await request('/setup/complete', {
method: 'POST',
body: {
password: adminPw,
site_name: siteName.trim() || 'RainWeb',
primary_color: theme.color,
theme_preset: theme.name,
},
});
showSnackbar('初始化完成!');
setTimeout(() => { window.location.href = '/'; }, 1000);
} catch (e) {
setError(e.message || '初始化失败');
setBusy(false);
}
};
if (status === null) {
return <div className="loading"><div className="spinner"></div></div>;
}
// 已完成初始化:提示并跳首页
if (status.setup_complete) {
return (
<div style={{ minHeight: '70vh', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
<div className="card" style={{ width: '100%', maxWidth: 420, padding: '40px 32px', textAlign: 'center' }}>
<h1 style={{ fontSize: 28, fontWeight: 500, marginBottom: 8 }}> 已完成初始化</h1>
<p className="text-muted" style={{ marginBottom: 24, fontSize: 14 }}>RainWeb 已初始化完成可正常使用</p>
<a href="/" className="btn btn-filled w-full">进入首页</a>
</div>
</div>
);
}
const stepDot = (n) => ({
width: n === step ? 28 : 10,
height: 10,
borderRadius: n === step ? 5 : '50%',
background: n < step ? 'var(--md-ref-primary-container)' : (n === step ? 'var(--md-ref-primary)' : 'var(--md-ref-outline-variant)'),
transition: 'all 0.3s',
});
return (
<div style={{ minHeight: '70vh', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}>
<div className="card" style={{ width: '100%', maxWidth: 480, padding: '40px 32px' }}>
<h1 style={{ fontSize: 28, fontWeight: 500, textAlign: 'center', marginBottom: 4 }}>🌧️ RainWeb</h1>
<p style={{ textAlign: 'center', color: 'var(--md-ref-on-surface-variant)', marginBottom: 28, fontSize: 14 }}>
欢迎使用请完成以下初始化设置
</p>
<div style={{ display: 'flex', gap: 8, justifyContent: 'center', marginBottom: 24 }}>
{[1, 2, 3].map((n) => <div key={n} style={stepDot(n)} />)}
</div>
{step === 1 && (
<div>
<h3 style={{ fontWeight: 500, marginBottom: 16 }}>设置管理员密码</h3>
<div className="form-group">
<label>新密码 *</label>
<input type="password" value={adminPw} onChange={(e) => setAdminPw(e.target.value)} placeholder="至少6位" autoComplete="new-password" />
</div>
<div className="form-group">
<label>确认密码 *</label>
<input type="password" value={adminPwConfirm} onChange={(e) => setAdminPwConfirm(e.target.value)} placeholder="再次输入" autoComplete="new-password" onKeyDown={(e) => { if (e.key === 'Enter') next(2); }} />
</div>
<button className="btn btn-filled w-full" onClick={() => next(2)}>下一步</button>
</div>
)}
{step === 2 && (
<div>
<h3 style={{ fontWeight: 500, marginBottom: 16 }}>网站设置</h3>
<div className="form-group">
<label>网站名称</label>
<input type="text" value={siteName} onChange={(e) => setSiteName(e.target.value)} placeholder="我的网站" />
</div>
<div className="form-group">
<label>选择主题色</label>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', gap: 12 }}>
{THEMES.map((t) => (
<div
key={t.name}
onClick={() => setTheme({ color: t.color, name: t.name, label: t.label })}
style={{
padding: 16,
borderRadius: 12,
border: '2px solid ' + (theme.name === t.name ? 'var(--md-ref-primary)' : 'var(--md-ref-outline-variant)'),
cursor: 'pointer',
textAlign: 'center',
background: theme.name === t.name ? 'var(--md-ref-primary-container)' : 'transparent',
transition: 'all 0.2s',
}}
>
<div style={{ width: 32, height: 32, borderRadius: '50%', margin: '0 auto 8px', background: t.color }} />
<div>{t.label}</div>
</div>
))}
</div>
</div>
<button className="btn btn-filled w-full" onClick={() => next(3)}>下一步</button>
</div>
)}
{step === 3 && (
<div>
<h3 style={{ fontWeight: 500, marginBottom: 16 }}>确认并完成</h3>
<div className="card" style={{ padding: 16, marginBottom: 16, fontSize: 14 }}>
<div style={{ marginBottom: 8 }}><strong>管理员密码</strong>: 已设置</div>
<div style={{ marginBottom: 8 }}><strong>网站名称</strong>: {siteName.trim() || 'RainWeb'}</div>
<div><strong>主题色</strong>: {theme.label}</div>
</div>
<button className="btn btn-filled w-full" onClick={complete} disabled={busy}>
{busy ? '处理中...' : '完成初始化'}
</button>
</div>
)}
{error && <div style={{ color: 'var(--md-ref-error)', fontSize: 14, marginTop: 12 }}>{error}</div>}
</div>
</div>
);
}