oxy-uni-ui 1.0.1 → 1.1.0

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 (54) hide show
  1. package/LICENSE +1 -1
  2. package/attributes.json +1 -1
  3. package/components/common/abstracts/variable.scss +32 -4
  4. package/components/common/util.ts +44 -0
  5. package/components/oxy-checkbox/index.scss +36 -6
  6. package/components/oxy-checkbox/oxy-checkbox.vue +5 -4
  7. package/components/oxy-checkbox/types.ts +2 -1
  8. package/components/oxy-col-picker/index.scss +18 -15
  9. package/components/oxy-col-picker/oxy-col-picker.vue +28 -3
  10. package/components/oxy-col-picker/types.ts +12 -0
  11. package/components/oxy-corner/index.scss +138 -0
  12. package/components/oxy-corner/oxy-corner.vue +66 -0
  13. package/components/oxy-corner/types.ts +43 -0
  14. package/components/oxy-drop-menu/index.scss +4 -0
  15. package/components/oxy-drop-menu/oxy-drop-menu.vue +5 -3
  16. package/components/oxy-drop-menu/types.ts +1 -1
  17. package/components/oxy-drop-menu-item/index.scss +4 -4
  18. package/components/oxy-drop-menu-item/oxy-drop-menu-item.vue +2 -0
  19. package/components/oxy-file-list/index.scss +83 -0
  20. package/components/oxy-file-list/oxy-file-list.vue +213 -0
  21. package/components/oxy-file-list/types.ts +54 -0
  22. package/components/oxy-list/index.scss +4 -0
  23. package/components/oxy-list/oxy-list.vue +125 -0
  24. package/components/oxy-list/types.ts +50 -0
  25. package/components/oxy-slider/index.scss +2 -2
  26. package/components/oxy-swiper/index.scss +1 -2
  27. package/components/oxy-textarea/oxy-textarea.vue +0 -4
  28. package/components/oxy-tree/components/tree-node-content.vue +72 -0
  29. package/components/oxy-tree/index.scss +61 -0
  30. package/components/oxy-tree/index.ts +51 -0
  31. package/components/oxy-tree/oxy-tree.vue +289 -0
  32. package/components/oxy-tree/types.ts +48 -0
  33. package/components/oxy-upload/images/audio.png +0 -0
  34. package/components/oxy-upload/images/excle.png +0 -0
  35. package/components/oxy-upload/images/other.png +0 -0
  36. package/components/oxy-upload/images/pdf.png +0 -0
  37. package/components/oxy-upload/images/pic.png +0 -0
  38. package/components/oxy-upload/images/txt.png +0 -0
  39. package/components/oxy-upload/images/video.png +0 -0
  40. package/components/oxy-upload/images/word.png +0 -0
  41. package/components/oxy-upload/index.scss +50 -0
  42. package/components/oxy-upload/oxy-upload.vue +93 -7
  43. package/components/oxy-upload/types.ts +22 -1
  44. package/components/oxy-virtual-scroll/index.scss +35 -0
  45. package/components/oxy-virtual-scroll/oxy-virtual-scroll.vue +184 -0
  46. package/components/oxy-virtual-scroll/types.ts +65 -0
  47. package/components/oxy-virtual-scroll/virtual-scroll.ts +81 -0
  48. package/global.d.ts +3 -0
  49. package/locale/lang/ar-SA.ts +2 -1
  50. package/locale/lang/en-US.ts +2 -1
  51. package/locale/lang/zh-CN.ts +2 -1
  52. package/package.json +1 -1
  53. package/tags.json +1 -1
  54. package/web-types.json +1 -1
@@ -1,9 +1,33 @@
1
1
  <template>
