Files
soft/frontend-patient/src/pages/services/ReportUploadPage.tsx
MingNian d6a432aec4 feat: extract secrets to .env, remove hardcoded credentials
- Backend: .env file for DB/JWT/Redis/MinIO config, appsettings.json cleared
- Backend: Program.cs loads .env at startup (no extra NuGet packages)
- Frontend: .env files for VITE_API_URL, api-clients use import.meta.env
- Added vite-env.d.ts type declarations for both frontends
- All hardcoded localhost:5000 replaced with env variable
- Added .env.example template for onboarding
2026-05-22 22:02:08 +08:00

106 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { Button } from '@/components/common/Button';
import { Input } from '@/components/common/Input';
import { PageHeader } from '@/components/layout/PageHeader';
import { ToastContainer, toast } from '@/components/common/Toast';
import * as reportService from '@/services/report.service';
import styles from './ReportUploadPage.module.css';
export function ReportUploadPage() {
const navigate = useNavigate();
const fileRef = useRef<HTMLInputElement>(null);
const [title, setTitle] = useState('');
const [category, setCategory] = useState('血液检查');
const [files, setFiles] = useState<File[]>([]);
const [uploading, setUploading] = useState(false);
const categories = ['血液检查', '心电图', '影像学', '尿液检查', '其他'];
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files) {
setFiles((prev) => [...prev, ...Array.from(e.target.files!)]);
}
if (fileRef.current) fileRef.current.value = '';
};
const removeFile = (index: number) => {
setFiles((prev) => prev.filter((_, i) => i !== index));
};
const handleSubmit = async () => {
if (!title.trim()) { toast('请输入报告名称', 'error'); return; }
setUploading(true);
try {
// 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(`${import.meta.env.VITE_API_URL}/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 {
setUploading(false);
}
};
return (
<div className="page--no-tab">
<PageHeader title="上传报告" />
<div className={styles.form}>
<Input label="报告名称" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="如:血脂全套检查报告" />
<div className={styles.catLabel}></div>
<div className={styles.catGrid}>
{categories.map((c) => (
<button key={c} className={`${styles.catChip} ${category === c ? styles.catActive : ''}`} onClick={() => setCategory(c)}>{c}</button>
))}
</div>
<div className={styles.uploadArea} onClick={() => fileRef.current?.click()}>
<div style={{ width: 72, height: 72, borderRadius: 18, background: 'var(--color-bg-secondary)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 12px' }}>
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#9BA0B4" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
</div>
<span style={{ fontSize: 14, color: '#6B7280' }}></span>
<span style={{ fontSize: 11, color: '#9CA3AF' }}>{files.length > 0 ? `已选 ${files.length} 个文件` : '支持 jpg、png、pdf可多选'}</span>
</div>
<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} ({(file.size / 1024).toFixed(0)}KB)
</span>
<button className={styles.fileRemove} onClick={() => removeFile(i)} style={{ fontWeight: 700 }}>×</button>
</div>
))}
</div>
)}
<Button variant="primary" size="lg" fullWidth loading={uploading} onClick={handleSubmit}>
{uploading ? '上传中...' : '提交上传'}
</Button>
</div>
<ToastContainer />
</div>
);
}