From 53012a16c9f186ff9dab22422ba4344917601b9f Mon Sep 17 00:00:00 2001 From: liugongyu <290219706@qq.com> Date: Wed, 7 Jan 2026 19:23:16 +0800 Subject: [PATCH] 首页 优化 --- components/uni-charts/uni-charts.vue | 180 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------------------------- pages/index/index.vue | 19 +++++++++---------- pages/workbench/index.vue | 4 ++-- 3 files changed, 128 insertions(+), 75 deletions(-) diff --git a/components/uni-charts/uni-charts.vue b/components/uni-charts/uni-charts.vue index 573313c..e09a353 100644 --- a/components/uni-charts/uni-charts.vue +++ b/components/uni-charts/uni-charts.vue @@ -38,7 +38,11 @@ export default { option: { type: Object, default () { - return {} + return { + yAxis: { + tickCount: 5 // 默认5个刻度,可通过option自定义,无上限 + } + } } }, width: { @@ -88,7 +92,41 @@ export default { this.drawLine() } }, - // 折线图绘制(移除平滑过渡,改为纯直线) + // 核心:自适应计算Y轴整数刻度(无任何数值限制) + calculateYAxisTicks (minVal, maxVal, tickCount = 5) { + // 1. 确保是整数,且最大值>最小值(无数值范围限制) + minVal = parseInt(minVal, 10) + maxVal = parseInt(maxVal, 10) + + // 边界处理:极值相等时自动扩展范围(避免刻度重复) + if (maxVal <= minVal) { + maxVal = minVal + tickCount // 扩展范围,保证刻度不重复 + } + + // 2. 动态计算整数步长(完全自适应,无固定值限制) + const totalRange = maxVal - minVal + let step = Math.ceil(totalRange / (tickCount - 1)) // 关键:按刻度数算步长 + step = step < 1 ? 1 : step // 步长至少为1,保证整数刻度 + + // 3. 生成连续不重复的整数刻度(无数值上限) + const ticks = [] + for (let i = 0; i < tickCount; i++) { + ticks.push(minVal + i * step) + } + + // 4. 确保最大值能覆盖数据(无限制) + if (ticks[ticks.length - 1] < maxVal) { + ticks.push(maxVal) + } + + return { + ticks: ticks, // 最终刻度数组(纯整数、不重复、无限制) + min: minVal, + max: Math.max(...ticks), + step: step + } + }, + // 折线图绘制(无限制版) drawLine () { if (!this.data.length || !this.categories.length) return; @@ -106,7 +144,7 @@ export default { // 清空画布 ctx.clearRect(0, 0, width, height) - // 调整Grid布局,彻底解决重叠 + // Grid布局(自适应,无固定值) const gridTop = grid.top ? (typeof grid.top === 'string' ? parseFloat(grid.top) / 100 * height : grid.top) : 60 const gridLeft = grid.left ? (typeof grid.left === 'string' ? parseFloat(grid.left) / 100 * width : grid.left) : 70 const gridRight = grid.right ? (typeof grid.right === 'string' ? parseFloat(grid.right) / 100 * width : grid.right) : 20 @@ -115,61 +153,58 @@ export default { const drawWidth = width - gridLeft - gridRight const drawHeight = height - gridTop - gridBottom - // Y轴最大值自适应(无小数点) + // 收集所有Y轴数据(无数值限制) let allValues = [] this.data.forEach(series => { allValues = allValues.concat(series.data) }) if (allValues.length === 0) return; - // 动态计算Y轴极值(保留10%顶部余量,转为整数) + // 基础极值(完全按数据来,无任何限制) const rawMaxVal = Math.max(...allValues) const rawMinVal = Math.min(...allValues) - const maxVal = yAxis.max || Math.ceil(rawMaxVal * 1.1) // 向上取整,保证能容纳最大值 - const minVal = yAxis.min || (rawMinVal < 0 ? Math.floor(rawMinVal * 1.1) : 0) // 向下取整(负数),默认0 - const valRange = maxVal - minVal || 1 - - // 计算X轴每个点的宽度 + // 用户可通过yAxis.min/max自定义极值,否则用数据极值(无限制) + const maxVal = yAxis.max !== undefined ? yAxis.max : rawMaxVal + const minVal = yAxis.min !== undefined ? yAxis.min : rawMinVal + // 刻度数量自定义(默认5个,可传任意数,无上限) + const tickCount = yAxis.tickCount || 5 + + // 核心:计算无限制、不重复的整数刻度 + const yAxisConfig = this.calculateYAxisTicks(minVal, maxVal, tickCount) + const { ticks: yTicks, min: alignedMinVal, max: alignedMaxVal } = yAxisConfig + const valRange = alignedMaxVal - alignedMinVal || 1 + + // X轴步长(自适应) const xStep = drawWidth / (this.categories.length - 1 || 1) - // X轴标签防拥挤 + // X轴标签防拥挤(自适应,无限制) const minLabelWidth = 30 const maxShowLabels = Math.floor(drawWidth / minLabelWidth) const labelInterval = maxShowLabels < this.categories.length ? Math.ceil(this.categories.length / maxShowLabels) : 1 - // 绘制网格线 + Y轴整数刻度 + // 绘制网格线 + Y轴整数刻度(无数值限制) ctx.setStrokeStyle(yAxis.splitLine?.lineStyle?.color || '#f5f5f7') ctx.setLineWidth(1) - const yTickCount = 5 - const yTickStep = valRange / yTickCount - for (let i = 0; i <= yTickCount; i++) { - const y = gridTop + drawHeight - (i * drawHeight / yTickCount) + yTicks.forEach((tickVal, i) => { + const y = gridTop + drawHeight - (i * drawHeight / (yTicks.length - 1)) // 绘制网格线 ctx.beginPath() ctx.moveTo(gridLeft, y) ctx.lineTo(width - gridRight, y) ctx.stroke() - // Y轴显示整数(无小数点) + // Y轴显示纯整数(无小数点、无任何数值限制) ctx.setFillStyle(yAxis.axisLabel?.color || '#666') ctx.setFontSize(yAxis.axisLabel?.fontSize || 12) - const val = minVal + (i * yTickStep) - // 转为整数(四舍五入),彻底去掉小数点 - const intVal = Math.round(val) - let valText = intVal.toString() - // 长数字处理(仍为整数格式) - if (valText.length > 6) { - valText = intVal.toLocaleString() // 用千分位显示长整数,如 1234567 → 1,234,567 - } - // 文字右对齐,避免折叠 + const valText = tickVal.toString() // 纯整数,直接转字符串 const textWidth = ctx.measureText(valText).width ctx.fillText(valText, gridLeft - 10 - textWidth, y + 5) - } + }) - // 绘制X轴标签 + // 绘制X轴标签(自适应) ctx.setFillStyle(xAxis.axisLabel?.color || '#666') ctx.setFontSize(xAxis.axisLabel?.fontSize || 12) this.categories.forEach((text, index) => { @@ -180,7 +215,6 @@ export default { labelText = text.slice(0, 6) + '...' } const textLines = labelText.split('\n') - textLines.forEach((line, lineIdx) => { const textWidth = ctx.measureText(line).width ctx.fillText(line, x - textWidth / 2, height - gridBottom + 20 + (lineIdx * 12)) @@ -188,29 +222,45 @@ export default { } }) - // 优化图例间距 + // 绘制图例(核心修复:颜色块和文字垂直居中对齐) if (legend.show) { - ctx.setFontSize(legend.textStyle?.fontSize || 12) + // 获取图例文字大小(统一基准) + const legendFontSize = legend.textStyle?.fontSize || 12 + ctx.setFontSize(legendFontSize) + // 定义颜色块尺寸(和文字高度匹配) + const legendBlockSize = legendFontSize * 0.8 // 颜色块大小 = 文字大小的80%,视觉更协调 + const legendBlockMargin = 5 // 颜色块和文字的间距 let legendX = gridLeft + 10 + // 计算垂直居中基准线 + const legendYBase = gridTop - 30 + this.data.forEach((series, idx) => { let seriesName = series.name || `系列${idx + 1}` if (seriesName.length > 8) { seriesName = seriesName.slice(0, 8) + '...' } const textWidth = ctx.measureText(seriesName).width - const legendItemWidth = textWidth + 25 - const legendY = gridTop - 30 + // 核心:计算颜色块和文字的垂直居中位置 + // 颜色块Y坐标 = 基准线 - 颜色块高度/2(居中) + const legendBlockY = legendYBase - legendBlockSize / 2 + // 文字Y坐标 = 基准线 + 文字高度/4(canvas文字居中的黄金比例) + const legendTextY = legendYBase + legendFontSize / 4 + + // 绘制颜色块(居中对齐) ctx.setFillStyle(series.color || color[idx % color.length]) - ctx.fillRect(legendX, legendY, 10, 10) + ctx.fillRect(legendX, legendBlockY, legendBlockSize, legendBlockSize) + + // 绘制文字(和颜色块垂直居中) ctx.setFillStyle('#666') - ctx.fillText(seriesName, legendX + 15, legendY + 8) + ctx.fillText(seriesName, legendX + legendBlockSize + legendBlockMargin, legendTextY) - legendX += Math.max(80, legendItemWidth + 10) + // 更新下一个图例的X坐标(包含间距) + legendX += legendBlockSize + legendBlockMargin + textWidth + 10 }) } - // ========== 核心修改:移除平滑曲线,改为纯直线连接 ========== + // 绘制折线(纯直线,无数值限制) this.data.forEach((series, seriesIdx) => { const seriesColor = series.color || color[seriesIdx % color.length] @@ -220,12 +270,12 @@ export default { series.data.forEach((value, index) => { const x = gridLeft + index * xStep - const y = gridTop + drawHeight - ((value - minVal) / valRange) * drawHeight + // 自适应计算Y坐标(无数值限制) + const y = gridTop + drawHeight - ((value - alignedMinVal) / valRange) * drawHeight if (index === 0) { ctx.moveTo(x, y) } else { - // 直接直线连接,移除所有贝塞尔曲线平滑逻辑 ctx.lineTo(x, y) } }) @@ -235,7 +285,7 @@ export default { ctx.draw() }, - // K线图绘制(同步修改Y轴为整数,K线本身无平滑逻辑,保持原样) + // K线图绘制(同步无限制逻辑 + 图例对齐修复) drawKline () { const ctx = uni.createCanvasContext('uni-charts', this) this.ctx = ctx @@ -244,7 +294,7 @@ export default { ctx.clearRect(0, 0, width, height) - // 调整Grid布局 + // Grid布局(自适应) const gridTop = grid.top ? (typeof grid.top === 'string' ? parseFloat(grid.top) / 100 * height : grid.top) : 50 const gridLeft = grid.left ? (typeof grid.left === 'string' ? parseFloat(grid.left) / 100 * width : grid.left) : 70 const gridRight = grid.right ? (typeof grid.right === 'string' ? parseFloat(grid.right) / 100 * width : grid.right) : 20 @@ -253,44 +303,47 @@ export default { const drawWidth = width - gridLeft - gridRight const drawHeight = height - gridTop - gridBottom - // Y轴自适应极值(整数) + // 收集所有Y轴数据(无限制) let allValues = [] this.data.forEach(item => { allValues = allValues.concat([item.open, item.high, item.low, item.close]) }) if (allValues.length === 0) return; + // 基础极值(无限制) const rawMaxVal = Math.max(...allValues) const rawMinVal = Math.min(...allValues) - const maxVal = yAxis.max || Math.ceil(rawMaxVal * 1.1) - const minVal = yAxis.min || (rawMinVal < 0 ? Math.floor(rawMinVal * 1.1) : 0) - const valRange = maxVal - minVal || 1 + const maxVal = yAxis.max !== undefined ? yAxis.max : rawMaxVal + const minVal = yAxis.min !== undefined ? yAxis.min : rawMinVal + const tickCount = yAxis.tickCount || 5 + + // 核心:无限制整数刻度计算 + const yAxisConfig = this.calculateYAxisTicks(minVal, maxVal, tickCount) + const { ticks: yTicks, min: alignedMinVal, max: alignedMaxVal } = yAxisConfig + const valRange = alignedMaxVal - alignedMinVal || 1 const xStep = drawWidth / (this.data.length || 1) - // 绘制网格线和Y轴整数刻度 + // 绘制网格线和Y轴刻度(无限制) ctx.setStrokeStyle('#f5f5f7') ctx.setLineWidth(1) - const yTickCount = 5 - for (let i = 0; i <= yTickCount; i++) { - const y = gridTop + drawHeight - (i * drawHeight / yTickCount) + + yTicks.forEach((tickVal, i) => { + const y = gridTop + drawHeight - (i * drawHeight / (yTicks.length - 1)) ctx.beginPath() ctx.moveTo(gridLeft, y) ctx.lineTo(width - gridRight, y) ctx.stroke() - // Y轴显示整数 + // 纯整数显示(无限制) ctx.setFillStyle(yAxis.axisLabel?.color || '#666') ctx.setFontSize(yAxis.axisLabel?.fontSize || 12) - const val = minVal + (i * valRange / yTickCount) - const intVal = Math.round(val) - let valText = intVal.toString() - if (valText.length > 6) valText = intVal.toLocaleString() + const valText = tickVal.toString() const textWidth = ctx.measureText(valText).width ctx.fillText(valText, gridLeft - 10 - textWidth, y + 5) - } + }) - // X轴标签(防拥挤) + // X轴标签(自适应) ctx.setFillStyle(xAxis.axisLabel?.color || '#666') ctx.setFontSize(xAxis.axisLabel?.fontSize || 12) const minLabelWidth = 30 @@ -308,16 +361,17 @@ export default { } }) - // 绘制K线(K线本身就是直线,无平滑逻辑) + // 绘制K线(无数值限制) this.data.forEach((item, index) => { const { open, high, low, close } = item const x = gridLeft + index * xStep + xStep / 2 - const yOpen = gridTop + drawHeight - ((open - minVal) / valRange) * drawHeight - const yClose = gridTop + drawHeight - ((close - minVal) / valRange) * drawHeight - const yHigh = gridTop + drawHeight - ((high - minVal) / valRange) * drawHeight - const yLow = gridTop + drawHeight - ((low - minVal) / valRange) * drawHeight + // 自适应坐标计算 + const yOpen = gridTop + drawHeight - ((open - alignedMinVal) / valRange) * drawHeight + const yClose = gridTop + drawHeight - ((close - alignedMinVal) / valRange) * drawHeight + const yHigh = gridTop + drawHeight - ((high - alignedMinVal) / valRange) * drawHeight + const yLow = gridTop + drawHeight - ((low - alignedMinVal) / valRange) * drawHeight - // 绘制高低线(直线) + // 绘制高低线 ctx.setStrokeStyle(close >= open ? color[0] : color[1]) ctx.setLineWidth(1) ctx.beginPath() @@ -347,7 +401,7 @@ export default { this.touchInfo.y = e.changedTouches[0].y }, touchEnd (e) { - // 可扩展点击交互逻辑 + // 可扩展交互逻辑,无限制 } } } diff --git a/pages/index/index.vue b/pages/index/index.vue index fa841ef..5424883 100644 --- a/pages/index/index.vue +++ b/pages/index/index.vue @@ -10,8 +10,8 @@ - - + + @@ -55,11 +55,10 @@ active-color="#0A86F4" inactive-color="#666" font-size="14px" - line-width="40" - line-height="3" + lineWidth="60" @change="handleTabChange" class="task-tab-container" - scrollable="false" + :scrollable="false" > @@ -150,9 +149,9 @@ const klineChartData = ref([ const klineOption = ref({ grid: { top: '25%', - left: '15%', + left: '12%', right: '8%', - bottom: '20%' + bottom: '16%' }, xAxis: { axisLabel: {fontSize: 12, color: '#666'} @@ -372,7 +371,7 @@ $border-color: #e5e5e5; // 新增边框颜色变量 .user-info-bar { background: url("https://img.jichengshanshui.com.cn:28207/appimg/bg.jpg") no-repeat; background-size: 100% 100%; - padding: 100px $spacing-lg 135px; + padding: 90px $spacing-lg 135px; display: flex; justify-content: space-between; align-items: center; @@ -393,8 +392,8 @@ $border-color: #e5e5e5; // 新增边框颜色变量 .msg-icon { position: relative; padding: 5px; + top:10px; border-radius: 50%; - transition: background-color 0.2s; &--hover { background-color: rgba(255, 255, 255, 0.2); @@ -413,7 +412,7 @@ $border-color: #e5e5e5; // 新增边框颜色变量 border-radius: $border-radius $border-radius 0 0; background-color: $bg-color; position: relative; - z-index: 2; + z-index: 999; overflow: hidden; padding-bottom: $spacing-lg; } diff --git a/pages/workbench/index.vue b/pages/workbench/index.vue index e4d7f80..7cd3579 100644 --- a/pages/workbench/index.vue +++ b/pages/workbench/index.vue @@ -166,7 +166,7 @@ const handleMenuClick = (item: MenuItem) => { top: 0; left: 0; width: 100%; - padding: 90px 15px 135px; + padding: 80px 15px 125px; background: url("https://img.jichengshanshui.com.cn:28207/appimg/bg.jpg") no-repeat; background-size: 100% 100%; z-index: 1; @@ -191,7 +191,7 @@ const handleMenuClick = (item: MenuItem) => { .content-wrap { position: relative; z-index: 2; - padding: 150px 0 0; + padding: 135px 0 0; display: flex; flex-direction: column; gap: 10px; -- libgit2 0.21.4