对答如牛 App(mobile 端)AI Slop 审查报告
1. 总体结论
这个 App 的骨架是健康的,slop 长在皮肤上。
Token 层(theme/tailwind.css + theme/variables.css)严格对齐了甲方规范:主色 #6C5CE7、六张模块卡浅底色与规范表格逐一对应、圆角 16/20/24 三档、三档轻阴影连数值都和规范一致。文案整体也是规范要求的「学习伙伴」语气而非「系统管理员」语气,没有出现「不只是 X,更是 Y」式 AI 文案腔。
真正的 AI 痕迹集中在一件事上:页面层大量绕过自家 token,直接抓 Tailwind 默认色板——amber-700 出现 32 次、rose-500/600 共 23 次,外加 emerald / sky / purple / gray / red 系散落各处,共约 90 处。这是典型的机器书写指纹:每一行局部看都「对」,合起来是一碗默认彩虹糖——同一个「危险」概念在代码里有 rose-400 / rose-500 / rose-600 / red-500 / danger-soft 五种写法。伴生问题是同一块「刷新失败」提示卡被复制了约 15 份(其中 4 份是在 4 个文件里各自重新定义的同名组件)、同一个控件在不同页面长着两种样子、以及两个从未生效过的失效样式类(写完没人看过渲染结果,这是 taxonomy 里最重的指控:not absence of taste, absence of looking)。
2. 审美基准:甲方要什么,我们守什么
甲方沟通资料(sync-drive)里的有效设计输入是一份完整的《设计风格与 Design System 规范》和一张 Dribbble 风格参考图。规范本身写明参考图「仅作为视觉风格参考,不直接复制」。提炼出的、本次整改据以收敛的基准:
| 维度 | 甲方规范口径 | 当前代码现状 |
|---|---|---|
| 色彩 | 主色 #6C5CE7 不大面积铺;状态色「明确但不刺眼」;模块卡用指定浅底色;禁全卡同紫、禁多渐变 | token 层 ✅ 完全一致;页面层 ❌ 约 90 处 Tailwind 默认色板渗漏 |
| 圆角 | 16/20/24 三档 + 胶囊;不用 4/6px 小圆角 | ✅ --radius-lg2/xl2/hero 三档,胶囊按钮是规范明确要求 |
| 阴影 | 三档轻阴影,禁重黑投影 | ✅ 三档数值与规范逐字一致 |
| 字号 | h1 28 / h2 22 / h3 18 / title 16 / body 14 / note 12 | ✅ .text-h1…text-note 工具类一一对应 |
| 文案 | 「学习伙伴」语气;错误文案讲原因和下一步 | ✅ 大体到位(「你好,未来的公务员」「利用思考时间列好答题提纲」),个别句子欠打磨 |
| 动效 | 轻动效:按下缩放 0.98,不做过重动效 | ⚠️ Card 已是 0.98,但三处按钮写成 0.95,口径不一 |
| 插画 | 10 张场景插画服务于功能(空状态/成功/错误/AI 等) | ⚠️ 插画系统已建好,但报名成功页没用它,用了一个 text-6xl 的 emerald 大对勾图标 |
结论:不需要「重新设计」,需要的是把页面层拉回它自己已经建好的规范,并把规范里缺的最后一块——状态文字色(ink 系)token——补上。
3. 确认的 AI 痕迹(12 组)
G1 · 状态色绕过 token:Tailwind 默认色板渗漏 高 · ~90 处
对应 tell:#03 warm cozy palette / #04 default semantic palette / #05 one-hue status box。
项目明明定义了自己的状态色(warning #FCD34D、danger-soft #FF8FA3、success #34D399、info #22D3EE)和六张 tint 卡,但状态文字没有对应 token,于是每个调用点各自伸手抓 Tailwind 默认值:
| 原类 | 次数 | 代表位置 | 问题 |
|---|---|---|---|
text-amber-700 | 32 | ReportPages / PracticePages / RegPages / OrderPages 等全部「提示」卡 | amber 底(#FEF3C7) + amber 字 = 教科书式 one-hue status box,且 32 处逐一手写 |
text-rose-500 / text-rose-600 / text-red-500 | 17 + 6 + 1 | 各错误提示;RegPages.tsx:929 混入了一个孤例 text-red-500 | 同一个「错误」概念三种色值并存,rose-500 在白底上对比度仅 ≈3.0:1 |
text-emerald-500/600/700、bg-emerald-500 | 10 | 签约完成提示、微信按钮、审题命中圆点、成功大对勾 | 项目成功色是 #34D399,emerald 系是又一套平行绿色 |
text-sky-600/700 | 7 | chip info 调、全款价格、HubPage 图标 | 项目 info 色是 #22D3EE,sky 系是平行蓝色 |
text-purple-500 | 3 | HubPage 图标 ×2、试听页教师头像字 | 主色 #6C5CE7 就在手边,却用了默认紫 |
gray-* 系 | 8 | BackendSwitch.tsx(开发工具悬浮球整体) | 与 ink/line/surface token 平行的一套灰 |
bg-amber-50、bg-amber-500、bg-rose-400/500 | 7 | BasePages.tsx:302、ReportPages 档位药丸、审题未命中圆点 | tint-notice / warning / danger-soft token 被旁路 |
为什么算 slop:不是「这些颜色丑」,而是没人选过它们。危险色横跨 rose-400→red-500 五个值、绿色横跨三个 emerald 明度,说明每一处都是生成时就近抓的默认值,而不是从项目色板推导的决定。规范 4.2 明文「禁止将所有状态都用主色表达」的反面正是「每个状态各自发明颜色」。
修复:在 @theme 增补 4 个状态文字色 token(从 variables.css 已有的 Ionic contrast 对色取值,真源不新增):--color-warning-ink: #78350f / --color-danger-ink: #881337 / --color-success-ink: #065f46 / --color-info-ink: #0369a1,然后全量替换。替换后整个 App 的状态色收敛为「规范六 tint 底 + 四 ink 字」一套口径,对比度全部达标。
G2 · 从未生效的失效样式类 高 · 2 处(1 个真实渲染缺陷)
对应 tell:#22 家族——「每一行局部正确、整体是错的,因为从没人看过渲染结果」。
ReportPages.tsx:184 'rounded-full bg-danger-soft … text-danger' ← text-danger 未定义,静默失效
ReportPages.tsx:185 'rounded-full bg-surface-2 … text-ink-3' ← bg-surface-2 未定义,静默失效
主题里只有 --color-danger-soft 和 --color-surface-muted,没有 danger 和 surface-2。实际渲染效果:报告页「维度评分标准」里命中的扣分项是粉底 + 继承的深灰字(本意显然不是),未命中项干脆没有底色、字浮在卡片上。这是本次审查发现的唯一真实渲染缺陷。
修复:命中项 bg-tint-error text-danger-ink,未命中项 bg-surface-muted text-ink-3。
G3 · 同一张提示卡里的色彩矛盾 中
RegPages.tsx:857-861 签约完成: Card tint="notice"(amber 黄底) + text-emerald-700(绿字)
RegPages.tsx:862-866 无需签约: Card tint="notice"(amber 黄底) + text-ink-2(灰字)
「已签署」是成功态却穿着提醒卡的黄底配绿字——绿字压黄底,两种语义色打架。相邻的「无需签约」又是第三种组合。生成式书写的典型症状:每个分支独立生成,没人横向看过这一组卡。
修复:成功态用 tint="local"(规范的浅绿卡)+ success-ink;中性态用素卡 + ink-2;提醒态保持 notice + warning-ink。状态与颜色一一对应。
G4 · 图标彩虹糖 中 · 10 处
对应 tell:#04「一碗默认彩虹糖」/ #25 icon-in-a-tint 的近亲。
HubPage.tsx 订单 sky-600 · 合同 primary · 住宿 purple-500 · 冲刺 emerald-600 · 权益 amber-500
练习 primary · 直播 purple-500 · 资料 primary · 安全 sky-600 · 设置 ink-2
「我的」页 10 个列表图标用了 6 种颜色,颜色和功能之间没有任何映射(住宿为什么紫、权益为什么黄?)。这正是 taxonomy 说的「nobody picked these hues」。同族问题:试听页教师头像字用 text-purple-500(RegPages.tsx:1065),登录页微信按钮用 text-emerald-600(BasePages.tsx:191——既不是项目色也不是微信品牌绿)。
修复:让颜色携带真实信息——可用功能图标一律 primary,未开放(planned)功能图标一律 ink-3,扫一眼就能分辨什么能点。教师头像字改 primary;微信按钮改用微信品牌色 #07C160(decoration must mean something:这个绿属于微信,不属于色板抽奖)。
G5 · 评分档位的红绿灯药丸 中 · 6 处
ReportPages.tsx:39-53 优秀 bg-emerald-500 / 良好 bg-primary / 中等 bg-amber-500 / 较差 bg-rose-500 (白字)
ReportPages.tsx:212 审题命中 bg-emerald-500 / 未命中 bg-rose-400 (白字)
对应 tell:#05「red/yellow/green is a traffic light, not a palette」。白字压 amber-500 对比度 ≈2.2:1,压 emerald-500 ≈2.1:1,均不达标;且这四个饱和色块和整页柔和 tint 卡的气质断裂。
修复:药丸改用 App 自己的 chip 语言——tint 底 + ink 字:优秀 bg-tint-local text-success-ink、良好 bg-tint-ai text-primary、中等 bg-tint-notice text-warning-ink、较差 bg-tint-error text-danger-ink。审题命中圆点同理。信息不变,音量调到和整个 App 一致。
G6 · 已完成步骤加删除线 低 · 1 处
PracticePages.tsx:821 评分中步骤条:已完成步骤 text-ink-3 line-through
对应 tell:#09——删除线的语义是「删掉了」,不是「完成了」。步骤左侧已有 ✓ 圆点表达完成,再划掉一次是双重表达且语义错误。修复:去掉 line-through,保留降灰。
G7 · 报名成功页的巨型图标 中 · 1 处
RegPages.tsx:811 <IonIcon icon={checkmarkCircle} className="text-6xl text-emerald-500" />
对应 tell:#18 巨型装饰图标。这个 App 明明有一套规范要求的场景插画系统(错误页都在用 error-gentle、完成页用 success-trophy),偏偏「报名成功」——全流程情绪最高点——放了一个 96px 的默认绿大对勾。这是插画系统建好了却没贯彻的断点。修复:换 <Illo name="success-trophy" />,与全 App 状态页语言统一。
G8 · 复制粘贴出来的组件 高 · ~30 处
对应 tell:slop 的本质是「无决定的重复」,代码层的表现就是同一块 UI 被生成 N 次而不是抽象一次:
| 重复物 | 规模 | 细节 |
|---|---|---|
| 「刷新失败,当前展示上次成功加载的…」提示卡 | 4 个同名组件定义 + ~11 处内联复制 | QueryRefreshWarning 在 PracticePages / HistoryAbilityPages 各定义一次,RefreshWarning 在 ReportPages 定义一次,HomePage/RegPages/OrderPages 再手写 ~11 份;文案尾部有的带句号有的不带,「…的内容」和「…的{scope}。」两种口径 |
| 居中 loading 块 | ~14 处 | <div class="grid justify-items-center py-16"><IonSpinner … 逐页复制,spinner 尺寸 h-6 / h-8 / h-10 三种、留白 py-6/12/16 三种,无规律 |
会话状态文案表 STATUS_TEXT | 3 份全等拷贝 | ai/HomePage、ReportPages、HistoryAbilityPages 各一份,将来改一处漏两处 |
| 首页/AI 首页主卡的胶囊按钮 | 2 处 | 同一视觉元素两套内边距:px-5 py-2 vs px-6 py-2.5 |
修复:ui.tsx 增加三个小组件——Notice(tone: notice/error/success,可带 action 按钮)、RefreshNotice(统一文案)、Loading(统一尺寸留白);STATUS_TEXT 收敛到 data/ai.ts 单一出口。全量替换后删掉 4 个局部定义。
G9 · 同一控件、两张面孔 中
bg-[#f9fafb] text-ink-2(无边框浅灰底)选中:
bg-tint-ai text-primary ring-1border border-line bg-white(白底描边)选中:
bg-primary text-white shadow-card完全相同的「男/女」二选一控件,两个页面两套视觉,连选中态的表达方式(tint+ring vs 实心主色)都不同。资料页版本还带着一条注释解释了为什么必须有可见边界——报名页版本恰好踩了这条注释警告的坑。同族:active:scale-95(3 处按钮)vs 规范 16.3 与 Card 组件的 0.98。
修复:统一为资料页的带边框版本(有注释论证过的那个);按下缩放统一 0.98。
G10 · 裸 hex 复制 低 · 3 处
bg-[#f9fafb] 在 tailwind.css 的 .field-box 和 ApplyFormPage 内联各写一次。规范 9.8 定义的输入框底色应当是一个 token。修复:增 --color-field: #f9fafb,两处引用。
G11 · 开发工具悬浮球的平行灰色系 低 · 8 处
BackendSwitch.tsx 整个组件用 gray-100/200/400/500/700 + shadow-lg,与项目的 ink/line/surface/shadow token 平行。虽是 DEV 工具,但它会出现在内测包里,且是最容易顺手收敛的文件。修复:换 token 类。
G12 · 文案微瑕 低 · 2 处
| 位置 | 现文案 | 问题与建议 |
|---|---|---|
| ai/HomePage.tsx:141 | 「还没有练习记录,点上方"开始练习"来第一次吧」 | 「来第一次吧」表述别扭 → 「完成第一次练习吧」 |
| PracticePages.tsx:830 | 「稍后查看,先去别处逛逛」 | 「逛逛」偏闲逛语感,与评分等待场景不匹配 → 「稍后查看,先去练别的」(保持伙伴语气、贴合场景) |
其余文案整体质量好:错误文案讲原因和下一步(「你的作答已保留,可以重试评分」),未发现「不只是 X 更是 Y」「告别 XX」等 AI 文案腔,也没有装饰性 emoji(唯一一个 ⚠️ 在代码注释里,不进产品)。
4. 复核后判定为「有意设计」保留的项
扫描器命中但读码后确认是被论证过的选择,不改(避免把 de-slop 做成另一种无脑 lint):
| 命中 | 判定 | 理由 |
|---|---|---|
大量 rounded-full 胶囊(tell #19) | 保留 | 甲方规范 2.4/9.4 明确要求「胶囊按钮、圆形图标按钮」,是设计系统的核心特征 |
录音按钮 animate-ping 光晕(tell #16) | 保留 | 背后是真实的「正在录音」状态——taxonomy 的判据是「有没有真实状态」,这里有;录音指示脉动是行业惯例 |
| 字体栈含 Inter(tell #33) | 保留 | 规范 5.1 指定「英文与数字:Inter / SF Pro」,且只是系统栈的末位回退,主体是 PingFang/思源黑体 |
| 紫色主色 + 六色 tint 卡(tell #01/04 疑似) | 保留 | #6C5CE7 与六张 tint 是甲方规范逐一定义的品牌色板,非默认值 |
| 定金/全款双色价格(tell #13 疑似) | 保留 | 0711 会议甲方拍板「全款与定金同等大小同等突出,双色区分」;仅把第二色从裸 sky-600 收敛为 info-ink token |
作答方式 grid-cols-3(tell #28) | 保留 | 文字/语音/视频是三个真实互斥选项,不是装饰性卡片网格 |
Card active:scale-[0.98](tell #26 疑似) | 保留 | 规范 16.3 明确「按钮按下缩放 0.98」;且只 transition transform 单属性,非 transition-all |
| 思考页同心倒计时圆环(tell #21 疑似) | 保留 | h-36/h-28 圆形嵌套天然同心,无圆角错位问题 |
5. 整改方案
三层动作,全部不触碰业务逻辑(仅 className、共享组件抽取、两句文案):
5.1 Token 增量(theme/tailwind.css)
@theme {
/* 状态文字色:取值 = variables.css 既有的 Ionic contrast 对色,单一真源 */
--color-warning-ink: #78350f; /* 压 tint-notice #FEF3C7 */
--color-danger-ink: #881337; /* 压 tint-error #FFE4E6 */
--color-success-ink: #065f46; /* 压 tint-local #DCFCE7 */
--color-info-ink: #0369a1; /* 压 tint-reg #DBEAFE,兼任全款价格色 */
--color-field: #f9fafb; /* 输入框底(规范 9.8) */
--color-wechat: #07c160; /* 微信品牌绿,仅登录按钮 */
}
5.2 组件收敛(components/ui.tsx)
<Notice tone="notice|error|success" action={…}>…</Notice> // 统一提示卡(tint 底 + ink 字 + 可选右侧动作)
<RefreshNotice scope="课程" /> // 统一「刷新失败」文案,消灭 15 份拷贝
<Loading /> // 统一居中加载块(尺寸/留白一档)
Chip 色调改用 ink token;STATUS_TEXT 收敛到 data/ai.ts
5.3 全量替换清单(按文件)
| 文件 | 动作 |
|---|---|
components/ui.tsx | chipTones 换 token;新增 Notice / RefreshNotice / Loading |
theme/tailwind.css | 增 6 个 token;field-box 引用 --color-field |
pages/ai/ReportPages.tsx | 修复 2 个失效类;档位药丸/审题圆点改 tint+ink;amber-700×8 → Notice;删本地 RefreshWarning |
pages/ai/PracticePages.tsx | 去 line-through;rose/amber → token/Notice;删本地 QueryRefreshWarning;文案 1 句 |
pages/ai/HomePage.tsx / HistoryAbilityPages.tsx | 提示卡 → RefreshNotice;loading → Loading;STATUS_TEXT 引用收敛;主卡按钮统一;文案 1 句 |
pages/registration/RegPages.tsx | 成功页换插画;签约状态卡语义配色;性别选择器统一;amber/rose/red/sky/purple 全量 → token;报名协议卡分态 |
pages/home/HomePage.tsx / pages/my/* | HubPage 图标改「可用=primary / 未开放=ink-3」;提示卡与 loading 收敛;rose → danger-ink |
pages/base/BasePages.tsx | amber-50 盒 → Notice;微信按钮 → 品牌绿;性别选择器保持描边版(作为统一基准) |
components/BackendSwitch.tsx | gray-* → ink/line/surface token;scale 统一 0.98 |
验证:整改后重跑 kill-ai-slop 扫描器确认色板类命中归零(保留项加注释说明);pnpm --filter @gk/mobile typecheck + mobile 测试全绿后提 PR。