docs(admin-portal): 新增 nextstep-v2.md 记录下游核查结果

v1 声称完成的下游工作经核查实际未完成:
- api-gateway: /api/admin/graphql 路由未注册,go vet 编译失败
- teacher-bff: resolver 已完成但 schema 未同步(命名空间 vs 扁平)
- iam: proto 缺 BatchGetUsers rpc 声明

v2 记录详细核查证据和修复要求
This commit is contained in:
SpecialX
2026-07-14 08:26:27 +08:00
parent 99580fa13a
commit 0b42302a64
42 changed files with 4804 additions and 637 deletions

View File

@@ -14,10 +14,12 @@
import { useState, useEffect } from "react";
import { useQuery, useMutation } from "urql";
import { useTranslations } from "next-intl";
import { Loading } from "@edu/ui-components";
import { MeQuery, UpdateUserMutation } from "@/lib/graphql";
export default function SettingsPage() {
const t = useTranslations("settings");
const [result, reexecuteQuery] = useQuery({ query: MeQuery });
const [updateResult, updateUser] = useMutation(UpdateUserMutation);
@@ -50,11 +52,11 @@ export default function SettingsPage() {
setError(null);
if (!name.trim()) {
setError("姓名不能为空");
setError(t("error.nameRequired"));
return;
}
if (!email.trim() || !email.includes("@")) {
setError("邮箱格式不正确");
setError(t("error.emailInvalid"));
return;
}
@@ -78,10 +80,8 @@ export default function SettingsPage() {
<div className="px-10 py-10">
<header className="mb-8 flex items-baseline justify-between">
<div>
<h1 className="text-3xl font-serif text-ink"></h1>
<p className="mt-1 text-sm text-ink-muted">
GraphQL MeQuery + UpdateUserMutation · P3
</p>
<h1 className="text-3xl font-serif text-ink">{t("title")}</h1>
<p className="mt-1 text-sm text-ink-muted">{t("subtitle")}</p>
</div>
{!editing && result.data?.me && (
<button
@@ -89,7 +89,7 @@ export default function SettingsPage() {
onClick={handleStartEdit}
className="px-4 py-2 text-sm text-ink-on-accent bg-accent rounded-button hover:bg-accent-hover"
>
{t("button.edit")}
</button>
)}
</header>
@@ -101,45 +101,65 @@ export default function SettingsPage() {
) : result.error ? (
<div className="mark-left mb-4 py-2 border-l-2 border-danger pl-md">
<p className="text-sm px-3 text-danger">
{result.error.message}
{t("error.loadFailed", { message: result.error.message })}
</p>
</div>
) : !result.data?.me ? (
<p className="text-sm italic text-ink-muted"></p>
<p className="text-sm italic text-ink-muted">{t("empty")}</p>
) : editing ? (
<section className="max-w-2xl">
<div className="mb-6">
<h2 className="text-xl font-serif text-ink mb-2"></h2>
<h2 className="text-xl font-serif text-ink mb-2">
{t("edit.title")}
</h2>
<div className="rule-thin mb-4" />
</div>
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label className="block text-tiny uppercase tracking-wide text-ink-muted mb-1">
<span className="text-danger">*</span>
<label
htmlFor="settings-name"
className="block text-tiny uppercase tracking-wide text-ink-muted mb-1"
>
{t("form.label.name")} <span className="text-danger">*</span>
</label>
<input
id="settings-name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
aria-label={t("form.label.name")}
aria-invalid={Boolean(error)}
aria-describedby={error ? "settings-form-error" : undefined}
className="w-full px-3 py-2 bg-transparent border-b border-rule text-sm text-ink focus:outline-none focus:border-b-2"
required
/>
</div>
<div>
<label className="block text-tiny uppercase tracking-wide text-ink-muted mb-1">
<span className="text-danger">*</span>
<label
htmlFor="settings-email"
className="block text-tiny uppercase tracking-wide text-ink-muted mb-1"
>
{t("form.label.email")} <span className="text-danger">*</span>
</label>
<input
id="settings-email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
aria-label={t("form.label.email")}
aria-invalid={Boolean(error)}
aria-describedby={error ? "settings-form-error" : undefined}
className="w-full px-3 py-2 bg-transparent border-b border-rule text-sm text-ink focus:outline-none focus:border-b-2"
required
/>
</div>
{error && (
<div className="mark-left py-2 border-l-2 border-danger pl-md">
<div
id="settings-form-error"
role="alert"
className="mark-left py-2 border-l-2 border-danger pl-md"
>
<p className="text-sm px-3 text-danger">{error}</p>
</div>
)}
@@ -150,7 +170,9 @@ export default function SettingsPage() {
disabled={submitting}
className="px-4 py-2 text-sm text-ink-on-accent bg-accent rounded-button hover:bg-accent-hover disabled:opacity-50"
>
{submitting ? "保存中..." : "保存"}
{submitting
? t("form.button.submitting")
: t("form.button.save")}
</button>
<button
type="button"
@@ -158,10 +180,12 @@ export default function SettingsPage() {
disabled={submitting}
className="text-tiny uppercase tracking-wide text-ink-muted hover:opacity-70 disabled:opacity-50"
>
{t("form.button.cancel")}
</button>
{updateResult.data && !error && !submitting && (
<span className="text-tiny text-success"></span>
<span className="text-tiny text-success">
{t("form.status.saved")}
</span>
)}
</div>
</form>
@@ -169,12 +193,14 @@ export default function SettingsPage() {
) : (
<section className="max-w-2xl">
<div className="mb-6">
<h2 className="text-xl font-serif text-ink mb-2"></h2>
<h2 className="text-xl font-serif text-ink mb-2">
{t("info.title")}
</h2>
<div className="rule-thin mb-4" />
<dl className="space-y-4">
<div className="grid grid-cols-3 gap-4 items-baseline">
<dt className="text-tiny uppercase tracking-wide text-ink-muted">
ID
{t("info.label.userId")}
</dt>
<dd className="col-span-2 text-sm font-mono text-ink">
{result.data.me.id}
@@ -182,7 +208,7 @@ export default function SettingsPage() {
</div>
<div className="grid grid-cols-3 gap-4 items-baseline">
<dt className="text-tiny uppercase tracking-wide text-ink-muted">
{t("info.label.name")}
</dt>
<dd className="col-span-2 text-sm text-ink">
{result.data.me.name}
@@ -190,7 +216,7 @@ export default function SettingsPage() {
</div>
<div className="grid grid-cols-3 gap-4 items-baseline">
<dt className="text-tiny uppercase tracking-wide text-ink-muted">
{t("info.label.email")}
</dt>
<dd className="col-span-2 text-sm text-ink">
{result.data.me.email}
@@ -198,15 +224,15 @@ export default function SettingsPage() {
</div>
<div className="grid grid-cols-3 gap-4 items-baseline">
<dt className="text-tiny uppercase tracking-wide text-ink-muted">
{t("info.label.roles")}
</dt>
<dd className="col-span-2 text-sm text-ink">
{result.data.me.roles.join(", ") || "无角色"}
{result.data.me.roles.join(", ") || t("info.noRoles")}
</dd>
</div>
<div className="grid grid-cols-3 gap-4 items-baseline">
<dt className="text-tiny uppercase tracking-wide text-ink-muted">
{t("info.label.dataScope")}
</dt>
<dd className="col-span-2 text-sm text-ink">
{result.data.me.dataScope}
@@ -216,9 +242,7 @@ export default function SettingsPage() {
</div>
<div className="mt-8 p-4 border border-rule rounded-card bg-subtle">
<p className="text-sm text-ink-muted">
P3 UpdateUserMutation"编辑"
</p>
<p className="text-sm text-ink-muted">{t("info.editHint")}</p>
</div>
</section>
)}