修改logo为上传

This commit is contained in:
2026-06-18 16:38:21 +08:00
parent 6f3faff72d
commit f9b3a51391
6 changed files with 286 additions and 130 deletions
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -28,7 +28,7 @@
} }
})(); })();
</script> </script>
<script type="module" crossorigin src="/assets/index-J332wOtZ.js"></script> <script type="module" crossorigin src="/assets/index-CXwDXEM6.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D7ShJUt4.css"> <link rel="stylesheet" crossorigin href="/assets/index-D7ShJUt4.css">
</head> </head>
<body> <body>
+14
View File
@@ -149,6 +149,20 @@ export async function uploadPdf(file: File, configKey: string): Promise<{ url: s
return res.json(); return res.json();
} }
export async function uploadLogo(file: File): Promise<{ url: string }> {
const formData = new FormData();
formData.append('file', file);
const token = localStorage.getItem('auth_token');
const baseUrl = (import.meta as any).env?.VITE_API_URL || 'http://localhost:8000/api';
const res = await fetch(`${baseUrl}/admin/upload-logo`, {
method: 'POST',
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
body: formData,
});
if (!res.ok) throw new Error('上传失败');
return res.json();
}
export async function getCreditRecords(filters?: { export async function getCreditRecords(filters?: {
user_id?: string; user_id?: string;
user_name?: string; user_name?: string;
+87 -1
View File
@@ -5,7 +5,7 @@ import {
import { import {
SettingOutlined, SaveOutlined, UploadOutlined, FilePdfOutlined, EyeOutlined, SettingOutlined, SaveOutlined, UploadOutlined, FilePdfOutlined, EyeOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { getSystemConfigs, updateSystemConfig, uploadPdf } from '../api'; import { getSystemConfigs, updateSystemConfig, uploadPdf, uploadLogo } from '../api';
import type { SystemConfig } from '../types'; import type { SystemConfig } from '../types';
const AdminSettings: React.FC = () => { const AdminSettings: React.FC = () => {
@@ -14,6 +14,7 @@ const AdminSettings: React.FC = () => {
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [uploading, setUploading] = useState(''); const [uploading, setUploading] = useState('');
const [form] = Form.useForm(); const [form] = Form.useForm();
const [logoPreview, setLogoPreview] = useState('');
useEffect(() => { useEffect(() => {
load(); load();
@@ -65,6 +66,23 @@ const AdminSettings: React.FC = () => {
return false; // prevent default upload return false; // prevent default upload
}; };
const handleLogoUpload = async (file: File) => {
setUploading('site_logo');
try {
const res = await uploadLogo(file);
// Update local state
setConfigs(prev => prev.map(c => c.key === 'site_logo' ? { ...c, value: res.url } : c));
form.setFieldsValue({ site_logo: res.url });
setLogoPreview(res.url);
message.success('Logo上传成功');
} catch {
message.error('上传失败');
} finally {
setUploading('');
}
return false; // prevent default upload
};
const groupedConfigs: Record<string, SystemConfig[]> = { const groupedConfigs: Record<string, SystemConfig[]> = {
'站点信息': configs.filter(c => c.key.startsWith('site_')), '站点信息': configs.filter(c => c.key.startsWith('site_')),
'协议配置': configs.filter(c => c.key === 'user_agreement_url' || c.key === 'privacy_policy_url'), '协议配置': configs.filter(c => c.key === 'user_agreement_url' || c.key === 'privacy_policy_url'),
@@ -89,6 +107,9 @@ const AdminSettings: React.FC = () => {
}; };
const getFieldComponent = (config: SystemConfig) => { const getFieldComponent = (config: SystemConfig) => {
if (config.key === 'site_logo') {
return <LogoUploadField config={config} />;
}
if (config.key === 'seo_description') { if (config.key === 'seo_description') {
return <Input.TextArea rows={3} placeholder={config.description} size="large" />; return <Input.TextArea rows={3} placeholder={config.description} size="large" />;
} }
@@ -111,6 +132,71 @@ const AdminSettings: React.FC = () => {
return <Input placeholder={config.description} size="large" />; return <Input placeholder={config.description} size="large" />;
}; };
const LogoUploadField: React.FC<{ config: SystemConfig }> = ({ config }) => {
const hasLogo = config.value && config.value.startsWith('/uploads/');
const baseUrl = (import.meta as any).env?.VITE_API_URL || 'http://localhost:8000/api';
const logoUrl = hasLogo ? `${baseUrl.replace('/api', '')}${config.value}` : '';
const handleRemove = () => {
setConfigs(prev => prev.map(c => c.key === 'site_logo' ? { ...c, value: '' } : c));
form.setFieldsValue({ site_logo: '' });
message.success('Logo已移除');
};
return (
<div style={{
padding: '16px', borderRadius: 10,
border: '1px solid #f0f0f5', background: '#fafbfc',
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
<Space>
<UploadOutlined style={{ color: '#6366f1', fontSize: 18 }} />
<Typography.Text strong>Logo</Typography.Text>
</Space>
<Space>
{hasLogo && (
<Button size="small" danger onClick={handleRemove}>
</Button>
)}
<Upload
accept="image/*"
showUploadList={false}
beforeUpload={handleLogoUpload}
>
<Button size="small" type="primary" icon={<UploadOutlined />} loading={uploading === 'site_logo'}>
{hasLogo ? '重新上传' : '上传Logo'}
</Button>
</Upload>
</Space>
</div>
{hasLogo ? (
<div style={{ textAlign: 'center' }}>
<img
src={logoUrl}
alt="Logo预览"
style={{
maxWidth: 200,
maxHeight: 80,
objectFit: 'contain',
border: '1px solid #e2e8f0',
borderRadius: 8,
padding: 8,
}}
/>
<Typography.Text type="secondary" style={{ fontSize: 12, display: 'block', marginTop: 8 }}>
200x40px PNGJPG
</Typography.Text>
</div>
) : (
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
Logo使Logo
</Typography.Text>
)}
</div>
);
};
const PdfUploadField: React.FC<{ config: SystemConfig }> = ({ config }) => { const PdfUploadField: React.FC<{ config: SystemConfig }> = ({ config }) => {
const label = config.key === 'user_agreement_url' ? '用户协议' : '隐私政策'; const label = config.key === 'user_agreement_url' ? '用户协议' : '隐私政策';
const hasFile = config.value && config.value.startsWith('/uploads/'); const hasFile = config.value && config.value.startsWith('/uploads/');
+98
View File
@@ -1577,6 +1577,104 @@ async def admin_generate_video(
return {"message": "ok", "record_id": record_id} return {"message": "ok", "record_id": record_id}
# ── File Uploads ─────────────────────────────────────────
import os
from fastapi import File, UploadFile
@router.post("/upload-pdf")
async def upload_pdf(
file: UploadFile = File(...),
config_key: str | None = None,
admin: User = Depends(get_admin_user),
db: AsyncSession = Depends(get_db),
):
"""Upload a PDF file and save URL to system config."""
from app.config import settings
from app.utils.id_gen import generate_id
if not file.filename:
raise HTTPException(status_code=400, detail="请选择文件")
if not file.filename.lower().endswith(".pdf"):
raise HTTPException(status_code=400, detail="仅支持PDF格式")
content = await file.read()
if len(content) > 10 * 1024 * 1024:
raise HTTPException(status_code=400, detail="文件大小不能超过10MB")
safe_name = f"pdf_{generate_id()}.pdf"
file_path = os.path.join(settings.UPLOAD_LOCAL_PATH, safe_name)
with open(file_path, "wb") as f:
f.write(content)
url = f"/uploads/{safe_name}"
if config_key:
result = await db.execute(
select(SystemConfig).where(SystemConfig.key == config_key).limit(1)
)
config = result.scalar_one_or_none()
if config:
config.value = url
else:
db.add(SystemConfig(
id=generate_id(),
key=config_key,
value=url,
))
await db.commit()
return {"url": url}
@router.post("/upload-logo")
async def upload_logo(
file: UploadFile = File(...),
admin: User = Depends(get_admin_user),
db: AsyncSession = Depends(get_db),
):
"""Upload a Logo image file and save URL to system config."""
from app.config import settings
from app.utils.id_gen import generate_id
if not file.filename:
raise HTTPException(status_code=400, detail="请选择文件")
allowed_extensions = ('.png', '.jpg', '.jpeg', '.gif', '.webp')
if not file.filename.lower().endswith(allowed_extensions):
raise HTTPException(status_code=400, detail="仅支持 PNG、JPG、GIF、WebP 格式图片")
content = await file.read()
if len(content) > 2 * 1024 * 1024:
raise HTTPException(status_code=400, detail="文件大小不能超过2MB")
safe_name = "site_logo.png"
file_path = os.path.join(settings.UPLOAD_LOCAL_PATH, safe_name)
with open(file_path, "wb") as f:
f.write(content)
url = f"/uploads/{safe_name}"
result = await db.execute(
select(SystemConfig).where(SystemConfig.key == "site_logo").limit(1)
)
config = result.scalar_one_or_none()
if config:
config.value = url
else:
db.add(SystemConfig(
id="cfg_site_logo",
key="site_logo",
value=url,
description="网站Logo图片",
))
await db.commit()
return {"url": url}
# ── Payment Stats ──────────────────────────────────────── # ── Payment Stats ────────────────────────────────────────
-42
View File
@@ -777,48 +777,6 @@ def create_app() -> FastAPI:
except Exception as e: except Exception as e:
return {"success": False, "error": str(e)} return {"success": False, "error": str(e)}
@application.post("/api/admin/upload-pdf")
async def upload_pdf(
file: UploadFile = File(...),
config_key: str = Form(...),
):
"""Upload a PDF file and save URL to system config."""
from app.dependencies import get_db
from app.models.system_config import SystemConfig
from app.models.base import async_session
from sqlalchemy import select
if not file.filename or not file.filename.endswith('.pdf'):
raise HTTPException(status_code=400, detail="仅支持PDF文件")
# Save file
safe_name = f"{config_key}.pdf"
file_path = os.path.join(settings.UPLOAD_LOCAL_PATH, safe_name)
content = await file.read()
with open(file_path, "wb") as f:
f.write(content)
url = f"/uploads/{safe_name}"
# Update system config
async with async_session() as db:
result = await db.execute(
select(SystemConfig).where(SystemConfig.key == config_key).limit(1)
)
config = result.scalar_one_or_none()
if config:
config.value = url
else:
db.add(SystemConfig(
id=f"cfg_{config_key}",
key=config_key,
value=url,
description="用户协议" if "agreement" in config_key else "隐私政策",
))
await db.commit()
return {"url": url}
@application.get("/internal/", response_class=HTMLResponse) @application.get("/internal/", response_class=HTMLResponse)
async def index(): async def index():
return """<!DOCTYPE html> return """<!DOCTYPE html>