Просмотр исходного кода

fix(scale-ruler): 优化刻度尺滚动定位逻辑

- 修复初始定位计算不准确的问题
- 改进滚动过程中值的计算方式
- 添加对极端位置的边界处理
- 优化触摸结束后的滚动位置调整
- 统一使用窗口宽度常量提升性能
- 增强组件在不同屏幕尺寸下的适应性
- 完善数值更新和变更事件的触发时机

feat(health-height): 添加刻度尺实时值显示

- 新增当前身高状态展示区域
- 打印刻度尺更新和变更事件日志
- 引入 rulerValue 响应式数据绑定
- 实现刻度尺与页面数据联动效果
mcbaiyun 2 месяцев назад
Родитель
Сommit
0056d2d90e
2 измененных файлов с 56 добавлено и 18 удалено
  1. 50 16
      src/components/scale-ruler.vue
  2. 6 2
      src/pages/health/height.vue

+ 50 - 16
src/components/scale-ruler.vue

@@ -16,7 +16,7 @@
           :key="idx"
           class="grid-item"
           :class="{ long: g.isLongGrid }"
-          :style="g.showText ? { height: '40px' } : gridItemStyle"
+          :style="{ width: props.gutter + 'px', height: g.showText ? '40px' : '24px' }"
         >
           <text v-if="g.showText" class="grid-num">{{ g.displayNum }}</text>
         </view>
@@ -80,36 +80,69 @@ buildGrid()
 // offsetScroll: amount to offset due to extra grids on left
 const offsetScroll = computed(() => extraGridCount * props.gutter)
 
+// window width and half (indicator is centered horizontally)
+const windowWidth = uni?.getSystemInfoSync?.().windowWidth || 375
+const halfWindow = windowWidth / 2
+
+// centered positions for extremes
+const minCenterLeft = computed(() => offsetScroll.value - halfWindow + props.gutter / 2)
+const maxCenterLeft = computed(() => totalUnits.value * props.gutter + offsetScroll.value - halfWindow + props.gutter / 2)
+
 // initial position
 onMounted(() => {
   // center on initial value
   const initIndex = Math.round((props.initialValue - props.min) / props.step)
-  scrollLeft.value = initIndex * props.gutter + offsetScroll.value - (uni?.getSystemInfoSync?.().windowWidth || 375) / 2 + props.gutter / 2
+  // use computed windowWidth to center the initial index under the indicator
+  scrollLeft.value = initIndex * props.gutter + offsetScroll.value - halfWindow + props.gutter / 2
 })
 
 function onScroll(e: any) {
   const left = e?.detail?.scrollLeft ?? e?.detail?.scrollLeft ?? 0
   actualScrollLeft.value = left
-  let value = Math.round((left - offsetScroll.value) / props.gutter) * props.step + props.min
+  // compute index under the centered indicator (float)
+  const centerPos = left + halfWindow - offsetScroll.value - props.gutter / 2
+  const idxFloat = centerPos / props.gutter
+  // calculate actual scrollable max
+  const maxScroll = Math.max(0, totalWidth.value - windowWidth)
+  // if center would go left of min, clamp scrollLeft to minCenterLeft and emit min
+  if (idxFloat <= 0) {
+    const clamped = Math.min(Math.max(minCenterLeft.value, 0), maxScroll)
+    scrollLeft.value = clamped
+    actualScrollLeft.value = clamped
+    emit('update:value', props.min)
+    return
+  }
+  // if center would go right of max, clamp to maxCenterLeft and emit max
+  if (idxFloat >= totalUnits.value) {
+    const clamped = Math.min(Math.max(maxCenterLeft.value, 0), maxScroll)
+    scrollLeft.value = clamped
+    actualScrollLeft.value = clamped
+    emit('update:value', props.max)
+    return
+  }
+  const idx = Math.round(idxFloat)
+  let value = idx * props.step + props.min
   if (value < props.min) value = props.min
   if (value > props.max) value = props.max
   emit('update:value', value)
 }
 
 function adjustScrollPosition() {
+  // snap so the nearest tick is centered under the indicator
   const left = actualScrollLeft.value
-  const minLeft = offsetScroll.value
-  const maxLeft = (totalUnits.value) * props.gutter + offsetScroll.value
-  if (left < minLeft) {
-    scrollLeft.value = minLeft
-  } else if (left > maxLeft) {
-    scrollLeft.value = maxLeft
-  } else {
-    // snap to nearest gutter
-    const dry = left - offsetScroll.value
-    const rem = Math.round(dry / props.gutter) * props.gutter
-    scrollLeft.value = rem + offsetScroll.value
-  }
+  // compute index under center
+  const idx = Math.round((left + halfWindow - offsetScroll.value - props.gutter / 2) / props.gutter)
+  // compute target scrollLeft that centers this index
+  const targetLeft = idx * props.gutter + offsetScroll.value - halfWindow + props.gutter / 2
+  // compute centered positions for min and max so extremes map to centered
+  const minCenterLeft = 0 + offsetScroll.value - halfWindow + props.gutter / 2
+  const maxCenterLeft = totalUnits.value * props.gutter + offsetScroll.value - halfWindow + props.gutter / 2
+  // actual scrollable bounds
+  const maxScroll = Math.max(0, totalWidth.value - windowWidth)
+  // clamp target to the centered min/max first, then to actual scroll range
+  const bounded = Math.min(Math.max(targetLeft, minCenterLeft), maxCenterLeft)
+  const clamped = Math.min(Math.max(bounded, 0), maxScroll)
+  scrollLeft.value = clamped
 }
 
 function onTouchEnd() {
@@ -117,7 +150,8 @@ function onTouchEnd() {
   adjustScrollPosition()
   // compute value and emit change
   const left = scrollLeft.value
-  let value = Math.round((left - offsetScroll.value) / props.gutter) * props.step + props.min
+  const idx = Math.round((left + halfWindow - offsetScroll.value - props.gutter / 2) / props.gutter)
+  let value = idx * props.step + props.min
   if (value < props.min) value = props.min
   if (value > props.max) value = props.max
   emit('change', value)

+ 6 - 2
src/pages/health/height.vue

@@ -16,8 +16,9 @@
   <canvas ref="chartCanvas" id="chartCanvas" canvas-id="heightChart" class="chart-canvas" width="340" height="120"></canvas>
     </view>
 
-  <!-- 刻度尺控制(示例) -->
-  <ScaleRuler :min="120" :max="220" :step="1" :gutter="8" :initialValue="170" @change="onRulerChange" @update:value="onRulerUpdate" />
+    <!-- 刻度尺控制(示例) -->
+    <view class="ruler-status">当前身高:{{ rulerValue }} cm</view>
+    <ScaleRuler :min="120" :max="220" :step="1" :gutter="8" :initialValue="170" @change="onRulerChange" @update:value="onRulerUpdate" />
 
   <view class="content">
       <view class="summary">共 {{ records.length }} 条记录,本月平均:{{ averageHeight }} cm</view>
@@ -463,9 +464,12 @@ const addHeight = ref<number | null>(null)
 const rulerValue = ref<number>(170)
 function onRulerUpdate(val: number) {
   rulerValue.value = val
+  // 实时打印用户滑动时的刻度(update 在滚动过程中会频繁触发)
+  console.log('[ScaleRuler] update:value ->', val)
 }
 function onRulerChange(val: number) {
   rulerValue.value = val
+  console.log('[ScaleRuler] change ->', val)
   // 额外行为可在此扩展,例如把值填入添加输入框
   addHeight.value = val
 }