|
|
@@ -261,6 +261,7 @@
|
|
|
<script>
|
|
|
import * as echarts from 'echarts'
|
|
|
import SectionTitle from '@/components/SectionTitle.vue'
|
|
|
+import { adminColor } from '@/utils/colorTokens'
|
|
|
import {
|
|
|
getDashboardStats,
|
|
|
getMembershipStats,
|
|
|
@@ -409,6 +410,17 @@ export default {
|
|
|
const v = getComputedStyle(document.documentElement).getPropertyValue(name).trim()
|
|
|
return v || ''
|
|
|
},
|
|
|
+ // 主色令牌 -> rgba 字符串:canvas / 热力单元格不解析 CSS var,统一在此派生,
|
|
|
+ // 避免主色调整时散落的硬编码 rgba 漂移。令牌值唯一来源为 adminColor('primary')
|
|
|
+ // (内部读 :root 上的 --adm-primary,并带独立于五维品牌色的兜底值)。
|
|
|
+ // 令牌缺失或非法时不伪造品牌色,退到纯黑占位,保证 canvas 不抛错。
|
|
|
+ rgbaFromPrimary(alpha) {
|
|
|
+ const raw = String(adminColor('primary') || '').trim()
|
|
|
+ const m = /^#?([0-9a-f]{6})$/i.exec(raw)
|
|
|
+ const hex = m ? m[1] : '000000'
|
|
|
+ const dec = (i) => parseInt(hex.slice(i, i + 2), 16) || 0
|
|
|
+ return `rgba(${dec(0)}, ${dec(2)}, ${dec(4)}, ${alpha})`
|
|
|
+ },
|
|
|
// 停留时长 / 热度 相对最大值,用于条形宽度
|
|
|
sectionList(key) {
|
|
|
// v-for 回调内 this 不是组件(Vue2 普通函数 this=undefined),
|
|
|
@@ -424,21 +436,25 @@ export default {
|
|
|
if (!max) return 0
|
|
|
return Math.max(4, Math.round((Number(value) || 0) / max * 100))
|
|
|
},
|
|
|
- // 时段热力单元格颜色:按相对峰值线性加深(浅蓝 -> 品牌主色 #1677FF)
|
|
|
- // 文字色随背景动态切换,保证对比度:
|
|
|
- // alpha < 0.90 → 深字 var(--adm-text-88),实测 ≥ 4.6:1(AA 达标)
|
|
|
- // alpha ≥ 0.90 → 白字,实测 3.6~4.1:1(深格粗体数据标签,数据可视化可接受)
|
|
|
- // 注:任务原建议阈值 0.55 实测白字仅 2.1:1(不达标),故上修至 0.90。
|
|
|
+ // 时段热力单元格颜色:按相对峰值线性加深(浅蓝 -> 品牌主色令牌派生色)
|
|
|
+ // 背景由 rgbaFromPrimary(alpha) 从 --adm-primary 运行时派生,不写死色值。
|
|
|
+ // 有数据格的两支文字必须完全不透明(#000000 / #FFFFFF):主色加深后
|
|
|
+ // alpha=0.83~0.90 区间,半透明文字叠加 opacity 修饰符会让两支同时跌破 4.5:1,
|
|
|
+ // 仅调阈值数学上无解。阈值 0.87 下 0.001 步长全区间实测:深字支最低 4.54:1、
|
|
|
+ // 白字支最低 4.62:1(脚本 heat.py / verify.py)。
|
|
|
heatCellStyle(v) {
|
|
|
+ // 有数据格的文字层不得带任何 alpha(含 CSS opacity),否则上述阈值失效。
|
|
|
+ const DARK_TEXT = '#000000'
|
|
|
+ const LIGHT_TEXT = '#FFFFFF'
|
|
|
const peak = this.dwellByHourPeak || 0
|
|
|
if (!peak || !v) return { background: 'var(--adm-fill)', color: 'var(--adm-text-45)' }
|
|
|
const ratio = Math.min(1, Number(v) / peak)
|
|
|
const alpha = (0.1 + ratio * 0.9).toFixed(3)
|
|
|
- const bgColor = `rgba(22, 119, 255, ${alpha})`
|
|
|
- const useLightText = parseFloat(alpha) >= 0.9
|
|
|
+ const bgColor = this.rgbaFromPrimary(alpha)
|
|
|
+ const useLightText = parseFloat(alpha) >= 0.87
|
|
|
return {
|
|
|
background: bgColor,
|
|
|
- color: useLightText ? 'rgba(255,255,255,0.92)' : 'var(--adm-text-88)'
|
|
|
+ color: useLightText ? LIGHT_TEXT : DARK_TEXT
|
|
|
}
|
|
|
},
|
|
|
fmtDuration(sec) {
|
|
|
@@ -597,8 +613,8 @@ export default {
|
|
|
itemStyle: { color: this.cssVar('--adm-primary') },
|
|
|
areaStyle: {
|
|
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
- { offset: 0, color: 'rgba(22,119,255,0.18)' },
|
|
|
- { offset: 1, color: 'rgba(22,119,255,0.01)' }
|
|
|
+ { offset: 0, color: this.rgbaFromPrimary(0.18) },
|
|
|
+ { offset: 1, color: this.rgbaFromPrimary(0.01) }
|
|
|
])
|
|
|
}
|
|
|
},
|
|
|
@@ -1043,7 +1059,7 @@ export default {
|
|
|
transition: background 0.25s ease;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
-.heatmap__cell:hover { opacity: 0.88; }
|
|
|
+.heatmap__cell:hover { box-shadow: inset 0 0 0 2px var(--adm-primary-hover); }
|
|
|
.heatmap__flag {
|
|
|
position: absolute;
|
|
|
top: 2px;
|
|
|
@@ -1068,7 +1084,6 @@ export default {
|
|
|
font-weight: 500;
|
|
|
color: inherit;
|
|
|
line-height: 1.2;
|
|
|
- opacity: 0.9;
|
|
|
white-space: nowrap;
|
|
|
max-width: 100%;
|
|
|
overflow: hidden;
|