2
- <view :class="['oxy-upload', customClass]" :style="customStyle">
2
+ <view :class="['oxy-upload', { 'oxy-upload__position-right': props.listPosition === 'right' }, customClass]" :style="customStyle">
3
+ <block v-if="showUpload && props.listPosition === 'right'">
4
+ <view :class="['oxy-upload__evoke-slot', customEvokeClass]" v-if="$slots.default" @click="onEvokeClick">
5
+ <slot></slot>
6
+ </view>
7
+ <!-- 唤起项 -->
8
+ <view v-else @click="onEvokeClick" :class="['oxy-upload__evoke', disabled ? 'is-disabled' : '', customEvokeClass]">
9
+ <!-- 唤起项图标 -->
10
+ <oxy-icon
11
+ class="oxy-upload__evoke-icon"
12
+ :name="props.evokeIcon ? props.evokeIcon : props.listType === 'card' ? 'add' : 'fill-camera'"
13
+ ></oxy-icon>
14
+ <!-- 有限制个数时确认是否展示限制个数 -->
15
+ <view v-if="limit && showLimitNum" class="oxy-upload__evoke-num">({{ uploadFiles.length }}/{{ limit }})</view>
16
+ </view>
17
+ </block>
18
+
3
19
  <!-- 预览列表 -->
4
- <view :class="['oxy-upload__preview', customPreviewClass]" v-for="(file, index) in uploadFiles" :key="index">
20
+ <view
21
+ :class="['oxy-upload__preview', { 'oxy-upload__preview-file': props.listType === 'card' }, customPreviewClass]"
22
+ v-for="(file, index) in uploadFiles"
23
+ :key="index"
24
+ >
5
25
  <!-- 成功时展示图片 -->
6
- <view class="oxy-upload__status-content">
26
+ <view v-if="props.listType === 'card'" class="oxy-upload__status-content">
27
+ <image v-if="isImage(file)" :src="file.url" :mode="imageMode" class="oxy-upload__picture" @click="onPreviewImage(file)" />
28
+ <image v-else :src="getUploadImage(file)" :mode="imageMode" class="oxy-upload__picture-icon" @click="onPreview(file)" />
29
+ </view>
30
+ <view v-else class="oxy-upload__status-content">
7
31
  <image v-if="isImage(file)" :src="file.url" :mode="imageMode" class="oxy-upload__picture" @click="onPreviewImage(file)" />
8
32
  <template v-else-if="isVideo(file)">
9
33
  <view class="oxy-upload__video" v-if="file.thumb" @click="onPreviewVideo(file)">
@@ -66,16 +90,26 @@
66
90
  ></oxy-icon>
67
91
  <!-- 自定义预览样式 -->
68
92
  <slot name="preview-cover" v-if="$slots['preview-cover']" :file="file" :index="index"></slot>
93
+ <oxy-tooltip v-if="props.listType === 'card'" placement="top" :content="file.name">
94
+ <text class="oxy-upload____status-content-name">{{ file.name }}</text>
95
+ </oxy-tooltip>
69
96
  </view>
70
97
 
71
- <block v-if="showUpload">
98
+ <block v-if="showUpload && props.listPosition === 'left'">
72
99
  <view :class="['oxy-upload__evoke-slot', customEvokeClass]" v-if="$slots.default" @click="onEvokeClick">
73
100
  <slot></slot>
74
101
  </view>
75
102
  <!-- 唤起项 -->
76
- <view v-else @click="onEvokeClick" :class="['oxy-upload__evoke', disabled ? 'is-disabled' : '', customEvokeClass]">
103
+ <view
104
+ v-else
105
+ @click="onEvokeClick"
106
+ :class="['oxy-upload__evoke', { 'oxy-upload__evoke-file': props.listType === 'card' }, disabled ? 'is-disabled' : '', customEvokeClass]"
107
+ >
77
108
  <!-- 唤起项图标 -->
78
- <oxy-icon class="oxy-upload__evoke-icon" name="fill-camera"></oxy-icon>
109
+ <oxy-icon
110
+ class="oxy-upload__evoke-icon"
111
+ :name="props.evokeIcon ? props.evokeIcon : props.listType === 'card' ? 'add' : 'fill-camera'"
112
+ ></oxy-icon>
79
113
  <!-- 有限制个数时确认是否展示限制个数 -->
80
114
  <view v-if="limit && showLimitNum" class="oxy-upload__evoke-num">({{ uploadFiles.length }}/{{ limit }})</view>
81
115
  </view>
