新增设备别名管理功能,支持设备别名的保存与解绑

This commit is contained in:
2026-04-17 15:08:39 +08:00
parent 71760cbb1f
commit 70d42e6792
9 changed files with 570 additions and 72 deletions
+155 -1
View File
@@ -2,7 +2,7 @@
import Link from "next/link";
import { useRouter } from "next/navigation";
import { startTransition, useState } from "react";
import { startTransition, useEffect, useState } from "react";
export function BindDeviceForm() {
const router = useRouter();
@@ -196,4 +196,158 @@ export function SendMessageForm({ deviceUuid }: SendMessageFormProps) {
) : null}
</form>
);
}
type ManageDeviceFormProps = {
deviceUuid: string;
initialAlias?: string | null;
deviceLabel: string;
};
export function ManageDeviceForm({
deviceUuid,
initialAlias,
deviceLabel,
}: ManageDeviceFormProps) {
const router = useRouter();
const [alias, setAlias] = useState(initialAlias ?? "");
const [saving, setSaving] = useState(false);
const [unbinding, setUnbinding] = useState(false);
const [notice, setNotice] = useState<string | null>(null);
useEffect(() => {
setAlias(initialAlias ?? "");
}, [initialAlias]);
async function handleSaveAlias(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
setSaving(true);
setNotice("正在保存设备别名……");
try {
const response = await fetch("/api/family/device-binding", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
deviceUuid,
alias,
}),
});
const payload = (await response.json().catch(() => null)) as
| { error?: string; alias?: string | null; deviceName?: string }
| null;
if (!response.ok) {
throw new Error(payload?.error || "设备别名保存失败。");
}
setAlias(payload?.alias?.trim() || "");
setNotice(
payload?.alias?.trim()
? `已将这台设备备注为“${payload.alias.trim()}”。\n`
: "已恢复默认设备名称。\n",
);
startTransition(() => {
router.refresh();
});
} catch (error) {
setNotice(
error instanceof Error ? error.message : "设备别名保存失败。",
);
} finally {
setSaving(false);
}
}
async function handleUnbind() {
if (!window.confirm(`解绑后将不再收到“${deviceLabel}”的留言和推送提醒,确定继续吗?`)) {
return;
}
setUnbinding(true);
setNotice("正在解绑这台设备……");
try {
const response = await fetch("/api/family/device-binding", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
deviceUuid,
}),
});
const payload = (await response.json().catch(() => null)) as
| { error?: string }
| null;
if (!response.ok) {
throw new Error(payload?.error || "解绑设备失败。",);
}
startTransition(() => {
router.push("/devices");
router.refresh();
});
} catch (error) {
setNotice(
error instanceof Error ? error.message : "解绑设备失败。",
);
setUnbinding(false);
}
}
return (
<section className="rounded-[28px] border border-white/70 bg-white/88 p-5">
<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>
<p className="mt-2 text-sm leading-7 text-[var(--muted)]">
设备别名会显示在设备列表、留言页和推送通知里,例如“外婆的手机”。
</p>
<form className="mt-4 flex flex-col gap-3 sm:flex-row" onSubmit={handleSaveAlias}>
<input
value={alias}
onChange={(event) => setAlias(event.target.value)}
maxLength={24}
placeholder="给这台设备起个好记的名字"
className="h-12 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"
/>
<button
type="submit"
disabled={saving || unbinding}
className="inline-flex h-12 min-w-32 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"
>
{saving ? "正在保存" : "保存别名"}
</button>
</form>
<div className="mt-4 flex flex-wrap gap-3">
<button
type="button"
onClick={() => {
void handleUnbind();
}}
disabled={saving || unbinding}
className="inline-flex h-11 items-center justify-center rounded-full border border-[rgba(160,74,48,0.28)] bg-white px-4 text-sm font-semibold text-[var(--ink)] transition hover:bg-[rgba(199,103,51,0.08)] disabled:cursor-not-allowed disabled:opacity-60"
>
{unbinding ? "正在解绑" : "解绑这台设备"}
</button>
</div>
{notice ? (
<p className="mt-3 whitespace-pre-line text-sm leading-7 text-[var(--muted)]">
{notice}
</p>
) : null}
</section>
);
}