三个形态 = PC 浏览器 / 手机 APP / 微信小程序。PC 管干重活,APP 管随时和随口,小程序管转给别人看。
上一版我把第三个位置放成了 H5 —— 那不是端,是渲染层:APP 里跑的是它,小程序里跑的也是它,
微信里点开的分享页还是它。把它算成一端,等于把「纸质」算成一种书。
证据不是这句话,是下面这几个文件:一份内容 system.js,一份数据 app.js,
一套交互 shell.js(语音也在这层)。三个形态都读它们。改一处,三端同时变。
system.js13 个页面的标题、正文、字段、按钮文案,只在里面出现一次。三个形态都从这里取,没有任何一页在自己文件里重写一遍。
app.js订阅 ¥598(299×2 月)、附件一 ¥8,292(13 位真实媒体 + 自有内容)、词池 40(32+8)、工单 8、链接 8、DiD +6.4pp。三形态读同一个对象,不各算各的。
shell.js勾清单、点条款、手写签名、付两笔钱、按住说话,三种形态点的是同一段代码。不会有「电脑上能签、手机上签不了」。



表单逼他先学会我们的术语;对话不用,翻译是我们的活。所以第 1 步没有公司名输入框、没有需求下拉、没有行业选择 —— 只有一个输入框、一个麦克风、一个发送键。下面四张图不是设计稿,是原型跑出来的四步(截自 APP 端,PC 与小程序同源)。




① 系统键盘听写。iOS / Android 键盘右下角那颗麦克风、Windows 的 Win+H、macOS 双击 Fn —— 这是操作系统级的能力,任何一个输入框里都能用,APP 和小程序一行代码都不用写,也不用申请麦克风权限(权限在输入法那边)。
② 平台 ASR。iOS SFSpeechRecognizer、Android SpeechRecognizer、小程序 wx.getRecorderManager + 后端识别(或微信同声传译插件)—— 这是应用级能力,能带上下文、能限时长、能直接落成字段。
③ Web Speech API。只有桌面 Chrome / Edge 靠得住。iOS Safari 至今没有实现(WebKit 官方标为不计划支持),微信内置浏览器直接禁用 —— 所以 H5 那条路本来就走不通,这也再一次说明 H5 只能当渲染层,不能当形态。



2019 第 0331 号,还得当场校验格式。这一步只有应用内做得到。| 形态 | 首页那句话(开放式) | 缺口字段(要引导) | 坑 |
|---|---|---|---|
| PC 浏览器 | 系统听写 Win+H / 双击 Fn,零代码 |
桌面 Chrome / Edge 的 Web Speech,或录音上传后端 | 国内 Chrome 的 Web Speech 走境外云,基本不可用;必须 HTTPS + 用户手势触发 |
| PC 客户端 | 同上 | 同上,可加本地 ASR(离线、不进云) | Electron 要显式声明麦克风权限,否则静默失败 |
| iOS APP | 系统键盘听写,零代码 | SFSpeechRecognizer,支持离线 |
Info.plist 两个权限文案缺一个,一调用就崩 |
| Android APP | 系统键盘听写,零代码 | SpeechRecognizer 或厂商 ASR |
国产 ROM 无 GMS 时 SpeechRecognizer 可能直接不可用,必须能降级到录音上传 |
| 微信小程序 | 键盘上的语音键,零代码 | wx.getRecorderManager + 后端 ASR;或微信同声传译插件 |
拿不到系统 ASR;插件有每日免费额度,超出按次付费;真机与模拟器录音格式不一致 |
| H5(微信内) | 键盘上的语音键 | 录音上传后端(MediaRecorder) |
Web Speech 在微信内置浏览器被禁用,别指望 |
input 事件 —— 发送键点亮、回车能发、缺口字段校验、Agent 解析,三端一字不差。


