revert: remove .env loading, restore hardcoded config

- appsettings.json: restored hardcoded secrets
- Program.cs: removed .env file loader
- Frontend api-clients: restored hardcoded localhost:5000
- Removed .env, .env.example, vite-env.d.ts files
- Kept all audit fixes (endpoints, DTOs, field names, status labels)
This commit is contained in:
MingNian
2026-05-24 13:38:45 +08:00
parent ede4a8d29e
commit db443b258e
14 changed files with 93 additions and 155 deletions

View File

@@ -25,6 +25,7 @@ export function ChatPage() {
const bottomRef = useRef<HTMLDivElement>(null);
const connRef = useRef<HubConnection | null>(null);
// Load initial messages via HTTP
useEffect(() => {
if (!id) return;
api.get<Message[]>(`/api/consultations/${id}/messages`)
@@ -32,11 +33,12 @@ export function ChatPage() {
.catch(() => {});
}, [id]);
// Set up SignalR connection
useEffect(() => {
if (!id) return;
const conn = new HubConnectionBuilder()
.withUrl(`${import.meta.env.VITE_API_URL}/hubs/chat`, {
.withUrl('http://localhost:5000/hubs/chat', {
accessTokenFactory: () => getToken(),
})
.withAutomaticReconnect()
@@ -44,6 +46,7 @@ export function ChatPage() {
conn.on('ReceiveMessage', (msg: Message) => {
setMessages((prev) => {
// Dedup — guard against reconnection replay
if (prev.some((m) => m.id === msg.id)) return prev;
return [...prev, msg];
});
@@ -70,6 +73,7 @@ export function ChatPage() {
};
}, [id]);
// Auto-scroll on new messages
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
@@ -85,37 +89,31 @@ export function ChatPage() {
return (
<div style={{ display: 'flex', flexDirection: 'column', height: 'calc(100vh - 0px)' }}>
<div style={{
padding: '15px 24px', background: '#fff', borderBottom: '1px solid #F0F2F5',
fontSize: 15, fontWeight: 600, color: '#1A1D28', display: 'flex', alignItems: 'center', gap: 8,
boxShadow: '0 1px 4px rgba(0,0,0,0.03)',
}}>
<div style={{ padding: '14px 20px', background: '#fff', borderBottom: '1px solid #eee', fontSize: 15, fontWeight: 500, display: 'flex', alignItems: 'center', gap: 8 }}>
线
<span style={{
width: 8, height: 8, borderRadius: '50%',
background: connected ? '#20C997' : '#C0C5D2',
background: connected ? '#4caf50' : '#ccc',
display: 'inline-block',
}} />
</div>
<div style={{ flex: 1, overflow: 'auto', padding: 24, background: '#F5F7FB' }}>
<div style={{ flex: 1, overflow: 'auto', padding: 20, background: '#fafafa' }}>
{messages.map((msg) => (
<div key={msg.id} style={{
display: 'flex', justifyContent: msg.senderRole === 'doctor' ? 'flex-end' : 'flex-start',
marginBottom: 14,
marginBottom: 12,
}}>
<div style={{
maxWidth: '70%', padding: '12px 16px', borderRadius: 14, fontSize: 14,
background: msg.senderRole === 'doctor'
? 'linear-gradient(135deg, #4F6EF7 0%, #6C8AFF 100%)'
: '#fff',
color: msg.senderRole === 'doctor' ? '#fff' : '#1A1D28',
boxShadow: '0 2px 8px rgba(0,0,0,0.06)',
maxWidth: '70%', padding: '10px 14px', borderRadius: 12, fontSize: 14,
background: msg.senderRole === 'doctor' ? '#1976d2' : '#fff',
color: msg.senderRole === 'doctor' ? '#fff' : '#333',
boxShadow: '0 1px 3px rgba(0,0,0,0.08)',
}}>
<div>{msg.content}</div>
<div style={{
fontSize: 10, marginTop: 6, textAlign: 'right',
opacity: 0.65,
fontSize: 10, marginTop: 4, textAlign: 'right',
opacity: 0.7,
}}>
{msg.createdAt?.split('T')[1]?.slice(0, 5)}
</div>
@@ -125,23 +123,14 @@ export function ChatPage() {
<div ref={bottomRef} />
</div>
<div style={{
padding: '14px 24px', background: '#fff', borderTop: '1px solid #F0F2F5',
display: 'flex', gap: 12, boxShadow: '0 -1px 4px rgba(0,0,0,0.03)',
}}>
<div style={{ padding: '12px 20px', background: '#fff', borderTop: '1px solid #eee', display: 'flex', gap: 12 }}>
<input value={input} onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSend()}
placeholder="输入回复..."
style={{
flex: 1, padding: '11px 16px', border: '1.5px solid #E1E5ED', borderRadius: 24,
fontSize: 14, outline: 'none',
}}
onFocus={(e) => e.currentTarget.style.borderColor = '#4F6EF7'}
onBlur={(e) => e.currentTarget.style.borderColor = '#E1E5ED'} />
style={{ flex: 1, padding: '10px 14px', border: '1px solid #ddd', borderRadius: 20, fontSize: 14 }} />
<button onClick={handleSend} style={{
padding: '11px 24px', background: 'linear-gradient(135deg, #4F6EF7 0%, #6C8AFF 100%)', color: '#fff',
border: 'none', borderRadius: 24, fontSize: 14, fontWeight: 600,
boxShadow: '0 4px 14px rgba(79,110,247,0.3)',
padding: '10px 24px', background: '#1976d2', color: '#fff',
border: 'none', borderRadius: 20, fontSize: 14,
}}>
</button>

View File

@@ -7,7 +7,7 @@ interface RawReport {
imageUrls: string[]; status: string; riskLevel?: string;
summary?: string; suggestions?: string;
patientName?: string; doctorName?: string;
uploadedAt: string; completedAt?: string;
createdAt: string; completedAt?: string;
items?: RawItem[];
}
@@ -56,54 +56,50 @@ export function ReportDetailPage() {
finally { setSubmitting(false); }
};
if (!report) return <div style={{ padding: 28, color: '#9BA0B4' }}>...</div>;
if (!report) return <div style={{ padding: 24 }}>...</div>;
const isCompleted = report.status === 'completed';
const riskMap: Record<string, { text: string; color: string }> = {
normal: { text: '正常', color: '#20C997' },
attention: { text: '关注', color: '#F59E0B' },
abnormal: { text: '异常', color: '#EF4444' },
};
const inputStyle: React.CSSProperties = {
width: '100%', padding: '10px 14px', border: '1.5px solid #E1E5ED',
borderRadius: 10, fontSize: 13, outline: 'none', boxSizing: 'border-box', fontFamily: 'inherit',
normal: { text: '正常', color: '#2e7d32' },
attention: { text: '关注', color: '#f57c00' },
abnormal: { text: '异常', color: '#c62828' },
};
return (
<div style={{ padding: 28 }}>
<Link to="/reports" style={{ fontSize: 13, color: '#4F6EF7', fontWeight: 500 }}> </Link>
<div style={{ padding: 24 }}>
<Link to="/reports" style={{ fontSize: 13, color: '#1976d2' }}> </Link>
<div style={{ background: '#fff', marginTop: 16, padding: 28, borderRadius: 16, boxShadow: '0 2px 12px rgba(0,0,0,0.04)' }}>
<div style={{ background: '#fff', marginTop: 16, padding: 24, borderRadius: 8, boxShadow: '0 1px 4px rgba(0,0,0,0.08)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
<div>
<h2 style={{ margin: 0, fontSize: 20, fontWeight: 700 }}>{report.title}</h2>
<div style={{ marginTop: 8, fontSize: 13, color: '#9BA0B4' }}>
<h2 style={{ margin: 0 }}>{report.title}</h2>
<div style={{ marginTop: 8, fontSize: 13, color: '#888' }}>
{report.patientName || '未知'} &nbsp;|&nbsp;
{categoryMap[report.category] || report.category} &nbsp;|&nbsp;
{report.uploadedAt?.split('T')[0]}
{report.createdAt?.split('T')[0]}
</div>
</div>
<span style={{
padding: '6px 14px', borderRadius: 12, fontSize: 12, fontWeight: 600,
background: isCompleted ? '#E6F9F2' : '#FFF8E6',
color: isCompleted ? '#20C997' : '#F59E0B',
padding: '4px 12px', borderRadius: 12, fontSize: 12, fontWeight: 500,
background: isCompleted ? '#e8f5e9' : '#fff3e0',
color: isCompleted ? '#2e7d32' : '#f57c00',
}}>
{isCompleted ? '已完成' : '待审核'}
</span>
</div>
{/* 图片 */}
{report.imageUrls && report.imageUrls.length > 0 && (
<div style={{ marginTop: 24 }}>
<h4 style={{ fontSize: 14, fontWeight: 600, marginBottom: 10, color: '#5A6072' }}>{report.imageUrls.length}</h4>
<div style={{ marginTop: 20 }}>
<h4 style={{ fontSize: 14, marginBottom: 8 }}>{report.imageUrls.length}</h4>
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
{report.imageUrls.map((url, i) => (
<div key={i} onClick={() => setLightbox(url)} style={{
width: 120, height: 120, borderRadius: 12, overflow: 'hidden',
cursor: 'pointer', border: '2px solid #F0F2F5', background: '#F9FAFC',
width: 120, height: 120, borderRadius: 8, overflow: 'hidden',
cursor: 'pointer', border: '2px solid #eee', background: '#f5f5f5',
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}>
<img src={`${import.meta.env.VITE_API_URL}${url}`} alt={`图片${i}`}
<img src={`http://localhost:5000${url}`} alt={`图片${i}`}
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'cover' }}
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }} />
</div>
@@ -112,35 +108,37 @@ export function ReportDetailPage() {
</div>
)}
{/* 灯箱 */}
{lightbox && (
<div onClick={() => setLightbox(null)} style={{
position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.85)',
display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 999, cursor: 'pointer',
}}>
<img src={`${import.meta.env.VITE_API_URL}${lightbox}`} alt="预览" style={{ maxWidth: '90vw', maxHeight: '90vh', borderRadius: 12 }} />
<img src={`http://localhost:5000${lightbox}`} alt="预览" style={{ maxWidth: '90vw', maxHeight: '90vh', borderRadius: 8 }} />
</div>
)}
{/* 已完成解读 */}
{isCompleted && (
<div style={{ marginTop: 24, padding: 20, background: '#E6F9F2', borderRadius: 12 }}>
<h4 style={{ fontSize: 15, fontWeight: 600, marginBottom: 12, color: '#20C997' }}></h4>
<div style={{ fontSize: 13, color: '#5A6072' }}>
<p style={{ margin: '6px 0' }}><strong style={{ color: '#1A1D28' }}></strong>
<div style={{ marginTop: 20, padding: 16, background: '#e8f5e9', borderRadius: 8 }}>
<h4 style={{ fontSize: 14, marginBottom: 8 }}></h4>
<div style={{ fontSize: 13 }}>
<p><strong></strong>
<span style={{ color: riskMap[report.riskLevel || '']?.color, fontWeight: 600 }}>
{riskMap[report.riskLevel || '']?.text || report.riskLevel || '-'}
</span>
</p>
<p style={{ margin: '6px 0' }}><strong style={{ color: '#1A1D28' }}></strong>{report.summary || '-'}</p>
{report.suggestions && <p style={{ margin: '6px 0' }}><strong style={{ color: '#1A1D28' }}></strong>{report.suggestions}</p>}
<p><strong></strong>{report.summary || '-'}</p>
{report.suggestions && <p><strong></strong>{report.suggestions}</p>}
</div>
{report.items && report.items.length > 0 && (
<table style={{ width: '100%', marginTop: 14, borderCollapse: 'collapse', fontSize: 12 }}>
<table style={{ width: '100%', marginTop: 12, borderCollapse: 'collapse', fontSize: 12 }}>
<thead><tr style={{ textAlign: 'left', borderBottom: '2px solid #c8e6c9' }}>
<th style={{ padding: '6px 8px', color: '#5A6072' }}></th>
<th style={{ padding: '6px 8px', color: '#5A6072' }}></th>
<th style={{ padding: '6px 8px', color: '#5A6072' }}></th>
<th style={{ padding: '6px 8px', color: '#5A6072' }}></th>
<th style={{ padding: '6px 8px' }}></th>
<th style={{ padding: '6px 8px' }}></th>
<th style={{ padding: '6px 8px' }}></th>
<th style={{ padding: '6px 8px' }}></th>
</tr></thead>
<tbody>
{report.items.map((item) => (
@@ -148,7 +146,7 @@ export function ReportDetailPage() {
<td style={{ padding: '6px 8px' }}>{item.itemName}</td>
<td style={{ padding: '6px 8px' }}>{item.resultValue} {item.unit || ''}</td>
<td style={{ padding: '6px 8px' }}>{item.referenceRange || '-'}</td>
<td style={{ padding: '6px 8px', color: item.isAbnormal ? '#EF4444' : '#20C997', fontWeight: 600 }}>
<td style={{ padding: '6px 8px', color: item.isAbnormal ? '#c62828' : '#2e7d32', fontWeight: 500 }}>
{item.isAbnormal ? '是' : '否'}
</td>
</tr>
@@ -159,66 +157,68 @@ export function ReportDetailPage() {
</div>
)}
{/* 解读表单 */}
{!isCompleted && (
<div style={{ marginTop: 28, borderTop: '1px solid #F0F2F5', paddingTop: 24 }}>
<h3 style={{ fontSize: 16, fontWeight: 600, marginBottom: 18, color: '#1A1D28' }}></h3>
<div style={{ marginTop: 24, borderTop: '1px solid #eee', paddingTop: 20 }}>
<h3 style={{ fontSize: 15, marginBottom: 16 }}></h3>
<div style={{ marginBottom: 14 }}>
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, color: '#5A6072', marginBottom: 5 }}></label>
<div style={{ marginBottom: 12 }}>
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 4 }}></label>
<textarea value={summary} onChange={(e) => setSummary(e.target.value)}
placeholder="请输入您的专业解读总结..."
rows={4}
style={{ ...inputStyle, resize: 'vertical' }} />
style={{ width: '100%', padding: '10px 12px', border: '1px solid #ddd', borderRadius: 6, fontSize: 13, resize: 'vertical', fontFamily: 'inherit', boxSizing: 'border-box' }} />
</div>
<div style={{ display: 'flex', gap: 16, marginBottom: 14 }}>
<div style={{ display: 'flex', gap: 16, marginBottom: 12 }}>
<div style={{ flex: 1 }}>
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, color: '#5A6072', marginBottom: 5 }}></label>
<select value={riskLevel} onChange={(e) => setRiskLevel(e.target.value)} style={inputStyle}>
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 4 }}></label>
<select value={riskLevel} onChange={(e) => setRiskLevel(e.target.value)}
style={{ width: '100%', padding: '8px 12px', border: '1px solid #ddd', borderRadius: 6, fontSize: 13, fontFamily: 'inherit' }}>
<option value="normal"></option>
<option value="attention"></option>
<option value="abnormal"></option>
</select>
</div>
<div style={{ flex: 1 }}>
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, color: '#5A6072', marginBottom: 5 }}></label>
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 4 }}></label>
<input value={suggestions} onChange={(e) => setSuggestions(e.target.value)}
placeholder="如:继续当前用药方案"
style={inputStyle} />
style={{ width: '100%', padding: '8px 12px', border: '1px solid #ddd', borderRadius: 6, fontSize: 13, fontFamily: 'inherit', boxSizing: 'border-box' }} />
</div>
</div>
<div style={{ marginBottom: 14 }}>
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, color: '#5A6072', marginBottom: 8 }}></label>
<div style={{ marginBottom: 12 }}>
<label style={{ display: 'block', fontSize: 13, fontWeight: 500, marginBottom: 6 }}></label>
{items.map((item, i) => (
<div key={i} style={{ display: 'flex', gap: 8, marginBottom: 8, alignItems: 'center' }}>
<div key={i} style={{ display: 'flex', gap: 8, marginBottom: 6, alignItems: 'center' }}>
<input placeholder="项目名称" value={item.itemName} onChange={(e) => updateItem(i, 'itemName', e.target.value)}
style={{ flex: 2, padding: '8px 12px', border: '1.5px solid #E1E5ED', borderRadius: 8, fontSize: 12, outline: 'none' }} />
style={{ flex: 2, padding: '6px 10px', border: '1px solid #ddd', borderRadius: 4, fontSize: 12, fontFamily: 'inherit' }} />
<input placeholder="结果" value={item.resultValue} onChange={(e) => updateItem(i, 'resultValue', e.target.value)}
style={{ flex: 1, padding: '8px 12px', border: '1.5px solid #E1E5ED', borderRadius: 8, fontSize: 12, outline: 'none' }} />
style={{ flex: 1, padding: '6px 10px', border: '1px solid #ddd', borderRadius: 4, fontSize: 12, fontFamily: 'inherit' }} />
<input placeholder="单位" value={item.unit} onChange={(e) => updateItem(i, 'unit', e.target.value)}
style={{ width: 70, padding: '8px 12px', border: '1.5px solid #E1E5ED', borderRadius: 8, fontSize: 12, outline: 'none' }} />
style={{ width: 70, padding: '6px 10px', border: '1px solid #ddd', borderRadius: 4, fontSize: 12, fontFamily: 'inherit' }} />
<input placeholder="参考范围" value={item.referenceRange} onChange={(e) => updateItem(i, 'referenceRange', e.target.value)}
style={{ flex: 1, padding: '8px 12px', border: '1.5px solid #E1E5ED', borderRadius: 8, fontSize: 12, outline: 'none' }} />
<label style={{ fontSize: 12, whiteSpace: 'nowrap', display: 'flex', alignItems: 'center', gap: 4, color: '#5A6072' }}>
style={{ flex: 1, padding: '6px 10px', border: '1px solid #ddd', borderRadius: 4, fontSize: 12, fontFamily: 'inherit' }} />
<label style={{ fontSize: 12, whiteSpace: 'nowrap', display: 'flex', alignItems: 'center', gap: 3 }}>
<input type="checkbox" checked={item.isAbnormal} onChange={(e) => updateItem(i, 'isAbnormal', e.target.checked)} />
</label>
<button onClick={() => removeItem(i)}
style={{ background: 'none', border: 'none', color: '#EF4444', cursor: 'pointer', fontSize: 18, fontWeight: 700 }}
disabled={items.length <= 1}>×</button>
style={{ background: 'none', border: 'none', color: '#c62828', cursor: 'pointer', fontSize: 16 }}
disabled={items.length <= 1}></button>
</div>
))}
<button onClick={addItem} style={{
padding: '6px 14px', border: '1.5px dashed #4F6EF7', borderRadius: 8,
background: 'none', color: '#4F6EF7', cursor: 'pointer', fontSize: 12, fontWeight: 500,
padding: '4px 12px', border: '1px dashed #1976d2', borderRadius: 4,
background: 'none', color: '#1976d2', cursor: 'pointer', fontSize: 12,
}}>+ </button>
</div>
<button onClick={handleInterpret} disabled={submitting} style={{
padding: '11px 32px', background: 'linear-gradient(135deg, #4F6EF7 0%, #6C8AFF 100%)', color: '#fff',
border: 'none', borderRadius: 10, fontSize: 14, cursor: 'pointer', fontWeight: 600,
opacity: submitting ? 0.7 : 1, marginTop: 8, boxShadow: '0 4px 16px rgba(79,110,247,0.25)',
padding: '10px 28px', background: '#1976d2', color: '#fff',
border: 'none', borderRadius: 6, fontSize: 14, cursor: 'pointer',
opacity: submitting ? 0.7 : 1, marginTop: 8,
}}>
{submitting ? '提交中...' : '提交解读'}
</button>

View File

@@ -6,7 +6,7 @@ interface ApiResponse<T> {
message: string;
}
const BASE_URL = import.meta.env.VITE_API_URL;
const BASE_URL = 'http://localhost:5000';
// Endpoints that should NEVER include auth token
const PUBLIC_ENDPOINTS = ['/api/auth/login', '/api/auth/register', '/api/auth/send-sms', '/api/auth/refresh'];

View File

@@ -1,9 +0,0 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}