refactor: update development standards and enhance ProfilePage with emergency contact editing
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user