system.js 里第 8 页的表只定义了一次(表头 + 8 行数据),
mode==='pc' 时输出 <table>,否则输出卡片列表。宽屏和窄屏的差别只有渲染方式,
数据一个字都没变,所以你不可能遇到「电脑上 8,890、手机上 8,236」。
| 维度 | PC 浏览器 | 手机 APP | 微信小程序 |
|---|---|---|---|
| 视口 | 1440 三栏 | 390 单列 + 手机框 | 375 单列 + 胶囊 |
| 导航 | 左侧 13 步列表(未确认前整列灰掉) | 原生导航 + 13 段进度(第 1 步不显示)+ 底部 5 tab(未确认前灰掉) | 胶囊 + 底部 5 tab(未确认前灰掉) |
| 表格 | 真表格 | 卡片列表 | 卡片列表 |
| 页数 | 13 页全量 | 13 页全量 | 高频 5 个入口 |
| 独有能力 | 鼠标手写签名、一屏对账 | 系统推送、悬浮语音键、无浏览器地址栏 | 转发即开、无需下载 |
| 语音输入 | 三端都有;面板与状态机同源,录音与识别各写一遍(全系统唯一不同源的模块) | ||
| 配色 | 商务蓝 | 商务蓝 | 微信绿 |
| 文案、字段、按钮 | 完全相同(同一份 system.js) | ||
| 金额、词数、工单数 | 完全相同(同一份 app.js) | ||
| 点击后的结果 | 完全相同(同一套 shell.js) | ||
| 签名、付款、勾清单 | 三端都能做,不是「手机上只能看」 | ||
adm_shot.py states)
index.html ← 你正在看的这一页
verify.py 回归:三形态各 70 + 跨形态 20 + 后台 37 + 8 角色 28 + UI 24 + 状态 38,共 357 条
audit.py UI 体检:溢出 / 文字裁切 / 点击区过小 / 正文对比度,22 屏一次扫完
audit_steps.py UI 体检(客户端):13 步逐页扫,三端各跑一遍
guard.py 红线:四条反向测试,11 条断言 —— 找不到违规证据才算拦住
// 归档(不是交付物)
_legacy/ V3 三角色版、H5 网页兜底版 —— 留着对照,不对外| 层 | 选媒逻辑 | 本次选的 | 小计 |
|---|---|---|---|
| A 行业垂媒 | 名称含医疗健康词 + 全国 + GEO 可发,同站去重后按报价降序取 2 | 医药卫生网 ¥1,410 · 亲贝网健康 ¥422 | ¥1,832 |
| B 本地新闻源 | 省级=浙江 + GEO 可发,按报价降序取前 5(全省只有 8 个位) | 浙新网 ¥247 · 新蓝网 ¥163 · 浙江传媒网 ¥150 · 搜狐浙江号 ¥113 · 义乌网 ¥75 | ¥748 |
| C 权威门户 | 央媒/党媒关键词 + 报价 ≥600,取最贵 1 位 + 覆盖平台最多 1 位 | 中国日报网 ¥1,653 · 新华报业网 ¥938 | ¥2,591 |
| D 全国中坚 | 全国 + 300≤报价<800,按覆盖 AI 平台数降序、同覆盖取低价,取 4 位 | 中国教育在线 · 凤凰网科技 · 医联媒体 · 新尧网 | ¥1,921 |
| 自有账号内容资产 30 条 × ¥40(不是媒体采购,是内容生产) | ¥1,200 | ||
| 附件一合计(13 位媒体 ¥7,092 + 自有内容 ¥1,200) | ¥8,292 | ||
python select_media.py
—— 排除软文包 / 港澳台 / 同站重复各多少条,脚本都会打印出来。
verify.py 跑出来的。


