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.
- package/attributes.json +1 -1
- package/components/common/abstracts/variable.scss +449 -271
- package/components/common/util.ts +25 -0
- package/components/composables/useDynamicVirtualScroll.ts +80 -0
- package/components/composables/useVirtualScroll.ts +40 -14
- package/components/oxy-action-sheet/index.scss +8 -8
- package/components/oxy-backtop/index.scss +3 -3
- package/components/oxy-badge/index.scss +2 -2
- package/components/oxy-button/index.scss +5 -8
- package/components/oxy-calendar/index.scss +10 -10
- package/components/oxy-calendar/oxy-calendar.vue +3 -3
- package/components/oxy-calendar-view/monthPanel/index.scss +4 -5
- package/components/oxy-calendar-view/monthPanel/month-panel.vue +72 -37
- package/components/oxy-calendar-view/monthPanel/types.ts +43 -1
- package/components/oxy-calendar-view/types.ts +1 -1
- package/components/oxy-calendar-view/utils.ts +12 -1
- package/components/oxy-calendar-view/year/index.scss +1 -1
- package/components/oxy-calendar-view/yearPanel/index.scss +3 -3
- package/components/oxy-calendar-view/yearPanel/types.ts +36 -2
- package/components/oxy-calendar-view/yearPanel/year-panel.vue +64 -45
- package/components/oxy-card/index.scss +4 -4
- package/components/oxy-cell/index.scss +2 -2
- package/components/oxy-cell-group/index.scss +2 -2
- package/components/oxy-checkbox/index.scss +75 -220
- package/components/oxy-checkbox-group/index.scss +2 -2
- package/components/oxy-col-picker/index.scss +3 -3
- package/components/oxy-collapse/index.scss +1 -1
- package/components/oxy-collapse-item/index.scss +2 -2
- package/components/oxy-corner/index.scss +4 -4
- package/components/oxy-count-to/oxy-count-to.vue +3 -3
- package/components/oxy-count-to/types.ts +1 -1
- package/components/oxy-date-strip-item/index.scss +4 -4
- package/components/oxy-datetime-picker/index.scss +5 -5
- package/components/oxy-datetime-picker/types.ts +1 -1
- package/components/oxy-datetime-picker-view/types.ts +2 -2
- package/components/oxy-drop-menu/index.scss +3 -3
- package/components/oxy-drop-menu-item/index.scss +2 -2
- package/components/oxy-fab/index.scss +1 -5
- package/components/oxy-file-list/index.scss +22 -22
- package/components/oxy-footer/index.scss +2 -2
- package/components/oxy-footer/oxy-footer.vue +2 -3
- package/components/oxy-form-item/index.scss +0 -5
- package/components/oxy-grid/oxy-grid.vue +1 -1
- package/components/oxy-grid-item/index.scss +1 -1
- package/components/oxy-guidance/index.scss +15 -15
- package/components/oxy-img/index.scss +2 -2
- package/components/oxy-img-cropper/index.scss +14 -16
- package/components/oxy-img-lazy/index.scss +0 -1
- package/components/oxy-index-anchor/index.scss +5 -5
- package/components/oxy-index-bar/index.scss +3 -3
- package/components/oxy-input/index.scss +2 -2
- package/components/oxy-input-number/index.scss +21 -3
- package/components/oxy-input-number/oxy-input-number.vue +9 -1
- package/components/oxy-keyboard/index.scss +3 -3
- package/components/oxy-link/index.scss +11 -10
- package/components/oxy-loading/index.scss +1 -1
- package/components/oxy-loadmore/index.scss +1 -1
- package/components/oxy-long-press-menu/index.scss +2 -2
- package/components/oxy-message-box/index.scss +10 -10
- package/components/oxy-message-box/oxy-message-box.vue +4 -5
- package/components/oxy-message-box/types.ts +0 -5
- package/components/oxy-navbar/index.scss +1 -1
- package/components/oxy-navbar/oxy-navbar.vue +2 -3
- package/components/oxy-pagination/index.scss +5 -4
- package/components/oxy-password-input/index.scss +4 -4
- package/components/oxy-picker/index.scss +14 -14
- package/components/oxy-picker/types.ts +1 -1
- package/components/oxy-picker-view/index.scss +2 -2
- package/components/oxy-picker-view/oxy-picker-view.vue +8 -5
- package/components/oxy-picker-view/types.ts +2 -2
- package/components/oxy-popover/index.scss +8 -8
- package/components/oxy-popup/index.scss +4 -4
- package/components/oxy-progress/index.scss +3 -3
- package/components/oxy-radio/index.scss +26 -16
- package/components/oxy-radio-group/index.scss +2 -3
- package/components/oxy-rich-text/index.scss +20 -24
- package/components/oxy-rich-text/mp-html/card/card.vue +3 -3
- package/components/oxy-rich-text/mp-html/mp-html.d.ts +2 -0
- package/components/oxy-rich-text/mp-html/mp-html.vue +6 -5
- package/components/oxy-rich-text/mp-html/node/node.vue +25 -2
- package/components/oxy-rich-text/mp-html/parser.js +6 -6
- package/components/oxy-rich-text/oxy-rich-text.vue +31 -8
- package/components/oxy-search/index.scss +6 -6
- package/components/oxy-segmented/index.scss +14 -15
- package/components/oxy-select/index.scss +117 -69
- package/components/oxy-select/oxy-select.vue +24 -11
- package/components/oxy-select-picker/index.scss +2 -2
- package/components/oxy-sidebar-item/index.scss +22 -13
- package/components/oxy-skeleton/index.scss +1 -1
- package/components/oxy-slider/index.scss +8 -9
- package/components/oxy-sort-button/index.scss +3 -5
- package/components/oxy-splitter/index.scss +19 -0
- package/components/oxy-splitter/oxy-splitter.vue +409 -0
- package/components/oxy-splitter/types.ts +75 -0
- package/components/oxy-splitter-panel/index.scss +366 -0
- package/components/oxy-splitter-panel/oxy-splitter-panel.vue +432 -0
- package/components/oxy-splitter-panel/types.ts +63 -0
- package/components/oxy-step/index.scss +13 -13
- package/components/oxy-stream-render/oxy-stream-render.vue +230 -4
- package/components/oxy-swiper-nav/index.scss +5 -5
- package/components/oxy-switch/index.scss +5 -5
- package/components/oxy-tab/index.scss +8 -2
- package/components/oxy-tabbar/index.scss +5 -5
- package/components/oxy-tabbar/oxy-tabbar.vue +3 -3
- package/components/oxy-table/index.scss +5 -6
- package/components/oxy-table-col/index.scss +4 -5
- package/components/oxy-tabs/index.scss +16 -14
- package/components/oxy-tag/index.scss +111 -36
- package/components/oxy-text/index.scss +1 -1
- package/components/oxy-textarea/index.scss +3 -7
- package/components/oxy-toast/index.scss +1 -1
- package/components/oxy-tooltip/index.scss +1 -1
- package/components/oxy-tree/index.scss +35 -15
- package/components/oxy-tree/oxy-tree.vue +113 -2
- package/components/oxy-tree/types.ts +1 -0
- package/components/oxy-upload/index.scss +20 -20
- package/components/oxy-video-preview/index.scss +3 -3
- package/components/oxy-virtual-scroll/index.scss +2 -2
- package/components/oxy-voice-player/index.scss +104 -75
- package/components/oxy-watermark/index.scss +1 -1
- package/global.d.ts +2 -0
- package/package.json +1 -1
- package/tags.json +1 -1
- 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: ${
|
|
11
|
+
:style="`height: ${scrollHeight}px`"
|
|
12
12
|
scroll-y
|
|
13
13
|
@scroll="monthScroll"
|
|
14
14
|
:scroll-top="scrollTop"
|
|
15
15
|
>
|
|
16
|
-
<view
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
:
|
|
20
|
-
:
|
|
21
|
-
:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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 {
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
355
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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版本起提供,仅微信小程序和支付宝小程序支持。
|
|
@@ -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'),
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
@include b(year-panel) {
|
|
6
6
|
@include e(title) {
|
|
7
7
|
color: $-dark-color;
|
|
8
|
-
box-shadow:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
120
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
+
}
|