Files
danyang-match/miniapp/app.wxss
T
WorkBuddy 2c325136ab fix(miniapp): 修复 WXSS 通配符导致编译失败,并把 WXSS 校验升级为真编译
问题
- v2 设计收敛在 app.wxss 新增 `.tap-gap > * + *`。WXSS 不支持通配符 `*`,
  wcc/wcsc 编译期直接报 `error at token '*'`,整个小程序编译不过。
- 更值得警惕的是当时 19 个 UI 契约守卫全绿:静态守卫刻意只扫
  `miniapp/components/`(page 的 wxss 允许标签选择器,套用会误报),
  app.wxss 落在扫描范围外,且判据里根本没有通配符这一项。

修复
- 移除 `.tap-gap > * + *`。复检 v2 新增的 .tap-min / .tap-gap / .focusable / .pressable
  四个工具类全仓零引用(等价能力已各自写在组件样式内,如 button/index.wxss 直接用
  `min-height: var(--tap-min)`),且 `.focusable:focus` / `.pressable:active` 在 view 上
  不生效(小程序须用 hover-class),属"写了等于没写",故整块移除,只保留 token。
- 修掉同块注释里的嵌套 `/* */`:内层结束标记会提前终止外层注释,其后的文字会被
  当成真实样式解析。该约束已写进注释,防止后人再踩。

守卫加固
- scripts/lib/wxss-selectors.mjs
  * 新增 findUniversalSelectors(通配符判据,排除 `*=` 属性匹配符与注释内星号)
  * 新增 findNestedComments(注释嵌套判据)
  * 修 stripComments:原实现用等长空格替换,把注释内的换行也吃掉,导致报错行号
    整体偏移(实测偏差 68 行);现保留换行,并在定位时跳过选择器前的注释占位空白
- ui-tests/wxss-component-selector-contracts.mjs
  * 第 1 级:miniapp/components/(原判据,保留)
  * 第 2 级(新增):全量 miniapp/**/*.wxss,覆盖 app.wxss 与 pages
  * 第 3 级(新增):注释嵌套
  * 三级各自带正/反自检样例

新增
- scripts/miniapp-compile-check.mjs:直接调用微信开发者工具内置的 wcc/wcsc 做真编译预检
  (wcsc 单独调用不解析 @import 相对路径,脚本会先内联再编译)。
  CI runner 无该工具时输出 SKIPPED 并退出 0;刻意不挂进 run-ui-contracts —— 一个在 CI 里
  什么都不做的守卫被计入"全绿",正是本项目反复强调的假绿。CI 拦截由上面的静态守卫承担。

验证(均实际执行)
- node scripts/miniapp-compile-check.mjs → WXSS 88 个 + WXML 86 个,0 失败
- 负向注入:把 `.tap-gap > * + *` 改回去 → 预检 FAIL(exit 1),报错与用户原始报错逐字一致
  `app.wxss(364:12): error at token '*'`;已还原并复测通过
- node scripts/run-ui-contracts.mjs → 20 / 20 PASS
2026-09-23 08:25:11 +08:00

