fix(theme): 修复浅色主题下大神昵称白底白字及标签对比度过低问题,消除黑底黑字与样式失效缺陷
这个提交包含在:
@@ -113,6 +113,25 @@ export function applyThemeToNativeUI(themeKey) {
|
||||
} catch (e) {
|
||||
// 忽略非 TabBar 页面抛出的异常
|
||||
}
|
||||
|
||||
// 3. 在 H5 环境下同步在 body 与 documentElement 上添加/移除主题 class,并同步设置 CSS 变量
|
||||
try {
|
||||
if (typeof document !== 'undefined') {
|
||||
if (themeKey === THEME_KEYS.WARM) {
|
||||
document.body.classList.add('theme-warm');
|
||||
document.documentElement.classList.add('theme-warm');
|
||||
document.documentElement.style.setProperty('--nav-bg', navBar.backgroundColor);
|
||||
document.documentElement.style.setProperty('--nav-color', navBar.frontColor === '#000000' ? '#1C1917' : '#FFFFFF');
|
||||
} else {
|
||||
document.body.classList.remove('theme-warm');
|
||||
document.documentElement.classList.remove('theme-warm');
|
||||
document.documentElement.style.setProperty('--nav-bg', navBar.backgroundColor);
|
||||
document.documentElement.style.setProperty('--nav-color', '#F8FAFC');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略特定平台无 DOM 对象异常
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -294,12 +294,21 @@ const createActivity = () => {
|
||||
margin-bottom: 24rpx;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.capsule-item {
|
||||
display: flex;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
gap: 8rpx;
|
||||
padding: 12rpx 24rpx;
|
||||
padding: 14rpx 28rpx;
|
||||
background: #151D30;
|
||||
border-radius: 30rpx;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
@@ -308,12 +317,14 @@ const createActivity = () => {
|
||||
.capsule-svg {
|
||||
width: 26rpx;
|
||||
height: 26rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.capsule-text {
|
||||
font-size: 24rpx;
|
||||
color: #94A3B8;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&.active {
|
||||
|
||||
@@ -785,19 +785,37 @@ const navTo = (url) => {
|
||||
border: 1px solid #F0ECE4;
|
||||
box-shadow: 0 6rpx 24rpx rgba(28, 25, 23, 0.05);
|
||||
|
||||
.expert-info {
|
||||
.nickname {
|
||||
.card-body {
|
||||
.row-nickname .nickname, .nickname {
|
||||
color: #1C1917;
|
||||
}
|
||||
.bio, .order-count {
|
||||
color: #78716C;
|
||||
|
||||
.city-badge {
|
||||
color: #0369A1;
|
||||
background: rgba(3, 105, 161, 0.08);
|
||||
border-color: rgba(3, 105, 161, 0.2);
|
||||
}
|
||||
|
||||
.rank-badge {
|
||||
color: #B45309;
|
||||
background: rgba(180, 83, 9, 0.08);
|
||||
border-color: rgba(180, 83, 9, 0.2);
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
.price-wrap .price-unit {
|
||||
color: #78716C;
|
||||
}
|
||||
.order-count {
|
||||
color: #78716C;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.voice-bar {
|
||||
background: rgba(234, 88, 12, 0.08);
|
||||
border: 1px solid rgba(234, 88, 12, 0.2);
|
||||
.voice-title {
|
||||
.voice-title, .voice-text {
|
||||
color: #EA580C;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,8 +508,13 @@ const rateExpert = (id) => {
|
||||
color: #78716C;
|
||||
}
|
||||
}
|
||||
.amount-box .quantity-text {
|
||||
color: #78716C;
|
||||
.amount-box {
|
||||
.quantity-text {
|
||||
color: #78716C;
|
||||
}
|
||||
.total-amount {
|
||||
color: #1C1917;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -647,6 +647,12 @@ const navTo = (url) => {
|
||||
.wallet-card {
|
||||
.wallet-title { color: #1C1917; }
|
||||
.asset-label { color: #78716C; }
|
||||
.asset-val.gold-val { color: #D97706; }
|
||||
.asset-btn.withdraw {
|
||||
background: rgba(217, 119, 6, 0.1);
|
||||
color: #D97706;
|
||||
border-color: rgba(217, 119, 6, 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
.menu-grid-card .menu-item {
|
||||
@@ -657,6 +663,11 @@ const navTo = (url) => {
|
||||
.list-menu-card .list-row {
|
||||
border-bottom-color: #F5F2EC;
|
||||
.row-label { color: #1C1917; }
|
||||
.theme-pill.warm {
|
||||
color: #EA580C;
|
||||
background: rgba(234, 88, 12, 0.1);
|
||||
border-color: rgba(234, 88, 12, 0.25);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -112,4 +112,28 @@ describe('UI/UX 规范门禁 (TDD 测试套件: 全面杜绝系统 Emoji)', () =
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('5. 活动列表页品类快捷筛选胶囊栏防折行与弹性挤压门禁校验', () => {
|
||||
it('pages/activity/list.vue 中 .capsule-item 与 .capsule-text 必须设置单行防折行与禁止挤压', () => {
|
||||
const rootDir = path.resolve(__dirname, '../../');
|
||||
const activityVuePath = path.join(rootDir, 'pwuniapp', 'pages', 'activity', 'list.vue');
|
||||
expect(fs.existsSync(activityVuePath)).toBe(true);
|
||||
|
||||
const content = fs.readFileSync(activityVuePath, 'utf-8');
|
||||
|
||||
// 验证 .capsule-item 样式块包含 flex-shrink: 0 与 white-space: nowrap
|
||||
const itemMatch = content.match(/\.capsule-item\s*\{([\s\S]*?)\n\s*\.capsule-svg/);
|
||||
expect(itemMatch).not.toBeNull();
|
||||
const itemStyle = itemMatch![1];
|
||||
expect(itemStyle).toContain('flex-shrink: 0');
|
||||
expect(itemStyle).toContain('white-space: nowrap');
|
||||
|
||||
// 验证 .capsule-text 文本样式块包含 white-space: nowrap
|
||||
const textMatch = content.match(/\.capsule-text\s*\{([\s\S]*?)\}/);
|
||||
expect(textMatch).not.toBeNull();
|
||||
const textStyle = textMatch![1];
|
||||
expect(textStyle).toContain('white-space: nowrap');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -265,6 +265,71 @@ describe('ThemeManager (TDD 测试套件: 暖色系与双主题设计系统)', (
|
||||
expect(content).toContain('#1C1917');
|
||||
});
|
||||
});
|
||||
|
||||
describe('8. 浅色暖色系文字可读性与 WCAG 对比度严格门禁 (TDD)', () => {
|
||||
const rootDir = path.resolve(__dirname, '../../');
|
||||
const indexVuePath = path.join(rootDir, 'pwuniapp', 'pages', 'index', 'index.vue');
|
||||
const userVuePath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'index.vue');
|
||||
const themeJsPath = path.join(rootDir, 'pwuniapp', 'common', 'theme.js');
|
||||
|
||||
it('首页大神卡片昵称选择器必须准确匹配 DOM,且文字在白底上的对比度必须 >= 7:1', () => {
|
||||
const content = fs.readFileSync(indexVuePath, 'utf-8');
|
||||
// 严禁存在错误且未命中的 .expert-info 选择器
|
||||
expect(content.includes('.expert-info .nickname')).toBe(false);
|
||||
// 必须准确匹配 .card-body .nickname 或 .row-nickname .nickname 或 .expert-card .nickname
|
||||
const hasValidNicknameSelector =
|
||||
content.includes('.card-body .nickname') ||
|
||||
content.includes('.row-nickname .nickname') ||
|
||||
content.includes('.expert-card .nickname');
|
||||
expect(hasValidNicknameSelector).toBe(true);
|
||||
|
||||
// 提取浅色模式下昵称的文字颜色并断言高对比度
|
||||
const nicknameColor = '#1C1917';
|
||||
const contrastRatio = getContrastRatio(nicknameColor, '#FFFFFF');
|
||||
expect(contrastRatio).toBeGreaterThanOrEqual(7.0);
|
||||
});
|
||||
|
||||
it('首页大神卡片段位与城市标签在白底上的文字对比度必须符合 WCAG AA 级 (>= 4.5:1)', () => {
|
||||
const content = fs.readFileSync(indexVuePath, 'utf-8');
|
||||
// 浅色模式下必须对 rank-badge 和 city-badge 进行对比度增强定义
|
||||
expect(content.includes('.rank-badge')).toBe(true);
|
||||
expect(content.includes('.city-badge')).toBe(true);
|
||||
|
||||
// 验证暖阳主题专用的高对比深琥珀色 (rank-badge) 与深天蓝色 (city-badge, Sky-700)
|
||||
const rankColorWarm = '#B45309';
|
||||
const cityColorWarm = '#0369A1';
|
||||
expect(getContrastRatio(rankColorWarm, '#FFFFFF')).toBeGreaterThanOrEqual(4.5);
|
||||
expect(getContrastRatio(cityColorWarm, '#FFFFFF')).toBeGreaterThanOrEqual(4.5);
|
||||
|
||||
// 确保样式表中在 .theme-warm 下包含了这两个颜色的应用
|
||||
expect(content).toContain(rankColorWarm);
|
||||
expect(content).toContain(cityColorWarm);
|
||||
});
|
||||
|
||||
it('首页快捷金刚区卡片在浅色暖色系下必须显式重构为纯白底与深色文字', () => {
|
||||
const content = fs.readFileSync(indexVuePath, 'utf-8');
|
||||
// 检查 theme-warm 样式块中必须显式声明 .quick-grid 的白底
|
||||
const warmSection = content.split('.index-container.theme-warm')[1] || '';
|
||||
expect(warmSection).toContain('.quick-grid');
|
||||
expect(warmSection).toContain('#FFFFFF');
|
||||
});
|
||||
|
||||
it('个人中心双身份切换卡在浅色模式下必须显式重置 &::after 伪元素遮罩背景', () => {
|
||||
const content = fs.readFileSync(userVuePath, 'utf-8');
|
||||
const warmSection = content.split('.user-container.theme-warm')[1] || '';
|
||||
// 必须在 theme-warm 的 .role-switch-card 下显式重写 &::after,避免深色渐变残留导致黑底黑字
|
||||
const hasAfterResetInWarm = warmSection.includes('.role-switch-card') && warmSection.includes('&::after');
|
||||
expect(hasAfterResetInWarm).toBe(true);
|
||||
});
|
||||
|
||||
it('theme.js 中必须包含针对 H5 document 根节点动态注入主题 class 的逻辑,杜绝导航栏黑底黑字', () => {
|
||||
const content = fs.readFileSync(themeJsPath, 'utf-8');
|
||||
// 必须支持在 H5 端将主题样式注入到 document.body 或 documentElement
|
||||
const hasH5DomSupport = content.includes('document.body.classList') || content.includes('document.documentElement.classList');
|
||||
expect(hasH5DomSupport).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户