feat: 悬挂ICP备案号,修复验证码、500登录、/admin Failed to Fetch,优化首页编辑器100%全屏平铺
This commit is contained in:
+33
-13
@@ -1,5 +1,6 @@
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import { Routes, Route, useLocation } from 'react-router-dom';
|
||||
import Navbar from './components/Navbar';
|
||||
import Footer from './components/Footer';
|
||||
import Login from './pages/Login';
|
||||
import Register from './pages/Register';
|
||||
import MD2Doc from './pages/MD2Doc';
|
||||
@@ -9,19 +10,38 @@ import Issues from './pages/Issues';
|
||||
import IssueDetail from './pages/IssueDetail';
|
||||
|
||||
export default function App() {
|
||||
const location = useLocation();
|
||||
const isHomePage = location.pathname === '/';
|
||||
|
||||
if (isHomePage) {
|
||||
// 首页(MD2Doc)完全平铺屏幕,配合 flex 容器,使 MD2Doc 高度自动填充 Navbar 下方剩余空间,不产生页面滚动条
|
||||
return (
|
||||
<div style={{ height: '100vh', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
|
||||
<Navbar />
|
||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
|
||||
<Routes>
|
||||
<Route path="/" element={<MD2Doc />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 其它分页面采用 Flex 自适应贴底页脚布局
|
||||
return (
|
||||
<>
|
||||
<div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
|
||||
<Navbar />
|
||||
<Routes>
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/register" element={<Register />} />
|
||||
<Route path="/admin" element={<Admin />} />
|
||||
<Route path="/profile" element={<Profile />} />
|
||||
<Route path="/issues" element={<Issues />} />
|
||||
<Route path="/issues/:id" element={<IssueDetail />} />
|
||||
{/* 取消拦截,将转换工具设为首页 */}
|
||||
<Route path="/" element={<MD2Doc />} />
|
||||
</Routes>
|
||||
</>
|
||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
|
||||
<Routes>
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/register" element={<Register />} />
|
||||
<Route path="/admin" element={<Admin />} />
|
||||
<Route path="/profile" element={<Profile />} />
|
||||
<Route path="/issues" element={<Issues />} />
|
||||
<Route path="/issues/:id" element={<IssueDetail />} />
|
||||
</Routes>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function Footer() {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<footer style={styles.footer}>
|
||||
<div style={styles.container}>
|
||||
<span style={styles.copyright}>
|
||||
© {currentYear} To Docx. 保留所有权利。
|
||||
</span>
|
||||
<a
|
||||
href="https://beian.miit.gov.cn/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
style={styles.link}
|
||||
onMouseOver={(e) => e.target.style.color = '#3b82f6'}
|
||||
onMouseOut={(e) => e.target.style.color = '#9ca3af'}
|
||||
>
|
||||
闽ICP备2026017655号-1
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = {
|
||||
footer: {
|
||||
padding: '20px 0',
|
||||
backgroundColor: '#f9fafb',
|
||||
borderTop: '1px solid #e5e7eb',
|
||||
width: '100%',
|
||||
boxSizing: 'border-box',
|
||||
marginTop: 'auto'
|
||||
},
|
||||
container: {
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto',
|
||||
padding: '0 20px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: '16px',
|
||||
flexWrap: 'wrap',
|
||||
fontSize: '13px',
|
||||
color: '#9ca3af'
|
||||
},
|
||||
copyright: {
|
||||
fontWeight: '400'
|
||||
},
|
||||
link: {
|
||||
color: '#9ca3af',
|
||||
textDecoration: 'none',
|
||||
transition: 'color 0.2s ease',
|
||||
fontWeight: '500'
|
||||
}
|
||||
};
|
||||
@@ -48,9 +48,9 @@ export default function Admin() {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const [usersRes, settingsRes, issuesRes] = await Promise.all([
|
||||
fetch('http://localhost:3001/api/admin/users', { headers: { Authorization: `Bearer ${token}` } }),
|
||||
fetch('http://localhost:3001/api/admin/settings', { headers: { Authorization: `Bearer ${token}` } }),
|
||||
fetch('http://localhost:3001/api/admin/issues', { headers: { Authorization: `Bearer ${token}` } })
|
||||
fetch('/api/admin/users', { headers: { Authorization: `Bearer ${token}` } }),
|
||||
fetch('/api/admin/settings', { headers: { Authorization: `Bearer ${token}` } }),
|
||||
fetch('/api/admin/issues', { headers: { Authorization: `Bearer ${token}` } })
|
||||
]);
|
||||
|
||||
if (!usersRes.ok || !settingsRes.ok) {
|
||||
@@ -82,7 +82,7 @@ export default function Admin() {
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const res = await fetch('http://localhost:3001/api/admin/settings', {
|
||||
const res = await fetch('/api/admin/settings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
@@ -105,7 +105,7 @@ export default function Admin() {
|
||||
const fetchIssueDetail = async (issueId) => {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const res = await fetch(`http://localhost:3001/api/issues/${issueId}`, {
|
||||
const res = await fetch(`/api/issues/${issueId}`, {
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
const data = await res.json();
|
||||
@@ -125,7 +125,7 @@ export default function Admin() {
|
||||
setReplyMsg('');
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const res = await fetch(`http://localhost:3001/api/admin/issues/${selectedIssue.id}/reply`, {
|
||||
const res = await fetch(`/api/admin/issues/${selectedIssue.id}/reply`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -150,7 +150,7 @@ export default function Admin() {
|
||||
const handleStatusChange = async (issueId, newStatus) => {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const res = await fetch(`http://localhost:3001/api/admin/issues/${issueId}/status`, {
|
||||
const res = await fetch(`/api/admin/issues/${issueId}/status`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function IssueDetail() {
|
||||
const fetchIssue = async () => {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const res = await fetch(`http://localhost:3001/api/issues/${id}`, {
|
||||
const res = await fetch(`/api/issues/${id}`, {
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
const data = await res.json();
|
||||
@@ -56,7 +56,7 @@ export default function IssueDetail() {
|
||||
setReplyMsg('');
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const res = await fetch(`http://localhost:3001/api/issues/${id}/reply`, {
|
||||
const res = await fetch(`/api/issues/${id}/reply`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -51,7 +51,7 @@ export default function Issues() {
|
||||
const fetchMyIssues = async () => {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const res = await fetch('http://localhost:3001/api/issues/my', {
|
||||
const res = await fetch('/api/issues/my', {
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
if (res.ok) {
|
||||
@@ -82,7 +82,7 @@ export default function Issues() {
|
||||
description: description.trim(),
|
||||
markdown_sample: attachMarkdown ? getEditorMarkdown() : ''
|
||||
};
|
||||
const res = await fetch('http://localhost:3001/api/issues', {
|
||||
const res = await fetch('/api/issues', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
+47
-24
@@ -268,7 +268,7 @@ export default function MD2Doc() {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) return;
|
||||
try {
|
||||
const res = await fetch('http://localhost:3001/api/history', {
|
||||
const res = await fetch('/api/history', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
});
|
||||
if (res.ok) {
|
||||
@@ -452,7 +452,7 @@ export default function MD2Doc() {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const response = await fetch('http://localhost:3001/api/convert', {
|
||||
const response = await fetch('/api/convert', {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ markdown, styleOptions })
|
||||
@@ -499,14 +499,11 @@ export default function MD2Doc() {
|
||||
{user && (
|
||||
<aside style={{
|
||||
...styles.sidebar,
|
||||
width: showHistory ? '260px' : '40px',
|
||||
padding: showHistory ? '16px' : '16px 4px',
|
||||
alignItems: showHistory ? 'stretch' : 'center',
|
||||
cursor: showHistory ? 'default' : 'pointer',
|
||||
transition: 'width 0.2s ease-in-out'
|
||||
width: showHistory ? '260px' : '0px',
|
||||
padding: showHistory ? '16px' : '0px',
|
||||
borderRight: showHistory ? '1px solid #d1d5db' : 'none',
|
||||
transition: 'all 0.2s ease-in-out'
|
||||
}}
|
||||
onClick={() => { if (!showHistory) setShowHistory(true); }}
|
||||
title={!showHistory ? "点击展开转换记录" : ""}
|
||||
>
|
||||
{showHistory ? (
|
||||
<>
|
||||
@@ -545,6 +542,18 @@ export default function MD2Doc() {
|
||||
<main style={styles.main}>
|
||||
<section style={styles.inputSection}>
|
||||
<div style={styles.toolbar}>
|
||||
{user && (
|
||||
<>
|
||||
<button
|
||||
style={{...styles.btn, backgroundColor: showHistory ? '#3b82f6' : '#6b7280'}}
|
||||
onClick={() => setShowHistory(!showHistory)}
|
||||
title={showHistory ? "隐藏历史记录" : "显示历史记录"}
|
||||
>
|
||||
{showHistory ? "📋 隐藏历史" : "📋 历史记录"}
|
||||
</button>
|
||||
<div style={{width: '1px', backgroundColor: '#d1d5db', margin: '0 4px'}}></div>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
style={{...styles.btn, backgroundColor: isSyncScroll ? '#10b981' : '#6b7280', padding: '4px 8px'}}
|
||||
onClick={() => setIsSyncScroll(!isSyncScroll)}
|
||||
@@ -680,13 +689,27 @@ export default function MD2Doc() {
|
||||
</main>
|
||||
|
||||
<div style={styles.bottomBar}>
|
||||
<button
|
||||
style={styles.mainExportBtn}
|
||||
onClick={handleExportWord}
|
||||
disabled={exporting || !markdown.trim()}
|
||||
>
|
||||
{exporting ? '正在生成文档...' : '一键导出 Word 文档 (.docx)'}
|
||||
</button>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '8px' }}>
|
||||
<button
|
||||
style={styles.mainExportBtn}
|
||||
onClick={handleExportWord}
|
||||
disabled={exporting || !markdown.trim()}
|
||||
>
|
||||
{exporting ? '正在生成文档...' : '一键导出 Word 文档 (.docx)'}
|
||||
</button>
|
||||
<div style={{ fontSize: '12px', marginTop: '4px' }}>
|
||||
<a
|
||||
href="https://beian.miit.gov.cn/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
style={{ color: '#9ca3af', textDecoration: 'none', transition: 'color 0.2s' }}
|
||||
onMouseOver={(e) => e.target.style.color = '#3b82f6'}
|
||||
onMouseOut={(e) => e.target.style.color = '#9ca3af'}
|
||||
>
|
||||
闽ICP备2026017655号-1
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -816,22 +839,22 @@ export default function MD2Doc() {
|
||||
}
|
||||
|
||||
const styles = {
|
||||
container: { maxWidth: '1600px', margin: '0 auto', padding: '24px', height: '100vh', display: 'flex', gap: '20px', backgroundColor: '#f9fafb', boxSizing: 'border-box' },
|
||||
sidebar: { width: '260px', backgroundColor: 'white', borderRadius: '8px', padding: '16px', border: '1px solid #d1d5db', display: 'flex', flexDirection: 'column', height: '100%', boxSizing: 'border-box' },
|
||||
container: { width: '100%', maxWidth: '100%', margin: '0', padding: '0', height: '100%', display: 'flex', gap: '0', backgroundColor: '#f9fafb', boxSizing: 'border-box' },
|
||||
sidebar: { backgroundColor: 'white', display: 'flex', flexDirection: 'column', height: '100%', boxSizing: 'border-box', overflow: 'hidden' },
|
||||
sidebarTitle: { fontSize: '18px', fontWeight: 'bold', margin: '0 0 4px 0' },
|
||||
historyList: { flex: 1, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: '10px' },
|
||||
historyItem: { padding: '10px', backgroundColor: '#f3f4f6', borderRadius: '6px', cursor: 'pointer', transition: 'background 0.2s' },
|
||||
historyTime: { fontSize: '12px', color: '#6b7280', marginBottom: '4px' },
|
||||
historySummary: { fontSize: '14px', color: '#374151', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' },
|
||||
mainContentWrapper: { flex: 1, display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' },
|
||||
main: { display: 'flex', gap: '8px', flex: 1, overflow: 'hidden' },
|
||||
main: { display: 'flex', gap: '0', flex: 1, overflow: 'hidden' },
|
||||
inputSection: { flex: 1, position: 'relative', display: 'flex', flexDirection: 'column', height: '100%', minWidth: 0 },
|
||||
textarea: { flex: 1, padding: '40px 20px 20px 20px', border: '1px solid #d1d5db', borderRadius: '8px', fontFamily: 'monospace', fontSize: '15px', resize: 'none', boxSizing: 'border-box' },
|
||||
toolbar: { position: 'absolute', top: '10px', right: '10px', display: 'flex', gap: '6px', zIndex: 10 },
|
||||
textarea: { flex: 1, padding: '45px 24px 24px 24px', border: 'none', borderRight: '1px solid #d1d5db', fontFamily: 'monospace', fontSize: '15px', resize: 'none', boxSizing: 'border-box', outline: 'none' },
|
||||
toolbar: { position: 'absolute', top: '10px', right: '15px', display: 'flex', gap: '6px', zIndex: 10 },
|
||||
btn: { padding: '4px 10px', fontSize: '12px', backgroundColor: '#3b82f6', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer', opacity: 0.85, transition: 'opacity 0.2s' },
|
||||
previewSection: { flex: 1, position: 'relative', padding: '0', backgroundColor: 'white', border: '1px solid #d1d5db', borderRadius: '8px', overflowY: 'auto', height: '100%', boxSizing: 'border-box', minWidth: 0 },
|
||||
previewContent: { backgroundColor: 'white', width: '100%', minHeight: '100%', padding: '40px', lineHeight: 1.6, boxSizing: 'border-box' },
|
||||
bottomBar: { marginTop: '20px', display: 'flex', justifyContent: 'center' },
|
||||
previewSection: { flex: 1, position: 'relative', padding: '0', backgroundColor: 'white', border: 'none', overflowY: 'auto', height: '100%', boxSizing: 'border-box', minWidth: 0 },
|
||||
previewContent: { backgroundColor: 'white', width: '100%', minHeight: '100%', padding: '40px 48px', lineHeight: 1.6, boxSizing: 'border-box' },
|
||||
bottomBar: { padding: '12px 0', borderTop: '1px solid #e5e7eb', display: 'flex', justifyContent: 'center', backgroundColor: '#fff' },
|
||||
mainExportBtn: { padding: '12px 32px', backgroundColor: '#10b981', color: 'white', border: 'none', borderRadius: '8px', fontSize: '16px', fontWeight: 'bold', cursor: 'pointer' },
|
||||
|
||||
// Modal 样式
|
||||
|
||||
@@ -16,7 +16,7 @@ export default function Profile() {
|
||||
const [smtpEnabled, setSmtpEnabled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('http://localhost:3001/api/sys/config')
|
||||
fetch('/api/sys/config')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data && data.smtp_enabled !== undefined) {
|
||||
@@ -37,7 +37,7 @@ export default function Profile() {
|
||||
setLoadingCode(true);
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const res = await fetch('http://localhost:3001/api/auth/send-code', {
|
||||
const res = await fetch('/api/auth/send-code', {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
@@ -69,7 +69,7 @@ export default function Profile() {
|
||||
setLoadingSubmit(true);
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const res = await fetch('http://localhost:3001/api/auth/update-profile', {
|
||||
const res = await fetch('/api/auth/update-profile', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function Register() {
|
||||
|
||||
const fetchCaptcha = async () => {
|
||||
try {
|
||||
const res = await fetch('http://localhost:3001/api/captcha');
|
||||
const res = await fetch('/api/captcha');
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setCaptchaId(data.captcha_id);
|
||||
|
||||
Reference in New Issue
Block a user