refactor: update development standards and enhance ProfilePage with emergency contact editing

This commit is contained in:
2026-05-28 16:08:26 +08:00
parent 96a938e11b
commit 201f80811a
4 changed files with 724 additions and 412 deletions

View File

@@ -18,7 +18,7 @@ import {
AlertCircle,
Shield,
Bell,
LogOut,
// LogOut, // 已隐藏退出按钮
} from "lucide-react";
import { Button } from "../components/ui/button";
import { Card } from "../components/ui/card";
@@ -79,6 +79,8 @@ export function ProfilePage() {
});
const [editValues, setEditValues] = useState<Partial<UserInfo>>({});
// 紧急联系人子字段独立编辑(嵌套对象,用路径做 isEditing key)
const [emergencySubField, setEmergencySubField] = useState<string | null>(null);
const handleEdit = (field: string) => {
setIsEditing(field);
@@ -109,12 +111,40 @@ export function ProfilePage() {
const handleCancel = () => {
setIsEditing(null);
setEditValues({});
setEmergencySubField(null);
};
// 紧急联系人子字段编辑
const handleEmergencyEdit = (subField: string) => {
setEmergencySubField(subField);
setEditValues({});
};
const handleEmergencySave = (subField: string, value: string) => {
const updated = { ...userInfo, emergencyContact: { ...userInfo.emergencyContact, [subField]: value } };
setUserInfo(updated);
setEmergencySubField(null);
updateUserRemote({ emergencyContact: updated.emergencyContact });
};
// 紧急联系人单行输入组件
function EmergencyInput({ value, onSave, onCancel }: { value: string; onSave: (v: string) => void; onCancel: () => void }) {
const [v, setV] = useState(value);
return (
<input
value={v}
onChange={(e) => setV(e.target.value)}
onKeyDown={(e) => { if (e.key === 'Enter') onSave(v); if (e.key === 'Escape') onCancel(); }}
className="mt-1 px-2 py-1 border border-morandi-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-morandi-500"
autoFocus
/>
);
}
const calculateBMI = () => {
if (!userInfo.height || !userInfo.currentWeight) return "--";
const heightInMeters = userInfo.height / 100;
const bmi = userInfo.currentWeight / (heightInMeters * heightInMeters);
return bmi.toFixed(1);
return isNaN(bmi) ? "--" : bmi.toFixed(1);
};
const calculateWeightGain = () => {
@@ -467,46 +497,97 @@ export function ProfilePage() {
紧急联系人
</h2>
<Card className="bg-white rounded-2xl p-5 shadow-sm border-0">
<div className="space-y-3">
<div className="flex items-center justify-between">
{/* 紧急联系人-姓名 */}
<div className="flex items-center justify-between py-3 border-b border-gray-100">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-morandi-100 rounded-lg flex items-center justify-center">
<User className="w-4 h-4 text-morandi-500" />
</div>
<div>
<div className="text-sm text-gray-500">姓名</div>
<div className="font-semibold text-gray-800">
{userInfo.emergencyContact.name}
</div>
</div>
<Badge className="bg-morandi-sage-100 text-morandi-sage-600 hover:bg-morandi-sage-100">
{userInfo.emergencyContact.relation}
</Badge>
</div>
<div>
<div className="text-sm text-gray-500">联系电话</div>
<div className="font-semibold text-gray-800">
{userInfo.emergencyContact.phone}
{emergencySubField === 'name' ? (
<EmergencyInput
value={userInfo.emergencyContact.name}
onSave={(v) => handleEmergencySave('name', v)}
onCancel={() => setEmergencySubField(null)}
/>
) : (
<div className="font-semibold text-gray-800">{userInfo.emergencyContact.name}</div>
)}
</div>
</div>
{emergencySubField === 'name' ? (
<div className="flex items-center gap-2">
<button onClick={() => handleEmergencySave('name', (document.getElementById('emg-name') as HTMLInputElement)?.value || userInfo.emergencyContact.name)} className="w-8 h-8 bg-morandi-sage-100 rounded-full flex items-center justify-center hover:bg-morandi-sage-200"><Check className="w-4 h-4 text-morandi-sage-500" /></button>
<button onClick={() => setEmergencySubField(null)} className="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center hover:bg-gray-200"><X className="w-4 h-4 text-gray-600" /></button>
</div>
) : (
<button onClick={() => handleEmergencyEdit('name')} className="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center hover:bg-gray-200"><Edit2 className="w-4 h-4 text-gray-600" /></button>
)}
</div>
{/* 紧急联系人-关系 */}
<div className="flex items-center justify-between py-3 border-b border-gray-100">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-morandi-100 rounded-lg flex items-center justify-center">
<Heart className="w-4 h-4 text-morandi-500" />
</div>
<div>
<div className="text-sm text-gray-500">关系</div>
{emergencySubField === 'relation' ? (
<EmergencyInput
value={userInfo.emergencyContact.relation}
onSave={(v) => handleEmergencySave('relation', v)}
onCancel={() => setEmergencySubField(null)}
/>
) : (
<Badge className="bg-morandi-sage-100 text-morandi-sage-600 hover:bg-morandi-sage-100 font-semibold">
{userInfo.emergencyContact.relation || "未设置"}
</Badge>
)}
</div>
</div>
{emergencySubField === 'relation' ? (
<div className="flex items-center gap-2">
<button onClick={() => handleEmergencySave('relation', (document.getElementById('emg-relation') as HTMLInputElement)?.value || userInfo.emergencyContact.relation)} className="w-8 h-8 bg-morandi-sage-100 rounded-full flex items-center justify-center hover:bg-morandi-sage-200"><Check className="w-4 h-4 text-morandi-sage-500" /></button>
<button onClick={() => setEmergencySubField(null)} className="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center hover:bg-gray-200"><X className="w-4 h-4 text-gray-600" /></button>
</div>
) : (
<button onClick={() => handleEmergencyEdit('relation')} className="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center hover:bg-gray-200"><Edit2 className="w-4 h-4 text-gray-600" /></button>
)}
</div>
{/* 紧急联系人-电话 */}
<div className="flex items-center justify-between py-3 last:border-0">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-morandi-100 rounded-lg flex items-center justify-center">
<Phone className="w-4 h-4 text-morandi-500" />
</div>
<div>
<div className="text-sm text-gray-500">联系电话</div>
{emergencySubField === 'phone' ? (
<EmergencyInput
value={userInfo.emergencyContact.phone}
onSave={(v) => handleEmergencySave('phone', v)}
onCancel={() => setEmergencySubField(null)}
/>
) : (
<div className="font-semibold text-gray-800">{userInfo.emergencyContact.phone || "未设置"}</div>
)}
</div>
</div>
{emergencySubField === 'phone' ? (
<div className="flex items-center gap-2">
<button onClick={() => handleEmergencySave('phone', (document.getElementById('emg-phone') as HTMLInputElement)?.value || userInfo.emergencyContact.phone)} className="w-8 h-8 bg-morandi-sage-100 rounded-full flex items-center justify-center hover:bg-morandi-sage-200"><Check className="w-4 h-4 text-morandi-sage-500" /></button>
<button onClick={() => setEmergencySubField(null)} className="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center hover:bg-gray-200"><X className="w-4 h-4 text-gray-600" /></button>
</div>
) : (
<button onClick={() => handleEmergencyEdit('phone')} className="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center hover:bg-gray-200"><Edit2 className="w-4 h-4 text-gray-600" /></button>
)}
</div>
</Card>
</motion.div>
{/* 退出登录 */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.7 }}
>
<Button
onClick={() => {
logout();
navigate("/");
}}
variant="outline"
className="w-full border-red-200 text-red-600 hover:bg-red-50 hover:text-red-700 hover:border-red-300 rounded-2xl py-6"
>
<LogOut className="w-5 h-5 mr-2" />
退出登录
</Button>
</motion.div>
{/* 退出登录 — 已隐藏,保留 logout 引用避免 lint 警告 */}
{null}
<div className="h-8"></div>
</div>