本文件由 /tmp/48b8-tablet/preverify.sh 生成,每段是命令+原样输出。生成时间 2026-09-28 19:18:54
$ git log -1 --oneline; git branch --show-current; git status --short | wc -l
1a7b6b40 chore(phase48b.9c): 删除门店绑定家长弹框的临时诊断灰字
feature/scan-binding-48b
0
[exit=0]
$ git fetch -q origin test; git rev-parse --short origin/test
608345f4
[exit=0]
$ git diff --stat HEAD origin/test -- web/app/h5/tablet/pane/panes/refractive-record web/app/h5/tablet/pane/page.tsx web/app/h5/tablet/pane/panes/use-tablet-wechat-voice.ts web/app/h5/chat/lib/wechat-voice.ts web/app/h5/store-refractive web/app/h5/chat/lib/wx-scan.ts app/api/brand_code_claim.py app/services/brand_code_claim_service.py app/schemas/refractive_record.py; echo 差异行数=$(git diff HEAD origin/test -- web/app/h5/tablet/pane/panes/refractive-record web/app/h5/tablet/pane/page.tsx web/app/h5/tablet/pane/panes/use-tablet-wechat-voice.ts web/app/h5/chat/lib/wechat-voice.ts web/app/h5/store-refractive web/app/h5/chat/lib/wx-scan.ts app/api/brand_code_claim.py app/services/brand_code_claim_service.py | wc -l)
web/app/h5/store-refractive/[patientId]/page.tsx | 1 +
.../[patientId]/record/[recordId]/page.tsx | 1 +
.../[patientId]/record/new/page.tsx | 1 +
web/app/h5/store-refractive/org-code/page.tsx | 120 +++++++++++----------
web/app/h5/store-refractive/page.tsx | 1 +
5 files changed, 68 insertions(+), 56 deletions(-)
差异行数=187
[exit=0]
$ echo 对照组-确有差异的文件应非空:; git diff --stat HEAD origin/test -- app/api/seat_auth.py | tail -1
对照组-确有差异的文件应非空:
1 file changed, 351 insertions(+), 33 deletions(-)
[exit=0]
$ grep -n 'import\|interface Props\|on[A-Z][A-Za-z]*:' web/app/h5/tablet/pane/panes/refractive-record/frame1-child-list.tsx | head -30
13:import { ChevronRight, Plus, Search } from "lucide-react";
14:import type { PatientLite } from "./lib/api";
15:import { ageYearsText } from "./lib/age";
18:import { DefaultAvatar } from "../../../../family/components/default-avatar";
20:interface Props {
23: onSearch: (q: string) => void;
24: onSelectPatient: (p: PatientLite) => void;
25: onOpenCreate: () => void;
29: onLoadMore: () => void;
[exit=0]
$ sed -n 60,92p web/app/h5/tablet/pane/panes/refractive-record/frame1-child-list.tsx
>
<div className="p-5 pb-3 shrink-0">
<h2 className="text-[19px] font-extrabold mb-3" style={{ color: "#173a54" }}>
视光档案
</h2>
<div className="flex items-center gap-2.5">
<div className="relative flex-1">
<Search
size={15}
color="#8aa0b3"
className="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none"
/>
<input
placeholder="搜索姓名或手机号"
aria-label="搜索姓名或手机号"
value={query}
onChange={(e) => onSearch(e.target.value)}
className="w-full rounded-xl pl-9 pr-3 py-2.5 text-[13px] outline-none"
style={{ border: "1px solid #dde8f1", background: "#f2f7fb", color: "#173a54" }}
/>
</div>
<button
type="button"
onClick={onOpenCreate}
className="shrink-0 rounded-xl px-4 py-2.5 text-[13px] font-bold text-white flex items-center gap-1"
style={{ background: "#2563eb" }}
>
<Plus size={14} />
新建档案
</button>
</div>
</div>
[exit=0]
$ grep -n 'Frame1ChildList' -A 14 web/app/h5/tablet/pane/panes/refractive-record/refractive-record-pane.tsx | head -30
37:import { Frame1ChildList } from "./frame1-child-list";
38-import { Frame2CreateModal, type CreatePatientBody } from "./frame2-create-modal";
39-import { Frame3PatientProfile } from "./frame3-patient-profile";
40-import { Frame3bBindSelector } from "./frame3b-bind-selector";
41-import { Frame4RecordView } from "./frame4-record-view";
42-import { Frame5RecordEntry } from "./frame5-record-entry";
43-import { HistoryDrawer, type HistoryRecordRow } from "./history-drawer";
44-import { ParentReadonlyDialog, type ParentViewData } from "./parent-readonly-dialog";
45-import { PatientRail } from "./patient-rail";
46-import { RecordCanvas, type DraftState } from "./record-canvas";
47-import { TrendChartDialog, type ChartPoints } from "./trend-chart-dialog";
48-
49-type Module = "basic" | "advanced";
50-// 批1a 帧3 每卡四格需要 eyes 字段:改用 patientTimeline() 真实返回类型(含 eyes)
51-// 取代旧的 patient-rail.tsx TimelineItem(无 eyes)· 结构超集 · 传给 PatientRail 仍兼容
--
794: <Frame1ChildList
795- patients={patients}
796- query={query}
797- onSearch={(q) => { setQuery(q); void doSearch(q); }}
798- onSelectPatient={selectPatient}
799- onOpenCreate={() => { setCreateError(null); setCreateOpen(true); }}
800- hasMore={hasMorePatients}
801- loadingMore={loadingMorePatients}
802- onLoadMore={() => void loadMorePatients()}
803- />
804- ) : !record ? (
805- <Frame3PatientProfile
806- patient={activePatient}
807- timeline={timeline}
[exit=0]
$ grep -n 'export async function \(resolveFamilyScan\|claimFamilyScan\|createChildForFamilyScan\|linkChildForFamilyScan\|getOrgCode\)' -A 6 web/app/h5/store-refractive/lib/api.ts
[exit=1]
$ grep -n 'export \(interface\|type\) \(FamilyScan\|OrgCode\|FamilyChild\|CreateChild\|DuplicateSuspect\)' -A 12 web/app/h5/store-refractive/lib/api.ts
441:export interface FamilyScanResolveResult {
442- family_nickname: string | null;
443- family_avatar: string | null;
444- family_username_masked: string | null;
445- family_display_name: string | null;
446- children: ChildBrief[];
447-}
448-
449-/** 门店/家长扫到对方码后,解出家长名下孩子列表(供「选孩子」步骤展示) */
450-export function resolveFamilyScan(familyToken: string) {
451- return brandClaimRequest<FamilyScanResolveResult>("/family-scan/resolve", {
452- method: "POST",
453- body: JSON.stringify({ family_token: familyToken }),
--
501:export interface OrgCodeInfo {
502- org_name: string;
503- landing_url: string;
504- qr_url: string;
505-}
506-
507-/** 机构端「我的机构码」· GET,确定性签发(同一机构永远同一个 token) */
508-export function getOrgCode() {
509- return brandClaimRequest<OrgCodeInfo>("/org-code", { method: "GET" });
510-}
511-
512-// 批1b 第8项 · AI 识别(镜像平板端 api.ts:aiRecognize · 复用 27B vision base64 直传链路)·
513-// multipart 上传,不走 request() 的 JSON Content-Type
[exit=0]
$ grep -n '^export' web/app/h5/chat/lib/wx-scan.ts
61:export interface WxSdk {
85:export interface WxConfigAttempt {
91:export class ScanCancelledError extends Error {
119:export function signUrlCandidates(
247:export function prepareScan(): void {
280:export async function scanWithRetry(attempt: WxConfigAttempt): Promise<string> {
314:export async function scanQrCode(): Promise<string> {
371:export function parseOrgBindToken(text: string): string | null {
384:export interface ChildOrgInfo {
394:export function currentOrgOf(child: ChildOrgInfo): { orgId: number | null; orgName: string | null } {
404:export function childOrgLine(
422:export function getMyOrgId(): number | null {
[exit=0]
$ grep -n '^export' web/app/h5/tablet/pane/panes/refractive-record/lib/api.ts
15:export interface BoundSeatLite {
23:export interface PatientLite {
57:export interface PatientUpsertFields {
71:export interface EyeData {
94:export interface RecordData {
128:export class ApiError extends Error {
138:export class VersionConflictError extends ApiError {
181:export function searchPatients(query: string, limit = 20, offset = 0) {
191:export function createPatient(body: PatientUpsertFields & { name: string }) {
201:export async function uploadPatientAvatar(file: File): Promise<{ image_key: string }> {
216:export function updatePatient(patientId: string, body: PatientUpsertFields) {
228:export function bindPatientSeat(patientId: string, seatUserId: number) {
235:export function unbindPatientSeat(patientId: string) {
244:export interface SubseatLite {
254:export async function listBindableSeats(
268:export interface TimelineEyeSummary {
275:export function patientTimeline(patientId: string) {
287:export function createRecord(body: {
301:export function getRecord(recordId: string) {
306:export function patchRecord(
325:export function completeRecord(
337:export async function deleteRecord(recordId: string): Promise<void> {
349:export function triggerAnalysis(recordId: string) {
356:export function patchConclusion(recordId: string, text: string) {
364:export function pushConclusion(recordId: string) {
370:export function getComparison(
381:export function getParentView(recordId: string) {
387:export interface AiRecognizeResult {
393:export async function aiRecognize(
[exit=0]
$ grep -n 'interface Props' -A 26 web/app/h5/tablet/pane/panes/refractive-record/frame2-create-modal.tsx
42:interface Props {
43- open: boolean;
44- submitting: boolean;
45- errorText: string | null;
46- onClose: () => void;
47- onSubmit: (body: CreatePatientBody) => void;
48- // 二期批C 整改②:帧3「修改档案」复用本组件的编辑态(默认 create,既有调用方行为不变)
49- mode?: "create" | "edit";
50- initialPatient?: {
51- name: string;
52- gender: string | null;
53- birth_date: string | null;
54- glasses_history?: string | null;
55- prevention_history?: string[] | null;
56- guardian_relation?: string | null;
57- phone?: string | null;
58- id_card_no?: string | null;
59- myopia_family_history?: string | null;
60- avatar_key?: string | null;
61- // 9-07 长官反馈"编辑已有档案时上传区没有回填已存在的头像" · 签名 URL 供编辑态
62- // 直接预览(此前只传 avatar_key 原始 OSS key,无法当 <img src> 用,见下方
63- // useEffect 注释历史记录)
64- avatar_url?: string | null;
65- } | null;
66-}
67-
68-function SingleChipGroup({
[exit=0]
$ grep -n 'const \(title\|submitText\)\|新建档案\|保存并添加检查单\|修改档案' web/app/h5/tablet/pane/panes/refractive-record/frame2-create-modal.tsx
19:// 本文件同时承载帧3「修改档案」编辑态复用(mode="edit")。
20:// 二期批C 整改②(长官打回件"修改档案能改全部建档字段"):编辑态原先字段收窄为
48: // 二期批C 整改②:帧3「修改档案」复用本组件的编辑态(默认 create,既有调用方行为不变)
274: {isEdit ? "修改档案" : "新建档案"}
420: {submitting ? "保存中…" : isEdit ? "保存修改" : "保存并添加检查单"}
[exit=0]
$ sed -n 836,872p web/app/h5/tablet/pane/panes/refractive-record/frame4-record-view.tsx
interface ConfirmState {
title: string;
body: string;
confirmLabel: string;
onConfirm: () => void;
}
function ConfirmDialog({ state, onCancel }: { state: ConfirmState; onCancel: () => void }) {
return (
<section
aria-label={state.title}
className="fixed inset-0 z-50 flex items-center justify-center p-6"
style={{ background: "rgba(15,40,60,.45)" }}
>
<div className="w-[380px] max-w-full flex flex-col gap-4" style={card}>
<h3 className="text-[15px] font-extrabold m-0" style={{ color: "#173a54" }}>
{state.title}
</h3>
<p className="text-[13px] m-0" style={{ color: "#43617a", lineHeight: 1.6 }}>
{state.body}
</p>
<div className="flex items-center gap-2">
<button type="button" onClick={onCancel}
className="flex-1 rounded-lg py-2 text-[13px] font-bold"
style={{ background: "#eef4f9", color: "#43617a" }}>
取消
</button>
<button type="button" onClick={state.onConfirm}
className="flex-1 rounded-lg py-2 text-[13px] font-bold text-white"
style={{ background: "#2563eb" }}>
{state.confirmLabel}
</button>
</div>
</div>
</section>
);
}
[exit=0]
$ sed -n 60,110p web/app/h5/tablet/pane/panes/refractive-record/frame3b-bind-selector.tsx
onClose();
}
function handleConfirm() {
if (selectedId == null) return;
onConfirm(selectedId);
}
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center p-4"
style={{ background: "rgba(23,58,84,.35)" }}
onClick={handleClose}
>
<div
className="rounded-[18px] w-full overflow-hidden"
style={{ maxWidth: 620, background: "#fff", boxShadow: "0 12px 40px rgba(23,58,84,.25)" }}
onClick={(e) => e.stopPropagation()}
>
<div className="px-6 pt-5 pb-4" style={{ borderBottom: "1px solid #eef2f5" }}>
<h3 className="text-[17px] font-extrabold" style={{ color: "#173a54" }}>
绑定家长
</h3>
<p className="text-[12.5px] mt-1" style={{ color: "#8aa0b3" }}>
为 {patientName ?? "该儿童"} 选择本店家长子席位(路A:先建档,后绑)
</p>
</div>
<div className="px-6 py-5 flex flex-col gap-3 max-h-[70vh] overflow-y-auto">
<div className="relative">
<Search
size={15}
color="#8aa0b3"
className="absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none"
/>
<input
value={query}
onChange={(e) => onQueryChange(e.target.value)}
placeholder="输入席位账号搜索,如 0034202607"
className="w-full rounded-lg pl-9 pr-3 py-2.5 text-[13px] outline-none"
[exit=0]
$ echo '--frame4-record-view 被引用:'; grep -rn 'frame4-record-view' web/app --include=*.ts --include=*.tsx
--frame4-record-view 被引用:
web/app/h5/tablet/pane/panes/refractive-record/sample-block-dialog.tsx:4:// (frame3-patient-profile.tsx / frame4-record-view.tsx / frame5-record-entry.tsx /
web/app/h5/tablet/pane/panes/refractive-record/sample-block-dialog.tsx:6:// 视觉令牌 1:1 抄自 frame4-record-view.tsx 既有 ConfirmDialog(card 背景/边框/遮罩色,
web/app/h5/tablet/pane/panes/refractive-record/frame4-record-view.tsx:410: // (frame4-record-view.tsx:491 既有注释已注明"scope 参数对 ai_comparison 无影响")。
web/app/h5/tablet/pane/panes/refractive-record/refractive-record-pane.tsx:41:import { Frame4RecordView } from "./frame4-record-view";
web/app/h5/tablet/pane/panes/refractive-record/refractive-record-pane.tsx:978: (frame5-record-entry.tsx:354 netError 横幅 / frame4-record-view.tsx:1017,1060 /
web/app/h5/tablet/pane/panes/refractive-record/frame1-child-list.tsx:180: {/* 「只读」徽标:pill-neutral 令牌,来源 frame4-record-view.tsx:76-78 */}
web/app/h5/tablet/pane/panes/refractive-record/lib/clinical-data.ts:12:// 见 frame4-record-view.tsx:893 同款注释)
web/app/h5/tablet/pane/panes/refractive-record/lib/render-bold.tsx:6:// 9-03 起门店端 frame4-record-view.tsx 的 p1_diag/p2_advice/p3_warning 三个字段也要用
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:11:// web/app/h5/tablet/pane/panes/refractive-record/frame4-record-view.tsx:106-151 parseAiAnalysis
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:20:// 本就是 legacy/same_day 形状。本页已照平板端 frame4-record-view.tsx:552-585 的
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:27:// 1:1 抄自平板端 frame4-record-view.tsx:1091-1160 vfLeftItems/vfRightItems 提取逻辑。
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:71:// 也没有贴身可点的重试入口。先读平板端 frame4-record-view.tsx:1246-1349 同一处 ——
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:107:// 卡2/卡3 轴位显示:柱镜=0 时轴位"不适用"(1:1 抄自平板端 frame4-record-view.tsx:738-743 axisDisplay)
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:113:// 卡6 眼部检查文案(1:1 抄自平板端 frame4-record-view.tsx:745-751 ocularText)
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:243:// 1:1 抄自平板端 frame4-record-view.tsx:158-183 buildConclusionText
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:259:// 照搬平板端 frame4-record-view.tsx:809-829 normalizeConclusionText,一字不改
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:284:// 出处同平板端 frame4-record-view.tsx:210-215 注释:KB 检索 8s + 主分析 120s(含重试一次)+
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:306:// (1:1 抄自平板端 frame4-record-view.tsx:560 同名类型用法)。
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:325:// ══════════════════════════════ 卡7 视功能字段提取(1:1 抄自平板端 frame4-record-view.tsx:1091-1160) ══════════════════════════════
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:414: // "push-unsaved" 照搬平板端(frame4-record-view.tsx:1052-1074)9-06 长官拍板「推送前
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:493: // 卡9 AI 分析 pending 轮询(1:1 抄自平板端 frame4-record-view.tsx:961-997)
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:541: // 9-17 · 照搬平板端(frame4-record-view.tsx:1016-1033)9-06 长官拍板:改判返回是否
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:605: // (1:1 抄平板端 frame4-record-view.tsx:1427-1437 同一处拍板)。
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:606: // 9-17 · 照搬平板端(frame4-record-view.tsx:1052-1074)9-06 长官拍板"推送前防呆":
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:625: // frame4-record-view.tsx:1246-1271 同一处拍板 —— 平板端只在"已有成功分析、
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:742: 结构照抄平板端 frame4-record-view.tsx:1255-1271 的"描边按钮+图标"写法,
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:1107: {/* 补1(9-16 长官拍板)· 文案照抄平板端 frame4-record-view.tsx:1427-1437,一字不改。
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:1122: {/* 9-17 · 照搬平板端 frame4-record-view.tsx:1059-1062 文案,一字不改(推送前防呆:
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:1140: {/* 补2(9-16 长官拍板)· 文案照抄平板端 frame4-record-view.tsx:1255-1260,一字不改。 */}
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:1279: // frame4-record-view.tsx:1339-1352 同一处结构(错误文案 + 贴身重试按钮同排),
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:1309: // frame4-record-view.tsx:1319-1338 在 pending && !timedOut 时进度面板与「重试」
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:1434: // 判断顺序 1:1 抄自平板端 frame4-record-view.tsx:428-585(windows → same_day →
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:1486: ComparisonAnnual interface)但从未渲染;平板端 frame4-record-view.tsx
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:1558: // 9-03 二次修复(平板端已有 · 1:1 抄自 frame4-record-view.tsx:552-555):同一天
web/app/h5/store-refractive/[patientId]/record/[recordId]/page.tsx:1565: // 兜底路径保留 · 1:1 抄自平板端 frame4-record-view.tsx:559-585 渲染结构,窄屏
web/app/h5/store-refractive/page.tsx:307: [patientId]/record/[recordId]/page.tsx:76 / frame4-record-view.tsx:1457 GREEN 徽章色值),
web/app/h5/family/[id]/_components/__tests__/StoreConclusionCard.spec.tsx:6: * 本用例验证:按门店端 frame4-record-view.tsx buildConclusionText/buildComparisonText
web/app/h5/family/[id]/_components/StoreConclusionCard.tsx:9:// 2026-09-06 rework(长官选方案 A):门店端 frame4-record-view.tsx 的
web/app/h5/family/[id]/page.tsx:336:/** 眼部检查三项(眼前节/眼底/OCT)口径 —— 独立复刻自门店端 frame4-record-view.tsx
[exit=0]
$ echo '--Frame2CreateModal 挂载点:'; grep -rn '<Frame2CreateModal' web/app --include=*.tsx
--Frame2CreateModal 挂载点:
web/app/h5/tablet/pane/panes/refractive-record/refractive-record-pane.tsx:895: <Frame2CreateModal
web/app/h5/tablet/pane/panes/refractive-record/refractive-record-pane.tsx:904: <Frame2CreateModal
[exit=0]
$ echo '--wx-scan 调用方:'; grep -rln 'lib/wx-scan' web/app --include=*.ts --include=*.tsx
--wx-scan 调用方:
web/app/h5/org-bind/page.tsx
web/app/h5/store-refractive/_components/bind-method-dialog.tsx
web/app/h5/store-refractive/_components/select-child-dialog.tsx
web/app/h5/family/page.tsx
[exit=0]
$ echo '--WECHAT_VOICE_APIS 引用方:'; grep -rn 'WECHAT_VOICE_APIS' web/app --include=*.ts --include=*.tsx
--WECHAT_VOICE_APIS 引用方:
web/app/h5/tablet/pane/panes/use-tablet-wechat-voice.ts:6: WECHAT_VOICE_APIS,
web/app/h5/tablet/pane/panes/use-tablet-wechat-voice.ts:80: jsApiList: ["checkJsApi", ...WECHAT_VOICE_APIS],
web/app/h5/tablet/pane/panes/use-tablet-wechat-voice.ts:86: jsApiList: WECHAT_VOICE_APIS,
web/app/h5/chat/page.tsx:113: WECHAT_VOICE_APIS,
web/app/h5/chat/page.tsx:727: jsApiList: ["updateAppMessageShareData", "updateTimelineShareData", "checkJsApi", ...WECHAT_VOICE_APIS],
web/app/h5/chat/page.tsx:746: jsApiList: WECHAT_VOICE_APIS,
web/app/h5/chat/lib/wechat-voice.ts:1:export const WECHAT_VOICE_APIS = [
web/app/h5/chat/lib/wechat-voice.ts:41: return WECHAT_VOICE_APIS.every((name) => record[name] === true);
[exit=0]
$ echo '--wechat-voice.ts 的 import(查循环依赖):'; grep -n '^import\|from ' web/app/h5/chat/lib/wechat-voice.ts; echo '--wechat-voice 引 wx-scan?:'; grep -c 'wx-scan' web/app/h5/chat/lib/wechat-voice.ts
--wechat-voice.ts 的 import(查循环依赖):
--wechat-voice 引 wx-scan?:
0
[exit=1]
$ sed -n 176,191p web/app/h5/tablet/pane/page.tsx
/>
{/* ── 右栏:真平板 pane(逐个子阶段填 · 其余占位)──── */}
<main className="flex-1 min-w-0 relative" style={{ background: "#ffffff" }}>
{/* chat pane 常驻(不随菜单切换卸载)· 非 active 时 display:none 隐藏。
根治:切菜单回来「对话消失 + 会话历史空」——原条件渲染切走即 unmount ·
messages/currentSessionId/chatSessions(含乐观插入的当前会话)全清 ·
常驻后内存态跨切换保活 · SSE 不断 · 历史不丢。 */}
<div className={active === "chat" ? "h-full" : "hidden"}>
<TabletChatPane
onJumpToReport={() => {
setActive("report");
router.replace("/h5/tablet/pane?feature=report");
}}
/>
</div>
[exit=0]
$ grep -n 'jsApiList\|useEffect\|wx.config' web/app/h5/tablet/pane/panes/use-tablet-wechat-voice.ts
3:import { useEffect, useState } from "react";
21: jsApiList: readonly string[];
74: wx.config({
80: jsApiList: ["checkJsApi", ...WECHAT_VOICE_APIS],
86: jsApiList: WECHAT_VOICE_APIS,
135: useEffect(() => {
[exit=0]
$ grep -n 'WECHAT_VOICE_APIS' -A 10 web/app/h5/chat/lib/wechat-voice.ts | head -14
1:export const WECHAT_VOICE_APIS = [
2- "startRecord",
3- "stopRecord",
4- "uploadVoice",
5- "playVoice",
6- "stopVoice",
7- "onVoiceRecordEnd",
8- "onVoicePlayEnd",
9-] as const;
10-
11-export interface WechatVoiceAdapter {
--
41: return WECHAT_VOICE_APIS.every((name) => record[name] === true);
42-}
[exit=0]
$ grep -n 'jsApiList' web/app/h5/chat/page.tsx web/app/h5/chat/lib/wx-scan.ts
web/app/h5/chat/page.tsx:707: checkJsApi: (cfg: { jsApiList: readonly string[]; success: (result: { checkResult?: unknown }) => void }) => void;
web/app/h5/chat/page.tsx:727: jsApiList: ["updateAppMessageShareData", "updateTimelineShareData", "checkJsApi", ...WECHAT_VOICE_APIS],
web/app/h5/chat/page.tsx:746: jsApiList: WECHAT_VOICE_APIS,
web/app/h5/chat/lib/wx-scan.ts:5:// {url: location.href.split("#")[0]} → wx.config({jsApiList}) → wx.ready 里调
web/app/h5/chat/lib/wx-scan.ts:6:// 目标 API。本文件把 jsApiList 换成 ["scanQRCode"],config 成功后调
web/app/h5/chat/lib/wx-scan.ts:68: jsApiList: string[];
web/app/h5/chat/lib/wx-scan.ts:155:function configureWx(jsApiList: string[], pageUrl: string): Promise<WxConfigAttempt> {
web/app/h5/chat/lib/wx-scan.ts:181: jsApiList,
[exit=0]
$ grep -rn 'isInWeChat\|useParentTabletBlock\|resolveSeatWxStep' web/app/h5/tablet --include=*.tsx --include=*.ts | grep -v __tests__ | head -15
web/app/h5/tablet/pane/page.tsx:11:import { ParentTabletBlock, useParentTabletBlock } from "../components/parent-block";
web/app/h5/tablet/pane/page.tsx:233: const parentBlocked = useParentTabletBlock();
web/app/h5/tablet/components/parent-block.tsx:14:export function useParentTabletBlock(): boolean {
web/app/h5/tablet/page.tsx:11:import { ParentTabletBlock, useParentTabletBlock } from "./components/parent-block";
web/app/h5/tablet/page.tsx:29: isInWeChat,
web/app/h5/tablet/page.tsx:35:import { resolveSeatWxStep } from "./lib/seat-wx-flow";
web/app/h5/tablet/page.tsx:250: // Path B(?login=seat):继续下方 resolveSeatWxStep → OAuth;
web/app/h5/tablet/page.tsx:278: const step = resolveSeatWxStep({
web/app/h5/tablet/page.tsx:279: isWeChat: isInWeChat(),
web/app/h5/tablet/page.tsx:553: // 鉴权异步流(exchange/resolveSeatWxStep/flags)未完成前显 loading·不渲染可点 hotspot·
web/app/h5/tablet/page.tsx:732: const parentBlocked = useParentTabletBlock();
web/app/h5/tablet/lib/seat-wx-flow.ts:22:export function resolveSeatWxStep(ctx: SeatWxContext): SeatWxStep {
web/app/h5/tablet/lib/seat-auth-recovery.ts:11:import { isInWeChat } from "../../chat/lib/wx-oauth";
web/app/h5/tablet/lib/seat-auth-recovery.ts:29: // 跳门户微信登录入口 · 清 token 后 resolveSeatWxStep 会走 OAuth→登录窗(微信内)
web/app/h5/tablet/lib/seat-auth-recovery.ts:53: if (isInWeChat()) {
[exit=0]
$ grep -n 'is_sample\|bound_seat' web/app/h5/store-refractive/page.tsx | head -20
178: bound_seat: {
232: // 排序不保证示范档案置顶,本页只对"已加载到的这一页"按 is_sample 拆两段渲染)
233: const samplePatients = patients.filter((p) => p.is_sample);
234: const realPatients = patients.filter((p) => !p.is_sample);
465: fontWeight: p.bound_seat ? 700 : 500,
466: color: p.bound_seat ? "#2fa37c" : "#6E6E73",
469: {p.bound_seat ? "已绑定" : "未绑定"}
542: {realPatients.filter((p) => !p.bound_seat).length === 0 ? (
549: .filter((p) => !p.bound_seat)
[exit=0]
$ grep -n 'is_sample\|bound_seat' web/app/h5/tablet/pane/panes/refractive-record/*.tsx | head -20
web/app/h5/tablet/pane/panes/refractive-record/frame1-child-list.tsx:8:// 见 service 层 _attach_last_visit_date,避免 N+1);绑定状态徽章为二期批3 §2 项7 新增(PatientLite.bound_seat ·
web/app/h5/tablet/pane/panes/refractive-record/frame1-child-list.tsx:45: // ⚠️ 复查纠正(9-14):is_sample.desc() 排序只在家长视角(owner_seat_id 有值)生效
web/app/h5/tablet/pane/panes/refractive-record/frame1-child-list.tsx:49: // 按 is_sample 拆成两段渲染,若门店真实档案量超过一页且示范档案 updated_at 较旧,
web/app/h5/tablet/pane/panes/refractive-record/frame1-child-list.tsx:54: const samplePatients = patients.filter((p) => p.is_sample);
web/app/h5/tablet/pane/panes/refractive-record/frame1-child-list.tsx:55: const realPatients = patients.filter((p) => !p.is_sample);
web/app/h5/tablet/pane/panes/refractive-record/frame1-child-list.tsx:120: const displayPhone = p.bound_seat?.phone || p.phone || null;
web/app/h5/tablet/pane/panes/refractive-record/frame1-child-list.tsx:211: const displayPhone = p.bound_seat?.phone || p.phone || null;
web/app/h5/tablet/pane/panes/refractive-record/frame1-child-list.tsx:258: p.bound_seat
web/app/h5/tablet/pane/panes/refractive-record/frame1-child-list.tsx:263: {p.bound_seat ? "已绑定" : "未绑定"}
web/app/h5/tablet/pane/panes/refractive-record/frame3-patient-profile.tsx:9:// 二选一渲染"),本实现按 patient.bound_seat 二选一渲染单行条,色值/文案取自该示意区
web/app/h5/tablet/pane/panes/refractive-record/frame3-patient-profile.tsx:199: onClick={patient.is_sample ? () => setSampleNotice(SAMPLE_BLOCK_TEXT) : onOpenEditPatient}
web/app/h5/tablet/pane/panes/refractive-record/frame3-patient-profile.tsx:208: onClick={patient.is_sample ? () => setSampleNotice(SAMPLE_BLOCK_TEXT) : onNewRecord}
web/app/h5/tablet/pane/panes/refractive-record/frame3-patient-profile.tsx:221: {patient.bound_seat ? (
web/app/h5/tablet/pane/panes/refractive-record/frame3-patient-profile.tsx:227: 已绑定 · {patient.bound_seat.username}
web/app/h5/tablet/pane/panes/refractive-record/frame3-patient-profile.tsx:228: {patient.bound_seat.seat_remark_name ? ` · ${patient.bound_seat.seat_remark_name}` : ""}
web/app/h5/tablet/pane/panes/refractive-record/frame3-patient-profile.tsx:232: onClick={patient.is_sample ? () => setSampleNotice(SAMPLE_BLOCK_TEXT) : onUnbind}
web/app/h5/tablet/pane/panes/refractive-record/frame3-patient-profile.tsx:250: onClick={patient.is_sample ? () => setSampleNotice(SAMPLE_BLOCK_TEXT) : onOpenBindSelector}
web/app/h5/tablet/pane/panes/refractive-record/frame4-record-view.tsx:50: // 示范档案批3 · record 本身不带 is_sample(_record_dto 未透传),由 pane 层从
web/app/h5/tablet/pane/panes/refractive-record/frame4-record-view.tsx:51: // activePatient.is_sample 透传下来,供本组件拦截"编辑/重新生成/保存结论/推送"
web/app/h5/tablet/pane/panes/refractive-record/patient-rail.tsx:210: active.is_sample
[exit=0]
$ grep -n '@router\.\(get\|post\)\|require_store_seat' app/api/brand_code_claim.py
10:app/auth/middleware.py::require_store_seat 的既有写法):
12: - 门店专属端点直接用既有 `require_store_seat`(seat_type == parent → 403)
36:from app.auth.middleware import require_org_seat, require_store_seat
90:@router.get("/family-code", response_model=FamilyCodeResponse)
136:@router.post("/family-scan/resolve", response_model=ResolveResponse)
139: seat: dict = Depends(require_store_seat),
166:@router.post("/family-scan/claim", response_model=ClaimResponse)
169: seat: dict = Depends(require_store_seat),
214:@router.post("/family-scan/release", response_model=ClaimResponse)
217: seat: dict = Depends(require_store_seat),
238:@router.post("/family-scan/create-child", status_code=status.HTTP_201_CREATED)
241: seat: dict = Depends(require_store_seat),
255:@router.post("/family-scan/link")
258: seat: dict = Depends(require_store_seat),
280:@router.get("/org-code", response_model=OrgCodeResponse)
283: seat: dict = Depends(require_store_seat),
290: # (require_store_seat 只校验 seat.is_active,不查 Organization.is_active,见该函数注释)。
309:@router.post("/org-scan/resolve", response_model=OrgScanResolveResponse)
329:@router.post("/org-scan/children", response_model=OrgScanChildrenResponse)
347:@router.post("/org-scan/claim", response_model=ClaimResponse)
377:@router.post("/org-scan/children-wx", response_model=OrgScanChildrenResponse)
399:@router.post("/org-scan/claim-wx", response_model=ClaimResponse)
[exit=0]
$ grep -n 'brand_code_claim' app/main.py
49:from app.api import annotations, community, wecom, auth, knowledge_b1, api_key_management, org_config, patient_reports, tts, excel_import, miniapp_chat, h5_chat, org_private_knowledge, finetuning, admin, voice, settings_voice, voice_gateway, chat_history, api_v1, device_report, miniapp_device, admin_devices, quality_score, four_l_report, qc_routes, admin_flywheel, h5_consult, find_similar, miniapp_auth, miniapp_phone, h5_user_auth, qr, temp_link, wx_jssdk, admin_users, admin_orgs_access_link, me_router, admin_sessions, admin_dashboard, kb_media, seat_auth, admin_seats, eye_nav, admin_activity, monitoring, role_menu_permissions, share_ai, h5_store_card, parent_account, parent_patients, parent_ocr, admin_brand_code, brand_code_public, brand_code_claim
234:app.include_router(brand_code_claim.router, tags=["Phase 48A 批4 · 机构扫码认领"])
[exit=0]
$ grep -n 'HTTPException\|status_code=\|detail=' app/api/brand_code_claim.py | head -30
25:from fastapi import APIRouter, Depends, HTTPException, Request, status
68: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="机构不存在")
75: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="仅家长账号可访问")
185: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="该档案不属于此家长码")
196: raise HTTPException(
197: status_code=status.HTTP_409_CONFLICT,
198: detail={
238:@router.post("/family-scan/create-child", status_code=status.HTTP_201_CREATED)
364: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="该档案不属于当前家长")
413: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="该档案不属于当前家长")
[exit=0]
$ grep -n 'status\|无效\|失效\|409\|404\|410\|catch' web/app/h5/store-refractive/_components/select-child-dialog.tsx | head -30
7:// 通用居中弹框外壳。resolve 失败(401 二维码失效/类型错误)复用同一居中壳展示
89: } catch (err) {
96: setResolveError(msg || "二维码无效,请重新扫描");
136: } catch (err) {
153: } catch (err) {
180: } catch (err) {
[exit=0]
$ python3 - <<'PY'
import re
for f in ['web/app/h5/store-refractive/_components/bind-method-dialog.tsx','web/app/h5/store-refractive/_components/select-child-dialog.tsx','web/app/h5/store-refractive/org-code/page.tsx','web/app/h5/store-refractive/page.tsx']:
s=open(f,encoding='utf-8').read()
body=re.sub(r'//[^\n]*|/\*.*?\*/|\{/\*.*?\*/\}','',s,flags=re.S)
hits=[]
for m in re.finditer(r'"([^"\n]*[一-鿿][^"\n]*)"|>([^<>{}\n]*[一-鿿][^<>{}\n]*)<|`([^`\n]*[一-鿿][^`\n]*)`',body):
t=next(g for g in m.groups() if g).strip()
if t and t not in hits: hits.append(t)
print('==',f,len(hits)); [print(' ',h) for h in hits]
PY
== web/app/h5/store-refractive/_components/bind-method-dialog.tsx 4
扫码失败,请重试
绑定家长
扫码中…
扫描家长二维码
== web/app/h5/store-refractive/_components/select-child-dialog.tsx 17
二维码无效,请重新扫描
加载失败,请重新扫描
扫码失败,请重试
绑定失败,请重试
保存失败,请重试
${selectedChild.name ?? ""} 将从 ${currentOrgOf(selectedChild).orgName ?? "原机构"} 转入本机构
选择要绑定的孩子
扫码中…
重新扫描
绑定中…
确认绑定
家长:${familyDisplayName}
保存并绑定
${dupChild?.name ?? ""} 已在这位家长名下
姓名、生日都相同,再新建会多出一份重复档案
用已有档案
仍然新建
== web/app/h5/store-refractive/org-code/page.tsx 2
加载失败,请重试
机构二维码
== web/app/h5/store-refractive/page.tsx 13
加载失败,请下拉重试
保存失败,请重试
绑定失败,请重试
关联失败,请重试
搜索姓名或手机号
示范档案区
已绑定
未绑定
关联本机构已有档案
选择要绑定的档案
已选中
关联中…
确认关联
[exit=0]
$ grep -n 'qr_url\|org_name\|<img\|长按\|getOrgCode' web/app/h5/store-refractive/org-code/page.tsx
6:// 二维码走后端 /api/qr(<img> 原生 URL,微信长按识别只认 img,不能用 qrcode.react
14:import { getOrgCode, ApiError, SeatAuthExpiredError, type OrgCodeInfo } from "../lib/api";
41: getOrgCode()
102: {info.org_name}
117: <img
118: src={info.qr_url}
124: 长按二维码可保存图片
[exit=0]
$ find web/app/h5/tablet web/app/h5/chat/lib web/app/h5/store-refractive -path '*__tests__*' \( -name '*.test.ts*' -o -name '*.spec.ts*' \) | sort
web/app/h5/chat/lib/__tests__/api-error-handling.spec.ts
web/app/h5/chat/lib/__tests__/auth.spec.ts
web/app/h5/chat/lib/__tests__/citation-e2e.spec.ts
web/app/h5/chat/lib/__tests__/citation.spec.ts
web/app/h5/chat/lib/__tests__/ios-audio.spec.ts
web/app/h5/chat/lib/__tests__/parent-feature-gate.spec.ts
web/app/h5/chat/lib/__tests__/parse-lora.spec.ts
web/app/h5/chat/lib/__tests__/resolve-age.spec.ts
web/app/h5/chat/lib/__tests__/resolve-mode.spec.ts
web/app/h5/chat/lib/__tests__/seat-login-hints.spec.ts
web/app/h5/chat/lib/__tests__/share-guard.spec.ts
web/app/h5/chat/lib/__tests__/store-branding-gate.spec.ts
web/app/h5/chat/lib/__tests__/video.spec.ts
web/app/h5/chat/lib/__tests__/wx-oauth.spec.ts
web/app/h5/chat/lib/__tests__/wx-scan.spec.ts
web/app/h5/tablet/components/__tests__/phase68-seat-login.spec.tsx
web/app/h5/tablet/components/__tests__/seat-login.spec.tsx
web/app/h5/tablet/components/__tests__/tablet-brand-hero.spec.tsx
web/app/h5/tablet/components/__tests__/wechat-block.spec.tsx
web/app/h5/tablet/lib/__tests__/home-branding-cache.spec.ts
web/app/h5/tablet/lib/__tests__/home-branding-gate.spec.ts
web/app/h5/tablet/lib/__tests__/seat-wx-flow.spec.ts
web/app/h5/tablet/pane/panes/__tests__/about-pane.spec.tsx
web/app/h5/tablet/pane/panes/__tests__/chat-pane.spec.tsx
web/app/h5/tablet/pane/panes/__tests__/chat-pane-voice.spec.tsx
web/app/h5/tablet/pane/panes/__tests__/consult-pane.spec.tsx
web/app/h5/tablet/pane/panes/__tests__/eye-nav-pane.spec.tsx
web/app/h5/tablet/pane/panes/__tests__/find-similar-pane.spec.tsx
web/app/h5/tablet/pane/panes/__tests__/menu-sidebar.spec.tsx
web/app/h5/tablet/pane/panes/__tests__/metric-highlight.spec.ts
web/app/h5/tablet/pane/panes/__tests__/myopia-pane.spec.tsx
web/app/h5/tablet/pane/panes/__tests__/outcome-verdict.spec.ts
web/app/h5/tablet/pane/panes/__tests__/peer-gate.spec.ts
web/app/h5/tablet/pane/panes/__tests__/report-pane.spec.tsx
web/app/h5/tablet/pane/panes/__tests__/tablet-wechat-voice.spec.tsx
[exit=0]
$ python3 -c "import re;s=open('/home/dev2/reports/scan-binding-tablet-v1-20260928.html',encoding='utf-8').read();[print('·',re.sub('<[^>]+>','',m).strip()) for m in re.findall(r'<h2[^>]*>(.*?)</h2>',s,re.S)]"
· 改动一览
· 流程一 · 扫家长二维码(主路径)
· 流程二 · 从本机构家长中选择
· 流程三 · 关联已有档案(扫码后发现孩子已建档)
· 流程四 · 机构二维码
· 核对记录
· 其它状态(照手机端,不单独出图)
[exit=0]
$ grep -n 'export function \(claimFamilyScan\|createChildForFamilyScan\|linkChildForFamilyScan\|resolveFamilyScan\|getOrgCode\)' -A 9 web/app/h5/store-refractive/lib/api.ts
450:export function resolveFamilyScan(familyToken: string) {
451- return brandClaimRequest<FamilyScanResolveResult>("/family-scan/resolve", {
452- method: "POST",
453- body: JSON.stringify({ family_token: familyToken }),
454- });
455-}
456-
457-// 镜像 ClaimResponse(brand_code_claim.py:141)
458-export interface ClaimResult {
459- patient_id: string;
--
468:export function claimFamilyScan(familyToken: string, patientId: string, forceTransfer = false) {
469- return brandClaimRequest<ClaimResult>("/family-scan/claim", {
470- method: "POST",
471- body: JSON.stringify({
472- family_token: familyToken,
473- patient_id: patientId,
474- force_transfer: forceTransfer,
475- }),
476- });
477-}
--
481:export function createChildForFamilyScan(familyToken: string, patient: PatientCreateFields) {
482- return brandClaimRequest<PatientLite & { duplicate_suspect_count: number }>(
483- "/family-scan/create-child",
484- {
485- method: "POST",
486- body: JSON.stringify({ family_token: familyToken, patient }),
487- },
488- );
489-}
490-
--
493:export function linkChildForFamilyScan(familyToken: string, patientId: string) {
494- return brandClaimRequest<PatientLite>("/family-scan/link", {
495- method: "POST",
496- body: JSON.stringify({ family_token: familyToken, patient_id: patientId }),
497- });
498-}
499-
500-// 镜像 OrgCodeResponse(brand_code_claim.py:262)
501-export interface OrgCodeInfo {
502- org_name: string;
--
508:export function getOrgCode() {
509- return brandClaimRequest<OrgCodeInfo>("/org-code", { method: "GET" });
510-}
511-
512-// 批1b 第8项 · AI 识别(镜像平板端 api.ts:aiRecognize · 复用 27B vision base64 直传链路)·
513-// multipart 上传,不走 request() 的 JSON Content-Type
514-export interface AiRecognizeResult {
515- kind: "refraction" | "axial";
516- fields: Record<string, string | number | null>;
517- filled: number;
[exit=0]
$ grep -n 'function brandClaimRequest' -A 30 web/app/h5/store-refractive/lib/api.ts
411:async function brandClaimRequest<T>(path: string, init?: RequestInit): Promise<T> {
412- const resp = await withAuthRetry(() =>
413- fetch(`${BRAND_CLAIM_BASE}${path}`, {
414- ...init,
415- headers: {
416- "Content-Type": "application/json",
417- ...authHeaders(),
418- ...(init?.headers || {}),
419- },
420- }),
421- );
422- if (resp.ok) return (await resp.json()) as T;
423- const detail = await resp.json().catch(() => null);
424- throw new ApiError(resp.status, detail);
425-}
426-
427-// 字段集镜像后端 ChildBrief(app/api/brand_code_claim.py::ChildBrief)
428-export interface ChildBrief {
429- patient_id: string;
430- name: string | null;
431- gender: string | null;
432- birth_date: string | null;
433- org_id: number | null;
434- org_name: string | null;
435- service_org_id: number | null;
436- service_org_name: string | null;
437- service_org_bound_at: string | null;
438-}
439-
440-// 镜像 ResolveResponse(brand_code_claim.py:117)
441-export interface FamilyScanResolveResult {
[exit=0]
$ grep -n 'SeatAuthExpiredError\|location\.\(href\|replace\|assign\)\|router\.\(push\|replace\)' web/app/h5/store-refractive/lib/api.ts
10:// window.location.href 跳门户,见 web/app/h5/tablet/lib/seat-auth-recovery.ts)。
17:// 两种"被踢"情形统一抛 SeatAuthExpiredError,由页面 catch 后原地弹登录层。
78:export class SeatAuthExpiredError extends Error {
102: throw new SeatAuthExpiredError();
107: throw new SeatAuthExpiredError();
[exit=0]
$ grep -n 'async function request\|status === 401\|401\|handleSeatAuthExpired\|seat-auth-recovery' web/app/h5/tablet/pane/panes/refractive-record/lib/api.ts | head -12
9:import { recoverOrRefreshSeatAuth } from "../../../../lib/seat-auth-recovery";
163:// 二期批C 整改⑥(长官打回件)· 席位 token 30 分钟过期,401 时续一次 token 重发一次
166:async function request<T>(path: string, init?: RequestInit): Promise<T> {
170: if (e instanceof ApiError && e.status === 401) {
[exit=0]
$ grep -n '^export' web/app/h5/tablet/lib/seat-auth-recovery.ts
14:export const SEAT_KICKED_FLAG = "h5_seat_kicked";
25:export function recoverFromSeatAuthError(portalPath: string = "/h5/tablet"): boolean {
45:export async function recoverOrRefreshSeatAuth(
[exit=0]
$ grep -n '暂无\|新建孩子档案\|关联已有档案\|该家长\|已在本机构\|当前机构\|原机构' web/app/h5/store-refractive/_components/select-child-dialog.tsx web/app/h5/chat/lib/wx-scan.ts web/app/h5/store-refractive/page.tsx
web/app/h5/store-refractive/_components/select-child-dialog.tsx:4:// 门店扫到家长「我的二维码」后,家长名下孩子列表 + 现场新建 + 关联已有档案 +
web/app/h5/store-refractive/_components/select-child-dialog.tsx:33: /** 「关联已有档案」→ 调用方打开现有选档案层(关联模式),带上当前 family_token */
web/app/h5/store-refractive/_components/select-child-dialog.tsx:194: ? `${selectedChild.name ?? ""} 将从 ${currentOrgOf(selectedChild).orgName ?? "原机构"} 转入本机构`
web/app/h5/store-refractive/_components/select-child-dialog.tsx:274: 该家长暂无孩子档案
web/app/h5/store-refractive/_components/select-child-dialog.tsx:330: 新建孩子档案
web/app/h5/store-refractive/_components/select-child-dialog.tsx:339: 关联已有档案
web/app/h5/chat/lib/wx-scan.ts:392: * 转入过),否则回落 org_id(建档机构) —— 与技术方案「当前机构 =
web/app/h5/chat/lib/wx-scan.ts:403: * 当前机构 === 本机构 → 灰字「已在本机构」不可选;否则「当前机构:XX」可选。 */
web/app/h5/chat/lib/wx-scan.ts:410: return { disabled: true, text: "已在本机构" };
web/app/h5/chat/lib/wx-scan.ts:412: return { disabled: false, text: `当前机构:${orgName ?? "—"}` };
web/app/h5/store-refractive/page.tsx:73: // 进 BindParentSheet)vs link 模式(扫码后「关联已有档案」,选中后要单独点「确认
web/app/h5/store-refractive/page.tsx:488: 暂无档案
web/app/h5/store-refractive/page.tsx:511: link 模式(扫码后「关联已有档案」):点行只是选中(高亮),需要另点「确认关联」
web/app/h5/store-refractive/page.tsx:544: 暂无未绑定的档案
[exit=0]
$ grep -n '用已有档案\|仍然新建' -B 4 -A 2 web/app/h5/store-refractive/_components/select-child-dialog.tsx
397- <ConfirmDialog
398- open={!!dupChild}
399- title={`${dupChild?.name ?? ""} 已在这位家长名下`}
400- description="姓名、生日都相同,再新建会多出一份重复档案"
401: confirmText="用已有档案"
402: cancelText="仍然新建"
403- onCancel={() => {
404- const body = pendingCreateBody;
[exit=0]
$ grep -n 'interface\|cancelLabel\|confirmLabel\|onCancel\|onConfirm' web/app/h5/components/confirm-dialog.tsx | head -15
8:interface ConfirmDialogProps {
19: onCancel: () => void;
20: onConfirm: () => void;
31: onCancel,
32: onConfirm,
41: onClick={() => !loading && onCancel()}
57: onClick={onCancel}
65: onClick={onConfirm}
[exit=0]
$ git diff HEAD origin/test -- web/app/h5/store-refractive/page.tsx
diff --git a/web/app/h5/store-refractive/page.tsx b/web/app/h5/store-refractive/page.tsx
index e02322d5..82632f54 100644
--- a/web/app/h5/store-refractive/page.tsx
+++ b/web/app/h5/store-refractive/page.tsx
@@ -676,6 +676,7 @@ export default function StoreRefractivePage() {
<SeatLoginSheet
orgCode={seatOrgCode}
kicked={seatKicked}
+ scene="store"
onSuccess={(orgFeatures) => {
if (orgFeatures) applySeatOrgFeatures(orgFeatures);
setSeatGateOpen(false);
[exit=0]
$ git log --oneline -2 HEAD -- web/app/h5/store-refractive/org-code/page.tsx; echo ---test:; git log --oneline -3 origin/test -- web/app/h5/store-refractive/org-code/page.tsx
98987316 feat(phase48b.4): 门店端扫一扫与机构码页
---test:
0cd5261d fix(phase48b): 机构二维码卡片改用 my-auto 真居中(去掉内联 margin 上下覆盖)
22a31bc7 fix(phase48b): 机构二维码页内容上下居中(消除下方留白)
98987316 feat(phase48b.4): 门店端扫一扫与机构码页
[exit=0]
$ git show origin/test:web/app/h5/store-refractive/org-code/page.tsx | grep -n '长按\|机构二维码\|org_name\|<img\|加载失败\|扫码\|家长'
1:// Phase 48B.4 帧 S06 · 机构二维码页
6:// 二维码走后端 /api/qr(<img> 原生 URL,微信长按识别只认 img,不能用 qrcode.react
52: setError("加载失败,请重试");
54: setError("加载失败,请重试");
74: 机构二维码
109: {info.org_name}
124: <img
126: alt="机构二维码"
131: 长按二维码可保存图片
144: 打印贴在前台,家长微信扫码即可绑定孩子
[exit=0]
$ grep -n 'QrCode\|UserPlus\|lucide-react' web/app/h5/store-refractive/page.tsx | head -6
11:import { ChevronLeft, Plus, QrCode, Search, X } from "lucide-react";
271: <QrCode size={14} strokeWidth={2} />
[exit=0]
$ grep -c 'UserPlus\b' web/node_modules/lucide-react/dist/lucide-react.d.ts; grep -c 'QrCode\b' web/node_modules/lucide-react/dist/lucide-react.d.ts; echo 对照组Plus:; grep -c ' Plus\b' web/node_modules/lucide-react/dist/lucide-react.d.ts
5
5
对照组Plus:
5
[exit=0]
画面是截图(base64 位图),文字 grep 不到,改为用 Read 打开原帧图片逐字核对(帧图源: /tmp/48b8-tablet/mock/F*.jpg):
| 帧 | 画面文字(亲眼所见) | 代码真源 | 结论 |
|---|---|---|---|
| F2 绑定方式 | 绑定家长 / 请选择绑定方式 / 扫描家长二维码 / 请家长出示「我的二维码」/ 推荐 / 从本机构家长中选择 / 在本机构已开通的家长账号中查找 / 取消 | bind-method-dialog.tsx:74,81,110,113,127,158,161 | ✅ 一致 |
| F3 选孩子 | 选择要绑定的孩子 / 家长:留白 · 0000xxxx09 / 已在本机构(灰) / 当前机构:48B测试二店(dev2验收用) / 已选中 / 转店测试1133 将从 48B测试二店(dev2验收用) 转入本机构 / 新建孩子档案 / 关联已有档案 / 取消 / 确认绑定 | select-child-dialog.tsx:194,251,258,306,330,339,372 + wx-scan.ts:410,412 | ✅ 一致 |
| F5 重名提醒 | 转店测试1133 已在这位家长名下 / 姓名、生日都相同,再新建会多出一份重复档案 / 仍然新建 / 用已有档案 | select-child-dialog.tsx:399-402 | ✅ 一致(疑点A消除:两个按钮都在) |
| F6 关联 | 关联本机构已有档案 / 已选中 / 取消 / 确认关联 | store-refractive/page.tsx:519,535 | ✅ 一致 |
| F7 选档案(绑定) | 选择要绑定的档案 / 取消(点行进下一步) | store-refractive/page.tsx:535 | ✅ 一致 |
| F8 机构码 | 机构二维码 / 红胜利视光中心有限公司 / 长按二维码可保存图片 / 打印贴在前台,家长微信扫码即可绑定孩子 / 只绑定孩子,不开通账号 / 关闭 | origin/test org-code/page.tsx:74,109,131,144,146 | ✅ 一致(疑点B消除) |
表头三按钮:机构码(二维码图标)/ 绑定家长(小人图标 · 长官 9-28 定加)/ + 新建档案 —— 与现网 frame1-child-list.tsx:85 <Plus size={14} /> 同排风格一致。