From 0df75c35e95ad2c3918626e63b1c4fa0e03fd9c8 Mon Sep 17 00:00:00 2001 From: MingNian <1281442923@qq.com> Date: Thu, 21 May 2026 16:05:21 +0800 Subject: [PATCH] refactor: single doctor, direct chat, any phone login, UI polish, fix animations --- .../Controllers/AuthController.cs | 15 +++- .../src/assets/styles/variables.css | 38 +++++----- .../src/components/layout/AppLayout.tsx | 9 +-- .../src/components/layout/StackLayout.tsx | 10 +-- .../src/pages/home/HomePage.module.css | 59 +++++++------- frontend-patient/src/pages/home/HomePage.tsx | 2 +- .../src/pages/services/ChatPage.tsx | 76 +++++++++---------- frontend-patient/src/router/index.tsx | 3 +- 8 files changed, 110 insertions(+), 102 deletions(-) diff --git a/backend/src/HealthManager.WebApi/Controllers/AuthController.cs b/backend/src/HealthManager.WebApi/Controllers/AuthController.cs index 3e72153..6a9998c 100644 --- a/backend/src/HealthManager.WebApi/Controllers/AuthController.cs +++ b/backend/src/HealthManager.WebApi/Controllers/AuthController.cs @@ -26,9 +26,20 @@ public class AuthController( { var user = await authService.GetUserByPhoneAsync(request.Phone); if (user == null) - return Unauthorized(new { message = "用户不存在" }); + { + // Demo: auto-register new users + var db = HttpContext.RequestServices.GetRequiredService(); + user = new User + { + Phone = request.Phone, + Name = "用户" + request.Phone[^4..], + Role = "patient", + PasswordHash = AuthService.HashPassword("demo123"), + }; + db.Users.Add(user); + await db.SaveChangesAsync(); + } - // Demo: accept any SMS code var accessToken = jwtProvider.GenerateAccessToken(user.Id, user.Name, user.Role); var refreshToken = jwtProvider.GenerateRefreshToken(); await authService.SaveRefreshTokenAsync(user.Id, refreshToken, DateTime.UtcNow.AddDays(7)); diff --git a/frontend-patient/src/assets/styles/variables.css b/frontend-patient/src/assets/styles/variables.css index f46176a..6bf59dc 100644 --- a/frontend-patient/src/assets/styles/variables.css +++ b/frontend-patient/src/assets/styles/variables.css @@ -1,10 +1,10 @@ :root { - /* Primary - Medical Blue */ - --color-primary: #1E6BFF; - --color-primary-light: #4D8FFF; - --color-primary-dark: #1055E0; - --color-primary-bg: #EBF3FF; - --color-primary-gradient: linear-gradient(135deg, #1E6BFF, #4D8FFF); + /* Primary - Rich Medical Blue */ + --color-primary: #2563EB; + --color-primary-light: #3B82F6; + --color-primary-dark: #1D4ED8; + --color-primary-bg: #EFF6FF; + --color-primary-gradient: linear-gradient(135deg, #2563EB, #4F8AF8); /* Status */ --color-success: #10B981; @@ -14,20 +14,15 @@ --color-danger: #EF4444; --color-danger-bg: #FEF2F2; - /* Risk */ - --color-risk-normal: #10B981; - --color-risk-attention: #F59E0B; - --color-risk-abnormal: #EF4444; - /* Neutral */ --color-white: #FFFFFF; - --color-bg: #F2F5FA; - --color-bg-secondary: #E8ECF2; - --color-border: #E2E8F0; - --color-border-light: #F0F2F5; + --color-bg: #F4F6FA; + --color-bg-secondary: #EBEEF3; + --color-border: #E4E7EC; + --color-border-light: #F0F1F4; /* Text */ - --color-text-primary: #1A1D28; + --color-text-primary: #1A1E2B; --color-text-secondary: #6B7280; --color-text-tertiary: #9CA3AF; --color-text-inverse: #FFFFFF; @@ -46,16 +41,17 @@ --radius-md: 12px; --radius-lg: 16px; --radius-xl: 20px; + --radius-2xl: 24px; --radius-full: 9999px; /* Shadows */ - --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.04); - --shadow-md: 0 2px 12px rgba(0, 0, 0, 0.06); - --shadow-lg: 0 4px 24px rgba(0, 0, 0, 0.08); + --shadow-xs: 0 1px 2px rgba(0,0,0,0.04); + --shadow-sm: 0 2px 8px rgba(0,0,0,0.05); + --shadow-md: 0 4px 16px rgba(0,0,0,0.07); + --shadow-lg: 0 8px 32px rgba(0,0,0,0.09); /* Font */ - --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', - 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; + --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; --font-size-xs: 11px; --font-size-sm: 12px; --font-size-base: 14px; diff --git a/frontend-patient/src/components/layout/AppLayout.tsx b/frontend-patient/src/components/layout/AppLayout.tsx index 07ee8ec..29c5187 100644 --- a/frontend-patient/src/components/layout/AppLayout.tsx +++ b/frontend-patient/src/components/layout/AppLayout.tsx @@ -9,13 +9,12 @@ export function AppLayout() { return (
- + diff --git a/frontend-patient/src/components/layout/StackLayout.tsx b/frontend-patient/src/components/layout/StackLayout.tsx index f1555f6..970376e 100644 --- a/frontend-patient/src/components/layout/StackLayout.tsx +++ b/frontend-patient/src/components/layout/StackLayout.tsx @@ -5,13 +5,13 @@ export function StackLayout() { const location = useLocation(); return ( - + diff --git a/frontend-patient/src/pages/home/HomePage.module.css b/frontend-patient/src/pages/home/HomePage.module.css index 02c590d..8499f45 100644 --- a/frontend-patient/src/pages/home/HomePage.module.css +++ b/frontend-patient/src/pages/home/HomePage.module.css @@ -7,36 +7,42 @@ display: flex; align-items: center; justify-content: center; + background: var(--color-white); + border-radius: 50%; + box-shadow: var(--shadow-xs); } .overviewCard { margin-bottom: 16px; - background: linear-gradient(135deg, #1E6BFF, #4D8FFF); + background: linear-gradient(135deg, #2563EB 0%, #3B82F6 40%, #5B9AFF 100%); color: #fff; + border-radius: var(--radius-xl); + padding: 20px; + overflow: hidden; } .overviewHeader { display: flex; justify-content: space-between; align-items: center; - margin-bottom: 16px; + margin-bottom: 18px; } .overviewTitle { font-size: var(--font-size-md); font-weight: 600; - color: #fff; + opacity: 0.9; } .overviewTime { font-size: var(--font-size-xs); - color: var(--color-text-tertiary); + opacity: 0.7; } .overviewData { display: flex; align-items: center; - gap: 16px; + gap: 20px; } .bpSection, @@ -50,7 +56,7 @@ .dataLabel { font-size: var(--font-size-xs); - color: var(--color-text-tertiary); + opacity: 0.7; } .bpValues { @@ -60,63 +66,58 @@ } .bpNum { - font-size: var(--font-size-3xl); + font-size: 36px; font-weight: 700; line-height: 1.1; + color: #fff; } -.risk_normal { color: var(--color-success); } -.risk_borderline { color: var(--color-warning); } -.risk_abnormal { color: var(--color-danger); } - .bpSep { font-size: var(--font-size-xl); - color: var(--color-text-tertiary); + opacity: 0.5; } .hrNum { - font-size: var(--font-size-3xl); + font-size: 36px; font-weight: 700; - color: var(--color-text-primary); line-height: 1.1; } .unit { font-size: var(--font-size-xs); - color: var(--color-text-tertiary); + opacity: 0.6; } .divider { width: 1px; height: 60px; - background: var(--color-border); + background: rgba(255,255,255,0.2); } /* Quick Actions */ .quickActions { display: grid; grid-template-columns: repeat(3, 1fr); - gap: 8px; + gap: 10px; margin-bottom: 16px; - background: var(--color-white); - border-radius: var(--radius-lg); - padding: 16px; - box-shadow: var(--shadow-sm); } .quickAction { display: flex; flex-direction: column; align-items: center; - gap: 6px; - padding: 12px 8px; - border-radius: var(--radius-md); - transition: background 0.15s; + gap: 8px; + padding: 14px 8px; + background: var(--color-white); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-sm); + transition: all 0.15s; -webkit-tap-highlight-color: transparent; } .quickAction:active { - background: var(--color-bg); + transform: scale(0.96); + box-shadow: var(--shadow-md); } .quickIcon { @@ -133,6 +134,8 @@ /* Health Tip */ .tipCard { margin-bottom: 16px; + background: linear-gradient(135deg, #FFFBEB, #FFF7ED); + border: 1px solid #FDE68A; } .tipHeader { @@ -145,7 +148,7 @@ .tipTitle { font-size: var(--font-size-sm); font-weight: 600; - color: var(--color-text-secondary); + color: #92400E; } .tipHint { @@ -156,6 +159,6 @@ .tipContent { font-size: var(--font-size-sm); - color: var(--color-text-secondary); + color: #78350F; line-height: 1.6; } diff --git a/frontend-patient/src/pages/home/HomePage.tsx b/frontend-patient/src/pages/home/HomePage.tsx index be10894..d2ecb76 100644 --- a/frontend-patient/src/pages/home/HomePage.tsx +++ b/frontend-patient/src/pages/home/HomePage.tsx @@ -15,7 +15,7 @@ import styles from './HomePage.module.css'; const QUICK_ACTIONS = [ { label: '测血压', icon: '💓', path: '/health/records?type=blood_pressure' }, { label: '记用药', icon: '💊', path: '/health/medications' }, - { label: '在线问诊', icon: '👨‍⚕️', path: '/services/consultation' }, + { label: '在线问诊', icon: '💬', path: '/services/consultation' }, { label: '报告解读', icon: '📋', path: '/services/reports' }, { label: '健康日历', icon: '📅', path: '/health/calendar' }, { label: '运动饮食', icon: '🏃', path: '/health/exercise-diet' }, diff --git a/frontend-patient/src/pages/services/ChatPage.tsx b/frontend-patient/src/pages/services/ChatPage.tsx index 49bd883..dc9fd86 100644 --- a/frontend-patient/src/pages/services/ChatPage.tsx +++ b/frontend-patient/src/pages/services/ChatPage.tsx @@ -1,5 +1,4 @@ import { useEffect, useState, useRef } from 'react'; -import { useParams } from 'react-router-dom'; import { PageHeader } from '@/components/layout/PageHeader'; import * as consultationService from '@/services/consultation.service'; import type { Consultation, ConsultationMessage, Doctor } from '@/types'; @@ -7,7 +6,6 @@ import { formatRelative } from '@/utils/format'; import styles from './ChatPage.module.css'; export function ChatPage() { - const { doctorId } = useParams<{ doctorId: string }>(); const [doctor, setDoctor] = useState(null); const [consultation, setConsultation] = useState(null); const [messages, setMessages] = useState([]); @@ -16,36 +14,43 @@ export function ChatPage() { const bottomRef = useRef(null); useEffect(() => { - if (doctorId) { - consultationService.getDoctor(doctorId).then((d) => setDoctor(d || null)); - consultationService.getConsultation(doctorId).then(async (c) => { - if (c) { - setConsultation(c); - } else { - const newC = await consultationService.startConsultation(doctorId); - setConsultation(newC); - } - }); - } - }, [doctorId]); - - // Fetch messages when consultation is loaded - useEffect(() => { - if (consultation?.id) { - consultationService.getDoctorReply(consultation.id).then(() => { - // The messages are fetched as a side effect; fetch them directly - import('@/services/api-client').then(({ api }) => { - api.get(`/api/consultations/${consultation.id}/messages`) - .then((res) => setMessages(res.data)); + // Get the first available doctor + consultationService.getDoctors().then((docs) => { + if (docs.length > 0) { + const doc = docs[0]; + setDoctor(doc); + // Find or create consultation + consultationService.getConsultation(doc.id).then(async (c) => { + if (c) { + setConsultation(c); + loadMessages(c.id); + } else { + const newC = await consultationService.startConsultation(doc.id, '在线咨询'); + setConsultation(newC); + } }); - }); - } - }, [consultation?.id]); + } + }); + }, []); + + const loadMessages = (cid: string) => { + import('@/services/api-client').then(({ api }) => { + api.get(`/api/consultations/${cid}/messages`) + .then((res) => setMessages(res.data)); + }); + }; useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]); + useEffect(() => { + // Poll for new messages every 3 seconds + if (!consultation?.id) return; + const timer = setInterval(() => loadMessages(consultation.id), 3000); + return () => clearInterval(timer); + }, [consultation?.id]); + const handleSend = async () => { if (!text.trim() || !consultation || sending) return; setSending(true); @@ -54,22 +59,17 @@ export function ChatPage() { const sent = await consultationService.sendMessage(consultation.id, msgText); setMessages((prev) => [...prev, sent]); setSending(false); - // Poll for doctor reply after delay - setTimeout(async () => { - const reply = await consultationService.getDoctorReply(consultation.id); - if (reply) { - setMessages((prev) => { - if (prev.find((m) => m.id === reply.id)) return prev; - return [...prev, reply]; - }); - } - }, 1500); }; return (
- +
+ {messages.length === 0 && ( +
+ 您好,我是{doctor?.name || '医生'},请问有什么可以帮您? +
+ )} {messages.map((msg) => (
e.key === 'Enter' && handleSend()} />
diff --git a/frontend-patient/src/router/index.tsx b/frontend-patient/src/router/index.tsx index 46045ce..931096f 100644 --- a/frontend-patient/src/router/index.tsx +++ b/frontend-patient/src/router/index.tsx @@ -75,8 +75,7 @@ export const router = createBrowserRouter([ { path: 'health/medications/add', element: }, { path: 'health/medications/:id', element: }, { path: 'health/exercise-diet', element: }, - { path: 'services/consultation', element: }, - { path: 'services/consultation/chat/:doctorId', element: }, + { path: 'services/consultation', element: }, { path: 'services/reports', element: }, { path: 'services/reports/upload', element: }, { path: 'services/reports/:id', element: },