修改logo为上传
This commit is contained in:
Vendored
+86
-86
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,支持 PNG、JPG 格式
|
||||||
|
</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/');
|
||||||
|
|||||||
@@ -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 ────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user