363 lines
16 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 丹阳公益婚恋服务平台 - 全局 Design Token
*
* v2(2026-09-22 设计系统夯实)
* ---------------------------------------------------------------
* 本次修订针对审计发现的系统性问题,逐条给出依据:
*
* 1) 品牌色不能承载文字 → 引入「基础色 / -strong」分级
* #E96B6B on 白 = 3.10:1,白字 on #E96B6B = 3.10:1,on #FFF3F2 = 2.85:1,
* 三条路径都不满足正文 4.5:1。故:
* --color-primary 仅用于大色块、渐变、光晕、装饰(非文字)
* --color-primary-strong 用于文字、小图标、纯文字链接(5.33:1 ✓)
* --color-primary-solid 实心按钮底(白字 ≥4.5:1 ✓)
* 功能色(success/warning/danger/info)同此分级。
*
* 2) 次要文字对比度过低
* 旧 --color-text-tertiary(#777) = 4.48(on 白,勉强)/ #999 实测 2.85 却被用了 71 次。
* 现将三级文字压到 #646C85 = 5.22:1,占位符保留浅色(占位符非正文)。
*
* 3) 颜色碎片化
* 审计前全站 182 种 hex、770 次硬编码(54 种灰、60 种蓝紫、40 种红橙)。
* 本文件是唯一的颜色事实源;页面层禁止再写 hex,一律 var(--token)。
*
* 4) 触摸目标
* 组件库默认按钮只有 40px、小号 28px、"喜欢"按钮 31px。
* 统一以 --tap-min(88rpx=44px) 为下限,--tap-gap 为最小间距。
*
* 5) 动效几乎未落地(--motion-* 全站仅 5 次引用)
* 补充 --ease-spring 与 --press-scale,让点击有即时反馈。
*
* 6) 夜间氛围层缺失(参考图有明显的光晕/星点,代码只有纯渐变)
* 新增 --mini-glow-* / --mini-star / --mini-glass / --mini-hairline。
*
* 单位说明:rpx(750rpx = 屏幕宽度,1rpx ≈ 0.5px @375pt)
* 旧 token 名全部保留(88 个 wxss 共 909 处引用),只更新取值,避免大范围失效。
*/
page {
/* ============ 品牌主色(分级:色块 / 文字 / 实心按钮) ============ */
--color-primary: #E96B6B; /* 基础色:渐变、光晕、装饰、非文字图形 */
--color-primary-strong: #B8434A; /* 文字与小图标(5.33:1 on 白,4.91:1 on 浅粉底) */
--color-primary-solid: #C24C50; /* 实心按钮底(白字 4.62:1) */
--color-primary-active: #D95F5F; /* 按压态(配合 --color-primary 使用) */
--color-primary-light: #FFF3F2;
--color-primary-lighter: #FFF8F7;
/* ============ 小程序参考图品牌主题(夜间紫蓝) ============ */
--mini-night-start: #172A72;
--mini-night-end: #25104F;
--mini-night-soft: #3445A1;
--mini-accent-pink: #FF4F91; /* 点缀色:仅用于色块/装饰,不作正文 */
--mini-accent-coral: #FF6A78;
--mini-card-bg: rgba(255, 255, 255, 0.98);
--mini-text: #1F2744;
--mini-muted: #7D839B;
--mini-card-radius: 24rpx;
--mini-text-on-night: #FFFFFF; /* on 深底 13.04:1 ✓ */
--mini-text-on-night-muted: rgba(255, 255, 255, 0.72); /* on 深底 ≈8:1 ✓ */
--mini-shadow: 0 16rpx 40rpx rgba(28, 30, 88, 0.14);
/* ---- 渐变(集中定义:此前 4 个页面各写了一份「橙红渐变」按钮) ---- */
/* --gradient-brand 明亮活泼,但白字仅 2.77:1 —— 只能用于无文字的装饰填充。 */
--gradient-brand: linear-gradient(135deg, #FF6A78 0%, #E96B6B 100%);
/* 承载白字的 CTA 渐变:两端 #C24C50(4.73:1) / #B8434A(5.33:1) 均达标。 */
--gradient-brand-solid: linear-gradient(135deg, #C24C50 0%, #B8434A 100%);
--gradient-night: linear-gradient(160deg, #172A72 0%, #25104F 100%);
--gradient-night-soft: linear-gradient(160deg, #3445A1 0%, #172A72 100%);
/* ---- 夜间氛围层(v2 新增;参考图有光晕与星点,此前完全没有) ---- */
--mini-glow-coral: rgba(255, 79, 145, 0.40);
--mini-glow-indigo: rgba(94, 124, 255, 0.32);
--mini-glow-soft: rgba(255, 255, 255, 0.10);
--mini-star: rgba(255, 255, 255, 0.70);
--mini-glass: rgba(255, 255, 255, 0.10); /* 深底玻璃面 */
--mini-glass-strong: rgba(255, 255, 255, 0.16);
--mini-hairline: rgba(255, 255, 255, 0.16); /* 深底分隔线 */
--mini-glass-success: rgba(90, 226, 184, 0.16); /* 深底上的成功态玻璃面 */
/* 星点层:多个小径向渐变叠在夜空渐变之上。用 radial-gradient 而非图片,
避免额外请求,也便于随主题换色(index 页脚注:装饰层不承载信息)。 */
--mini-starfield:
radial-gradient(circle at 12% 18%, var(--mini-star) 0, transparent 2.5rpx),
radial-gradient(circle at 31% 66%, var(--mini-star) 0, transparent 2rpx),
radial-gradient(circle at 57% 12%, var(--mini-star) 0, transparent 2rpx),
radial-gradient(circle at 79% 52%, var(--mini-star) 0, transparent 3rpx),
radial-gradient(circle at 93% 84%, var(--mini-star) 0, transparent 2rpx);
/* 横幅压暗层:保证任何底图上的白色标题都有足够对比度 */
--mini-banner-scrim: linear-gradient(90deg, rgba(23, 42, 114, 0.84) 0%, rgba(37, 16, 79, 0.40) 56%, rgba(255, 79, 145, 0.12) 100%);
/* ============ 功能色(同样分级) ============ */
--color-success: #2E9B68; /* 色块/图标 */
--color-success-strong: #17805A; /* 文字(4.92:1 ✓) */
--color-success-light: #ECF8F2;
--color-warning: #E59A32; /* 色块/图标(作文字仅 2.33:1) */
--color-warning-strong: #9C5F14; /* 文字(5.17:1 ✓) */
--color-warning-light: #FFF7E9;
--color-danger: #D94A4A; /* 色块/图标 */
--color-danger-strong: #C62828; /* 文字(5.62:1 ✓) */
--color-danger-light: #FFF0F0;
--color-info: #4A7CD4; /* 色块/图标 */
--color-info-strong: #2A5DB0; /* 文字(6.38:1 ✓) */
--color-info-light: #EEF4FF;
/* ============ 强调紫(参考图的标签语言;此前散落成 6 种写法) ============ */
--color-accent-purple: #8B63BC; /* 色块/装饰 */
--color-accent-purple-strong: #7C60BC; /* 文字(5.03:1 ✓) */
--color-accent-purple-light: #F3EEFF;
/* 深色夜空底上的强调色(不可用于浅色底) */
--color-success-on-night: #A6F5D7; /* on #172A72 = 10.35:1 ✓ */
/* ============ 文字色(实测对比度 on 白 / on 页面底) ============ */
--color-text-strong: #1A1F36; /* 标题 16.24 / 14.90 ✓ */
--color-text-primary: #2A3050; /* 正文 12.83 / 11.77 ✓ */
--color-text-secondary: #4A5169; /* 次要正文 7.86 / 7.21 ✓ */
--color-text-tertiary: #646C85; /* 说明文字 5.22 / 4.79 ✓(旧 #777 为 4.48) */
--color-text-placeholder: #9AA1B4; /* 占位符(非正文,可浅) */
--color-text-disabled: #B8BDC9;
--color-text-inverse: #FFFFFF; /* 深色背景上的文字 */
/* ============ 边框与分割线 ============ */
--color-border: #E6E8EF;
--color-border-strong: #D8DBE5;
--color-divider: #EFF0F5;
--color-border-focus: #B8434A; /* 焦点环用 strong,保证 3:1 可见度 */
--color-border-error: #C62828;
/* ============ 背景(避免纯白/纯灰平铺,页面底为暖白) ============ */
--color-bg-page: #F6F5F3;
--color-bg-card: #FFFFFF;
--color-bg-subtle: #F5F6FA; /* 次级区块、内嵌列表 */
--color-bg-muted: #EEF0F6; /* 更弱的填充 */
--color-bg-input: #F7F8FB; /* 表单输入框底(此前被引用但从未定义,导致背景失效) */
--color-bg-disabled: #F3F4F7;
--color-bg-overlay: rgba(0, 0, 0, 0.45);
/* ============ 字体 ============ */
/* 小程序无法可靠加载 Web Font(wx.loadFontFace 依赖 HTTPS 白名单且有闪动风险),
故使用中文系统字栈:iOS 走苹方、Android 走思源/系统黑体。
排版辨识度靠字号阶梯与字重建立,而非依赖字体本身。 */
--font-family-cn: -apple-system, "PingFang SC", "Noto Sans SC", "Hiragino Sans GB", "Microsoft YaHei", system-ui, sans-serif;
--font-family-number: "SF Pro Display", -apple-system, "DIN Alternate", "PingFang SC", sans-serif;
/* 字号阶梯 —— 正文 32rpx(16px) 起,元信息下限 24rpx(12px) */
--font-size-micro: 22rpx; /* 11px 仅角标/装饰,禁用于正文 */
--font-size-xs: 24rpx; /* 12px 元信息下限 */
--font-size-sm: 26rpx; /* 13px 次要说明 */
--font-size-md: 28rpx; /* 14px 次要正文 */
--font-size-base: 32rpx; /* 16px 正文 */
--font-size-lg: 36rpx; /* 18px 小标题 */
--font-size-xl: 40rpx; /* 20px 区块标题 */
--font-size-2xl: 48rpx; /* 24px 页面标题 */
--font-size-3xl: 56rpx; /* 28px 大标题 */
--font-size-display: 64rpx; /* 32px Hero */
/* 字重:只用 4 档。禁止 550/650/750 等非百位值 —— Android 无对应字重会回退,
导致同一设计在不同机型上粗细不一致(审计发现 30 处)。 */
--font-weight-regular: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
--line-height-tight: 1.25;
--line-height-normal: 1.5;
--line-height-relaxed: 1.65;
--letter-spacing-tight: 0;
--letter-spacing-wide: 1rpx;
--letter-spacing-wider: 2rpx;
/* ============ 间距(4px 栅格) ============ */
--spacing-1: 8rpx;
--spacing-2: 16rpx;
--spacing-3: 24rpx;
--spacing-4: 32rpx;
--spacing-5: 40rpx;
--spacing-6: 48rpx;
--spacing-8: 64rpx;
--spacing-10: 80rpx;
--spacing-12: 96rpx;
--spacing-page-x: 32rpx;
--spacing-section: 48rpx;
--spacing-card-inner: 32rpx;
--spacing-tight: 16rpx;
/* ============ 圆角 ============ */
--radius-sm: 16rpx;
--radius-md: 24rpx;
--radius-lg: 32rpx;
--radius-xl: 40rpx;
--radius-2xl: 48rpx;
--radius-full: 9999rpx;
/* ============ 阴影(统一为一套,此前 30+ 种近似值) ============ */
--shadow-card: 0 8rpx 28rpx rgba(35, 35, 95, 0.07);
--shadow-raised: 0 12rpx 36rpx rgba(35, 35, 95, 0.10);
--shadow-popup: 0 16rpx 64rpx rgba(0, 0, 0, 0.10);
--shadow-night: 0 16rpx 40rpx rgba(28, 30, 88, 0.18);
/* ============ 边框宽度 ============ */
--border-width: 1rpx;
--border-width-strong: 2rpx;
/* ============ 触摸目标(无障碍下限 44×44px) ============ */
--tap-min: 88rpx; /* 44px 最小可点区域 */
--tap-comfortable: 96rpx; /* 48px 舒适尺寸:主操作 */
--tap-gap: 16rpx; /* 8px 相邻目标最小间距 */
/* ============ 动效 ============ */
--motion-fast: 120ms;
--motion-normal: 200ms;
--motion-slow: 300ms;
--ease-standard: cubic-bezier(0.32, 0.72, 0, 1);
--ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1);
--press-scale: 0.97;
--press-opacity: 0.88;
/* ============ 层级 z-index ============ */
--z-base: 1;
--z-sticky: 100;
--z-tabbar: 200;
--z-dropdown: 500;
--z-modal: 1000;
--z-toast: 2000;
--z-ambient: 0;
/* ============ 页面基础样式 ============ */
background-color: var(--color-bg-page);
font-family: var(--font-family-cn);
font-size: var(--font-size-base);
color: var(--color-text-primary);
line-height: var(--line-height-normal);
}
/* ==========================================================================
通用工具类
========================================================================== */
.container {
padding: var(--spacing-page-x);
}
.flex { display: flex; }
.flex-column { display: flex; flex-direction: column; }
.flex-center { display: flex; align-items: center; justify-content: center; }
.flex-between { display: flex; align-items: center; justify-content: space-between; }
.flex-start { display: flex; align-items: center; justify-content: flex-start; }
.flex-1 { flex: 1; min-width: 0; }
.flex-shrink-0 { flex-shrink: 0; }
.wrap { flex-wrap: wrap; }
/* 4 列宫格:用 flex + 百分比宽度实现。
注:display:grid 在 WebView 渲染下可用,但 Skyline 不支持,故统一改为 flex 以免疫渲染引擎迁移。*/
.grid-4 {
display: flex;
flex-wrap: wrap;
}
.grid-4 > .grid-item {
width: 25%;
box-sizing: border-box;
}
.text-strong { color: var(--color-text-strong); }
.text-primary { color: var(--color-text-primary); }
.text-secondary { color: var(--color-text-secondary); }
.text-tertiary { color: var(--color-text-tertiary); }
.text-placeholder { color: var(--color-text-placeholder); }
.text-white { color: var(--color-text-inverse); }
.text-brand { color: var(--color-primary-strong); }
/* 品牌色作文字只能用 -strong 变体:基础色 #E96B6B 在白底仅 3.10:1。
保留 .text-brand-soft 这个类名是为了不破坏既有引用,取值已统一到 -strong,
如需「更亮的品牌色」请用于背景/图形而非文字。 */
.text-brand-soft { color: var(--color-primary-strong); }
.text-success { color: var(--color-success-strong); }
.text-warning { color: var(--color-warning-strong); }
.text-danger { color: var(--color-danger-strong); }
.text-info { color: var(--color-info-strong); }
.font-micro { font-size: var(--font-size-micro); }
.font-xs { font-size: var(--font-size-xs); }
.font-sm { font-size: var(--font-size-sm); }
.font-md { font-size: var(--font-size-md); }
.font-base { font-size: var(--font-size-base); }
.font-lg { font-size: var(--font-size-lg); }
.font-xl { font-size: var(--font-size-xl); }
.font-2xl { font-size: var(--font-size-2xl); }
.font-3xl { font-size: var(--font-size-3xl); }
.font-display { font-size: var(--font-size-display); }
.font-regular { font-weight: var(--font-weight-regular); }
.font-medium { font-weight: var(--font-weight-medium); }
.font-semibold { font-weight: var(--font-weight-semibold); }
.font-bold { font-weight: var(--font-weight-bold); }
.mt-1 { margin-top: var(--spacing-1); }
.mt-2 { margin-top: var(--spacing-2); }
.mt-3 { margin-top: var(--spacing-3); }
.mt-4 { margin-top: var(--spacing-4); }
.mt-5 { margin-top: var(--spacing-5); }
.mt-6 { margin-top: var(--spacing-6); }
.mb-1 { margin-bottom: var(--spacing-1); }
.mb-2 { margin-bottom: var(--spacing-2); }
.mb-3 { margin-bottom: var(--spacing-3); }
.mb-4 { margin-bottom: var(--spacing-4); }
.mb-5 { margin-bottom: var(--spacing-5); }
.mb-6 { margin-bottom: var(--spacing-6); }
/* 文本省略 */
.ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.ellipsis-2 {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
/* 安全区底部留白(避开 Home Indicator) */
.safe-area-bottom {
padding-bottom: env(safe-area-inset-bottom);
}
/* 页面卡片基础样式 */
.card-base {
background: var(--color-bg-card);
border-radius: var(--radius-lg);
padding: var(--spacing-card-inner);
box-shadow: var(--shadow-card);
}
/* 分割线 */
.divider {
height: var(--border-width);
background: var(--color-divider);
}
/* ==========================================================================
可访问性:只用 token,不用工具类(2026-09-23 修订)
--------------------------------------------------------------------------
v2 曾在此处新增 .tap-min / .tap-gap / .focusable / .pressable 四个工具类。
复检发现全部零引用(等价能力已各自写在组件样式内,例如 button/index.wxss
直接使用 min-height:var(--tap-min)),且存在两个硬问题:
1) `.tap-gap > * + *` 用了通配符与兄弟选择器 —— WXSS 不支持,
wcc 编译期直接报 `error at token '*'`,导致整个小程序编译失败。
(这是编译错误,不是静默失效,代价最高。)
2) `.pressable:active` / `.focusable:focus-visible` 的伪类在 view 上
不生效(小程序须用 hover-class),属于"写了等于没写"。
故整块移除,保留 token。需要热区/间距时直接引用 token:
min-height: var(--tap-min); → 88rpx = 44px,最小可点区域
gap: var(--tap-gap); → 16rpx = 8px,相邻目标间距(flex gap)
【WXSS 禁用清单】星号通配符、兄弟选择器、属性选择器。
【注释约束】WXSS 注释**不可嵌套**:内层注释的结束标记会提前终止外层注释,
其后的文字会被当成真实样式解析,导致编译失败或样式被污染。
(2026-09-23 实证:本块注释初版即踩此坑。)
========================================================================== */