From 65681087779f017aa5067d205e984983a3a0599a Mon Sep 17 00:00:00 2001 From: Pine Date: Sun, 30 Aug 2026 22:32:49 +0800 Subject: [PATCH] =?UTF-8?q?feat(users):=20=E5=BB=BA=E5=8F=B7=E8=A7=84?= =?UTF-8?q?=E5=88=99(=E6=89=8B=E6=9C=BA=E5=8F=B7=E5=BF=85=E5=A1=AB/?= =?UTF-8?q?=E9=9A=8F=E6=9C=BA=E8=B4=A6=E5=8F=B7)+=E6=89=8B=E6=9C=BA?= =?UTF-8?q?=E5=8F=B7=E7=BB=91=E5=AE=9A=E8=A1=A8=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 管理端与园区端用户创建/编辑表单同步收紧,usersApi 新增 bindPhone Co-Authored-By: Claude --- src/api/resources.ts | 2 ++ src/pages/park/users-tab.tsx | 14 ++++++++------ src/pages/users/index.tsx | 21 ++++++++++++++++----- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/api/resources.ts b/src/api/resources.ts index 1ea5eff..c23789d 100644 --- a/src/api/resources.ts +++ b/src/api/resources.ts @@ -33,6 +33,8 @@ export const usersApi = { api.post(`/admin/users/${id}/status`, { status }), classify: (id: string, body: { certification_status?: string; affiliation?: string; park_id?: string; park_name?: string; account_type?: string }) => api.post(`/admin/users/${id}/classification`, body), + bindPhone: (id: string, phone: string) => + api.patch(`/admin/users/${id}/phone`, { phone }), }; // 兼容可能的分页包装 { items, total } export async function unwrap(p: Promise): Promise { diff --git a/src/pages/park/users-tab.tsx b/src/pages/park/users-tab.tsx index 2f7a304..76028b7 100644 --- a/src/pages/park/users-tab.tsx +++ b/src/pages/park/users-tab.tsx @@ -37,9 +37,10 @@ const GENDERS = [ ]; const EMPTY: Record = { - username: "", password: "", nickname: "", gender: "", birthday: "", phone: "", + password: "", nickname: "", gender: "", birthday: "", phone: "", id_card: "", ethnicity: "", grad_school_major: "", grad_time: "", email: "", company: "", }; +const PHONE_RE = /^1[3-9]\d{9}$/; const genderLabel = (g?: string) => GENDERS.find((x) => x.value === g)?.label || "-"; const CERT_ZH: Record = { uncertified: "未认证", pending: "待审", certified: "已认证", rejected: "已驳回" }; @@ -60,8 +61,9 @@ export function UsersTab() { }; const save = async () => { - if (!form.username.trim()) { window.alert("账号必填"); return; } + if (!PHONE_RE.test(form.phone.trim())) { window.alert("请填写正确的 11 位手机号"); return; } if (!form.nickname.trim()) { window.alert("姓名必填"); return; } + if (!form.password) { window.alert("请设置初始密码"); return; } setBusy(true); try { await parkApi.createParkUser(form); @@ -122,11 +124,11 @@ export function UsersTab() { {/* 新增账号 */} - 新增账号创建本园区用户账号。 + 新增账号账号由系统随机生成;手机号必填且全局唯一。 - + @@ -170,7 +172,7 @@ function ProfileForm({ form, setForm, includePassword, lockUsername }: { form: R const set = (k: string, v: string) => setForm({ ...form, [k]: v }); return (
- set("username", e.target.value)} /> + {lockUsername ? : null} {includePassword && set("password", e.target.value)} />} set("nickname", e.target.value)} placeholder="姓名(必填)" /> @@ -180,7 +182,7 @@ function ProfileForm({ form, setForm, includePassword, lockUsername }: { form: R set("birthday", e.target.value)} /> - set("phone", e.target.value)} /> + set("phone", e.target.value)} placeholder="11 位手机号(必填)" /> set("email", e.target.value)} /> set("id_card", e.target.value)} /> set("ethnicity", e.target.value)} /> diff --git a/src/pages/users/index.tsx b/src/pages/users/index.tsx index 9d106f9..f46bc4a 100644 --- a/src/pages/users/index.tsx +++ b/src/pages/users/index.tsx @@ -22,9 +22,10 @@ const ACCOUNT_ROLES = [ { label: "服务方", value: "service", hint: "服务商 / 甲方企业等(统一归服务方)" }, ]; const EMPTY = { - username: "", nickname: "", password: "", role: "opc_member", + phone: "", nickname: "", password: "", role: "opc_member", certification_status: "uncertified", affiliation: "independent", park_id: "", park_name: "", }; +const PHONE_RE = /^1[3-9]\d{9}$/; const SOURCE_ZH: Record = { mini_program: "小程序", web: "Web站点", desktop: "桌面端", park: "园区", admin: "管理员", seed: "种子", wx: "微信", phone: "手机号", unknown: "未知", @@ -45,7 +46,7 @@ export default function UsersPage() { const openEdit = (r: UserRow) => { setEditing(r); setForm({ - username: r.username || "", nickname: r.nickname || "", role: r.role || "opc_member", + username: r.username || "", phone: r.phone || "", nickname: r.nickname || "", role: r.role || "opc_member", certification_status: r.certification_status || "uncertified", affiliation: r.affiliation || "independent", park_id: r.park_id || "", park_name: r.park_name || "", @@ -57,7 +58,7 @@ export default function UsersPage() { setBusy(true); try { if (editing) { - // 丰富编辑:改资料/角色 + 认证/所属/园区 + // 丰富编辑:改资料/角色 + 认证/所属/园区 + 绑定手机号 await usersApi.update(editing.id, { nickname: form.nickname, role: form.role }); await usersApi.classify(editing.id, { certification_status: form.certification_status, @@ -65,8 +66,11 @@ export default function UsersPage() { park_id: form.park_id || undefined, park_name: form.park_name || undefined, }); + if (PHONE_RE.test((form.phone || "").trim()) && form.phone.trim() !== (editing.phone || "")) { + await usersApi.bindPhone(editing.id, form.phone.trim()); + } } else { - await usersApi.create({ username: form.username, nickname: form.nickname, password: form.password || "123456", role: form.role }); + await usersApi.create({ phone: form.phone.trim(), nickname: form.nickname, password: form.password || "123456", role: form.role }); } setOpen(false); res.reload(); } finally { setBusy(false); } @@ -81,6 +85,7 @@ export default function UsersPage() { const columns: Column[] = [ { key: "username", header: "账号", cell: (r) => {r.username} }, + { key: "phone", header: "手机号", cell: (r) => r.phone || "-" }, { key: "nickname", header: "昵称", cell: (r) => r.nickname || "-" }, { key: "source", header: "来源", cell: (r) => SOURCE_ZH[r.source ?? ""] || r.source || "-" }, { key: "certification_status", header: "认证", cell: (r) => CERT_ZH[r.certification_status ?? ""] || r.certification_status || "未认证" }, @@ -132,8 +137,14 @@ export default function UsersPage() {
)}
-
+ {editing ? ( +
+ ) : null} + {!editing &&
} {!editing &&
} + {editing && ( +
+ )}