feat: 按测测功能重构 H5 首页与各子页,并修正顶栏「+」添加档案菜单

对齐测测交互:首页「+」支持邀请填档案/添加档案/邀请合盘;各 Tab 与工具页按同一视觉与功能标准落地;本地对照截图目录显式忽略。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-05 14:32:35 +08:00
co-authored by Cursor
parent bd22d9dddd
commit 53eb4577b3
35 changed files with 8900 additions and 1356 deletions
+247
View File
@@ -0,0 +1,247 @@
#!/usr/bin/env python3
"""Capture CeCe homepage top-right 「+」→ add family/friend archive flow."""
from __future__ import annotations
import json
import os
import re
import subprocess
import time
from pathlib import Path
ROOT = Path("/Users/jack/Project/digital-psychology")
TOOLS = ROOT / "tools" / "android"
ANDROID_HOME = Path("/opt/homebrew/share/android-commandlinetools")
PKG = "com.xxwolo.cc5"
ACT = f"{PKG}/com.cece.app.MainActivity"
STAMP = time.strftime("%Y%m%d-%H%M%S")
OUT = ROOT / ".tmp" / "cece-validation" / time.strftime("%Y%m%d") / f"home-plus-{STAMP}"
NOTES = OUT / "NOTES.md"
os.environ["ANDROID_HOME"] = str(ANDROID_HOME)
os.environ["ANDROID_SDK_ROOT"] = str(ANDROID_HOME)
os.environ["PATH"] = (
f"{ANDROID_HOME}/emulator:{ANDROID_HOME}/platform-tools:"
f"/opt/homebrew/bin:/usr/bin:/bin:" + os.environ.get("PATH", "")
)
def sh(args, timeout=120):
return subprocess.run(args, capture_output=True, text=True, timeout=timeout)
def adb(*args, timeout=120):
return sh(["adb", *args], timeout=timeout)
def tool(*args, timeout=120):
r = sh([str(TOOLS), *args, "--json"], timeout=timeout)
try:
return json.loads(r.stdout or "{}")
except Exception:
return {"error": r.stderr, "raw": (r.stdout or "")[:500]}
def label(e):
return (e.get("text") or e.get("contentDesc") or e.get("contentDescription") or "").strip()
def elements(data):
return data.get("elements") or []
def dump():
return tool("ui", "dump")
def center(e):
c = e.get("center")
if isinstance(c, dict):
return int(c["x"]), int(c["y"])
b = e.get("bounds") or {}
if isinstance(b, dict) and "width" in b:
return int(b["x"] + b["width"] / 2), int(b["y"] + b["height"] / 2)
return None
def area(e):
b = e.get("bounds") or {}
return int(b.get("width") or 0) * int(b.get("height") or 0)
def tap_xy(x, y, wait=1.6):
adb("shell", "input", "tap", str(x), str(y))
time.sleep(wait)
def back(n=1):
for _ in range(n):
adb("shell", "input", "keyevent", "4")
time.sleep(1.0)
def shot(rel: str, note: str = ""):
path = OUT / rel
path.parent.mkdir(parents=True, exist_ok=True)
tool("screenshot", "--out", str(path))
labs = sorted({label(e).split("\n", 1)[0] for e in elements(dump()) if label(e)})
(OUT / rel.replace(".png", ".labels.txt")).write_text("\n".join(labs), encoding="utf-8")
with NOTES.open("a", encoding="utf-8") as f:
f.write(f"- `{rel}` {note}\n")
f.write(f" labels: {', '.join(labs[:40])}\n")
print("SHOT", rel, note, flush=True)
return path
def save_dump(rel):
path = OUT / rel
path.parent.mkdir(parents=True, exist_ok=True)
data = dump()
path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
return data
def first_lines(data=None):
data = data or dump()
return [label(e).split("\n", 1)[0] for e in elements(data) if label(e)]
def go_home():
adb("shell", "am", "start", "-n", ACT)
time.sleep(2.0)
# tap 首页 tab
tap_xy(108, 2255, wait=1.2)
# scroll top
for _ in range(2):
adb("shell", "input", "swipe", "540", "500", "540", "1600", "280")
time.sleep(0.5)
def find_plus_candidates(data):
"""Top-right + / 添加 / contentDesc."""
hits = []
for e in elements(data):
lab = label(e)
first = lab.split("\n", 1)[0]
b = e.get("bounds") or {}
y = int(b.get("y") or 0)
x = int(b.get("x") or 0)
w = int(b.get("width") or 0)
h = int(b.get("height") or 0)
if y > 280:
continue
if x < 700:
continue
# plus-like
if first in ("+", "", "添加", "新建", "添加档案") or "添加" in lab or lab in ("+", ""):
hits.append(e)
continue
cd = (e.get("contentDesc") or e.get("contentDescription") or "")
if any(k in cd for k in ("添加", "新建", "plus", "Plus", "+")):
hits.append(e)
continue
# small square top-right icon without text
if e.get("clickable") and not lab and w < 120 and h < 120 and x > 850:
hits.append(e)
hits.sort(key=lambda e: (area(e), -(center(e) or (0, 0))[0]))
return hits
def tap_contains(needle, ymax=9999):
data = dump()
cands = []
for e in elements(data):
lab = label(e)
if needle not in lab:
continue
b = e.get("bounds") or {}
if int(b.get("y") or 0) > ymax:
continue
if not e.get("clickable") and area(e) > 200 * 200:
continue
cands.append(e)
cands.sort(key=area)
if not cands:
return False
xy = center(cands[0])
if not xy:
return False
print(f" TAP contains {needle!r} @ {xy}", flush=True)
tap_xy(*xy)
return True
def explore_add_flow():
OUT.mkdir(parents=True, exist_ok=True)
NOTES.write_text(f"# home + archive capture {STAMP}\n\n", encoding="utf-8")
if not re.search(r"emulator-\d+\s+device", adb("devices").stdout or ""):
raise SystemExit("no emulator")
go_home()
shot("00-home.png", "home before +")
data = save_dump("00-home.json")
plus = find_plus_candidates(data)
print("plus candidates:", len(plus), flush=True)
for e in plus[:8]:
print(" ", label(e) or "(empty)", center(e), e.get("bounds"), flush=True)
opened = False
if plus:
xy = center(plus[0])
if xy:
print(f"tap plus candidate {xy}", flush=True)
tap_xy(*xy, wait=2.0)
opened = True
if not opened:
# fallback top-right +
for xy in ((1008, 168), (990, 155), (1020, 190), (960, 170)):
print(f"fallback tap {xy}", flush=True)
tap_xy(*xy, wait=1.8)
fls = first_lines()
if any(
k in " ".join(fls)
for k in ("添加档案", "新建档案", "家人", "朋友", "伴侣", "关系", "昵称", "生日", "档案")
):
opened = True
break
back(1)
time.sleep(0.6)
shot("01-after-plus.png", "after tapping +")
data = save_dump("01-after-plus.json")
fls = first_lines(data)
print("after-plus labels:", fls[:30], flush=True)
# Dive relation type chips if present
for lab in ("家人", "朋友", "伴侣", "同事", "其他", "添加档案", "新建档案", "创建档案", "立即添加"):
if lab in fls or any(lab in x for x in fls):
if tap_contains(lab, ymax=1800):
time.sleep(1.2)
shot(f"02-tap-{lab}.png", f"tap {lab}")
save_dump(f"02-tap-{lab}.json")
# Try fill-ish fields visibility
shot("03-form.png", "add archive form state")
save_dump("03-form.json")
# Look for 关系 / 生日 / 保存
for lab in ("保存", "完成", "确定", "下一步", "生成"):
if tap_contains(lab, ymax=2200):
time.sleep(1.2)
shot(f"04-{lab}.png", f"tap {lab}")
break
# Summary of labels for NOTES
final = first_lines()
with NOTES.open("a", encoding="utf-8") as f:
f.write("\n## Summary labels after +\n")
f.write("\n".join(f"- {x}" for x in final[:80]))
f.write("\n")
print("DONE", OUT, flush=True)
if __name__ == "__main__":
explore_add_flow()