fix: bat uses dotnet directly, doctor frontend gets port 5174

This commit is contained in:
MingNian
2026-05-21 15:20:55 +08:00
parent 51c7c89ec5
commit 3ef25e734f
9 changed files with 358 additions and 83 deletions

View File

@@ -1,12 +1,25 @@
import { Outlet } from 'react-router-dom';
import { Outlet, useLocation } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion';
import { TabBar } from './TabBar';
import styles from './AppLayout.module.css';
export function AppLayout() {
const location = useLocation();
return (
<div className={styles.layout}>
<main className={styles.main}>
<Outlet />
<AnimatePresence mode="wait">
<motion.div
key={location.pathname}
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -8 }}
transition={{ duration: 0.15, ease: 'easeOut' }}
>
<Outlet />
</motion.div>
</AnimatePresence>
</main>
<TabBar />
</div>

View File

@@ -1,5 +1,20 @@
import { Outlet } from 'react-router-dom';
import { Outlet, useLocation } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion';
export function StackLayout() {
return <Outlet />;
const location = useLocation();
return (
<AnimatePresence mode="wait">
<motion.div
key={location.pathname}
initial={{ opacity: 0, x: 40 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -40 }}
transition={{ duration: 0.2, ease: 'easeOut' }}
>
<Outlet />
</motion.div>
</AnimatePresence>
);
}

View File

@@ -13,7 +13,7 @@ export function ReportUploadPage() {
const [title, setTitle] = useState('');
const [category, setCategory] = useState('血液检查');
const [files, setFiles] = useState<File[]>([]);
const [loading, setLoading] = useState(false);
const [uploading, setUploading] = useState(false);
const categories = ['血液检查', '心电图', '影像学', '尿液检查', '其他'];
@@ -21,7 +21,6 @@ export function ReportUploadPage() {
if (e.target.files) {
setFiles((prev) => [...prev, ...Array.from(e.target.files!)]);
}
// Reset so the same file can be selected again
if (fileRef.current) fileRef.current.value = '';
};
@@ -31,15 +30,33 @@ export function ReportUploadPage() {
const handleSubmit = async () => {
if (!title.trim()) { toast('请输入报告名称', 'error'); return; }
setLoading(true);
setUploading(true);
try {
await reportService.uploadReport({ title, category });
// Step 1: Upload files
const imageUrls: string[] = [];
for (const file of files) {
const formData = new FormData();
formData.append('file', file);
const token = JSON.parse(localStorage.getItem('hrt_auth') || '{}')?.state?.token;
const res = await fetch('http://localhost:5000/api/files/upload', {
method: 'POST',
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
body: formData,
});
if (res.ok) {
const data = await res.json();
imageUrls.push(data.url);
}
}
// Step 2: Create report
await reportService.uploadReport({ title, category, imageUrls });
toast('上传成功');
setTimeout(() => navigate('/services/reports'), 800);
} catch {
toast('上传失败,请检查后端是否运行', 'error');
} finally {
setLoading(false);
setUploading(false);
}
};
@@ -59,30 +76,25 @@ export function ReportUploadPage() {
<div className={styles.uploadArea} onClick={() => fileRef.current?.click()}>
<span style={{ fontSize: 36 }}>📷</span>
<span style={{ fontSize: 14, color: '#6B7280' }}></span>
<span style={{ fontSize: 11, color: '#9CA3AF' }}> jpgpng</span>
<span style={{ fontSize: 11, color: '#9CA3AF' }}>{files.length > 0 ? `已选 ${files.length} 个文件` : '支持 jpg、png、pdf可多选'}</span>
</div>
<input
ref={fileRef}
type="file"
accept="image/*"
multiple
style={{ display: 'none' }}
onChange={handleFileChange}
/>
<input ref={fileRef} type="file" accept="image/*,.pdf" multiple style={{ display: 'none' }} onChange={handleFileChange} />
{files.length > 0 && (
<div className={styles.fileList}>
{files.map((file, i) => (
<div key={i} className={styles.fileItem}>
<span className={styles.fileName}>📎 {file.name}</span>
<span className={styles.fileName}>
{file.type.startsWith('image/') ? '🖼' : '📄'} {file.name} ({(file.size / 1024).toFixed(0)}KB)
</span>
<button className={styles.fileRemove} onClick={() => removeFile(i)}></button>
</div>
))}
</div>
)}
<Button variant="primary" size="lg" fullWidth loading={loading} onClick={handleSubmit}>
<Button variant="primary" size="lg" fullWidth loading={uploading} onClick={handleSubmit}>
{uploading ? '上传中...' : '提交上传'}
</Button>
</div>
<ToastContainer />

View File

@@ -47,11 +47,11 @@ export async function getReports(): Promise<Report[]> {
return res.data.map(mapReport);
}
export async function uploadReport(data: { title: string; category: string }): Promise<Report> {
export async function uploadReport(data: { title: string; category: string; imageUrls?: string[] }): Promise<Report> {
const res = await api.post<RawReport>('/api/reports', {
title: data.title,
category: data.category,
imageUrls: [],
imageUrls: data.imageUrls || [],
});
return mapReport(res.data);
}