diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..626c799
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,18 @@
+# Dependency directories
+node_modules/
+client/node_modules/
+
+# Build outputs
+client/dist/
+dist/
+
+# Python cache
+__pycache__/
+*.pyc
+
+# Local backups and configs
+app.ini.bak
+database.json
+settings.json
+.idea/
+.vscode/
diff --git a/client/src/App.jsx b/client/src/App.jsx
index ac372c5..cae323e 100644
--- a/client/src/App.jsx
+++ b/client/src/App.jsx
@@ -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 (
+
+ );
+ }
+
+ // 其它分页面采用 Flex 自适应贴底页脚布局
return (
- <>
+
-
- } />
- } />
- } />
- } />
- } />
- } />
- {/* 取消拦截,将转换工具设为首页 */}
- } />
-
- >
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+
);
}
\ No newline at end of file
diff --git a/client/src/components/Footer.jsx b/client/src/components/Footer.jsx
new file mode 100644
index 0000000..c7fd0da
--- /dev/null
+++ b/client/src/components/Footer.jsx
@@ -0,0 +1,57 @@
+import React from 'react';
+
+export default function Footer() {
+ const currentYear = new Date().getFullYear();
+
+ return (
+
+ );
+}
+
+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'
+ }
+};
diff --git a/client/src/pages/Admin.jsx b/client/src/pages/Admin.jsx
index 5a63385..57868cd 100644
--- a/client/src/pages/Admin.jsx
+++ b/client/src/pages/Admin.jsx
@@ -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',
diff --git a/client/src/pages/IssueDetail.jsx b/client/src/pages/IssueDetail.jsx
index 8e85383..4415a3e 100644
--- a/client/src/pages/IssueDetail.jsx
+++ b/client/src/pages/IssueDetail.jsx
@@ -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',
diff --git a/client/src/pages/Issues.jsx b/client/src/pages/Issues.jsx
index fc9b008..46cd7b1 100644
--- a/client/src/pages/Issues.jsx
+++ b/client/src/pages/Issues.jsx
@@ -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',
diff --git a/client/src/pages/MD2Doc.jsx b/client/src/pages/MD2Doc.jsx
index 8c12e27..fe309a1 100644
--- a/client/src/pages/MD2Doc.jsx
+++ b/client/src/pages/MD2Doc.jsx
@@ -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 && (