@@ -96,12 +130,19 @@ export default {
96
130
  </script>
97
131
 
98
132
  <script lang="ts" setup>
133
+ import ImgPdf from './images/pdf.png'
134
+ import ImgWord from './images/word.png'
135
+ import ImgAudio from './images/audio.png'
136
+ import ImgVideo from './images/video.png'
137
+ import ImgPic from './images/pic.png'
138
+ import ImgOther from './images/other.png'
99
139
  import OxyIcon from '../oxy-icon/oxy-icon.vue'
100
140
  import OxyVideoPreview from '../oxy-video-preview/oxy-video-preview.vue'
101
141
  import OxyLoading from '../oxy-loading/oxy-loading.vue'
142
+ import OxyTooltip from '../oxy-tooltip/oxy-tooltip.vue'
102
143
 
103
144
  import { computed, ref, watch } from 'vue'
104
- import { context, isEqual, isImageUrl, isVideoUrl, isFunction, isDef, deepClone } from '../common/util'
145
+ import { context, isEqual, isImageUrl, isVideoUrl, isAudioUrl, isPdfUrl, isDocUrl, isFunction, isDef, deepClone } from '../common/util'
105
146
  import { useTranslate } from '../composables/useTranslate'
106
147
  import { useUpload } from '../composables/useUpload'
107
148
  import {
@@ -119,6 +160,15 @@ import {
119
160
  } from './types'
120
161
  import type { VideoPreviewInstance } from '../oxy-video-preview/types'
121
162
 
163
+ const imgs: AnyObject = {
164
+ pdf: ImgPdf,
165
+ word: ImgWord,
166
+ audio: ImgAudio,
167
+ video: ImgVideo,
168
+ pic: ImgPic,
169
+ other: ImgOther
170
+ }
171
+
122
172
  const props = defineProps(uploadProps)
123
173
 
124
174
  const emit = defineEmits<{
@@ -660,6 +710,14 @@ function onPreviewFile(file: UploadFileItem) {
660
710
  }
661
711
  }
662
712
 
713
+ function onPreview(file: UploadFileItem) {
714
+ if (isVideo(file)) {
715
+ onPreviewVideo(file)
716
+ } else {
717
+ onPreviewFile(file)
718
+ }
719
+ }
720
+
663
721
  function isVideo(file: UploadFileItem) {
664
722
  return (file.name && isVideoUrl(file.name)) || isVideoUrl(file.url)
665
723
  }
@@ -667,6 +725,34 @@ function isVideo(file: UploadFileItem) {
667
725
  function isImage(file: UploadFileItem) {
668
726
  return (file.name && isImageUrl(file.name)) || isImageUrl(file.url)
669
727
  }
728
+
729
+ function isAudio(file: UploadFileItem) {
730
+ return (file.name && isAudioUrl(file.name)) || isAudioUrl(file.url)
731
+ }
732
+
733
+ function isPdf(file: UploadFileItem) {
734
+ return (file.name && isPdfUrl(file.name)) || isPdfUrl(file.url)
735
+ }
736
+
737
+ function isDoc(file: UploadFileItem) {
738
+ return (file.name && isDocUrl(file.name)) || isDocUrl(file.url)
739
+ }
740
+
741
+ function getUploadImage(file: UploadFileItem) {
742
+ if (isPdf(file)) {
743
+ return imgs.pdf
744
+ } else if (isDoc(file)) {
745
+ return imgs.word
746
+ } else if (isAudio(file)) {
747
+ return imgs.audio
748
+ } else if (isVideo(file)) {
749
+ return imgs.video
750
+ } else if (isImage(file)) {
751
+ return imgs.pic
752
+ } else {
753
+ return imgs.other
754
+ }
755
+ }
670
756
  </script>
671
757
  <style lang="scss" scoped>
672
758
  @import './index.scss';
@@ -68,6 +68,8 @@ export type UploadSizeType = 'original' | 'compressed'
68
68
  export type UploadFileType = 'image' | 'video' | 'media' | 'all' | 'file'
69
69
  export type UploadCameraType = 'front' | 'back'
70
70
  export type UploadStatusType = 'pending' | 'loading' | 'success' | 'fail'
71
+ export type UploadListType = 'default' | 'card'
72
+ export type UploadListPosition = 'left' | 'right'
71
73
 
72
74
  export type UploadBeforePreviewOption = {
73
75
  file: UploadFileItem
@@ -342,7 +344,26 @@ export const uploadProps = {
342
344
  * H5支持全部类型过滤。
343
345
  * 微信小程序支持all和file时过滤,其余平台不支持。
344
346
  */
345
- extension: Array as PropType<string[]>
347
+ extension: Array as PropType<string[]>,
348
+ /**
349
+ * 唤起图标
350
+ * 类型:string
351
+ * 可选值:'fill-camera' / 'add'
352
+ * 默认值:fill-camera
353
+ */
354
+ evokeIcon: makeStringProp(''),
355
+ /**
356
+ * 文件列表的类型
357
+ * 类型:string
358
+ */
359
+ listType: makeStringProp<UploadListType>('default'),
360
+ /**
361
+ * 文件列表的位置
362
+ * 类型:string
363
+ * 可选值:'left' / 'right'
364
+ * 默认值:left
365
+ */
366
+ listPosition: makeStringProp<UploadListPosition>('left')
346
367
  }
347
368
 
348
369
  export type UploadProps = ExtractPropTypes<typeof uploadProps>
@@ -0,0 +1,35 @@
1
+ @import './../common/abstracts/_mixin.scss';
2
+ @import './../common/abstracts/variable.scss';
3
+ @include b(virtual-scroll) {
4
+ position: relative;
5
+ width: 100%;
6
+
7
+ @include e(view) {
8
+ width: 100%;
9
+ height: 100%;
10
+ }
11
+ @include e(container) {
12
+ position: relative;
13
+ .oxy-virtual-scroll__items {
14
+ position: absolute;
15
+ top: 0;
16
+ left: 0;
17
+ width: 100%;
18
+ }
19
+ }
20
+
21
+ @include e(back-top) {
22
+ position: absolute;
23
+ right: 20px;
24
+ bottom: 20px;
25
+ width: 40px;
26
+ height: 40px;
27
+ background: rgba(0, 0, 0, 0.6);
28
+ border-radius: 50%;
29
+ display: flex;
30
+ align-items: center;
31
+ justify-content: center;
32
+ cursor: pointer;
33
+ z-index: 100;
34
+ }
35
+ }
@@ -0,0 +1,184 @@
1
+ <template>
2
+ <view :class="['oxy-virtual-scroll', customClass]" :style="{ height: height }">
3
+ <!-- 滚动内容区域 -->
4
+ <scroll-view
5
+ ref="scrollView"
6
+ class="oxy-virtual-scroll__view"
7
+ :scroll-y="true"
8
+ :scroll-x="scrollX"
9
+ :scroll-top="scrollTop"
10
+ :scroll-into-view="itemId"
11
+ v-bind="$attrs"
12
+ @scroll="onScroll"
13
+ @scrolltoupper="onScrollUpper"
14
+ @scrolltolower="onScrollLower"
15
+ >
16
+ <view class="oxy-virtual-scroll__container" :style="{ height: totalHeight + 'px' }">
17
+ <view class="oxy-virtual-scroll__items" :style="{ transform: `translateY(${virtualOffsetY}px)` }">
18
+ <slot name="item" v-for="(item, index) in virtualData" :item="item" :index="startIndex + index"></slot>
19
+ <slot name="bottom"></slot>
20
+ </view>
21
+ </view>
22
+ </scroll-view>
23
+
24
+ <!-- 回到顶部按钮 -->
25
+ <view v-if="showBackToTop && showBackTopBtn" class="oxy-virtual-scroll__back-top" @click="scrollToTop">
26
+ <oxy-icon name="backtop" color="#fff" size="20px"></oxy-icon>
27
+ </view>
28
+ </view>
29
+ </template>
30
+
31
+ <script lang="ts">
32
+ export default {
33
+ name: 'oxy-virtual-scroll',
34
+ options: {
35
+ addGlobalClass: true,
36
+ virtualHost: true,
37
+ styleIsolation: 'shared'
38
+ }
39
+ }
40
+ </script>
41
+
42
+ <script lang="ts" setup>
43
+ import { computed, onMounted, ref, watch, nextTick } from 'vue'
44
+ import type { ScrollViewOnScrollEvent, ScrollViewOnScrolltolowerEvent, ScrollViewOnScrolltoupperEvent } from '@uni-helper/uni-app-types'
45
+ import { VirtualScrollEngine } from './virtual-scroll'
46
+ import { virtualScrollProps } from './types'
47
+ defineOptions({
48
+ inheritAttrs: false
49
+ })
50
+ const props = defineProps(virtualScrollProps)
51
+ const emit = defineEmits(['scroll', 'scroll-to-upper', 'scroll-to-lower'])
52
+
53
+ const scrollView = ref(null)
54
+ const itemId = ref<string>('')
55
+ const scrollTop = ref<number>(0)
56
+ const showBackTopBtn = ref<boolean>(false)
57
+ const virtualData = ref<any[]>([])
58
+ const startIndex = ref<number>(0)
59
+ const virtualOffsetY = ref<number>(0)
60
+ const virtualEngine = ref<any>(null)
61
+
62
+ // 显示的数据(索引处理后的)
63
+ const displayData = computed<any[]>(() => {
64
+ return props.data
65
+ })
66
+
67
+ // 虚拟列表总高度
68
+ const totalHeight = computed(() => {
69
+ return displayData.value.length * parseFloat(props.itemHeight) // 假设每项高度50px
70
+ })
71
+
72
+ watch(
73
+ () => props.data,
74
+ () => {
75
+ nextTick(initScrollData)
76
+ },
77
+ {
78
+ immediate: true,
79
+ deep: true
80
+ }
81
+ )
82
+
83
+ onMounted(() => {
84
+ initScrollEngine()
85
+ })
86
+
87
+ // 初始化滚动数据
88
+ function initScrollData() {
89
+ if (!props.virtual) {
90
+ // 非虚拟滚动模式:直接使用全部数据
91
+ virtualData.value = displayData.value
92
+ virtualOffsetY.value = 0
93
+ return
94
+ }
95
+ virtualEngine.value = new VirtualScrollEngine({
96
+ containerHeight: parseFloat(props.height),
97
+ itemHeight: parseFloat(props.itemHeight),
98
+ data: displayData.value
99
+ })
100
+ updateVisibleData()
101
+ }
102
+
103
+ // 初始化滚动引擎
104
+ function initScrollEngine() {
105
+ if (!props.virtual) return
106
+ virtualEngine.value = new VirtualScrollEngine({
107
+ containerHeight: parseFloat(props.height),
108
+ itemHeight: parseFloat(props.itemHeight),
109
+ data: displayData.value
110
+ })
111
+ }
112
+
113
+ // 更新可见数据
114
+ function updateVisibleData() {
115
+ if (!props.virtual) return // 非虚拟模式不处理
116
+ if (virtualEngine.value && scrollView.value) {
117
+ const { visibleData, offsetY } = virtualEngine.value.updateVisibleData(scrollTop.value || 0)
118
+ virtualData.value = visibleData
119
+ virtualOffsetY.value = offsetY
120
+ }
121
+ }
122
+
123
+ // 滚动事件
124
+ function onScroll(event: ScrollViewOnScrollEvent) {
125
+ scrollTop.value = event.detail.scrollTop
126
+ showBackTopBtn.value = scrollTop.value > parseFloat(props.backToTopThreshold)
127
+ updateVisibleData()
128
+ emit('scroll', event)
129
+ }
130
+
131
+ // 滚动到顶部
132
+ function onScrollUpper(event: ScrollViewOnScrolltoupperEvent) {
133
+ emit('scroll-to-upper', event)
134
+ }
135
+
136
+ // 滚动到底部
137
+ function onScrollLower(event: ScrollViewOnScrolltolowerEvent) {
138
+ emit('scroll-to-lower', event)
139
+ }
140
+
141
+ // 回到顶部
142
+ function scrollToTop() {
143
+ scrollTop.value = 0
144
+ nextTick(() => {
145
+ scrollTop.value = 0
146
+ })
147
+ }
148
+ function scrollToBottom() {
149
+ scrollToPosition(totalHeight.value)
150
+ }
151
+
152
+ // 滚动到指定位置
153
+ function scrollToPosition(position: number | string) {
154
+ scrollTop.value = typeof position === 'number' ? position : parseFloat(position)
155
+ }
156
+
157
+ // 滚动到指定元素
158
+ function scrollToElement(item: any) {
159
+ const index = props.data.findIndex((o) => item[props.idKey] && o[props.idKey] && o[props.idKey] === item[props.idKey])
160
+ if (index > 0) {
161
+ const scrollDistance = parseFloat(props.itemHeight) * index
162
+ scrollToPosition(scrollDistance)
163
+ }
164
+ }
165
+ function scrollToElementById(id: string | number) {
166
+ const index = props.data.findIndex((o) => id && o[props.idKey] && o[props.idKey] === id)
167
+ if (index > 0) {
168
+ const scrollDistance = parseFloat(props.itemHeight) * index
169
+ scrollToPosition(scrollDistance)
170
+ }
171
+ }
172
+
173
+ defineExpose({
174
+ scrollToTop,
175
+ scrollToBottom,
176
+ scrollToPosition,
177
+ scrollToElement,
178
+ scrollToElementById
179
+ })
180
+ </script>
181
+
182
+ <style lang="scss" scoped>
183
+ @import './index.scss';
184
+ </style>
@@ -0,0 +1,65 @@
1
+ import type { ComponentPublicInstance, ExtractPropTypes, PropType } from 'vue'
2
+ import { baseProps, makeRequiredProp, makeBooleanProp, makeStringProp } from '../common/props'
3
+
4
+ export const virtualScrollProps = {
5
+ ...baseProps,
6
+ /**
7
+ * 虚拟列表数据源
8
+ * 类型:array
9
+ * 默认值:[]
10
+ */
11
+ data: makeRequiredProp(Array as PropType<any[]>),
12
+ /**
13
+ * id的取值字段
14
+ * 类型:string
15
+ * 默认值:'id'
16
+ */
17
+ idKey: makeStringProp('id'),
18
+
19
+ /**
20
+ * 容器高度
21
+ * 类型:string
22
+ * 默认值:'100%'
23
+ */
24
+ height: makeStringProp('100%'),
25
+ /**
26
+ * 是否显示回到顶部按钮
27
+ * 类型:boolean
28
+ * 默认值:false
29
+ */
30
+ showBackToTop: makeBooleanProp(false),
31
+ /**
32
+ * 滚动多远显示backToTop
33
+ * 类型:number
34
+ * 默认值:'300px'
35
+ */
36
+ backToTopThreshold: makeStringProp('300px'),
37
+ /**
38
+ * 单个项目高度
39
+ * 类型:number
40
+ * 默认值:'50px'
41
+ */
42
+ itemHeight: makeStringProp('50px'),
43
+ /**
44
+ * 是否开启水平滚动
45
+ * 类型:boolean
46
+ * 默认值:false
47
+ */
48
+ scrollX: makeBooleanProp(false),
49
+ /**
50
+ * 是否开启虚拟滚动
51
+ * 类型:boolean
52
+ * 默认值:true
53
+ */
54
+ virtual: makeBooleanProp(true)
55
+ }
56
+
57
+ export type CollapseExpose = {
58
+ scrollToTop: () => void
59
+ scrollToBottom: () => void
60
+ scrollToPosition: (position: number | string) => void
61
+ scrollToElement: (item: any) => void
62
+ scrollToElementById: (id: string | number) => void
63
+ }
64
+ export type VirtualScrollProps = ExtractPropTypes<typeof virtualScrollProps>
65
+ export type VirtualScrollInstance = ComponentPublicInstance<VirtualScrollProps, CollapseExpose>
@@ -0,0 +1,81 @@
1
+ /**
2
+ * 虚拟滚动引擎 - 优化长列表性能
3
+ */
4
+
5
+ export interface VirtualScrollItem {
6
+ key?: string
7
+ items?: VirtualScrollItem[]
8
+ [key: string]: any
9
+ }
10
+
11
+ export interface VirtualScrollOptions {
12
+ containerHeight?: number
13
+ itemHeight?: number
14
+ bufferSize?: number
15
+ data?: VirtualScrollItem[]
16
+ }
17
+
18
+ export interface VisibleDataResult {
19
+ visibleData: VirtualScrollItem[]
20
+ startIndex: number
21
+ endIndex: number
22
+ offsetY: number
23
+ }
24
+
25
+ export class VirtualScrollEngine {
26
+ containerHeight: number
27
+ itemHeight: number
28
+ bufferSize: number
29
+ data: VirtualScrollItem[]
30
+ visibleData: VirtualScrollItem[]
31
+ startIndex: number
32
+ endIndex: number
33
+ scrollTop: number
34
+ cache: Map<string, VisibleDataResult>
35
+
36
+ constructor(options: VirtualScrollOptions = {}) {
37
+ this.containerHeight = options.containerHeight || 400
38
+ this.itemHeight = options.itemHeight || 50
39
+ this.bufferSize = options.bufferSize || 5
40
+ this.data = options.data || []
41
+
42
+ this.visibleData = []
43
+ this.startIndex = 0
44
+ this.endIndex = 0
45
+ this.scrollTop = 0
46
+
47
+ this.cache = new Map()
48
+ }
49
+
50
+ // 更新可见区域数据
51
+ updateVisibleData(scrollTop: number): VisibleDataResult {
52
+ this.scrollTop = scrollTop
53
+
54
+ const cacheKey = `${scrollTop}_${this.data.length}`
55
+ if (this.cache.has(cacheKey)) {
56
+ return this.cache.get(cacheKey)!
57
+ }
58
+
59
+ const visibleCount = Math.ceil(this.containerHeight / this.itemHeight)
60
+ this.startIndex = Math.max(0, Math.floor(scrollTop / this.itemHeight) - this.bufferSize)
61
+ this.endIndex = Math.min(this.data.length - 1, this.startIndex + visibleCount + this.bufferSize * 2)
62
+
63
+ this.visibleData = this.data.slice(this.startIndex, this.endIndex + 1)
64
+
65
+ const result: VisibleDataResult = {
66
+ visibleData: this.visibleData,
67
+ startIndex: this.startIndex,
68
+ endIndex: this.endIndex,
69
+ offsetY: this.startIndex * this.itemHeight
70
+ }
71
+
72
+ // 缓存优化
73
+ if (this.cache.size > 50) {
74
+ const firstKey = this.cache.keys().next().value as string
75
+ this.cache.delete(firstKey)
76
+ }
77
+ this.cache.set(cacheKey, result)
78
+
79
+ return result
80
+ }
81
+ }
package/global.d.ts CHANGED
@@ -59,10 +59,12 @@ declare module 'vue' {
59
59
  OxyTab: typeof import('./components/oxy-tab/oxy-tab.vue')['default']
60
60
  OxyTabs: typeof import('./components/oxy-tabs/oxy-tabs.vue')['default']
61
61
  OxyTag: typeof import('./components/oxy-tag/oxy-tag.vue')['default']
62
+ OxyCorner: typeof import('./components/oxy-corner/oxy-corner.vue')['default']
62
63
  OxyToast: typeof import('./components/oxy-toast/oxy-toast.vue')['default']
63
64
  OxyTooltip: typeof import('./components/oxy-tooltip/oxy-tooltip.vue')['default']
64
65
  OxyTransition: typeof import('./components/oxy-transition/oxy-transition.vue')['default']
65
66
  OxyUpload: typeof import('./components/oxy-upload/oxy-upload.vue')['default']
67
+ OxyFileList: typeof import('./components/oxy-file-list/oxy-file-list.vue')['default']
66
68
  OxyNotify: typeof import('./components/oxy-notify/oxy-notify.vue')['default']
67
69
  OxyWatermark: typeof import('./components/oxy-watermark/oxy-watermark.vue')['default']
68
70
  OxyCircle: typeof import('./components/oxy-circle/oxy-circle.vue')['default']
@@ -75,6 +77,7 @@ declare module 'vue' {
75
77
  OxyNavbarCapsule: typeof import('./components/oxy-navbar-capsule/oxy-navbar-capsule.vue')['default']
76
78
  OxyTable: typeof import('./components/oxy-table/oxy-table.vue')['default']
77
79
  OxyTableCol: typeof import('./components/oxy-table-col/oxy-table-col.vue')['default']
80
+ OxyVirtualScroll: typeof import('./components/oxy-virtual-scroll/oxy-virtual-scroll.vue')['default']
78
81
  OxySidebar: typeof import('./components/oxy-sidebar/oxy-sidebar.vue')['default']
79
82
  OxySidebarItem: typeof import('./components/oxy-sidebar-item/oxy-sidebar-item.vue')['default']
80
83
  OxyFab: typeof import('./components/oxy-fab/oxy-fab.vue')['default']
@@ -55,7 +55,8 @@ export default {
55
55
  colPicker: {
56
56
  title: 'حدد لون',
57
57
  placeholder: 'حدد لون',
58
- select: 'حدد'
58
+ select: 'حدد',
59
+ confirm: 'تأكيد'
59
60
  },
60
61
  loadmore: {
61
62
  loading: 'جارٍ التحميل...',
@@ -55,7 +55,8 @@ export default {
55
55
  colPicker: {
56
56
  title: 'Select',
57
57
  placeholder: 'Select',
58
- select: 'Select'
58
+ select: 'Select',
59
+ confirm: 'OK'
59
60
  },
60
61
  loadmore: {
61
62
  loading: 'Loading...',
@@ -51,7 +51,8 @@ export default {
51
51
  colPicker: {
52
52
  title: '请选择',
53
53
  placeholder: '请选择',
54
- select: '请选择'
54
+ select: '请选择',
55
+ confirm: '确定'
55
56
  },
56
57
  datetimePicker: {
57
58
  start: '开始时间',
package/package.json CHANGED
@@ -1 +1 @@
1
- {"id":"oxy-uni-ui","name":"oxy-uni-ui","displayName":"oxy-uni-ui 基于vue3+Typescript的高颜值组件库","version":"1.0.1","license":"MIT","description":"一个基于Vue3+TS开发的uni-app组件库,提供70+高质量组件,支持暗黑模式、国际化和自定义主题。","keywords":["oxy-uni-ui","国际化","组件库","vue3","暗黑模式"],"main":"index.ts","engines":{"HBuilderX":"^3.8.7"},"dcloudext":{"type":"component-vue","sale":{"regular":{"price":"0.00"},"sourcecode":{"price":"0.00"}},"contact":{"qq":""},"declaration":{"ads":"无","data":"插件不采集任何数据","permissions":"无"},"npmurl":"https://www.npmjs.com/package/oxy-uni-ui"},"vetur":{"tags":"tags.json","attributes":"attributes.json"},"web-types":"web-types.json","uni_modules":{"dependencies":[],"encrypt":[],"platforms":{"cloud":{"tcb":"y","aliyun":"y","alipay":"n"},"client":{"Vue":{"vue2":"n","vue3":"y"},"App":{"app-vue":"y","app-nvue":"n","app-uvue":"n"},"H5-mobile":{"Safari":"y","Android Browser":"y","微信浏览器(Android)":"y","QQ浏览器(Android)":"y"},"H5-pc":{"Chrome":"y","IE":"u","Edge":"y","Firefox":"y","Safari":"y"},"小程序":{"微信":"y","阿里":"y","百度":"u","字节跳动":"u","QQ":"y","钉钉":"y","快手":"u","飞书":"u","京东":"u"},"快应用":{"华为":"u","联盟":"u"}}}},"peerDependencies":{"vue":">=3.2.47"}}
1
+ {"id":"oxy-uni-ui","name":"oxy-uni-ui","displayName":"oxy-uni-ui 基于vue3+Typescript的高颜值组件库","version":"1.1.0","license":"MIT","description":"一个基于Vue3+TS开发的uni-app组件库,提供70+高质量组件,支持暗黑模式、国际化和自定义主题。","keywords":["oxy-uni-ui","国际化","组件库","vue3","暗黑模式"],"main":"index.ts","engines":{"HBuilderX":"^3.8.7"},"dcloudext":{"type":"component-vue","sale":{"regular":{"price":"0.00"},"sourcecode":{"price":"0.00"}},"contact":{"qq":""},"declaration":{"ads":"无","data":"插件不采集任何数据","permissions":"无"},"npmurl":"https://www.npmjs.com/package/oxy-uni-ui"},"vetur":{"tags":"tags.json","attributes":"attributes.json"},"web-types":"web-types.json","uni_modules":{"dependencies":[],"encrypt":[],"platforms":{"cloud":{"tcb":"y","aliyun":"y","alipay":"n"},"client":{"Vue":{"vue2":"n","vue3":"y"},"App":{"app-vue":"y","app-nvue":"n","app-uvue":"n"},"H5-mobile":{"Safari":"y","Android Browser":"y","微信浏览器(Android)":"y","QQ浏览器(Android)":"y"},"H5-pc":{"Chrome":"y","IE":"u","Edge":"y","Firefox":"y","Safari":"y"},"小程序":{"微信":"y","阿里":"y","百度":"u","字节跳动":"u","QQ":"y","钉钉":"y","快手":"u","飞书":"u","京东":"u"},"快应用":{"华为":"u","联盟":"u"}}}},"peerDependencies":{"vue":">=3.2.47"}}