oxy-uni-ui 2.0.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/attributes.json +1 -1
  2. package/components/common/abstracts/variable.scss +449 -271
  3. package/components/common/util.ts +25 -0
  4. package/components/composables/useDynamicVirtualScroll.ts +80 -0
  5. package/components/composables/useVirtualScroll.ts +40 -14
  6. package/components/oxy-action-sheet/index.scss +8 -8
  7. package/components/oxy-backtop/index.scss +3 -3
  8. package/components/oxy-badge/index.scss +2 -2
  9. package/components/oxy-button/index.scss +5 -8
  10. package/components/oxy-calendar/index.scss +10 -10
  11. package/components/oxy-calendar/oxy-calendar.vue +3 -3
  12. package/components/oxy-calendar-view/monthPanel/index.scss +4 -5
  13. package/components/oxy-calendar-view/monthPanel/month-panel.vue +72 -37
  14. package/components/oxy-calendar-view/monthPanel/types.ts +43 -1
  15. package/components/oxy-calendar-view/types.ts +1 -1
  16. package/components/oxy-calendar-view/utils.ts +12 -1
  17. package/components/oxy-calendar-view/year/index.scss +1 -1
  18. package/components/oxy-calendar-view/yearPanel/index.scss +3 -3
  19. package/components/oxy-calendar-view/yearPanel/types.ts +36 -2
  20. package/components/oxy-calendar-view/yearPanel/year-panel.vue +64 -45
  21. package/components/oxy-card/index.scss +4 -4
  22. package/components/oxy-cell/index.scss +2 -2
  23. package/components/oxy-cell-group/index.scss +2 -2
  24. package/components/oxy-checkbox/index.scss +75 -220
  25. package/components/oxy-checkbox-group/index.scss +2 -2
  26. package/components/oxy-col-picker/index.scss +3 -3
  27. package/components/oxy-collapse/index.scss +1 -1
  28. package/components/oxy-collapse-item/index.scss +2 -2
  29. package/components/oxy-corner/index.scss +4 -4
  30. package/components/oxy-count-to/oxy-count-to.vue +3 -3
  31. package/components/oxy-count-to/types.ts +1 -1
  32. package/components/oxy-date-strip-item/index.scss +4 -4
  33. package/components/oxy-datetime-picker/index.scss +5 -5
  34. package/components/oxy-datetime-picker/types.ts +1 -1
  35. package/components/oxy-datetime-picker-view/types.ts +2 -2
  36. package/components/oxy-drop-menu/index.scss +3 -3
  37. package/components/oxy-drop-menu-item/index.scss +2 -2
  38. package/components/oxy-fab/index.scss +1 -5
  39. package/components/oxy-file-list/index.scss +22 -22
  40. package/components/oxy-footer/index.scss +2 -2
  41. package/components/oxy-footer/oxy-footer.vue +2 -3
  42. package/components/oxy-form-item/index.scss +0 -5
  43. package/components/oxy-grid/oxy-grid.vue +1 -1
  44. package/components/oxy-grid-item/index.scss +1 -1
  45. package/components/oxy-guidance/index.scss +15 -15
  46. package/components/oxy-img/index.scss +2 -2
  47. package/components/oxy-img-cropper/index.scss +14 -16
  48. package/components/oxy-img-lazy/index.scss +0 -1
  49. package/components/oxy-index-anchor/index.scss +5 -5
  50. package/components/oxy-index-bar/index.scss +3 -3
  51. package/components/oxy-input/index.scss +2 -2
  52. package/components/oxy-input-number/index.scss +21 -3
  53. package/components/oxy-input-number/oxy-input-number.vue +9 -1
  54. package/components/oxy-keyboard/index.scss +3 -3
  55. package/components/oxy-link/index.scss +11 -10
  56. package/components/oxy-loading/index.scss +1 -1
  57. package/components/oxy-loadmore/index.scss +1 -1
  58. package/components/oxy-long-press-menu/index.scss +2 -2
  59. package/components/oxy-message-box/index.scss +10 -10
  60. package/components/oxy-message-box/oxy-message-box.vue +4 -5
  61. package/components/oxy-message-box/types.ts +0 -5
  62. package/components/oxy-navbar/index.scss +1 -1
  63. package/components/oxy-navbar/oxy-navbar.vue +2 -3
  64. package/components/oxy-pagination/index.scss +5 -4
  65. package/components/oxy-password-input/index.scss +4 -4
  66. package/components/oxy-picker/index.scss +14 -14
  67. package/components/oxy-picker/types.ts +1 -1
  68. package/components/oxy-picker-view/index.scss +2 -2
  69. package/components/oxy-picker-view/oxy-picker-view.vue +8 -5
  70. package/components/oxy-picker-view/types.ts +2 -2
  71. package/components/oxy-popover/index.scss +8 -8
  72. package/components/oxy-popup/index.scss +4 -4
  73. package/components/oxy-progress/index.scss +3 -3
  74. package/components/oxy-radio/index.scss +26 -16
  75. package/components/oxy-radio-group/index.scss +2 -3
  76. package/components/oxy-rich-text/index.scss +20 -24
  77. package/components/oxy-rich-text/mp-html/card/card.vue +3 -3
  78. package/components/oxy-rich-text/mp-html/mp-html.d.ts +2 -0
  79. package/components/oxy-rich-text/mp-html/mp-html.vue +6 -5
  80. package/components/oxy-rich-text/mp-html/node/node.vue +25 -2
  81. package/components/oxy-rich-text/mp-html/parser.js +6 -6
  82. package/components/oxy-rich-text/oxy-rich-text.vue +31 -8
  83. package/components/oxy-search/index.scss +6 -6
  84. package/components/oxy-segmented/index.scss +14 -15
  85. package/components/oxy-select/index.scss +117 -69
  86. package/components/oxy-select/oxy-select.vue +24 -11
  87. package/components/oxy-select-picker/index.scss +2 -2
  88. package/components/oxy-sidebar-item/index.scss +22 -13
  89. package/components/oxy-skeleton/index.scss +1 -1
  90. package/components/oxy-slider/index.scss +8 -9
  91. package/components/oxy-sort-button/index.scss +3 -5
  92. package/components/oxy-splitter/index.scss +19 -0
  93. package/components/oxy-splitter/oxy-splitter.vue +409 -0
  94. package/components/oxy-splitter/types.ts +75 -0
  95. package/components/oxy-splitter-panel/index.scss +366 -0
  96. package/components/oxy-splitter-panel/oxy-splitter-panel.vue +432 -0
  97. package/components/oxy-splitter-panel/types.ts +63 -0
  98. package/components/oxy-step/index.scss +13 -13
  99. package/components/oxy-stream-render/oxy-stream-render.vue +230 -4
  100. package/components/oxy-swiper-nav/index.scss +5 -5
  101. package/components/oxy-switch/index.scss +5 -5
  102. package/components/oxy-tab/index.scss +8 -2
  103. package/components/oxy-tabbar/index.scss +5 -5
  104. package/components/oxy-tabbar/oxy-tabbar.vue +3 -3
  105. package/components/oxy-table/index.scss +5 -6
  106. package/components/oxy-table-col/index.scss +4 -5
  107. package/components/oxy-tabs/index.scss +16 -14
  108. package/components/oxy-tag/index.scss +111 -36
  109. package/components/oxy-text/index.scss +1 -1
  110. package/components/oxy-textarea/index.scss +3 -7
  111. package/components/oxy-toast/index.scss +1 -1
  112. package/components/oxy-tooltip/index.scss +1 -1
  113. package/components/oxy-tree/index.scss +35 -15
  114. package/components/oxy-tree/oxy-tree.vue +113 -2
  115. package/components/oxy-tree/types.ts +1 -0
  116. package/components/oxy-upload/index.scss +20 -20
  117. package/components/oxy-video-preview/index.scss +3 -3
  118. package/components/oxy-virtual-scroll/index.scss +2 -2
  119. package/components/oxy-voice-player/index.scss +104 -75
  120. package/components/oxy-watermark/index.scss +1 -1
  121. package/global.d.ts +2 -0
  122. package/package.json +1 -1
  123. package/tags.json +1 -1
  124. package/web-types.json +1 -1
