Commit 53012a16c9f186ff9dab22422ba4344917601b9f

Authored by 刘淇
1 parent cf70629b

首页 优化

components/uni-charts/uni-charts.vue
... ... @@ -38,7 +38,11 @@ export default {
38 38 option: {
39 39 type: Object,
40 40 default () {
41   - return {}
  41 + return {
  42 + yAxis: {
  43 + tickCount: 5 // 默认5个刻度,可通过option自定义,无上限
  44 + }
  45 + }
42 46 }
43 47 },
44 48 width: {
... ... @@ -88,7 +92,41 @@ export default {
88 92 this.drawLine()
89 93 }
90 94 },
91   - // 折线图绘制(移除平滑过渡,改为纯直线)
  95 + // 核心:自适应计算Y轴整数刻度(无任何数值限制)
  96 + calculateYAxisTicks (minVal, maxVal, tickCount = 5) {
  97 + // 1. 确保是整数,且最大值>最小值(无数值范围限制)
  98 + minVal = parseInt(minVal, 10)
  99 + maxVal = parseInt(maxVal, 10)
  100 +
  101 + // 边界处理:极值相等时自动扩展范围(避免刻度重复)
  102 + if (maxVal <= minVal) {
  103 + maxVal = minVal + tickCount // 扩展范围,保证刻度不重复
  104 + }
  105 +
  106 + // 2. 动态计算整数步长(完全自适应,无固定值限制)
  107 + const totalRange = maxVal - minVal
  108 + let step = Math.ceil(totalRange / (tickCount - 1)) // 关键:按刻度数算步长
  109 + step = step < 1 ? 1 : step // 步长至少为1,保证整数刻度
  110 +
  111 + // 3. 生成连续不重复的整数刻度(无数值上限)
  112 + const ticks = []
  113 + for (let i = 0; i < tickCount; i++) {
  114 + ticks.push(minVal + i * step)
  115 + }
  116 +
  117 + // 4. 确保最大值能覆盖数据(无限制)
  118 + if (ticks[ticks.length - 1] < maxVal) {
  119 + ticks.push(maxVal)
  120 + }
  121 +
  122 + return {
  123 + ticks: ticks, // 最终刻度数组(纯整数、不重复、无限制)
  124 + min: minVal,
  125 + max: Math.max(...ticks),
  126 + step: step
  127 + }
  128 + },
  129 + // 折线图绘制(无限制版)
92 130 drawLine () {
93 131 if (!this.data.length || !this.categories.length) return;
94 132  
... ... @@ -106,7 +144,7 @@ export default {
106 144 // 清空画布
107 145 ctx.clearRect(0, 0, width, height)
108 146  
109   - // 调整Grid布局,彻底解决重叠
  147 + // Grid布局(自适应,无固定值)
110 148 const gridTop = grid.top ? (typeof grid.top === 'string' ? parseFloat(grid.top) / 100 * height : grid.top) : 60
111 149 const gridLeft = grid.left ? (typeof grid.left === 'string' ? parseFloat(grid.left) / 100 * width : grid.left) : 70
112 150 const gridRight = grid.right ? (typeof grid.right === 'string' ? parseFloat(grid.right) / 100 * width : grid.right) : 20
... ... @@ -115,61 +153,58 @@ export default {
115 153 const drawWidth = width - gridLeft - gridRight
116 154 const drawHeight = height - gridTop - gridBottom
117 155  
118   - // Y轴最大值自适应(无小数点
  156 + // 收集所有Y轴数据(无数值限制
119 157 let allValues = []
120 158 this.data.forEach(series => {
121 159 allValues = allValues.concat(series.data)
122 160 })
123 161 if (allValues.length === 0) return;
124 162  
125   - // 动态计算Y轴极值(保留10%顶部余量,转为整数
  163 + // 基础极值(完全按数据来,无任何限制
126 164 const rawMaxVal = Math.max(...allValues)
127 165 const rawMinVal = Math.min(...allValues)
128   - const maxVal = yAxis.max || Math.ceil(rawMaxVal * 1.1) // 向上取整,保证能容纳最大值
129   - const minVal = yAxis.min || (rawMinVal < 0 ? Math.floor(rawMinVal * 1.1) : 0) // 向下取整(负数),默认0
130   - const valRange = maxVal - minVal || 1
131   -
132   - // 计算X轴每个点的宽度
  166 + // 用户可通过yAxis.min/max自定义极值,否则用数据极值(无限制)
  167 + const maxVal = yAxis.max !== undefined ? yAxis.max : rawMaxVal
  168 + const minVal = yAxis.min !== undefined ? yAxis.min : rawMinVal
  169 + // 刻度数量自定义(默认5个,可传任意数,无上限)
  170 + const tickCount = yAxis.tickCount || 5
  171 +
  172 + // 核心:计算无限制、不重复的整数刻度
  173 + const yAxisConfig = this.calculateYAxisTicks(minVal, maxVal, tickCount)
  174 + const { ticks: yTicks, min: alignedMinVal, max: alignedMaxVal } = yAxisConfig
  175 + const valRange = alignedMaxVal - alignedMinVal || 1
  176 +
  177 + // X轴步长(自适应)
133 178 const xStep = drawWidth / (this.categories.length - 1 || 1)
134 179  
135   - // X轴标签防拥挤
  180 + // X轴标签防拥挤(自适应,无限制)
136 181 const minLabelWidth = 30
137 182 const maxShowLabels = Math.floor(drawWidth / minLabelWidth)
138 183 const labelInterval = maxShowLabels < this.categories.length
139 184 ? Math.ceil(this.categories.length / maxShowLabels)
140 185 : 1
141 186  
142   - // 绘制网格线 + Y轴整数刻度
  187 + // 绘制网格线 + Y轴整数刻度(无数值限制)
143 188 ctx.setStrokeStyle(yAxis.splitLine?.lineStyle?.color || '#f5f5f7')
144 189 ctx.setLineWidth(1)
145   - const yTickCount = 5
146   - const yTickStep = valRange / yTickCount
147 190  
148   - for (let i = 0; i <= yTickCount; i++) {
149   - const y = gridTop + drawHeight - (i * drawHeight / yTickCount)
  191 + yTicks.forEach((tickVal, i) => {
  192 + const y = gridTop + drawHeight - (i * drawHeight / (yTicks.length - 1))
150 193 // 绘制网格线
151 194 ctx.beginPath()
152 195 ctx.moveTo(gridLeft, y)
153 196 ctx.lineTo(width - gridRight, y)
154 197 ctx.stroke()
155 198  
156   - // Y轴显示整数(无小数点
  199 + // Y轴显示纯整数(无小数点、无任何数值限制
157 200 ctx.setFillStyle(yAxis.axisLabel?.color || '#666')
158 201 ctx.setFontSize(yAxis.axisLabel?.fontSize || 12)
159   - const val = minVal + (i * yTickStep)
160   - // 转为整数(四舍五入),彻底去掉小数点
161   - const intVal = Math.round(val)
162   - let valText = intVal.toString()
163   - // 长数字处理(仍为整数格式)
164   - if (valText.length > 6) {
165   - valText = intVal.toLocaleString() // 用千分位显示长整数,如 1234567 → 1,234,567
166   - }
167   - // 文字右对齐,避免折叠
  202 + const valText = tickVal.toString() // 纯整数,直接转字符串
168 203 const textWidth = ctx.measureText(valText).width
169 204 ctx.fillText(valText, gridLeft - 10 - textWidth, y + 5)
170   - }
  205 + })
171 206  
172   - // 绘制X轴标签
  207 + // 绘制X轴标签(自适应)
173 208 ctx.setFillStyle(xAxis.axisLabel?.color || '#666')
174 209 ctx.setFontSize(xAxis.axisLabel?.fontSize || 12)
175 210 this.categories.forEach((text, index) => {
... ... @@ -180,7 +215,6 @@ export default {
180 215 labelText = text.slice(0, 6) + '...'
181 216 }
182 217 const textLines = labelText.split('\n')
183   -
184 218 textLines.forEach((line, lineIdx) => {
185 219 const textWidth = ctx.measureText(line).width
186 220 ctx.fillText(line, x - textWidth / 2, height - gridBottom + 20 + (lineIdx * 12))
... ... @@ -188,29 +222,45 @@ export default {
188 222 }
189 223 })
190 224  
191   - // 优化图例间距
  225 + // 绘制图例(核心修复:颜色块和文字垂直居中对齐)
192 226 if (legend.show) {
193   - ctx.setFontSize(legend.textStyle?.fontSize || 12)
  227 + // 获取图例文字大小(统一基准)
  228 + const legendFontSize = legend.textStyle?.fontSize || 12
  229 + ctx.setFontSize(legendFontSize)
  230 + // 定义颜色块尺寸(和文字高度匹配)
  231 + const legendBlockSize = legendFontSize * 0.8 // 颜色块大小 = 文字大小的80%,视觉更协调
  232 + const legendBlockMargin = 5 // 颜色块和文字的间距
194 233 let legendX = gridLeft + 10
  234 + // 计算垂直居中基准线
  235 + const legendYBase = gridTop - 30
  236 +
195 237 this.data.forEach((series, idx) => {
196 238 let seriesName = series.name || `系列${idx + 1}`
197 239 if (seriesName.length > 8) {
198 240 seriesName = seriesName.slice(0, 8) + '...'
199 241 }
200 242 const textWidth = ctx.measureText(seriesName).width
201   - const legendItemWidth = textWidth + 25
202 243  
203   - const legendY = gridTop - 30
  244 + // 核心:计算颜色块和文字的垂直居中位置
  245 + // 颜色块Y坐标 = 基准线 - 颜色块高度/2(居中)
  246 + const legendBlockY = legendYBase - legendBlockSize / 2
  247 + // 文字Y坐标 = 基准线 + 文字高度/4(canvas文字居中的黄金比例)
  248 + const legendTextY = legendYBase + legendFontSize / 4
  249 +
  250 + // 绘制颜色块(居中对齐)
204 251 ctx.setFillStyle(series.color || color[idx % color.length])
205   - ctx.fillRect(legendX, legendY, 10, 10)
  252 + ctx.fillRect(legendX, legendBlockY, legendBlockSize, legendBlockSize)
  253 +
  254 + // 绘制文字(和颜色块垂直居中)
206 255 ctx.setFillStyle('#666')
207   - ctx.fillText(seriesName, legendX + 15, legendY + 8)
  256 + ctx.fillText(seriesName, legendX + legendBlockSize + legendBlockMargin, legendTextY)
208 257  
209   - legendX += Math.max(80, legendItemWidth + 10)
  258 + // 更新下一个图例的X坐标(包含间距)
  259 + legendX += legendBlockSize + legendBlockMargin + textWidth + 10
210 260 })
211 261 }
212 262  
213   - // ========== 核心修改:移除平滑曲线,改为纯直线连接 ==========
  263 + // 绘制折线(纯直线,无数值限制)
214 264 this.data.forEach((series, seriesIdx) => {
215 265 const seriesColor = series.color || color[seriesIdx % color.length]
216 266  
... ... @@ -220,12 +270,12 @@ export default {
220 270  
221 271 series.data.forEach((value, index) => {
222 272 const x = gridLeft + index * xStep
223   - const y = gridTop + drawHeight - ((value - minVal) / valRange) * drawHeight
  273 + // 自适应计算Y坐标(无数值限制)
  274 + const y = gridTop + drawHeight - ((value - alignedMinVal) / valRange) * drawHeight
224 275  
225 276 if (index === 0) {
226 277 ctx.moveTo(x, y)
227 278 } else {
228   - // 直接直线连接,移除所有贝塞尔曲线平滑逻辑
229 279 ctx.lineTo(x, y)
230 280 }
231 281 })
... ... @@ -235,7 +285,7 @@ export default {
235 285  
236 286 ctx.draw()
237 287 },
238   - // K线图绘制(同步修改Y轴为整数,K线本身无平滑逻辑,保持原样
  288 + // K线图绘制(同步无限制逻辑 + 图例对齐修复
239 289 drawKline () {
240 290 const ctx = uni.createCanvasContext('uni-charts', this)
241 291 this.ctx = ctx
... ... @@ -244,7 +294,7 @@ export default {
244 294  
245 295 ctx.clearRect(0, 0, width, height)
246 296  
247   - // 调整Grid布局
  297 + // Grid布局(自适应)
248 298 const gridTop = grid.top ? (typeof grid.top === 'string' ? parseFloat(grid.top) / 100 * height : grid.top) : 50
249 299 const gridLeft = grid.left ? (typeof grid.left === 'string' ? parseFloat(grid.left) / 100 * width : grid.left) : 70
250 300 const gridRight = grid.right ? (typeof grid.right === 'string' ? parseFloat(grid.right) / 100 * width : grid.right) : 20
... ... @@ -253,44 +303,47 @@ export default {
253 303 const drawWidth = width - gridLeft - gridRight
254 304 const drawHeight = height - gridTop - gridBottom
255 305  
256   - // Y轴自适应极值(整数
  306 + // 收集所有Y轴数据(无限制
257 307 let allValues = []
258 308 this.data.forEach(item => {
259 309 allValues = allValues.concat([item.open, item.high, item.low, item.close])
260 310 })
261 311 if (allValues.length === 0) return;
262 312  
  313 + // 基础极值(无限制)
263 314 const rawMaxVal = Math.max(...allValues)
264 315 const rawMinVal = Math.min(...allValues)
265   - const maxVal = yAxis.max || Math.ceil(rawMaxVal * 1.1)
266   - const minVal = yAxis.min || (rawMinVal < 0 ? Math.floor(rawMinVal * 1.1) : 0)
267   - const valRange = maxVal - minVal || 1
  316 + const maxVal = yAxis.max !== undefined ? yAxis.max : rawMaxVal
  317 + const minVal = yAxis.min !== undefined ? yAxis.min : rawMinVal
  318 + const tickCount = yAxis.tickCount || 5
  319 +
  320 + // 核心:无限制整数刻度计算
  321 + const yAxisConfig = this.calculateYAxisTicks(minVal, maxVal, tickCount)
  322 + const { ticks: yTicks, min: alignedMinVal, max: alignedMaxVal } = yAxisConfig
  323 + const valRange = alignedMaxVal - alignedMinVal || 1
268 324  
269 325 const xStep = drawWidth / (this.data.length || 1)
270 326  
271   - // 绘制网格线和Y轴整数刻度
  327 + // 绘制网格线和Y轴刻度(无限制)
272 328 ctx.setStrokeStyle('#f5f5f7')
273 329 ctx.setLineWidth(1)
274   - const yTickCount = 5
275   - for (let i = 0; i <= yTickCount; i++) {
276   - const y = gridTop + drawHeight - (i * drawHeight / yTickCount)
  330 +
  331 + yTicks.forEach((tickVal, i) => {
  332 + const y = gridTop + drawHeight - (i * drawHeight / (yTicks.length - 1))
277 333 ctx.beginPath()
278 334 ctx.moveTo(gridLeft, y)
279 335 ctx.lineTo(width - gridRight, y)
280 336 ctx.stroke()
281 337  
282   - // Y轴显示整数
  338 + // 纯整数显示(无限制)
283 339 ctx.setFillStyle(yAxis.axisLabel?.color || '#666')
284 340 ctx.setFontSize(yAxis.axisLabel?.fontSize || 12)
285   - const val = minVal + (i * valRange / yTickCount)
286   - const intVal = Math.round(val)
287   - let valText = intVal.toString()
288   - if (valText.length > 6) valText = intVal.toLocaleString()
  341 + const valText = tickVal.toString()
289 342 const textWidth = ctx.measureText(valText).width
290 343 ctx.fillText(valText, gridLeft - 10 - textWidth, y + 5)
291   - }
  344 + })
292 345  
293   - // X轴标签(防拥挤
  346 + // X轴标签(自适应
294 347 ctx.setFillStyle(xAxis.axisLabel?.color || '#666')
295 348 ctx.setFontSize(xAxis.axisLabel?.fontSize || 12)
296 349 const minLabelWidth = 30
... ... @@ -308,16 +361,17 @@ export default {
308 361 }
309 362 })
310 363  
311   - // 绘制K线(K线本身就是直线,无平滑逻辑
  364 + // 绘制K线(无数值限制
312 365 this.data.forEach((item, index) => {
313 366 const { open, high, low, close } = item
314 367 const x = gridLeft + index * xStep + xStep / 2
315   - const yOpen = gridTop + drawHeight - ((open - minVal) / valRange) * drawHeight
316   - const yClose = gridTop + drawHeight - ((close - minVal) / valRange) * drawHeight
317   - const yHigh = gridTop + drawHeight - ((high - minVal) / valRange) * drawHeight
318   - const yLow = gridTop + drawHeight - ((low - minVal) / valRange) * drawHeight
  368 + // 自适应坐标计算
  369 + const yOpen = gridTop + drawHeight - ((open - alignedMinVal) / valRange) * drawHeight
  370 + const yClose = gridTop + drawHeight - ((close - alignedMinVal) / valRange) * drawHeight
  371 + const yHigh = gridTop + drawHeight - ((high - alignedMinVal) / valRange) * drawHeight
  372 + const yLow = gridTop + drawHeight - ((low - alignedMinVal) / valRange) * drawHeight
319 373  
320   - // 绘制高低线(直线)
  374 + // 绘制高低线
321 375 ctx.setStrokeStyle(close >= open ? color[0] : color[1])
322 376 ctx.setLineWidth(1)
323 377 ctx.beginPath()
... ... @@ -347,7 +401,7 @@ export default {
347 401 this.touchInfo.y = e.changedTouches[0].y
348 402 },
349 403 touchEnd (e) {
350   - // 可扩展点击交互逻辑
  404 + // 可扩展交互逻辑,无限制
351 405 }
352 406 }
353 407 }
... ...
pages/index/index.vue
... ... @@ -10,8 +10,8 @@
10 10 </view>
11 11 <view class="msg-icon" @click="handleMsgClick" hover-class="msg-icon--hover">
12 12 <up-icon name="chat" color="#fff" size="24"/>
13   - <view class="msg-badge" v-if="msgCount > 0">
14   - <up-badge type="error" max="999" :value="msgCount"></up-badge>
  13 + <view class="msg-badge" >
  14 + <up-badge type="error" showZero="true" max="999" :value="msgCount"></up-badge>
15 15 </view>
16 16 </view>
17 17 </view>
... ... @@ -55,11 +55,10 @@
55 55 active-color="#0A86F4"
56 56 inactive-color="#666"
57 57 font-size="14px"
58   - line-width="40"
59   - line-height="3"
  58 + lineWidth="60"
60 59 @change="handleTabChange"
61 60 class="task-tab-container"
62   - scrollable="false"
  61 + :scrollable="false"
63 62 ></up-tabs>
64 63  
65 64 <!-- z-paging分页列表 -->
... ... @@ -150,9 +149,9 @@ const klineChartData = ref([
150 149 const klineOption = ref({
151 150 grid: {
152 151 top: '25%',
153   - left: '15%',
  152 + left: '12%',
154 153 right: '8%',
155   - bottom: '20%'
  154 + bottom: '16%'
156 155 },
157 156 xAxis: {
158 157 axisLabel: {fontSize: 12, color: '#666'}
... ... @@ -372,7 +371,7 @@ $border-color: #e5e5e5; // 新增边框颜色变量
372 371 .user-info-bar {
373 372 background: url("https://img.jichengshanshui.com.cn:28207/appimg/bg.jpg") no-repeat;
374 373 background-size: 100% 100%;
375   - padding: 100px $spacing-lg 135px;
  374 + padding: 90px $spacing-lg 135px;
376 375 display: flex;
377 376 justify-content: space-between;
378 377 align-items: center;
... ... @@ -393,8 +392,8 @@ $border-color: #e5e5e5; // 新增边框颜色变量
393 392 .msg-icon {
394 393 position: relative;
395 394 padding: 5px;
  395 + top:10px;
396 396 border-radius: 50%;
397   - transition: background-color 0.2s;
398 397  
399 398 &--hover {
400 399 background-color: rgba(255, 255, 255, 0.2);
... ... @@ -413,7 +412,7 @@ $border-color: #e5e5e5; // 新增边框颜色变量
413 412 border-radius: $border-radius $border-radius 0 0;
414 413 background-color: $bg-color;
415 414 position: relative;
416   - z-index: 2;
  415 + z-index: 999;
417 416 overflow: hidden;
418 417 padding-bottom: $spacing-lg;
419 418 }
... ...
pages/workbench/index.vue
... ... @@ -166,7 +166,7 @@ const handleMenuClick = (item: MenuItem) =&gt; {
166 166 top: 0;
167 167 left: 0;
168 168 width: 100%;
169   - padding: 90px 15px 135px;
  169 + padding: 80px 15px 125px;
170 170 background: url("https://img.jichengshanshui.com.cn:28207/appimg/bg.jpg") no-repeat;
171 171 background-size: 100% 100%;
172 172 z-index: 1;
... ... @@ -191,7 +191,7 @@ const handleMenuClick = (item: MenuItem) =&gt; {
191 191 .content-wrap {
192 192 position: relative;
193 193 z-index: 2;
194   - padding: 150px 0 0;
  194 + padding: 135px 0 0;
195 195 display: flex;
196 196 flex-direction: column;
197 197 gap: 10px;
... ...