全新UI语言
This commit is contained in:
@@ -87,8 +87,8 @@ export function AccountAuthForm({ redirectTo }: AccountAuthFormProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-[32px] border border-white/75 bg-white/84 p-6 shadow-[0_26px_60px_rgba(115,76,42,0.12)] backdrop-blur sm:p-7">
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<div>
|
||||
<div className="segment">
|
||||
{[
|
||||
{ id: "login", label: "已有账号,直接登录" },
|
||||
{ id: "register", label: "第一次使用,创建账号" },
|
||||
@@ -103,11 +103,7 @@ export function AccountAuthForm({ redirectTo }: AccountAuthFormProps) {
|
||||
setMode(item.id as AuthMode);
|
||||
setNotice(null);
|
||||
}}
|
||||
className={`inline-flex h-11 items-center justify-center rounded-full px-4 text-sm font-semibold transition ${
|
||||
active
|
||||
? "bg-[var(--ink)] text-white shadow-[0_12px_30px_rgba(36,27,20,0.18)]"
|
||||
: "border border-[var(--line)] bg-white text-[var(--ink)] hover:bg-[var(--paper-soft)]"
|
||||
}`}
|
||||
className={active ? "active" : ""}
|
||||
>
|
||||
{item.label}
|
||||
</button>
|
||||
@@ -115,28 +111,21 @@ export function AccountAuthForm({ redirectTo }: AccountAuthFormProps) {
|
||||
})}
|
||||
</div>
|
||||
|
||||
<form className="mt-6 space-y-4" onSubmit={handleSubmit}>
|
||||
<form style={{ display: "flex", flexDirection: "column", gap: 10, marginTop: 12 }} onSubmit={handleSubmit}>
|
||||
<div>
|
||||
<label className="text-sm font-semibold text-[var(--ink)]" htmlFor="caregiver-username">
|
||||
用户名
|
||||
</label>
|
||||
<label className="sr-only" htmlFor="caregiver-username">用户名</label>
|
||||
<input
|
||||
id="caregiver-username"
|
||||
value={username}
|
||||
onChange={(event) => setUsername(event.target.value)}
|
||||
autoComplete="username"
|
||||
placeholder="例如:family_guardian"
|
||||
className="mt-2 h-13 w-full rounded-[22px] border border-[var(--line)] bg-[var(--paper-soft)] px-4 text-sm text-[var(--ink)] outline-none transition focus:border-[var(--copper)] focus:bg-white"
|
||||
placeholder="用户名"
|
||||
className="field"
|
||||
/>
|
||||
<p className="mt-2 text-xs leading-6 text-[var(--muted)]">
|
||||
建议使用 3 到 24 位字母、数字、下划线或横线,方便全家统一登录。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-semibold text-[var(--ink)]" htmlFor="caregiver-password">
|
||||
密码
|
||||
</label>
|
||||
<label className="sr-only" htmlFor="caregiver-password">密码</label>
|
||||
<input
|
||||
id="caregiver-password"
|
||||
type="password"
|
||||
@@ -144,18 +133,13 @@ export function AccountAuthForm({ redirectTo }: AccountAuthFormProps) {
|
||||
onChange={(event) => setPassword(event.target.value)}
|
||||
autoComplete={mode === "login" ? "current-password" : "new-password"}
|
||||
placeholder="请输入密码"
|
||||
className="mt-2 h-13 w-full rounded-[22px] border border-[var(--line)] bg-[var(--paper-soft)] px-4 text-sm text-[var(--ink)] outline-none transition focus:border-[var(--copper)] focus:bg-white"
|
||||
className="field"
|
||||
/>
|
||||
<p className="mt-2 text-xs leading-6 text-[var(--muted)]">
|
||||
密码至少 8 位。同一账号可以在多台家属设备登录,消息提醒会同步到所有已登录设备。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{mode === "register" ? (
|
||||
<div>
|
||||
<label className="text-sm font-semibold text-[var(--ink)]" htmlFor="caregiver-password-confirm">
|
||||
再输入一次密码
|
||||
</label>
|
||||
<label className="sr-only" htmlFor="caregiver-password-confirm">再输入一次密码</label>
|
||||
<input
|
||||
id="caregiver-password-confirm"
|
||||
type="password"
|
||||
@@ -163,7 +147,7 @@ export function AccountAuthForm({ redirectTo }: AccountAuthFormProps) {
|
||||
onChange={(event) => setConfirmPassword(event.target.value)}
|
||||
autoComplete="new-password"
|
||||
placeholder="再次输入密码"
|
||||
className="mt-2 h-13 w-full rounded-[22px] border border-[var(--line)] bg-[var(--paper-soft)] px-4 text-sm text-[var(--ink)] outline-none transition focus:border-[var(--copper)] focus:bg-white"
|
||||
className="field"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -171,7 +155,8 @@ export function AccountAuthForm({ redirectTo }: AccountAuthFormProps) {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
className="inline-flex h-12 w-full items-center justify-center rounded-full bg-[var(--copper)] px-5 text-sm font-semibold text-white transition hover:bg-[var(--copper-deep)] disabled:cursor-not-allowed disabled:opacity-60"
|
||||
className="btn primary block"
|
||||
style={{ minHeight: 44 }}
|
||||
>
|
||||
{submitting
|
||||
? mode === "login"
|
||||
@@ -184,10 +169,10 @@ export function AccountAuthForm({ redirectTo }: AccountAuthFormProps) {
|
||||
</form>
|
||||
|
||||
{notice ? (
|
||||
<p className="mt-4 rounded-[20px] bg-[var(--paper-soft)] px-4 py-3 text-sm leading-7 text-[var(--muted)]">
|
||||
<p style={{ margin: "10px 0 0", borderRadius: 8, background: "var(--surface)", padding: "10px 12px", color: "var(--muted)", fontSize: 12, lineHeight: 1.6 }}>
|
||||
{notice}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,45 +31,28 @@ export function BindDeviceForm() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-[28px] border border-white/70 bg-white/80 p-5 shadow-[0_24px_60px_rgba(125,80,46,0.12)] backdrop-blur">
|
||||
<div className="flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between">
|
||||
<div className="max-w-xl">
|
||||
<p className="text-sm font-medium tracking-[0.18em] text-[var(--copper)] uppercase">
|
||||
添加设备
|
||||
</p>
|
||||
<h3 className="mt-2 font-[family-name:var(--font-display)] text-2xl text-[var(--ink)]">
|
||||
扫码或粘贴设备码连接长辈
|
||||
</h3>
|
||||
<p className="mt-2 text-sm leading-7 text-[var(--muted)]">
|
||||
让长辈打开手机上的「家人连接」页面,扫描二维码最快;也可以将设备码粘贴到下方。
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href="/scan"
|
||||
className="inline-flex h-12 items-center justify-center rounded-full bg-[var(--copper)] px-5 text-sm font-semibold text-white transition hover:bg-[var(--copper-deep)]"
|
||||
>
|
||||
扫码连接
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<form className="mt-5 flex flex-col gap-3 lg:flex-row" onSubmit={handleSubmit}>
|
||||
<div className="card card-pad">
|
||||
<p style={{ margin: 0, fontSize: 13, fontWeight: 600 }}>用设备码添加</p>
|
||||
<form className="button-row" style={{ marginTop: 9 }} onSubmit={handleSubmit}>
|
||||
<input
|
||||
value={rawCode}
|
||||
onChange={(event) => setRawCode(event.target.value)}
|
||||
placeholder="粘贴设备码或二维码链接"
|
||||
className="h-13 flex-1 rounded-full border border-[var(--line)] bg-[var(--paper-soft)] px-5 text-sm text-[var(--ink)] outline-none transition focus:border-[var(--copper)] focus:bg-white"
|
||||
placeholder="粘贴长辈屏幕下方的设备码"
|
||||
className="field"
|
||||
style={{ height: 36, flex: 1, minWidth: 0 }}
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
className="inline-flex h-13 min-w-36 items-center justify-center rounded-full bg-[var(--olive)] px-5 text-sm font-semibold text-white transition hover:bg-[var(--olive-deep)] disabled:cursor-not-allowed disabled:opacity-60"
|
||||
className="btn dark"
|
||||
style={{ minHeight: 36, flex: "none" }}
|
||||
>
|
||||
{submitting ? "正在连接" : "立即绑定"}
|
||||
{submitting ? "连接中" : "连接"}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{notice ? (
|
||||
<p className="mt-3 whitespace-pre-line text-sm leading-7 text-[var(--muted)]">
|
||||
<p style={{ margin: "8px 0 0", whiteSpace: "pre-line", color: "var(--success)", fontSize: 12 }}>
|
||||
{notice}
|
||||
</p>
|
||||
) : null}
|
||||
@@ -142,20 +125,14 @@ export function SendMessageForm({ deviceUuid }: SendMessageFormProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="rounded-[28px] border border-white/70 bg-white/88 p-5" onSubmit={handleSubmit}>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<p className="text-sm font-medium tracking-[0.18em] text-[var(--copper)] uppercase">
|
||||
留言
|
||||
</p>
|
||||
<h4 className="mt-2 font-[family-name:var(--font-display)] text-xl text-[var(--ink)]">
|
||||
给长辈写一句问候
|
||||
</h4>
|
||||
</div>
|
||||
<form className="card card-pad" onSubmit={handleSubmit}>
|
||||
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 }}>
|
||||
<h4 style={{ margin: 0, fontSize: 13, fontWeight: 600 }}>给长辈留言</h4>
|
||||
<select
|
||||
value={importance}
|
||||
onChange={(event) => setImportance(event.target.value)}
|
||||
className="h-11 rounded-full border border-[var(--line)] bg-[var(--paper-soft)] px-4 text-sm text-[var(--ink)] outline-none"
|
||||
className="field"
|
||||
style={{ width: "auto", height: 32, padding: "0 8px", fontSize: 12 }}
|
||||
>
|
||||
<option value="LOW">温和提醒</option>
|
||||
<option value="NORMAL">日常问候</option>
|
||||
@@ -169,24 +146,22 @@ export function SendMessageForm({ deviceUuid }: SendMessageFormProps) {
|
||||
onChange={(event) => setContent(event.target.value)}
|
||||
rows={4}
|
||||
placeholder="例如:妈,晚上天气凉,记得添件衣服。晚饭后我给您打电话。"
|
||||
className="mt-4 w-full rounded-[24px] border border-[var(--line)] bg-[var(--paper-soft)] px-4 py-4 text-sm leading-7 text-[var(--ink)] outline-none transition focus:border-[var(--copper)] focus:bg-white"
|
||||
className="field"
|
||||
style={{ marginTop: 8 }}
|
||||
/>
|
||||
|
||||
<div className="mt-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<p className="text-sm leading-7 text-[var(--muted)]">
|
||||
长辈和智能助理聊天时,就能看到这条留言。
|
||||
</p>
|
||||
<div style={{ display: "flex", alignItems: "center", justifyContent: "flex-end", marginTop: 9 }}>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
className="inline-flex h-12 min-w-32 items-center justify-center rounded-full bg-[var(--ink)] px-5 text-sm font-semibold text-white transition hover:bg-[var(--ink-soft)] disabled:cursor-not-allowed disabled:opacity-60"
|
||||
className="btn primary small"
|
||||
>
|
||||
{submitting ? "正在送达" : "写好并送出"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{notice ? (
|
||||
<div className="mt-3 space-y-2 text-sm leading-7 text-[var(--muted)]">
|
||||
<div style={{ marginTop: 9, color: "var(--success)", fontSize: 12, lineHeight: 1.6 }}>
|
||||
<p className="whitespace-pre-line">{notice}</p>
|
||||
{createdMessagePublicId ? (
|
||||
<Link
|
||||
@@ -356,4 +331,4 @@ export function ManageDeviceForm({
|
||||
) : null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+52
-78
@@ -1,39 +1,29 @@
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Home,
|
||||
MessageCircle,
|
||||
Smartphone,
|
||||
Activity,
|
||||
ScanLine,
|
||||
Settings,
|
||||
} from "lucide-react";
|
||||
import { ChevronLeft, Home, MessageCircle, Smartphone, UserRound } from "lucide-react";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
const navigationItems = [
|
||||
{ href: "/", label: "首页", icon: Home },
|
||||
{ href: "/", label: "概览", icon: Home },
|
||||
{ href: "/messages", label: "留言", icon: MessageCircle },
|
||||
{ href: "/devices", label: "设备", icon: Smartphone },
|
||||
{ href: "/activity", label: "动态", icon: Activity },
|
||||
{ href: "/scan", label: "扫码", icon: ScanLine },
|
||||
{ href: "/settings", label: "设置", icon: Settings },
|
||||
{ href: "/settings", label: "我的", icon: UserRound },
|
||||
];
|
||||
|
||||
function isActivePath(currentPath: string, href: string) {
|
||||
if (href === "/") {
|
||||
return currentPath === "/";
|
||||
}
|
||||
|
||||
if (href === "/") return currentPath === "/";
|
||||
return currentPath === href || currentPath.startsWith(`${href}/`);
|
||||
}
|
||||
|
||||
type PanelShellProps = {
|
||||
currentPath: string;
|
||||
title: string;
|
||||
description: string;
|
||||
description?: string;
|
||||
caregiverLabel: string;
|
||||
children: ReactNode;
|
||||
actions?: ReactNode;
|
||||
eyebrow?: string;
|
||||
backHref?: string;
|
||||
showTabs?: boolean;
|
||||
unreadCount?: number;
|
||||
};
|
||||
|
||||
export function PanelShell({
|
||||
@@ -43,71 +33,55 @@ export function PanelShell({
|
||||
caregiverLabel,
|
||||
children,
|
||||
actions,
|
||||
eyebrow = "家人连线",
|
||||
backHref,
|
||||
showTabs = !backHref,
|
||||
unreadCount = 0,
|
||||
}: PanelShellProps) {
|
||||
const initial = caregiverLabel.trim().slice(-1) || "安";
|
||||
|
||||
return (
|
||||
<main className="relative overflow-hidden px-4 py-6 sm:px-6 lg:px-8 lg:py-8">
|
||||
<div className="pointer-events-none absolute inset-x-0 top-0 h-72 bg-[radial-gradient(circle_at_top,rgba(255,255,255,0.8),transparent_70%)]" />
|
||||
<div className="mx-auto max-w-7xl space-y-6">
|
||||
<header className="paper-panel relative overflow-hidden rounded-[36px] border border-white/70 px-6 py-7 shadow-[0_35px_90px_rgba(105,68,41,0.13)] sm:px-8 lg:px-10 lg:py-8">
|
||||
<div className="absolute -right-12 top-[-50px] h-44 w-44 rounded-full bg-[rgba(53,84,141,0.14)] blur-2xl" />
|
||||
<div className="absolute left-10 top-10 h-24 w-24 rounded-full border border-[rgba(118,132,94,0.18)] z-0 pointer-events-none" />
|
||||
|
||||
<div className="flex flex-col gap-6 xl:flex-row xl:items-start xl:justify-between">
|
||||
<div className="max-w-4xl">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{navigationItems.map((item) => {
|
||||
const active = isActivePath(currentPath, item.href);
|
||||
const Icon = item.icon;
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={`inline-flex h-11 items-center justify-center gap-2 rounded-full px-4 text-sm font-semibold transition ${
|
||||
active
|
||||
? "bg-[var(--ink)] text-white shadow-[0_12px_30px_rgba(36,27,20,0.18)]"
|
||||
: "border border-[var(--line)] bg-white/82 text-[var(--ink)] hover:bg-[var(--paper-soft)]"
|
||||
}`}
|
||||
>
|
||||
<Icon size={16} />
|
||||
<span>{item.label}</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<p className="mt-6 text-sm font-semibold tracking-[0.24em] text-[var(--copper)] uppercase">
|
||||
{eyebrow}
|
||||
</p>
|
||||
<h1 className="mt-4 max-w-3xl font-[family-name:var(--font-display)] text-4xl leading-[1.15] text-[var(--ink)] sm:text-5xl">
|
||||
{title}
|
||||
</h1>
|
||||
<p className="mt-5 max-w-2xl text-base leading-8 text-[var(--muted)] sm:text-lg">
|
||||
{description}
|
||||
</p>
|
||||
|
||||
<div className="mt-6 flex flex-wrap gap-3 text-sm text-[var(--muted)]">
|
||||
<span className="rounded-full border border-[var(--line)] bg-white/76 px-4 py-2">
|
||||
账号:{caregiverLabel}
|
||||
</span>
|
||||
<form action="/api/account/logout" method="post">
|
||||
<button
|
||||
type="submit"
|
||||
className="inline-flex h-10 items-center justify-center rounded-full border border-[var(--line)] bg-white px-4 font-semibold text-[var(--ink)] transition hover:bg-[var(--paper-soft)]"
|
||||
>
|
||||
退出登录
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<main className="app-frame">
|
||||
<div className={`app-content app-stack${backHref ? " detail" : ""}`}>
|
||||
{backHref ? (
|
||||
<header className="detail-header">
|
||||
<Link href={backHref} className="back-button" aria-label="返回">
|
||||
<ChevronLeft size={20} strokeWidth={2} />
|
||||
</Link>
|
||||
<h1>{title}</h1>
|
||||
{actions}
|
||||
</header>
|
||||
) : (
|
||||
<header className="page-heading">
|
||||
<div>
|
||||
<h1>{title}</h1>
|
||||
{description ? <p>{description}</p> : null}
|
||||
</div>
|
||||
|
||||
{actions ? <div className="w-full max-w-md xl:pt-2">{actions}</div> : null}
|
||||
</div>
|
||||
</header>
|
||||
{actions ?? (
|
||||
<Link href="/settings" className="avatar" aria-label="我的">
|
||||
{initial}
|
||||
</Link>
|
||||
)}
|
||||
</header>
|
||||
)}
|
||||
|
||||
{children}
|
||||
</div>
|
||||
|
||||
{showTabs ? (
|
||||
<nav className="bottom-nav" aria-label="主导航">
|
||||
{navigationItems.map((item) => {
|
||||
const active = isActivePath(currentPath, item.href);
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<Link key={item.href} href={item.href} className={`nav-item${active ? " active" : ""}`}>
|
||||
<Icon size={22} strokeWidth={1.8} />
|
||||
<span>{item.label}</span>
|
||||
{item.href === "/messages" && unreadCount > 0 ? <span className="nav-badge">{unreadCount}</span> : null}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
) : null}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user