@@ -8,27 +8,34 @@
8
8
  </view>
9
9
  <scroll-view
10
10
  :class="`oxy-month-panel__container ${!!timeType ? 'oxy-month-panel__container--time' : ''}`"
11
- :style="`height: ${unitConvert(scrollHeight, 0, { output: 'px' })}`"
11
+ :style="`height: ${scrollHeight}px`"
12
12
  scroll-y
13
13
  @scroll="monthScroll"
14
14
  :scroll-top="scrollTop"
15
15
  >
16
- <view v-for="(item, index) in months" :key="index" :id="`month${index}`">
17
- <month
18
- :type="type"
19
- :date="item.date"
20
- :value="value"
21
- :min-date="minDate"
22
- :max-date="maxDate"
23
- :first-day-of-week="firstDayOfWeek"
24
- :formatter="formatter"
25
- :max-range="maxRange"
26
- :range-prompt="rangePrompt"
27
- :allow-same-day="allowSameDay"
28
- :default-time="defaultTime"
29
- :showTitle="index !== 0"
30
- @change="handleDateChange"
31
- />
16
+ <view class="oxy-month-panel__list" :style="`height: ${totalHeight}px; position: relative;`">
17
+ <view
18
+ v-for="item in visibleMonths"
19
+ :key="item.date"
20
+ :id="`month${item.index}`"
21
+ :style="`position: absolute; top: ${item.top}px; width: 100%;`"
22
+ >
23
+ <month
24
+ :type="type"
25
+ :date="item.date"
26
+ :value="value"
27
+ :min-date="minDate"
28
+ :max-date="maxDate"
29
+ :first-day-of-week="firstDayOfWeek"
30
+ :formatter="formatter"
31
+ :max-range="maxRange"
32
+ :range-prompt="rangePrompt"
33
+ :allow-same-day="allowSameDay"
34
+ :default-time="defaultTime"
35
+ :showTitle="item.index !== 0"
36
+ @change="handleDateChange"
37
+ />
38
+ </view>
32
39
  </view>