| 屏 | 只回答一个问题 | 不做会怎样 |
|---|---|---|
| 工作台 | 今天哪几件不做会赔钱 | 六件事平铺成六十件,人就会只做最吵的那件 |
| 客户与项目 | 这一单卡在哪一步 | 合同价款和附件一并成一列,谁都会顺手加成一个数 |
| 知识库审核 | 这条能不能进报告 | 口述数字写进报告 = 虚假宣传,举证责任在平台 |
| 采集与引擎 | 哪个源坏了、哪个引擎偏了 | 用一个偏差 31% 的引擎出报告,4 引擎会悄悄变成 3 引擎 |
| 媒体库存 | 这 13 位锁给谁了、什么时候释放 | 位锁着收不到钱,别人也买不走 —— 待回款是库存损耗,不是财务问题 |
| 工单台 | 哪个工单今天到期 | SLA 按天算,所有工单都在最后两小时一起爆 |
| 链接与回收 | 8 条链接还剩几条活着 | N 行 = N 链接的验收对不上,客户有理由扣这一行的钱 |
| 报价单生成 | 每一行该进哪张纸 | 媒体钱混进合同价款,签的是一个客户没同意过的数 |
| 合同与收款 | 两笔钱各自收到哪 | 一笔钱算两次,或者两笔钱合成一笔催 |
| 效果报告 | 这个数敢不敢写进合同 | 假设不披露,Ci 不写,客户拿 4 引擎的截图来对,对不上 |
| 红线拦截日志 | 四条红线今天真被触发过吗 | 没被触发过的红线,只是写在 PPT 上的四句话 |
| 配置与渠道 | 什么能改、什么改不了 | N 类规则能被关掉,前面 11 屏全部作废 |
verify.py 里新增 37 条后台断言:12 屏都渲染得出来、金额与三端同源(598 / 8,292 / 8,890 一分不差)、
N 类行里查不到 data-do="pass"、拖拽后合同区金额不变、条款区不出现 8,890、冻结引擎不进合成、13 位库存合计 7,092。
全过才算交付 —— 后台最怕的不是难看,是点下去没反应还没人发现。
最容易做错的是「给别的角色做同一套页面、少给几个按钮」。那不是角色,那是同一份原型的阉割版。 真正的差异在信息层级:同一笔媒体款,交付看到「位锁着,占库存」,媒体看到「空转 4 天,亏 ¥84」, 商务看到「客户还差 ¥6,180 没付」,管理者看到「应收 33,872、实收 9,488」。四个数字都对,问的不是同一件事。



「看着还行」不是标准。这轮把四件能判定对错的事交给脚本去量:元素有没有超出容器、 文字有没有被裁掉半截、点击目标够不够大、正文对比度够不够。前两样肉眼勉强能看出来, 后两样截图上看不出来 —— 3.2:1 的灰和 4.6:1 的灰,在截图里都是「淡淡的灰」, 但一个在手机户外屏上根本读不出来。
| 项 | 标准 | 修前 | 修后 | 怎么处理的 |
|---|---|---|---|---|
| 横向溢出 | 不出现横向滚动 | 0 | 0 | 本来就过。1024 / 1280 / 1440 / 1920 四个宽度都测了 |
| 文字被裁切 | 不出现省略号以外的截断 | 0 | 0 | 本来就过 |
| 点击目标 | ≥ 30 × 22 px | 55 | 0 | 行内按钮只有 16px 高(看原文 / 溯源 / 移出 / 不要这行)。用 padding 撑到 26px 再负 margin 抵掉,行高不变 |
| 正文对比度 | ≥ 4.5 : 1 | 243 | 0 | 次要文字色 #5F6B7C 在浅底上只有 3.2:1;状态色(绿 / 橙 / 红)当 11px 文字用也不够。统一按「在自身浅底上仍 ≥4.5」重定 |
| 键盘焦点 | Tab 走一圈能看见在哪 | 0 处 | 全覆盖 | 全站只有输入框有 focus 样式,按钮和导航项一个都没有 —— 补 :focus-visible,深色底换白框 |
verify.py 常驻守着。对比度、点击区、溢出,改一次配色或加一个功能就可能坏。坏了以后页面照样能跑、截图照样好看, 只有真正用的人会觉得「字有点糊」「这个按钮怎么老点不中」。
所以 verify.py 里加了 24 条:6 个代表页 × 4 项,全 0 才算过。
少了这一层,前面跑过的脚本结论就只是「那一次是对的」。
换色是全局变量,一处改全站变,抽样足够抓回归。全量 61 页跑一次要十几分钟,塞进每次回归没人愿意等,最后一定被跳过。
正文里「在 APP 上看…」这类链接,高度被行高约束,放大就破坏排版。WCAG 2.5.8 对此有明确豁免,脚本里也按这条判据跳过 —— 不是为了让数字好看而改判据。
① 报价页有一行「本单合计(仅供参考)¥8,890」。合同页却写着「任何条款不含 8,890」。 同一个系统里同一个数,一页能出现一页不能,客户一定问。改成「两笔相加 · 不设这个数」并说明为什么不设 —— 「仅供参考」四个字在报价单上没有效力,反而暗示「可以这么算」。
② PC 窄屏(≤1280)右栏直接 display:none。金额和进度就这么没了,客户找不到自己要付多少钱。 改成底部一条横向数字带,只留最关键的两块。
③ 底部一条 38px 深色演示通栏压在客户界面上。会被当成产品功能。它其实和左导航底部那组是同一套入口 —— 删掉重复的,把「总览」并进左导航。演示控件不该占产品的位置。
三处都不是「不好看」,是两处说法打架。这类问题改配色改不掉,只能把其中一个删掉或改对。
上一轮量的是「看得见的东西正不正」。这一轮补的是另一层:没有数据的时候,这一屏长什么样。 体检时数了一遍,全站 61 个页面里只有 1 处真正的空态 —— 其余要么是空白的白卡片,要么干脆什么都不渲染。 这不是忘了做,是「先做有数据的样子」更容易:数据一多截图就好看,评审就容易过。 但真上线第一天,运营面对的全是空的。
这一层只有一个核心判断: 四种「空」长得一模一样,用户下一步却完全不同。把它们混成一句「暂无数据」, 是这个系统里最贵的一种界面错误。
| 类型 | 用户的真实处境 | 界面第一句必须说什么 | 下一步 |
|---|---|---|---|
| 真空 | 确实还没有,而且这很正常 | 「还没有 X」+ 怎么产生第一条 | 去创建 / 去接 |
| 筛空 | 数据有,被条件筛没了 | 「这 3 个条件没有同时满足的」+ 回显条件 | 清空 / 放宽哪一条 |
| 挡空 | 有数据,这个账号看不到 | 「有,只是你看不到」+ 找谁能开 | 找人 / 申请 |
| 错空 | 压根没拿到数据 | 「没取到,不是没有」+ 错误码 | 重试 / 用缓存 |
最贵的是把 错空 画成 真空: 媒体接口超时、库存显示为空 → 交付以为没位可投 → 跟客户说做不了 → 客户走了。 真实情况只是接口挂了 6 秒。




