diff --git a/backend/app/models/user.py b/backend/app/models/user.py index 6788f66..86671dc 100644 --- a/backend/app/models/user.py +++ b/backend/app/models/user.py @@ -21,6 +21,8 @@ class User(Document): name: str = "" phone: Optional[str] = None age: Optional[int] = None + email: str = "" + address: str = "" height: Optional[float] = None # cm pre_pregnancy_weight: Optional[float] = None # kg current_weight: Optional[float] = None # kg diff --git a/backend/app/routers/user.py b/backend/app/routers/user.py index b90f529..92d9659 100644 --- a/backend/app/routers/user.py +++ b/backend/app/routers/user.py @@ -18,6 +18,8 @@ def _user_to_response(user: User) -> UserProfileResponse: id=str(user.id), name=user.name, phone=user.phone, + email=user.email, + address=user.address, age=user.age, height=user.height, pre_pregnancy_weight=user.pre_pregnancy_weight, diff --git a/backend/app/schemas/user.py b/backend/app/schemas/user.py index 9ebfb45..41735df 100644 --- a/backend/app/schemas/user.py +++ b/backend/app/schemas/user.py @@ -14,6 +14,8 @@ class UserProfileUpdate(BaseModel): name: Optional[str] = None age: Optional[int] = None phone: Optional[str] = None + email: Optional[str] = None + address: Optional[str] = None height: Optional[float] = None pre_pregnancy_weight: Optional[float] = None current_weight: Optional[float] = None @@ -52,6 +54,8 @@ class UserProfileResponse(BaseModel): id: str name: str phone: Optional[str] + email: str = "" + address: str = "" age: Optional[int] height: Optional[float] pre_pregnancy_weight: Optional[float] diff --git a/frontend/src/app/context/UserContext.tsx b/frontend/src/app/context/UserContext.tsx index 93e0015..b46f5ab 100644 --- a/frontend/src/app/context/UserContext.tsx +++ b/frontend/src/app/context/UserContext.tsx @@ -12,6 +12,8 @@ export interface UserProfile { name: string; age: number | null; phone: string; + email: string; + address: string; avatar_url: string | null; lastPeriodDate: string; // YYYY-MM-DD dueDate: string; @@ -36,6 +38,8 @@ const DEFAULT_PROFILE: UserProfile = { name: "", age: null, phone: "", + email: "", + address: "", avatar_url: null, lastPeriodDate: "", dueDate: "", @@ -156,6 +160,8 @@ function mapDtoToProfile(dto: UserProfileDTO): UserProfile { name: dto.name || "", age: dto.age, phone: dto.phone || "", + email: dto.email || "", + address: dto.address || "", avatar_url: dto.avatar_url || null, lastPeriodDate: lmpDate, dueDate, @@ -180,7 +186,7 @@ interface UserContextValue { updateUserRemote: (data: Partial) => Promise; setToken: (token: string) => void; setTokenAndUser: (token: string, info: BasicUserInfo) => void; - fetchProfile: () => Promise; + fetchProfile: () => Promise; logout: () => void; } @@ -220,6 +226,8 @@ export function UserProvider({ children }: { children: ReactNode }) { if (data.name !== undefined) payload.name = data.name; if (data.age !== undefined) payload.age = data.age; if (data.phone !== undefined) payload.phone = data.phone; + if (data.email !== undefined) payload.email = data.email; + if (data.address !== undefined) payload.address = data.address; if (data.height !== undefined) payload.height = data.height; if (data.prePregnancyWeight !== undefined) payload.pre_pregnancy_weight = data.prePregnancyWeight; if (data.currentWeight !== undefined) payload.current_weight = data.currentWeight; diff --git a/frontend/src/app/pages/ProfilePage.tsx b/frontend/src/app/pages/ProfilePage.tsx index 3949983..2a7bc98 100644 --- a/frontend/src/app/pages/ProfilePage.tsx +++ b/frontend/src/app/pages/ProfilePage.tsx @@ -58,8 +58,8 @@ export function ProfilePage() { name: user.name || "未设置", age: user.age || 0, phone: user.phone || "未设置", - email: "", - address: "", + email: user.email || "", + address: user.address || "", avatar_url: user.avatar_url || null, lastPeriod: user.lastPeriodDate || "", dueDate: user.dueDate || "", @@ -88,24 +88,17 @@ export function ProfilePage() { }; const handleSave = (field: string) => { - const updated = { ...userInfo, ...(editValues as any) }; + const newVal = (editValues as any)[field]; + if (newVal === undefined) return; + const updated = { ...userInfo, [field]: newVal }; setUserInfo(updated); setIsEditing(null); setEditValues({}); - // Sync back to global context + backend - updateUserRemote({ - name: updated.name, - age: updated.age, - phone: updated.phone, - lastPeriodDate: updated.lastPeriod, - height: updated.height, - prePregnancyWeight: updated.prePregnancyWeight, - currentWeight: updated.currentWeight, - bloodType: updated.bloodType, - allergies: updated.allergies, - medicalHistory: updated.medicalHistory, - emergencyContact: updated.emergencyContact, - }); + // 字段名映射:userInfo key → UserProfile key + const keyMap: Record = { lastPeriod: 'lastPeriodDate' }; + const profileKey = keyMap[field] || field; + // 只提交修改的属性 + updateUserRemote({ [profileKey]: newVal } as any); }; const handleCancel = () => { @@ -114,6 +107,29 @@ export function ProfilePage() { setEmergencySubField(null); }; + // 列表字段编辑(过敏史/既往病史) + const [addingField, setAddingField] = useState(null); + const [newItemText, setNewItemText] = useState(""); + + const handleRemoveItem = (field: 'allergies' | 'medicalHistory', index: number) => { + const list = userInfo[field].filter((_, i) => i !== index); + const placeholder = field === 'allergies' ? ["无"] : ["无重大病史"]; + const updated = { ...userInfo, [field]: list.length ? list : placeholder }; + setUserInfo(updated); + updateUserRemote({ [field]: list } as any); + }; + + const handleAddItem = (field: 'allergies' | 'medicalHistory') => { + if (!newItemText.trim()) return; + const cleaned = (userInfo[field][0] === "无" || userInfo[field][0] === "无重大病史") + ? [] : userInfo[field]; + const updated = { ...userInfo, [field]: [...cleaned, newItemText.trim()] }; + setUserInfo(updated); + setNewItemText(""); + setAddingField(null); + updateUserRemote({ [field]: updated[field] } as any); + }; + // 紧急联系人子字段编辑 const handleEmergencyEdit = (subField: string) => { setEmergencySubField(subField); @@ -127,10 +143,11 @@ export function ProfilePage() { }; // 紧急联系人单行输入组件 - function EmergencyInput({ value, onSave, onCancel }: { value: string; onSave: (v: string) => void; onCancel: () => void }) { + function EmergencyInput({ value, onSave, onCancel, inputId }: { value: string; onSave: (v: string) => void; onCancel: () => void; inputId?: string }) { const [v, setV] = useState(value); return ( setV(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') onSave(v); if (e.key === 'Escape') onCancel(); }} @@ -443,43 +460,74 @@ export function ProfilePage() { 医疗信息 -
-
-
血型
- - {userInfo.bloodType} - -
+ +
药物过敏史
-
+
{userInfo.allergies.map((allergy, index) => ( - + {allergy} + {allergy !== "无" && ( + + )} ))} - + {addingField === 'allergies' ? ( +
+ setNewItemText(e.target.value)} + onKeyDown={(e) => { if (e.key === 'Enter') handleAddItem('allergies'); if (e.key === 'Escape') { setAddingField(null); setNewItemText(""); }}} + placeholder="输入..." + className="px-3 py-1 border border-morandi-200 rounded-full text-sm focus:outline-none focus:ring-2 focus:ring-morandi-500" + autoFocus + /> + +
+ ) : ( + + )}
既往病史
-
+
{userInfo.medicalHistory.map((history, index) => ( - + {history} + {history !== "无重大病史" && ( + + )} ))} - + {addingField === 'medicalHistory' ? ( +
+ setNewItemText(e.target.value)} + onKeyDown={(e) => { if (e.key === 'Enter') handleAddItem('medicalHistory'); if (e.key === 'Escape') { setAddingField(null); setNewItemText(""); }}} + placeholder="输入..." + className="px-3 py-1 border border-morandi-200 rounded-full text-sm focus:outline-none focus:ring-2 focus:ring-morandi-500" + autoFocus + /> + +
+ ) : ( + + )}
@@ -507,6 +555,7 @@ export function ProfilePage() {
姓名
{emergencySubField === 'name' ? ( handleEmergencySave('name', v)} onCancel={() => setEmergencySubField(null)} @@ -535,6 +584,7 @@ export function ProfilePage() {
关系
{emergencySubField === 'relation' ? ( handleEmergencySave('relation', v)} onCancel={() => setEmergencySubField(null)} @@ -565,6 +615,7 @@ export function ProfilePage() {
联系电话
{emergencySubField === 'phone' ? ( handleEmergencySave('phone', v)} onCancel={() => setEmergencySubField(null)} diff --git a/frontend/src/app/services/api.ts b/frontend/src/app/services/api.ts index 5d1b6e7..641421e 100644 --- a/frontend/src/app/services/api.ts +++ b/frontend/src/app/services/api.ts @@ -96,6 +96,8 @@ export interface UserProfileDTO { id: string; name: string; phone: string | null; + email: string; + address: string; age: number | null; height: number | null; pre_pregnancy_weight: number | null;