33
40
  </scroll-view>
34
41
  <view v-if="timeType" class="oxy-month-panel__time">
@@ -65,10 +72,23 @@ export default {
65
72
  import OxyPickerView from '../../oxy-picker-view/oxy-picker-view.vue'
66
73
  import { computed, ref, watch, onMounted } from 'vue'
67
74
  import { debounce, isArray, isEqual, isNumber, pause, unitConvert } from '../../common/util'
68
- import { compareMonth, formatMonthTitle, getMonthEndDay, getMonths, getTimeData, getWeekLabel } from '../utils'
75
+ import {
76
+ compareMonth,
77
+ formatMonthTitle,
78
+ getMonthEndDay,
79
+ getMonths,
80
+ getTimeData,
81
+ getWeekLabel,
82
+ designPxToRuntimePx,
83
+ MONTH_ROW_HEIGHT,
84
+ MONTH_ROW_GAP,
85
+ MONTH_TITLE_HEIGHT,
86
+ TIME_PICKER_HEIGHT
87
+ } from '../utils'
69
88
  import Month from '../month/month.vue'
70
89
  import { monthPanelProps, type MonthInfo, type MonthPanelTimeType, type MonthPanelExpose } from './types'
71
90
  import { useTranslate } from '../../composables/useTranslate'
91
+ import { useDynamicVirtualScroll } from '../../composables/useDynamicVirtualScroll'
72
92
  import type { CalendarItem } from '../types'
73
93
 
74
94
  const props = defineProps(monthPanelProps)
@@ -77,18 +97,13 @@ const emit = defineEmits(['change', 'pickstart', 'pickend'])
77
97
  const { translate } = useTranslate('calendar-view')
78
98
 
79
99
  const scrollTop = ref<number>(0) // 滚动位置
100
+ const currentScrollTop = ref<number>(0) // 当前虚拟渲染滚动位置
80
101
  const scrollIndex = ref<number>(0) // 当前显示的月份索引
81
102
  const timeValue = ref<number[]>([]) // 当前选中的时分秒
82
103
 
83
104
  const timeType = ref<MonthPanelTimeType>('') // 当前时间类型,是开始还是结束
84
105
  const innerValue = ref<string | number | (number | null)[]>('') // 内部保存一个值,用于判断新老值,避免监听器触发
85
106
 
86
- const designPxToRuntimePx = (designPx: number): number => unitConvert(`${designPx * 2}rpx`)
87
- const MONTH_ROW_HEIGHT = designPxToRuntimePx(64)
88
- const MONTH_ROW_GAP = designPxToRuntimePx(4)
89
- const MONTH_TITLE_HEIGHT = designPxToRuntimePx(45)
90
- const TIME_PICKER_HEIGHT = designPxToRuntimePx(125)
91
-
92
107
  const handleChange = debounce((value) => {
93
108
  emit('change', {
94
109
  value
@@ -139,23 +154,44 @@ const weekLabel = computed(() => {
139
154
  // 滚动区域的高度
140
155
  const scrollHeight = computed(() => {
141
156
  const panelHeight = designPxToRuntimePx(props.panelHeight)
142
- const scrollHeight: number = timeType.value ? panelHeight - TIME_PICKER_HEIGHT : panelHeight
157
+ const scrollHeight: number = panelHeight - (props.showPanelTitle ? MONTH_TITLE_HEIGHT : 0) - (timeType.value ? TIME_PICKER_HEIGHT : 0)
143
158
  return scrollHeight
144
159
  })
145
160
 
146
161
  // 月份日期和月份高度
147
162
  const months = computed<MonthInfo[]>(() => {
163
+ let top = 0
148
164
  return getMonths(props.minDate, props.maxDate).map((month, index) => {
149
165
  const offset = (7 + new Date(month).getDay() - props.firstDayOfWeek) % 7
150
166
  const totalDay = getMonthEndDay(new Date(month).getFullYear(), new Date(month).getMonth() + 1)
151
167
  const rows = Math.ceil((offset + totalDay) / 7)
168
+ const height = rows * MONTH_ROW_HEIGHT + (rows - 1) * MONTH_ROW_GAP + (index === 0 ? 0 : MONTH_TITLE_HEIGHT)
169
+ const currentTop = top
170
+ top += height
152
171
  return {
153
- height: rows * MONTH_ROW_HEIGHT + (rows - 1) * MONTH_ROW_GAP + (index === 0 ? 0 : MONTH_TITLE_HEIGHT),
172
+ height,
173
+ top: currentTop,
154
174
  date: month
155
175
  }
156
176
  })
157
177
  })
158
178
 
179
+ const totalHeight = computed(() => {
180
+ if (months.value.length === 0) return 0
181
+ const last = months.value[months.value.length - 1]
182
+ return last.top + last.height
183
+ })
184
+
185
+ // 滚动区域的高度(用于计算可视区域)
186
+ const viewportHeight = computed(() => designPxToRuntimePx(props.panelHeight))
187
+
188
+ // 引入动态虚拟滚动 Hook
189
+ const { visibleData: visibleMonths, getTargetIndex } = useDynamicVirtualScroll<MonthInfo>({
190
+ data: months,
191
+ scrollTop: currentScrollTop,
192
+ viewportHeight
193
+ })
194
+
159
195
  watch(
160
196
  () => props.type,
161
197
  (val) => {
@@ -229,8 +265,13 @@ async function scrollIntoView() {
229
265
  }
230
266
  scrollTop.value = 0
231
267
  if (top > 0) {
268
+ const targetTop = top + (activeMonthIndex > 0 && compareMonth(months.value[0].date, activeDate) !== 0 ? MONTH_TITLE_HEIGHT : 0)
269
+ currentScrollTop.value = targetTop
270
+ // 在首次渲染前同步 currentScrollTop,避免闪烁
271
+ scrollTop.value = targetTop
232
272
  await pause()
233
- scrollTop.value = top + (activeMonthIndex > 0 ? MONTH_TITLE_HEIGHT : 0)
273
+ } else {
274
+ currentScrollTop.value = 0
234
275
  }
235
276
  }
236
277
  /**
@@ -351,8 +392,9 @@ const monthScroll = (event: { detail: { scrollTop: number } }) => {
351
392
  if (months.value.length <= 1) {
352
393
  return
353
394
  }
354
- const scrollTop = Math.max(0, event.detail.scrollTop)
355
- doSetSubtitle(scrollTop)
395
+ const st = Math.max(0, event.detail.scrollTop)
396
+ currentScrollTop.value = st
397
+ doSetSubtitle(st)
356
398
  }
357
399
 
358
400
  /**
@@ -360,14 +402,7 @@ const monthScroll = (event: { detail: { scrollTop: number } }) => {
360
402
  * scrollTop 滚动条位置
361
403
  */
362
404
  function doSetSubtitle(scrollTop: number) {
363
- let height: number = 0 // 月份高度和
364
- for (let index = 0; index < months.value.length; index++) {
365
- height = height + months.value[index].height
366
- if (scrollTop < height) {
367
- scrollIndex.value = index
368
- return
369
- }
370
- }
405
+ scrollIndex.value = getTargetIndex(scrollTop)
371
406
  }
372
407
 
373
408
  defineExpose<MonthPanelExpose>({
@@ -8,25 +8,67 @@ import type { CalendarFormatter, CalendarTimeFilter, CalendarType } from '../typ
8
8
  export interface MonthInfo {
9
9
  date: number
10
10
  height: number
11
+ top: number
11
12
  }
12
13
 
13
14
  export const monthPanelProps = {
15
+ /**
16
+ * 日期类型
17
+ */
14
18
  type: makeRequiredProp(String as PropType<CalendarType>),
19
+ /**
20
+ * 选中值
21
+ */
15
22
  value: makeRequiredProp([Number, Array, null] as PropType<number | (number | null)[] | null>),
23
+ /**
24
+ * 最小日期时间戳
25
+ */
16
26
  minDate: makeRequiredProp(Number),
27
+ /**
28
+ * 最大日期时间戳
29
+ */
17
30
  maxDate: makeRequiredProp(Number),
31
+ /**
32
+ * 一周的第一天
33
+ */
18
34
  firstDayOfWeek: makeRequiredProp(Number),
35
+ /**
36
+ * 格式化函数
37
+ */
19
38
  formatter: Function as PropType<CalendarFormatter>,
39
+ /**
40
+ * 最大范围限制
41
+ */
20
42
  maxRange: Number,
43
+ /**
44
+ * 超出范围提示
45
+ */
21
46
  rangePrompt: String,
47
+ /**
48
+ * 是否允许选择同一天
49
+ */
22
50
  allowSameDay: makeBooleanProp(false),
51
+ /**
52
+ * 是否展示面板标题
53
+ */
23
54
  showPanelTitle: makeBooleanProp(false),
55
+ /**
56
+ * 默认时间
57
+ */
24
58
  defaultTime: {
25
59
  type: [Array] as PropType<Array<number[]>>
26
60
  },
61
+ /**
62
+ * 面板高度
63
+ */
27
64
  panelHeight: makeRequiredProp(Number),
28
- // type 为 'datetime' 或 'datetimerange' 时有效,用于过滤时间选择器的数据
65
+ /**
66
+ * 用于过滤时间选择器的数据
67
+ */
29
68
  timeFilter: Function as PropType<CalendarTimeFilter>,
69
+ /**
70
+ * 是否隐藏秒
71
+ */
30
72
  hideSecond: makeBooleanProp(false),
31
73
  /**
32
74
  * 是否在手指松开时立即触发picker-view的 change 事件。若不开启则会在滚动动画结束后触发 change 事件,1.2.25版本起提供,仅微信小程序和支付宝小程序支持。
@@ -53,7 +53,7 @@ export const calendarViewProps = {
53
53
  /**
54
54
  * 可滚动面板的高度
55
55
  */
56
- panelHeight: makeNumberProp(378),
56
+ panelHeight: makeNumberProp(756),
57
57
  /**
58
58
  * type 为 'datetime' 或 'datetimerange' 时有效,用于过滤时间选择器的数据
59
59
  */
@@ -1,10 +1,21 @@
1
1
  import { computed } from 'vue'
2
2
  import dayjs from '../../dayjs'
3
- import { isArray, isFunction, padZero } from '../common/util'
3
+ import { isArray, isFunction, padZero, unitConvert } from '../common/util'
4
4
  import { useTranslate } from '../composables/useTranslate'
5
5
  import type { CalendarDayType, CalendarItem, CalendarTimeFilter, CalendarType } from './types'
6
6
  const { translate } = useTranslate('calendar-view')
7
7
 
8
+ export const designPxToRuntimePx = (designPx: number): number => unitConvert(`${designPx}rpx`)
9
+ export const MONTH_ROW_HEIGHT = designPxToRuntimePx(128)
10
+ export const MONTH_ROW_GAP = designPxToRuntimePx(8)
11
+ export const MONTH_TITLE_HEIGHT = designPxToRuntimePx(90)
12
+ export const YEAR_TITLE_HEIGHT = designPxToRuntimePx(90)
13
+ export const TIME_PICKER_HEIGHT = designPxToRuntimePx(250)
14
+ export const PANEL_EXTRA_WITH_TITLE = designPxToRuntimePx(52)
15
+ export const PANEL_EXTRA_NO_TITLE = designPxToRuntimePx(32)
16
+ export const YEAR_HEIGHT_WITHOUT_TITLE = MONTH_ROW_HEIGHT * 3 + MONTH_ROW_GAP * 2
17
+ export const YEAR_HEIGHT_WITH_TITLE = YEAR_HEIGHT_WITHOUT_TITLE + YEAR_TITLE_HEIGHT
18
+
8
19
  const weeks = computed(() => {
9
20
  return [
10
21
  translate('weeks.sun'),
@@ -58,7 +58,7 @@
58
58
  }
59
59
 
60
60
  @include when(selected) {
61
- color: #fff;
61
+ color: $-calendar-selected-color;
62
62
 
63
63
  .oxy-year__month-text {
64
64
  border-radius: $-calendar-active-border;
@@ -5,7 +5,7 @@
5
5
  @include b(year-panel) {
6
6
  @include e(title) {
7
7
  color: $-dark-color;
8
- box-shadow: 0 8rpx 16rpx 0 rgba(255, 255,255, 0.02);
8
+ box-shadow: $-calendar-panel-shadow-dark;
9
9
  }
10
10
  }
11
11
  }
@@ -15,10 +15,10 @@
15
15
  padding: $-calendar-panel-padding;
16
16
 
17
17
  @include e(title) {
18
- padding: 10rpx 0;
18
+ padding: 16rpx 0;
19
19
  text-align: center;
20
20
  font-size: $-calendar-panel-title-fs;
21
21
  color: $-calendar-panel-title-color;
22
- box-shadow: 0 8rpx 16rpx 0 rgba(0, 0, 0, 0.02);
22
+ box-shadow: $-calendar-panel-shadow-light;
23
23
  }
24
24
  }
@@ -3,26 +3,60 @@ import { makeBooleanProp, makeRequiredProp } from '../../common/props'
3
3
  import type { CalendarFormatter, CalendarType } from '../types'
4
4
 
5
5
  /**
6
- * 月份信息
6
+ * 年份信息
7
7
  */
8
8
  export interface YearInfo {
9
9
  date: number
10
10
  height: number
11
+ top: number
11
12
  }
12
13
 
13
14
  export const yearPanelProps = {
15
+ /**
16
+ * 日期类型
17
+ */
14
18
  type: makeRequiredProp(String as PropType<CalendarType>),
15
- value: makeRequiredProp([Number, Array] as PropType<number | (number | null)[] | null>),
19
+ /**
20
+ * 选中值
21
+ */
22
+ value: makeRequiredProp([Number, Array, null] as PropType<number | (number | null)[] | null>),
23
+ /**
24
+ * 最小日期时间戳
25
+ */
16
26
  minDate: makeRequiredProp(Number),
27
+ /**
28
+ * 最大日期时间戳
29
+ */
17
30
  maxDate: makeRequiredProp(Number),
31
+ /**
32
+ * 格式化函数
33
+ */
18
34
  formatter: Function as PropType<CalendarFormatter>,
35
+ /**
36
+ * 最大范围限制
37
+ */
19
38
  maxRange: Number,
39
+ /**
40
+ * 超出范围提示
41
+ */
20
42
  rangePrompt: String,
43
+ /**
44
+ * 是否允许选择同一天
45
+ */
21
46
  allowSameDay: makeBooleanProp(false),
47
+ /**
48
+ * 是否展示标题
49
+ */
22
50
  showPanelTitle: makeBooleanProp(false),
51
+ /**
52
+ * 默认时间
53
+ */
23
54
  defaultTime: {
24
55
  type: [Array] as PropType<Array<number[]>>
25
56
  },
57
+ /**
58
+ * 面板高度
59
+ */
26
60
  panelHeight: makeRequiredProp(Number)
27
61
  }
28
62
 
@@ -1,28 +1,24 @@
1
1
  <template>
2
2
  <view class="oxy-year-panel">
3
3
  <view v-if="showPanelTitle" class="oxy-year-panel__title">{{ title }}</view>
4
- <scroll-view
5
- class="oxy-year-panel__container"
6
- :style="`height: ${unitConvert(scrollHeight, 0, { output: 'px' })}`"
7
- scroll-y
8
- @scroll="yearScroll"
9
- :scroll-top="scrollTop"
10
- >
11
- <view v-for="(item, index) in years" :key="index" :id="`year${index}`">
12
- <year
13
- :type="type"
14
- :date="item.date"
15
- :value="value"
16
- :min-date="minDate"
17
- :max-date="maxDate"
18
- :max-range="maxRange"
19
- :formatter="formatter"
20
- :range-prompt="rangePrompt"
21
- :allow-same-day="allowSameDay"
22
- :default-time="defaultTime"
23
- :showTitle="index !== 0"
24
- @change="handleDateChange"
25
- />
4
+ <scroll-view class="oxy-year-panel__container" :style="`height: ${scrollHeight}px`" scroll-y @scroll="yearScroll" :scroll-top="scrollTop">
5
+ <view class="oxy-year-panel__list" :style="`height: ${totalHeight}px; position: relative;`">
6
+ <view v-for="item in visibleYears" :key="item.date" :id="`year${item.index}`" :style="`position: absolute; top: ${item.top}px; width: 100%;`">
7
+ <year
8
+ :type="type"
9
+ :date="item.date"
10
+ :value="value"
11
+ :min-date="minDate"
12
+ :max-date="maxDate"
13
+ :max-range="maxRange"
14
+ :formatter="formatter"
15
+ :range-prompt="rangePrompt"
16
+ :allow-same-day="allowSameDay"
17
+ :default-time="defaultTime"
18
+ :showTitle="item.index !== 0"
19
+ @change="handleDateChange"
20
+ />
21
+ </view>
26
22
  </view>
27
23
  </scroll-view>
28
24
  </view>
@@ -39,8 +35,19 @@ export default {
39
35
 
40
36
  <script lang="ts" setup>
41
37
  import { computed, ref, onMounted } from 'vue'
42
- import { compareYear, formatYearTitle, getYears } from '../utils'
38
+ import {
39
+ compareYear,
40
+ formatYearTitle,
41
+ getYears,
42
+ designPxToRuntimePx,
43
+ YEAR_HEIGHT_WITH_TITLE,
44
+ YEAR_HEIGHT_WITHOUT_TITLE,
45
+ PANEL_EXTRA_NO_TITLE,
46
+ PANEL_EXTRA_WITH_TITLE,
47
+ YEAR_TITLE_HEIGHT
48
+ } from '../utils'
43
49
  import { isArray, isNumber, pause, unitConvert } from '../../common/util'
50
+ import { useDynamicVirtualScroll } from '../../composables/useDynamicVirtualScroll'
44
51
  import Year from '../year/year.vue'
45
52
  import { yearPanelProps, type YearInfo, type YearPanelExpose } from './types'
46
53
 
@@ -48,34 +55,47 @@ const props = defineProps(yearPanelProps)
48
55
  const emit = defineEmits(['change'])
49
56
 
50
57
  const scrollTop = ref<number>(0) // 滚动位置
58
+ const currentScrollTop = ref<number>(0) // 当前虚拟渲染滚动位置
51
59
  const scrollIndex = ref<number>(0) // 当前显示的年份索引
52
60
 
53
- const designPxToRuntimePx = (designPx: number): number => unitConvert(`${designPx * 2}rpx`)
54
- const YEAR_MONTH_HEIGHT = designPxToRuntimePx(64)
55
- const YEAR_MONTH_GAP = designPxToRuntimePx(4)
56
- const YEAR_TITLE_HEIGHT = designPxToRuntimePx(45)
57
- const PANEL_EXTRA_WITH_TITLE = designPxToRuntimePx(26)
58
- const PANEL_EXTRA_NO_TITLE = designPxToRuntimePx(16)
59
- const YEAR_HEIGHT_WITHOUT_TITLE = YEAR_MONTH_HEIGHT * 3 + YEAR_MONTH_GAP * 2
60
- const YEAR_HEIGHT_WITH_TITLE = YEAR_HEIGHT_WITHOUT_TITLE + YEAR_TITLE_HEIGHT
61
-
62
61
  // 滚动区域的高度
63
62
  const scrollHeight = computed(() => {
64
63
  const panelHeight = designPxToRuntimePx(props.panelHeight)
65
- const scrollHeight: number = panelHeight + (props.showPanelTitle ? PANEL_EXTRA_WITH_TITLE : PANEL_EXTRA_NO_TITLE)
64
+ const scrollHeight: number = panelHeight - (props.showPanelTitle ? YEAR_TITLE_HEIGHT : 0)
66
65
  return scrollHeight
67
66
  })
68
67
 
69
68
  // 年份信息
70
69
  const years = computed<YearInfo[]>(() => {
70
+ let top = 0
71
71
  return getYears(props.minDate, props.maxDate).map((year, index) => {
72
+ const height = index === 0 ? YEAR_HEIGHT_WITHOUT_TITLE : YEAR_HEIGHT_WITH_TITLE
73
+ const currentTop = top
74
+ top += height
72
75
  return {
73
76
  date: year,
74
- height: index === 0 ? YEAR_HEIGHT_WITHOUT_TITLE : YEAR_HEIGHT_WITH_TITLE
77
+ height,
78
+ top: currentTop
75
79
  }
76
80
  })
77
81
  })
78
82
 
83
+ const totalHeight = computed(() => {
84
+ if (years.value.length === 0) return 0
85
+ const last = years.value[years.value.length - 1]
86
+ return last.top + last.height
87
+ })
88
+
89
+ // 滚动区域的高度(用于计算可视区域)
90
+ const viewportHeight = computed(() => designPxToRuntimePx(props.panelHeight))
91
+
92
+ // 引入动态虚拟滚动 Hook
93
+ const { visibleData: visibleYears, getTargetIndex } = useDynamicVirtualScroll<YearInfo>({
94
+ data: years,
95
+ scrollTop: currentScrollTop,
96
+ viewportHeight
97
+ })
98
+
79
99
  // 标题
80
100
  const title = computed(() => {
81
101
  return formatYearTitle(years.value[scrollIndex.value].date)
@@ -107,8 +127,13 @@ async function scrollIntoView() {
107
127
  }
108
128
  scrollTop.value = 0
109
129
  if (top > 0) {
130
+ const targetTop = top + (activeDate && compareYear(years.value[0].date, activeDate) !== 0 ? YEAR_TITLE_HEIGHT : 0)
131
+ currentScrollTop.value = targetTop
132
+ // 在首次渲染前同步 currentScrollTop,避免闪烁
133
+ scrollTop.value = targetTop
110
134
  await pause()
111
- scrollTop.value = top + YEAR_TITLE_HEIGHT
135
+ } else {
136
+ currentScrollTop.value = 0
112
137
  }
113
138
  }
114
139
 
@@ -116,8 +141,9 @@ const yearScroll = (event: { detail: { scrollTop: number } }) => {
116
141
  if (years.value.length <= 1) {
117
142
  return
118
143
  }
119
- const scrollTop = Math.max(0, event.detail.scrollTop)
120
- doSetSubtitle(scrollTop)
144
+ const st = Math.max(0, event.detail.scrollTop)
145
+ currentScrollTop.value = st
146
+ doSetSubtitle(st)
121
147
  }
122
148
 
123
149
  /**
@@ -125,14 +151,7 @@ const yearScroll = (event: { detail: { scrollTop: number } }) => {
125
151
  * scrollTop 滚动条位置
126
152
  */
127
153
  function doSetSubtitle(scrollTop: number) {
128
- let height: number = 0 // 月份高度和
129
- for (let index = 0; index < years.value.length; index++) {
130
- height = height + years.value[index].height
131
- if (scrollTop < height) {
132
- scrollIndex.value = index
133
- return
134
- }
135
- }
154
+ scrollIndex.value = getTargetIndex(scrollTop)
136
155
  }
137
156
 
138
157
  function handleDateChange({ value }: { value: number[] }) {
@@ -5,7 +5,7 @@
5
5
  background-color: $-dark-background2;
6
6
 
7
7
  @include when(rectangle) {
8
-
8
+
9
9
  .oxy-card__content {
10
10
  @include halfPixelBorder('top', 0, $-dark-border-color);
11
11
  }
@@ -31,7 +31,7 @@
31
31
  border-radius: $-card-radius;
32
32
  box-shadow: $-card-shadow-color;
33
33
  font-size: $-card-fs;
34
- margin-bottom: 24rpx;
34
+ margin-bottom: $-spacing-24;
35
35
 
36
36
  @include when(rectangle) {
37
37
  margin-left: 0;
@@ -40,7 +40,7 @@
40
40
  box-shadow: none;
41
41
 
42
42
  .oxy-card__title-content {
43
- font-size: $-card-fs;
43
+ font-size: $-card-title-fs;
44
44
  }
45
45
  .oxy-card__content {
46
46
  position: relative;
@@ -68,4 +68,4 @@
68
68
  padding: $-card-footer-padding;
69
69
  text-align: right;
70
70
  }
71
- }
71
+ }
@@ -109,7 +109,7 @@
109
109
  }
110
110
 
111
111
  @include e(label) {
112
- margin-top: 4rpx;
112
+ margin-top: 8rpx;
113
113
  font-size: $-cell-label-fs;
114
114
  color: $-cell-label-color;
115
115
  }
@@ -151,7 +151,7 @@
151
151
 
152
152
  @include edeep(arrow-right) {
153
153
  display: block;
154
- margin-left: 16rpx;
154
+ margin-left: $-spacing-16;
155
155
  width: $-cell-arrow-size;
156
156
  font-size: $-cell-arrow-size;
157
157
  color: $-cell-arrow-color;
@@ -44,7 +44,7 @@
44
44
  font-size: $-cell-group-title-fs;
45
45
  color: $-cell-group-title-color;
46
46
  font-weight: $-fw-medium;
47
- line-height: 1.43;
47
+ line-height: $-line-height-loose;
48
48
  }
49
49
  @include e(right) {
50
50
  color: $-cell-group-value-color;
@@ -53,4 +53,4 @@
53
53
  @include e(body) {
54
54
  background: $-color-white;
55
55
  }
56
- }
56
+ }