From 129b53854144ef5c8d345a1917158510188297b7 Mon Sep 17 00:00:00 2001 From: huangzhenbao <17818024429@163.com> Date: Thu, 26 Feb 2026 10:48:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E9=A1=B5=E9=9D=A27?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../analytics/AnalyticsUserGenderSection.uvue | 132 +++++++++++------- 1 file changed, 79 insertions(+), 53 deletions(-) diff --git a/components/analytics/AnalyticsUserGenderSection.uvue b/components/analytics/AnalyticsUserGenderSection.uvue index ab4a979e..59e807b0 100644 --- a/components/analytics/AnalyticsUserGenderSection.uvue +++ b/components/analytics/AnalyticsUserGenderSection.uvue @@ -5,24 +5,20 @@ - + - - - {{ item.name }} + + + {{ item['name'] }} - + - + 总用户数 {{ totalUsers }} @@ -46,11 +42,11 @@ export default { }, data() { return { - totalUsers: 525, + totalUsers: 789, genderData: [ - { value: 450, name: '未知', itemStyle: { color: '#919eab' } }, - { value: 50, name: '男', itemStyle: { color: '#1890ff' } }, - { value: 25, name: '女', itemStyle: { color: '#febc2c' } } + { value: 400, name: '男', itemStyle: { color: '#1890ff' } }, + { value: 300, name: '女', itemStyle: { color: '#febc2c' } }, + { value: 89, name: '未知', itemStyle: { color: '#919eab' } } ], chartOption: {} as any, resizeObserver: null as any | null @@ -68,8 +64,12 @@ export default { } }, mounted() { - this.initChart() this.setupResizeSystem() + + // 📌 参考 AnalyticsPieChart 的延迟初始化策略,解决 H5 渲染 0 宽高问题 + setTimeout(() => { + this.initChart() + }, 300) }, unmounted() { if (this.resizeObserver != null) { @@ -84,7 +84,16 @@ export default { }, methods: { initChart() { - // Step 4: 修复 option 自适应,使用百分比 radius 和 center + // 📌 参考 AnalyticsPieChart 的数据映射和 PlainObject 处理 + const plainData = (this.genderData as Array).map((it) => { + const itemStyle = it['itemStyle'] ? this.toPlainObject(it['itemStyle']) : {} as any + return { + name: String(it['name']), + value: Number(it['value']), + itemStyle: itemStyle + } + }) + const option = { tooltip: { trigger: 'item', @@ -94,15 +103,12 @@ export default { textStyle: { color: '#333' }, extraCssText: 'box-shadow: 0 2px 8px rgba(0,0,0,0.15); border-radius: 4px;' }, - // 关闭 ECharts 的普通图例,使用外部自定义图例 legend: { show: false }, series: [ { name: '性别比例', type: 'pie', - // 百分比定义半径,防止裁切 - radius: ['60%', '78%'], - // 图表在容器内绝对居中 + radius: ['58%', '75%'], center: ['50%', '50%'], avoidLabelOverlap: false, label: { show: false }, @@ -111,7 +117,7 @@ export default { scaleSize: 10, label: { show: false } }, - data: this.genderData + data: plainData } ] } @@ -163,13 +169,29 @@ export default { toPlainObject(obj: any): any { if (obj == null) return null if (typeof obj !== 'object') return obj - if (Array.isArray(obj)) return obj.map((item : any) : any => this.toPlainObject(item)) + if (Array.isArray(obj)) { + return obj.map((item: any): any => this.toPlainObject(item)) + } const plain: any = {} for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { const value = obj[key] - if (typeof value === 'function' || key.startsWith('_') || key === 'toJSON') continue - plain[key] = (value != null && typeof value === 'object' && !Array.isArray(value)) ? this.toPlainObject(value) : value + if (typeof value === 'function' || key.startsWith('_') || key === 'toJSON') { + continue + } + if (value != null && typeof value === 'object' && !Array.isArray(value)) { + // 📌 参考 AnalyticsPieChart 的优化逻辑,处理普通对象拷贝 + let isSimple = true + for (const k in value) { + if (typeof value[k] === 'object' && value[k] !== null) { + isSimple = false + break + } + } + plain[key] = isSimple ? { ...value } : this.toPlainObject(value) + } else { + plain[key] = value + } } } return plain @@ -182,22 +204,22 @@ export default { .gender-card { background: #fff; border-radius: 4px; - padding: 20px; + padding: 24px; display: flex; flex-direction: column; - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04); + box-shadow: 0 2px 20px rgba(0, 0, 0, 0.03); box-sizing: border-box; width: 100%; } .card-header { - margin-bottom: 20px; + margin-bottom: 24px; flex-shrink: 0; } .title { font-size: 16px; - font-weight: bold; + font-weight: 700; color: #333; } @@ -205,16 +227,19 @@ export default { flex: 1; width: 100%; display: flex; - flex-direction: row; - align-items: center; + flex-direction: column; + align-items: flex-start; } +/* 图例区 (横向排列) 对齐图片 */ .legend-col { - width: 80px; + width: 100%; display: flex; - flex-direction: column; - gap: 16px; - flex-shrink: 0; + flex-direction: row; + align-items: center; + flex-wrap: wrap; + gap: 24px; + margin-bottom: 24px; } .legend-item { @@ -226,34 +251,33 @@ export default { .legend-dot { width: 16px; - height: 10px; + height: 12px; border-radius: 2px; } .legend-label { font-size: 14px; - color: #666; + color: #333; } +/* 核心图表列 (centered) */ .chart-col { - flex: 1; + width: 100%; position: relative; - /** - * 确定高度策略 - 响应式复刻 CRMEB - * >=1200px (两列) 时保持紧凑高度 - */ - height: clamp(320px, 35vh, 400px); - overflow: hidden; + height: 300px; + overflow: visible !important; + display: flex; + justify-content: center; } -/* 响应式断点:<1200px 切换为单列,图表需要更大空间 */ +/* 响应式断点 - 维持垂直布局并增加空间 */ @media (max-width: 1199.98px) { .chart-col { - height: clamp(480px, 50vh, 600px); + height: 350px; } } -/* 强制覆盖 EChartsView 内部样式,确保绝对定位生效 */ +/* 强制覆盖 EChartsView 样式 (确保完整铺满并通过 ECharts 配置居中) */ :deep(.donut-chart) { width: 100% !important; height: 100% !important; @@ -262,7 +286,7 @@ export default { position: relative !important; width: 100% !important; height: 100% !important; - overflow: hidden !important; + overflow: visible !important; } .ec-canvas { @@ -273,6 +297,7 @@ export default { } } +/* 图表中心文字 (必须绝对居中于容器,微调位置垂直视觉居中) */ .center-text { position: absolute; top: 50%; @@ -287,15 +312,16 @@ export default { } .total-label { - font-size: 13px; - color: #999; - margin-bottom: 4px; + font-size: 15px; + color: #666; + margin-bottom: 0; + font-weight: 700; } .total-value { - font-size: 32px; - font-weight: bold; + font-size: 48px; + font-weight: 700; color: #333; - line-height: 1; + line-height: 1.1; }