写一份《空态设计规范》放进 Wiki,第 3 个页面就会开始偷懒,第 8 个页面就开始复制第 3 个页面的「暂无数据」去糊接口报错。 文档没人回看,代码天天被复制。
所以规范做成了一道门(state.js):五个出口各带必填项,缺了不报错、不静默,而是
照常渲染 + 挂一条红字。原型阶段红着,评审一眼看见;正式实现换成 lint 拦截,这块根本渲染不出来。
empty 要下一步动作 · nofilter 要条件回显 · denied 要「找谁」 ·
broken 要错误码,且标题里敢写「暂无数据」直接报错 · allClear 要一句「这是正常的」。
做到工单台才发现:看板某列没积压、审核队列清零,那是好事,硬塞一个按钮(哪怕叫「刷新」)等于在暗示出问题了。
于是加了 allClear,必填的不是动作,而是一句确认。规范做成代码,会在第 6 个页面撞出第 1 个页面没想到的情况 —— 这是好事。
接口挂了 KPI 还挂着上一次的 8,空态说「还没有工单」而上面说「8 个」—— 这比不给空态更让人困惑,因为用户拿到两个互相矛盾的结论。
所以数据态一开,这一页所有数字同时变「—」或 0,副标题也跟着改口: 「工单数据没取到 —— 下面的数字和看板都不作数,先别拿它安排人」。
拦截日志切到空态时,下方「哪几条规则最常响」整块隐藏 —— 留着它,就是一边说「今天一次都没拦」一边画着 2 次、1 次的柱子。
以前 chatSend 在任何输入下都直接进流程 —— 客户打「你好」,系统当成企业名建了个项目,
他在第三步看到一个叫「你好」的公司。这不是难看,是错的。
现在接不住的输入不推进流程,回一句「这句我没接住 —— 不是没这个能力,是没听出你要什么」, 并保留首页那三个例子让他照着改一句。这就是对话里的「筛空」。 三端共用同一段代码,改一处三端同时生效。
node 层(22 条):把 state.js 当纯函数跑,验证五个出口的必填校验、五种空各有独立类名、
broken 拒绝「暂无数据」、retryable:false 时真的不给重试按钮。
浏览器层(16 条):8 个真实页面逐条查「这块真的渲染出来了吗」,外加一条反向断言 —— 全站不许再出现「暂无数据」这四个字。
只测 node 会漏掉「组件对但页面没接」(这一轮真踩了两次);只测浏览器会漏掉「校验没生效」—— 红字只在缺参时出现,正常页面根本看不出来。
input 事件:发送键点亮、回车能发。不是「填进去就完了」。补缺口时点「说一句」,麦克风问的就是那一项。
verify.py 跑出来的:三端分别渲染第 8 / 10 / 11 / 12 页,比对金额、行数、文案、语音落字,不等就报错。
平台交付的是内容资产:账号定位、栏目规划、选题库、封面模板、正文、发布日历。号是客户的,客户用自己的号发。
不代注册、不代养号、不做矩阵。批量注册 + 矩阵发布是 T/CAPT 026—2026 明令禁止的行为,也是 3·15 点名的那类做法。宁可少卖,不做。
① 价目表里没有这一项 —— 销售想卖也开不出单;② 合同模板加《服务范围排除》条款,列明不含账号注册、代运营、矩阵发布;③ 对外材料(方案、话术、报价单)过一遍关键词扫描,命中「代注册 / 代养号 / 矩阵 / 多账号」即拦。
「帮我们多发几个号」会顺着口子全进来,最后卖出去的东西就从内容服务变成账号代运营 —— 那是另一门生意,也是另一种风险。
词池 40 = 干预 32 + 留出 8。留出组在合同里约定不优化,期满免费补做一轮 —— 这是 DiD 唯一能成立的前提。
不写补偿,签约现场一定被砍;不写留出组,第 8 周那个 +6.4pp 就永远无法证明是你干的。
① 合同生成器做必备条款校验:缺留出组条款,合同不出单(跟「没签名不能下一步」同一套机制);② 词表在 P1 冻结时就切出 32 + 8,留出 8 个进附件二、由客户签字;③ 期满补做做成自动工单,到期生成,不靠人记。
效果有了也说不清是谁做的。续费桌上拿不出「不优化就不涨」的对照数,第二年只能靠关系续。
原方案按 行业 35 + 地域 25 + DA 15 + 收录率 15 + 价格 10 加权打分,取 Top 12。查了真实库存才知道走不通:246,293 条里 GEO 可发只有 1,408 条(0.57%),浙江全省 8 条 —— 分到省只剩个位数,归一化后分数差全是噪声。而且 DA / 收录率这类字段库里根本没有,那两个权重是拍出来的。
现在改成四层分层:A 行业垂媒 / B 本地新闻源 / C 权威门户 / D 全国中坚,每层带硬排除(软文包、港澳台、同站重复、「其他」兜底标签)。客户的动作是「换掉不满意的两个」,本来也不需要从十万条里挑。
① 四层规则与排除词写成配置文件,换行业改配置、不改代码;② 报价单生成器硬校验:任一媒体行缺 RWY- 编码或数据来源,禁止出单;③ 下单前过网关复核,索引价与网关价不一致以网关为准,差异留痕。
13 个媒体位就是编的。客户拿编码去媒介优选一查,第一位就对不上。
合同价款只写技术服务费 ¥598(299/月 × 2 月)。媒体 ¥8,292 在《附件一 信源媒体分发清单》,单独结算,不构成合同价款、不作为生效条件。原来写「清单就是合同标的」是错的 —— 媒体款进合同,发票、税目、违约口径全都不对。
但清单照样可核算:每行有编码、单价、数量、合计;客户删一行,附件金额当场变,清单版本号跟着变。三处(清单 / 附件 / 收款)必须同一个数,系统自动校验。
① 合同模板里根本没有「媒体金额」这个字段 —— 不是校验不通过,是填不进去;② 附件一独立版本号,客户删行只重出附件、合同价款一分不动;③ 收款两个订单号,P2/P3 工单要等媒体款到账才解锁(原型已实现)。
媒体款混进合同,客户只付技术服务费时违约条款算不清;改成打包价、清单只当附件,付款当场就吵。
SFSpeechRecognizer,Android 走 SpeechRecognizer(无 GMS 机型会不可用),小程序只能录音上传或同声传译插件。三端真机都没测过 —— 噪声、方言、权限被拒这三件事一件都还没碰。select_media.py 里的画像和关键词,重跑一次;不是改几句文案。python verify.py(319 条断言,含后台 37 + 8 角色 28 + UI 24)· 红线脚本:python guard.py(11 条)· UI 体检:python audit.py / audit_steps.py· 旧版(V3 三角色、H5 网页兜底)在 _legacy/,不对外。