@tplc/business 0.2.38 → 0.2.39

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/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [0.2.39](http://gitlab888.30jia.com.cn/bhBank/zero-code-pro/compare/v0.1.46...v0.2.39) (2024-12-27)
6
+
7
+
8
+ ### 🚀 Chore | 构建/工程依赖/工具
9
+
10
+ * **release:** 0.1.47 ([b785621](http://gitlab888.30jia.com.cn/bhBank/zero-code-pro/commit/b78562141021c6b4ed0568765004f6e4bc55fa32))
11
+ * **release:** 0.2.37 ([19d81b9](http://gitlab888.30jia.com.cn/bhBank/zero-code-pro/commit/19d81b9d3f6022e1c8523b258bf7793a6d3b133a))
12
+ * **release:** 0.2.38 ([5acc214](http://gitlab888.30jia.com.cn/bhBank/zero-code-pro/commit/5acc214f80be26f5f80bdfe282918019b43315ae))
13
+
14
+
15
+ ### ✨ Features | 新功能
16
+
17
+ * calendar 尺寸 ([5670ca5](http://gitlab888.30jia.com.cn/bhBank/zero-code-pro/commit/5670ca5fa482d9d0e57d32f15b9f7ab369210c3d))
18
+ * 图片支持渐进式 ([08523e0](http://gitlab888.30jia.com.cn/bhBank/zero-code-pro/commit/08523e0eb244da1da3b46dca4d999360600edcf5))
19
+ * 增加 lcb-product-item 图片展示类型选择 ([24706b3](http://gitlab888.30jia.com.cn/bhBank/zero-code-pro/commit/24706b3e87aee2fc6d02961e35a82147974199c4))
20
+ * 支持头像昵称 ([a1061f0](http://gitlab888.30jia.com.cn/bhBank/zero-code-pro/commit/a1061f0646637c797cdd2f5d9dbadd0862256398))
21
+ * 更新product ([3fb575b](http://gitlab888.30jia.com.cn/bhBank/zero-code-pro/commit/3fb575b7be9ec92b10c3cd36d19ee9593999bb4a))
22
+
5
23
  ### [0.2.38](http://gitlab888.30jia.com.cn/bhBank/zero-code-pro/compare/v0.1.47...v0.2.38) (2024-12-26)
6
24
 
7
25
 
@@ -23,6 +23,8 @@ const props = withDefaults(
23
23
  )
24
24
  defineSlots<{
25
25
  coverImg(props: { value: any }): any
26
+ headImg(props: { value: any }): any
27
+ userName(props: { value: any }): any
26
28
  productName(props: { value: any }): any
27
29
  subTitle(props: { value: any }): any
28
30
  price(props: { value: any }): any
@@ -76,7 +78,7 @@ const value = computed(() => {
76
78
  </script>
77
79
 
78
80
  <template>
79
- <template
81
+ <block
80
82
  v-if="
81
83
  visible && (prop === 'coverImg' || (!!value && (isArray(value) ? value?.length > 0 : true)))
82
84
  "
@@ -274,5 +276,15 @@ const value = computed(() => {
274
276
  <view>{{ value }}</view>
275
277
  </view>
276
278
  </slot>
277
- </template>
279
+ <slot :value="value" v-if="prop === 'headImg'" name="headImg">
280
+ <view :class="className" :style="style" class="w-4 h-4">
281
+ <wd-img :src="value" width="100%" height="100%" radius="50%" mode="aspectFill" />
282
+ </view>
283
+ </slot>
284
+ <slot :value="value" v-if="prop === 'userName'" name="userName">
285
+ <view :class="className" :style="style" class="text-20rpx text-#333 truncate">
286
+ <view>{{ value }}</view>
287
+ </view>
288
+ </slot>
289
+ </block>
278
290
  </template>
@@ -28,6 +28,7 @@ const props = withDefaults(defineProps<LcbProductItemProps>(), {
28
28
  titleVisible: true,
29
29
  subTitleVisible: true,
30
30
  priceVisible: true,
31
+ userInfoVisible: false,
31
32
  priceTipsVisible: true,
32
33
  priceUnitVisible: true,
33
34
  priceSuffixVisible: true,
@@ -47,11 +48,12 @@ const props = withDefaults(defineProps<LcbProductItemProps>(), {
47
48
  distanceVisible: true,
48
49
  })
49
50
  const attrs = useAttrs()
50
-
51
51
  provide('lcb-product-item-props', props)
52
52
 
53
53
  defineSlots<{
54
54
  coverImg(props: { value: any }): any
55
+ userName(props: { value: any }): any
56
+ headImg(props: { value: any }): any
55
57
  productName(props: { value: any }): any
56
58
  subTitle(props: { value: any }): any
57
59
  price(props: { value: any }): any
@@ -238,11 +240,15 @@ defineSlots<{
238
240
  </view>
239
241
  </view>
240
242
  <ItemValue prop="priceTips">
241
- <template #priceTips="{ value }">
243
+ <!-- <template #priceTips="{ value }">
242
244
  <slot name="priceTips" :value="value" />
243
- </template>
245
+ </template> -->
244
246
  </ItemValue>
245
247
  </view>
248
+ <view class="flex gap-2 items-center" v-if="userInfoVisible">
249
+ <ItemValue prop="headImg"></ItemValue>
250
+ <ItemValue prop="userName"></ItemValue>
251
+ </view>
246
252
  </view>
247
253
  <slot name="itemRightSection" />
248
254
  </view>
@@ -398,7 +404,12 @@ defineSlots<{
398
404
  </template>
399
405
  </ItemValue>
400
406
  </view>
407
+ <view class="flex gap-2 items-center" v-if="userInfoVisible">
408
+ <ItemValue prop="headImg"></ItemValue>
409
+ <ItemValue prop="userName"></ItemValue>
410
+ </view>
401
411
  </view>
412
+
402
413
  <slot name="itemBottomSection" />
403
414
  </view>
404
415
  <slot name="itemRightSection" />
@@ -18,6 +18,8 @@ export interface LcbProductItemProps {
18
18
 
19
19
  coverImg?: any
20
20
  productName?: any
21
+ headImg?: string
22
+ userName?: string
21
23
  subTitle?: any
22
24
  price?: any
23
25
  priceUnit?: any
@@ -41,6 +43,7 @@ export interface LcbProductItemProps {
41
43
  coverImgVisible?: boolean
42
44
  titleVisible?: boolean
43
45
  subTitleVisible?: boolean
46
+ userInfoVisible?: boolean
44
47
  priceVisible?: boolean
45
48
  priceUnitVisible?: boolean
46
49
  priceSuffixVisible?: boolean
@@ -0,0 +1,191 @@
1
+ <template>
2
+ <view
3
+ class="waterfall-item__box"
4
+ :class="{ animation: animation }"
5
+ :style="{ 'margin-bottom': gutter + 'rpx' }"
6
+ >
7
+ <view class="waterfall-item radius mr-bg-card">
8
+ <view
9
+ class="skeleton-box radius"
10
+ :style="{ height: skeletonHeight + 'px' }"
11
+ v-if="!hideSkeleton"
12
+ >
13
+ <view class="skeletons">
14
+ <view
15
+ class="skeleton__item"
16
+ style="width: 100%; height: 130px; border-radius: 5px 5px 0px 0px"
17
+ ></view>
18
+ <view
19
+ class="skeleton__item"
20
+ style="width: 50%; height: 18px; margin-top: 10px; border-radius: 0rpx 5px 5px 0px"
21
+ ></view>
22
+ <view
23
+ class="skeleton__item"
24
+ style="width: 80%; height: 18px; margin-top: 10px; border-radius: 0rpx 5px 5px 0px"
25
+ ></view>
26
+ <view
27
+ class="skeleton__item"
28
+ style="width: 50%; height: 18px; margin-top: 10px; border-radius: 0rpx 5px 5px 0px"
29
+ ></view>
30
+ </view>
31
+ </view>
32
+ <view class="content-box" :class="{ loading: !hideSkeleton }">
33
+ <view class="content-box__image">
34
+ <slot name="image">
35
+ <image
36
+ :src="imageUrl"
37
+ @load="onImageLoad"
38
+ @error="onImageError"
39
+ mode="widthFix"
40
+ @tap="clickItem(item)"
41
+ />
42
+ </slot>
43
+ </view>
44
+ <view class="content-box__extra">
45
+ <slot name="extra">
46
+ <view class="padding-sm" v-if="item.title || item.subtitle">
47
+ <view class="content-box__title text-df mr-text-color">{{ item.title }}</view>
48
+ <view class="content-box__subtitle text-sm mr-text-color-sub">
49
+ {{ item.subtitle }}
50
+ </view>
51
+ </view>
52
+ </slot>
53
+ </view>
54
+ </view>
55
+ </view>
56
+ </view>
57
+ </template>
58
+
59
+ <script>
60
+ export default {
61
+ name: 'waterfallItem',
62
+ props: {
63
+ item: {
64
+ type: Object,
65
+ default() {
66
+ return {}
67
+ },
68
+ },
69
+ gutter: {
70
+ type: Number,
71
+ default: 10,
72
+ },
73
+ animation: {
74
+ type: Boolean,
75
+ default: true,
76
+ },
77
+ skeletonHeight: {
78
+ type: Number,
79
+ default: 240,
80
+ },
81
+ showSkeleton: {
82
+ type: Boolean,
83
+ default: true,
84
+ },
85
+ },
86
+ data() {
87
+ return {
88
+ imageUrl: '',
89
+ hideSkeleton: false,
90
+ }
91
+ },
92
+ created() {
93
+ this.hideSkeleton = !this.showSkeleton
94
+ this.imageUrl = `${this.item.imageUrl}?x-oss-process=image/resize,w_350,m_lfit`
95
+ },
96
+ methods: {
97
+ onImageLoad() {
98
+ setTimeout(() => {
99
+ this.hideSkeleton = true
100
+ }, 100)
101
+ },
102
+ onImageError() {
103
+ this.imageUrl =
104
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAYAAACAvzbMAAAAAXNSR0IArs4c6QAAHC9JREFUeF7t3Q2QpHldH/D/v+d2PXeRkxcPzAEWQS0IGGIoKL34ghJAoyBQgBS+JPhy4uZmnn/vnXeKL3hRA17u5vn3bXGE8wUsFEQ0iqggGnw3iSaEJJoyipYEjsPcnXoge3CzM3/rmXRP9Q6zuzO/ndmZqfp01dTsdD/f//PMp3913+vp7qdzciFAgAABAgGBHMiIECBAgACBpEAMAQECBAiEBBRIiE2IAAECBBSIGSBAgACBkIACCbEJESBAgIACMQMECBAgEBJQICE2IQIECBBQIGaAAAECBEICCiTEJkSAAAECCsQMECBAgEBIQIGE2IQIECBAQIGYAQIECBAICSiQEJsQAQIECCgQM0CAAAECIQEFEmITIkCAAAEFYgYIECBAICSgQEJsQgQIECCgQMwAAQIECIQEFEiITYgAAQIEFIgZIECAAIGQgAIJsQkRIECAgAIxAwQIECAQElAgITYhAgQIEFAgZoAAAQIEQgIKJMQmRIAAAQIKxAwQIECAQEhAgYTYhAgQIEBAgZgBAgQIEAgJKJAQmxABAgQIKBAzQIAAAQIhAQUSYhMiQIAAAQViBggQIEAgJKBAQmxCBAgQIKBAzAABAgQIhAQUSIhNiAABAgQUiBkgQIAAgZCAAgmxCREgQICAAjEDBAgQIBASUCAhNiECBAgQUCBmgAABAgRCAgokxCZEgAABAgrEDBAgQIBASECBhNiECBAgQECBmAECBAgQCAkokBCbEAECBAgoEDNAgAABAiEBBRJiEyJAgAABBWIGCBAgQCAkoEBCbEIECBAgoEDMAAECBAiEBBRIiE2IAAECBBSIGSBAgACBkIACCbEJESBAgIACMQMECBAgEBJQICE2IQIECBBQIGaAAAECBEICCiTEJkSAAAECCsQMECBAgEBIQIGE2IQIECBAQIGYAQIECBAICSiQEJsQAQIECCgQM0CAAAECIQEFEmITIkCAAAEFYgYIECBAICSgQEJsQgQIECCgQMwAAQIECIQEFEiITYgAAQIEFIgZIECAAIGQgAIJsQkRIECAgAIxAwQIECAQElAgITYhAgQIEFAgZoAAAQIEQgIKJMQmRIAAAQIKxAwQIECAQEhAgYTYhAgQIEBAgZgBAgQIEAgJKJAQmxABAgQIKBAzQIAAAQIhAQUSYhMiQIAAAQViBggQIEAgJKBAQmxCBAgQIKBAzAABAgQIhAQUSIhNiAABAgQUiBkgQIAAgZCAAgmxCREgQICAAjEDBAgQIBASUCAhNiECBAgQUCBmgAABAgRCAgokxCZEgAABAgrEDBAgQIBASECBhNiECBAgQECBmAECBAgQCAkokBCbEAECBAgoEDNAgAABAiEBBRJiEyJAgAABBWIGCBAgQCAkoEBCbEIECBAgoEDMAAECBAiEBBRIiE2IAAECBBSIGSBAgACBkIACCbEJESBAgIACMQMECBAgEBJQICE2IQIECBBQIGaAAAECBEICCiTEJkSAAAECCsQMECBAgEBIQIGE2IQIECBAQIGYAQIECBAICSiQEJsQAQIECCgQM0CAAAECIQEFEmITIkCAAAEFYgYIECBAICSgQEJsQgQIECCgQMwAAQIECIQEFEiITYgAAQIEFIgZIECAAIGQgAIJsQkRIECAgAIxAwQIECAQElAgITYhAgQIEFAgZoAAAQIEQgIKJMQmRIAAAQIKxAwQIECAQEhAgYTYhAgQIEBAgZgBAgQIEAgJKJAQmxABAgQIKBAzQIAAAQIhAQUSYhMiQIAAAQViBggQIEAgJKBAQmxCBAgQIKBAzAABAgQIhAQUSIhNiAABAgQUiBkgQIAAgZCAAgmxCREgQICAAjEDBAgQIBASUCAhNiECBAgQUCBmgAABAgRCAgokxCZEgAABAgrEDBAgQIBASECBhNiECBAgQECBmAECBAgQCAkokBCbEAECBAgoEDNAgAABAiEBBRJiEyJAgAABBWIGCBAgQCAkoEBCbEIECBAgoEDMAAECBAiEBBRIiE2IAAECBBSIGSBAgACBkIACCbEJESBAgIACMQMECBAgEBJQICE2IQIECBBQIGaAAAECBEICCiTEJkSAAAECCsQMECBAgEBIQIGE2IQIECBAQIGYAQIECBAICSiQEJsQAQIECCgQM0CAAAECIQEFEmITIkCAAAEFYgYIECBAICSgQEJsQgQIECCgQMwAAQIECIQEFEiITYgAAQIEFIgZIECAAIGQgAIJsQkRIECAgAIxAwQIECAQElAgITYhAgQIEFAgZoAAAQIEQgIKJMQmRIAAAQIKxAwQIECAQEhAgYTYhAgQIEBAgZgBAgQIEAgJKJAQ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CBAgoEDMAAECBAiEBBRIiE2IAAECBBSIGSBAgACBkIACCbEJESBAgIACMQMECBAgEBJQICE2IQIECBBQIGaAAAECBEICCiTEJkSAAAECCsQMECBAgEBIQIGE2IQIECBAQIGYAQIECBAICSiQEJsQAQIECCgQM0CAAAECIQEFEmITIkCAAAEFYgYIECBAICSgQEJsQgQIECCgQMwAAQIECIQEFEiITYgAAQIEFIgZIECAAIGQgAIJsQkRIECAgAIxAwQIECAQElAgITYhAgQIEFAgZoAAAQIEQgIKJMQmRIAAAQIKxAwQIECAQEhAgYTYhAgQIEBAgZgBAgQIEAgJKJAQmxABAgQIKBAzQIAAAQIhAQUSYhMiQIAAAQViBggQIEAgJKBAQmxCBAgQIKBAzAABAgQIhAQUSIhNiAABAgQUiBkgQIAAgZCAAgmxCREgQICAAjEDBAgQIBASUCAhNiECBAgQUCBmgAABAgRCAgokxCZEgAABAgrEDBAgQIBASECBhNiECBAgQECBmAECBAgQCAkokBCbEAECBAgoEDNAgAABAiEBBRJiEyJAgAABBWIGCBAgQCAkoEBCbEIECBAgoEDMAAECBAiEBBRIiE2IAAECBBSIGSBAgACBkIACCbEJESBAgIACMQMECBAgEBJQICE2IQIECBBQIGaAAAECBEICCiTEJkSAAAECCsQMECBAgEBIQIGE2IQIECBAQIGYAQIECBAICSiQEJsQAQIECCgQM0CAAAECIQEFEmITIkCAAAEFYgYIECBAICSgQEJsQgQIECCgQMwAAQIECIQEFEiITYgAAQIEFIgZIECAAIGQgAIJsQkRIECAgAIxAwQIECAQElAgITYhAgQIEFAgZoAAAQIEQgIKJMQmRIAAAQIKxAwQIECAQEhAgYTYhAgQIEBAgZgBAgQIEAgJKJAQmxABAgQIKBAzQIAAAQIhAQUSYhMiQIAAAQViBggQIEAgJKBAQmxCBAgQIKBAzAABAgQIhAQUSIhNiAABAgQUiBkgQIAAgZCAAgmxCREgQICAAjEDBAgQIBASUCAhNiECBAgQUCBmgAABAgRCAgokxCZEgAABAgrEDBAgQIBASECBhNiECBAgQECBmAECBAgQCAkokBCbEAECBAgoEDNAgAABAiEBBRJiEyJAgAABBWIGCBAgQCAkoEBCbEIECBAgoEDMAAECBAiEBBRIiE2IAAECBBSIGSBAgACBkIACCbEJESBAgIACMQMECBAgEBJQICE2IQIECBBQIGaAAAECBEICfw/SotDNxtksnAAAAABJRU5ErkJggg=='
105
+ setTimeout(() => {
106
+ this.hideSkeleton = true
107
+ }, 100)
108
+ },
109
+ clickItem(item) {
110
+ this.$emit('onclick', item)
111
+ },
112
+ },
113
+ }
114
+ </script>
115
+
116
+ <style scoped lang="scss">
117
+ .waterfall-item__box {
118
+ &.animation {
119
+ transition: all 0.3s linear;
120
+ }
121
+ }
122
+
123
+ .waterfall-item {
124
+ width: 100%;
125
+ box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.1);
126
+ position: relative;
127
+ overflow: hidden;
128
+ border-radius: 6px;
129
+ }
130
+
131
+ .skeleton-box {
132
+ box-sizing: border-box;
133
+ background-color: #ffffff;
134
+
135
+ .skeletons {
136
+ .skeleton__item {
137
+ /* #ifdef APP-NVUE */
138
+ background-color: #f1f2f4;
139
+ /* #endif */
140
+ /* #ifndef APP-NVUE */
141
+ background: linear-gradient(90deg, #f1f2f4 25%, #e6e6e6 37%, #f1f2f4 50%);
142
+ background-size: 400% 100%;
143
+ /* #endif */
144
+ animation: skeleton 1.8s ease infinite;
145
+ }
146
+ }
147
+ }
148
+
149
+ @keyframes skeleton {
150
+ 0% {
151
+ background-position: 100% 50%;
152
+ }
153
+
154
+ 100% {
155
+ background-position: 0 50%;
156
+ }
157
+ }
158
+
159
+ .content-box {
160
+ position: static;
161
+
162
+ &.loading {
163
+ position: absolute;
164
+ width: 100%;
165
+ top: 0;
166
+ left: 0;
167
+ opacity: 0;
168
+ pointer-events: none;
169
+ z-index: -1;
170
+ }
171
+
172
+ .content-box__image {
173
+ position: relative;
174
+ overflow: hidden;
175
+
176
+ &__hide {
177
+ display: none !important;
178
+ }
179
+ }
180
+
181
+ .content-box__extra {
182
+ .content-box__title {
183
+ font-weight: bold;
184
+ }
185
+
186
+ .content-box__subtitle {
187
+ margin-top: 10rpx;
188
+ }
189
+ }
190
+ }
191
+ </style>
@@ -0,0 +1,550 @@
1
+ <template>
2
+ <view class="miran-waterfall">
3
+ <!-- #ifndef APP-NVUE -->
4
+ <view class="miran-waterfall__gap_left" :style="[gapLeftStyle]"></view>
5
+ <template v-if="columnNum >= 1">
6
+ <view id="miran-waterfall-1" class="miran-waterfall__column">
7
+ <slot name="list1"></slot>
8
+ </view>
9
+ </template>
10
+ <template v-if="columnNum >= 2">
11
+ <view class="miran-waterfall__gap_center" :style="[gapCenterStyle]"></view>
12
+ <view id="miran-waterfall-2" class="miran-waterfall__column">
13
+ <slot name="list2"></slot>
14
+ </view>
15
+ </template>
16
+ <template v-if="columnNum >= 3">
17
+ <view class="miran-waterfall__gap_center" :style="[gapCenterStyle]"></view>
18
+ <view id="miran-waterfall-3" class="miran-waterfall__column">
19
+ <slot name="list3"></slot>
20
+ </view>
21
+ </template>
22
+ <template v-if="columnNum >= 4">
23
+ <view class="miran-waterfall__gap_center" :style="[gapCenterStyle]"></view>
24
+ <view id="miran-waterfall-4" class="miran-waterfall__column">
25
+ <slot name="list4"></slot>
26
+ </view>
27
+ </template>
28
+ <template v-if="columnNum >= 5">
29
+ <view class="miran-waterfall__gap_center" :style="[gapCenterStyle]"></view>
30
+ <view id="miran-waterfall-5" class="miran-waterfall__column">
31
+ <slot name="list5"></slot>
32
+ </view>
33
+ </template>
34
+ <view class="miran-waterfall__gap_right" :style="[gapRightStyle]"></view>
35
+ <!-- #endif -->
36
+ <!-- #ifdef APP-NVUE -->
37
+ <view class="waterfall-warapper">
38
+ <waterfall
39
+ :column-count="columnNum"
40
+ :show-scrollbar="false"
41
+ column-width="auto"
42
+ :column-gap="columnGap"
43
+ :left-gap="leftGap"
44
+ :right-gap="rightGap"
45
+ :always-scrollable-vertical="true"
46
+ :style="[nvueWaterfallStyle]"
47
+ @loadmore="scrollToBottom"
48
+ >
49
+ <slot></slot>
50
+ </waterfall>
51
+ </view>
52
+ <!-- #endif -->
53
+ </view>
54
+ </template>
55
+ <script>
56
+ /**
57
+ * 瀑布流
58
+ * @description
59
+ * @tutorial
60
+ * @property {Array} value/modelValue 瀑布流数组数据,非nvue生效 (默认 [] )
61
+ * @property {String} idKey 数据的id值,根据id值对数据执行删除操作,如数据为:{id: 1, name: 'uv-ui'},那么该值设置为id,非nvue有效 (默认 '' )
62
+ * @property {String | Number} addTime 每次插入数据的事件间隔,间隔越长能保证两列高度相近,但是用户体验不好,单位ms,非nvue生效(默认 200 )
63
+ * @property {String | Number} columnCount 瀑布流的列数(默认 2 )
64
+ * @property {String | Number} columnGap 列与列的间隙(默认 0 )
65
+ * @property {String | Number} leftGap 左边和列表的间隙(默认 0 )
66
+ * @property {String | Number} rightGap 右边和列表的间隙(默认 0 )
67
+ * @property {Boolean} showScrollbar 控制是否出现滚动条,仅nvue有效 (默认 false )
68
+ * @property {String | Number} width 瀑布流的宽度,nvue生效 (默认 屏幕宽 )
69
+ * @property {String | Number} height 瀑布流的高度,nvue生效 (默认 屏幕高 )
70
+ * @property {Object} customStyle 定义需要用到的外部样式
71
+ *
72
+ * @example <miran-waterfall v-model="list"></miran-waterfall>
73
+ */
74
+ export default {
75
+ name: 'miran-waterfall',
76
+ props: {
77
+ // 瀑布流数据
78
+ // #ifdef VUE2
79
+ value: {
80
+ type: Array,
81
+ default: () => [],
82
+ },
83
+ // #endif
84
+ // #ifdef VUE3
85
+ modelValue: {
86
+ type: Array,
87
+ default: () => [],
88
+ },
89
+ // #endif
90
+ // 数据的id值,根据id值对数据执行删除操作
91
+ // 如数据为:{id: 1, name: 'uv-ui'},那么该值设置为id
92
+ idKey: {
93
+ type: String,
94
+ default: 'id',
95
+ },
96
+ // 每次插入数据的事件间隔,间隔越长能保证两列高度相近,但是用户体验不好,单位ms
97
+ addTime: {
98
+ type: Number,
99
+ default: 200,
100
+ },
101
+ // 瀑布流的列数,默认2,最高为5
102
+ columnCount: {
103
+ type: [Number, String],
104
+ default: 2,
105
+ },
106
+ // 列与列的间隙,默认20
107
+ columnGap: {
108
+ type: [Number, String],
109
+ default: 10,
110
+ },
111
+ // 左边和列表的间隙
112
+ leftGap: {
113
+ type: [Number, String],
114
+ default: 15,
115
+ },
116
+ // 右边和列表的间隙
117
+ rightGap: {
118
+ type: [Number, String],
119
+ default: 15,
120
+ },
121
+ // 是否显示滚动条,仅nvue生效
122
+ showScrollbar: {
123
+ type: [Boolean],
124
+ default: false,
125
+ },
126
+ // 瀑布流的宽度,nvue生效
127
+ width: {
128
+ type: [Number, String],
129
+ default: '',
130
+ },
131
+ // 瀑布流的高度,nvue生效
132
+ height: {
133
+ type: [Number, String],
134
+ default: '',
135
+ },
136
+ },
137
+
138
+ computed: {
139
+ // 破坏value变量引用,否则数据会保持不变
140
+ valueCom() {
141
+ // #ifdef VUE2
142
+ return this.value
143
+ // #endif
144
+ },
145
+ copyValue() {
146
+ return uni.$g.obj.deepClone(this.valueCom)
147
+ },
148
+ columnNum() {
149
+ return this.columnCount <= 0 ? 0 : this.columnCount >= 5 ? 5 : this.columnCount
150
+ },
151
+ gapLeftStyle() {
152
+ const style = {}
153
+ style.width = this.addUnit(this.leftGap)
154
+ return style
155
+ },
156
+ gapRightStyle() {
157
+ const style = {}
158
+ style.width = this.addUnit(this.rightGap)
159
+ return style
160
+ },
161
+ gapCenterStyle() {
162
+ const style = {}
163
+ style.width = this.addUnit(this.columnGap)
164
+ return style
165
+ },
166
+ nvueWaterfallStyle() {
167
+ const style = {}
168
+ if (this.width !== 0) style.width = this.addUnit(this.width)
169
+ if (this.height !== 0) style.height = this.addUnit(this.height)
170
+ // 如果没有定义列表高度,则默认使用屏幕高度
171
+ const sysInfo = uni.getSystemInfoSync()
172
+ if (!style.width) style.width = this.addUnit(sysInfo.windowWidth, 'px')
173
+ if (!style.height) style.height = this.addUnit(sysInfo.windowHeight, 'px')
174
+ return uni.$g.obj.deepMerge(style, this.$uv.addStyle(this.customStyle))
175
+ },
176
+ },
177
+ watch: {
178
+ copyValue: {
179
+ // #ifndef APP-NVUE
180
+ handler(nVal, oVal) {
181
+ if (nVal.length !== 0) {
182
+ // 取出数组发生变化的部分
183
+ const startIndex = Array.isArray(oVal) && oVal.length > 0 ? oVal.length : 0
184
+ // 拼接原有数据
185
+ this.tempList = this.tempList.concat(uni.$g.obj.deepClone(nVal.slice(startIndex)))
186
+ this.splitData()
187
+ }
188
+ },
189
+ immediate: true,
190
+ deep: true,
191
+ // #endif
192
+ },
193
+ },
194
+ data() {
195
+ return {
196
+ list1: [],
197
+ list2: [],
198
+ list3: [],
199
+ list4: [],
200
+ list5: [],
201
+ // 临时列表
202
+ tempList: [],
203
+ columnWidth: 0,
204
+ columnHeights: [],
205
+ }
206
+ },
207
+ created() {
208
+ this.columnHeights = new Array(this.columnNum).fill(0)
209
+ this.columnWidth =
210
+ (uni.upx2px(750) - this.leftGap - this.rightGap - (this.columnNum - 1) * this.columnGap) /
211
+ this.columnNum
212
+ },
213
+ methods: {
214
+ /**
215
+ * 添加单位
216
+ */
217
+ addUnit: function (value = 'auto', unit = 'px') {
218
+ value = String(value)
219
+ // 用uvui内置验证规则中的number判断是否为数值
220
+ return uni.$g.valid.number(value) ? `${value}${unit}` : value
221
+ },
222
+ /**
223
+ * 进行延时
224
+ */
225
+ sleep: function (value = 30) {
226
+ return new Promise((resolve) => {
227
+ setTimeout(() => {
228
+ resolve()
229
+ }, value)
230
+ })
231
+ },
232
+ /**
233
+ * 媒体查询
234
+ * @param {Object} selector
235
+ * @param {Object} context
236
+ */
237
+ getRect: function (selector, context) {
238
+ return new Promise((resolve) => {
239
+ const query = uni.createSelectorQuery().in(context)
240
+ query
241
+ .select(selector)
242
+ .boundingClientRect((rect) => {
243
+ rect ? resolve(rect) : resolve(false)
244
+ })
245
+ .exec()
246
+ })
247
+ },
248
+ /**
249
+ * 去除空格
250
+ */
251
+ trim: function (str, pos = 'both') {
252
+ str = String(str)
253
+ if (pos === 'both') {
254
+ return str.replace(/^\s+|\s+$/g, '')
255
+ }
256
+ if (pos === 'left') {
257
+ return str.replace(/^\s*/, '')
258
+ }
259
+ if (pos === 'right') {
260
+ return str.replace(/(\s*$)/g, '')
261
+ }
262
+ if (pos === 'all') {
263
+ return str.replace(/\s+/g, '')
264
+ }
265
+ return str
266
+ },
267
+ /**
268
+ * 样式转换
269
+ */
270
+ addStyle: function (customStyle, target = 'object') {
271
+ // 字符串转字符串,对象转对象情形,直接返回
272
+ if (
273
+ uni.$g.valid.empty(customStyle) ||
274
+ (typeof customStyle === 'object' && target === 'object') ||
275
+ (target === 'string' && typeof customStyle === 'string')
276
+ ) {
277
+ return customStyle
278
+ }
279
+ // 字符串转对象
280
+ if (target === 'object') {
281
+ // 去除字符串样式中的两端空格(中间的空格不能去掉,比如padding: 20px 0如果去掉了就错了),空格是无用的
282
+ customStyle = this.trim(customStyle)
283
+ // 根据";"将字符串转为数组形式
284
+ const styleArray = customStyle.split(';')
285
+ const style = {}
286
+ // 历遍数组,拼接成对象
287
+ for (let i = 0; i < styleArray.length; i++) {
288
+ // 'font-size:20px;color:red;',如此最后字符串有";"的话,会导致styleArray最后一个元素为空字符串,这里需要过滤
289
+ if (styleArray[i]) {
290
+ const item = styleArray[i].split(':')
291
+ style[this.trim(item[0])] = this.trim(item[1])
292
+ }
293
+ }
294
+ return style
295
+ }
296
+ // 这里为对象转字符串形式
297
+ let string = ''
298
+ for (const i in customStyle) {
299
+ // 驼峰转为中划线的形式,否则css内联样式,无法识别驼峰样式属性名
300
+ const key = i.replace(/([A-Z])/g, '-$1').toLowerCase()
301
+ string += `${key}:${customStyle[i]};`
302
+ }
303
+ // 去除两端空格
304
+ return this.trim(string)
305
+ },
306
+ /**
307
+ * 滚动到底部触发事件
308
+ * @param {Object} e
309
+ */
310
+ scrollToBottom: function () {
311
+ this.sleep(30).then(() => {
312
+ this.$emit('scrollToBottom')
313
+ })
314
+ },
315
+ /**
316
+ * 媒体查询
317
+ */
318
+ startObserver(selector) {
319
+ return new Promise((resolve) => {
320
+ this.intersectionObserver = uniselector.createIntersectionObserver(this, {
321
+ thresholds: [0, 1],
322
+ initialRatio: 0,
323
+ observeAll: false,
324
+ })
325
+ this.intersectionObserver.relativeToViewport()
326
+ this.intersectionObserver.observe(selector, (res) => {
327
+ const rect = res.boundingClientRect
328
+ this.intersectionObserver.disconnect()
329
+ this.intersectionObserver = null
330
+ rect ? resolve(rect) : resolve(false)
331
+ })
332
+ })
333
+ },
334
+ /**
335
+ * 拆分数据
336
+ */
337
+ splitData: async function () {
338
+ if (!this.tempList.length) return
339
+
340
+ for (let i = 1; i <= this.columnNum; i++) {
341
+ const rect = await this.getRect(`#miran-waterfall-${i}`, this)
342
+ if (!rect) return this.splitData()
343
+ this.columnHeights[i - 1] = rect.height
344
+ }
345
+
346
+ let index = 999
347
+
348
+ // 判断数据是不是处理第一行
349
+ for (let i = 0; i < this.columnNum; i++) {
350
+ if (this.columnHeights[i] === 0) {
351
+ index = i
352
+ break
353
+ }
354
+ }
355
+
356
+ const emitList = {}
357
+
358
+ // 设置项的位置属性并更新列高
359
+ if (index < this.columnNum) {
360
+ // 设置列名称
361
+ emitList.name = `list${index + 1}`
362
+ } else {
363
+ // 获取列高数组中最低高度
364
+ const minHeight = Math.min(...this.columnHeights)
365
+ // 查找最低高度所在列序
366
+ const minIndex = this.columnHeights.indexOf(minHeight)
367
+ // 设置列名称
368
+ emitList.name = `list${minIndex + 1}`
369
+ }
370
+
371
+ const item = this.tempList[0]
372
+
373
+ if (!item) return this.splitData()
374
+
375
+ emitList.value = {
376
+ ...item,
377
+ width: this.columnWidth,
378
+ }
379
+
380
+ this.$emit('changeList', emitList)
381
+
382
+ // 移除临时数组中已处理的数据
383
+ this.tempList.splice(0, 1)
384
+
385
+ // 如果还有数据则继续执行
386
+ if (this.tempList.length) {
387
+ let _timeout = this.addTime
388
+ // 部分平台在延时较短的情况会出现BUG
389
+ // #ifdef MP-BAIDU
390
+ _timeout = _timeout < 200 ? 200 : _timeout
391
+ // #endif
392
+ await this.sleep(_timeout)
393
+
394
+ this.splitData()
395
+ } else {
396
+ this.$emit('finish')
397
+ }
398
+ },
399
+ /**
400
+ * 暂存未用
401
+ */
402
+ renderData() {
403
+ const {
404
+ // 列数量
405
+ columnCount,
406
+ // 列宽
407
+ columnWidth,
408
+ // 骨架高度
409
+ skeletonHeight,
410
+ // 间隙宽高
411
+ gutter,
412
+ // 左右宽度
413
+ lrPading,
414
+ // 唯一标识关键词
415
+ keyId,
416
+ // 缓存项
417
+ cache = {},
418
+ } = this
419
+
420
+ // 标识关键词为空
421
+ if (!keyId) return
422
+
423
+ // 临时数据集为空
424
+ if (!this.tempList.length) return
425
+
426
+ if (!this.columnHeights) {
427
+ this.columnHeights = new Array(columnCount).fill(0)
428
+ }
429
+
430
+ const item = this.tempList[0]
431
+
432
+ // 唯一标识
433
+ const itemKey = item[keyId]
434
+
435
+ // 缓存为空,初始化数据
436
+ if (!cache[itemKey]) {
437
+ cache[itemKey] = {
438
+ top: 0,
439
+ left: 0,
440
+ width: columnWidth,
441
+ height: skeletonHeight,
442
+ }
443
+ }
444
+
445
+ let index = 999
446
+ // 判断数据是不是处理第一行
447
+ for (let i = 0; i < columnCount; i++) {
448
+ if (this.columnHeights[i] === 0) {
449
+ index = i
450
+ break
451
+ }
452
+ }
453
+
454
+ // 设置项的位置属性并更新列高
455
+ if (index < columnCount) {
456
+ // 第一行数据处理
457
+ // 设置第一行项上边距为0
458
+ cache[itemKey].top = 0
459
+ // 设置第一行项左边距为左间隙+列序号*(列宽+间隙)
460
+ cache[itemKey].left = lrPading + index * (columnWidth + gutter)
461
+ // 设置列高数组中列序号为index的值为列高+项高度
462
+ this.columnHeights[index] += cache[itemKey].height
463
+ } else {
464
+ // 其它行数据处理
465
+ // 获取列高数组中最低高度
466
+ const minHeight = Math.min(...this.columnHeights)
467
+ // 查找最低高度所在列序
468
+ const minIndex = this.columnHeights.indexOf(minHeight)
469
+ // 设置项上边距为最小高度+间隙
470
+ cache[itemKey].top = minHeight + gutter
471
+ // 设置项左边距为左间隙+列序号*(列宽+间隙)
472
+ cache[itemKey].left = lrPading + minIndex * (columnWidth + gutter)
473
+ // 设置列高数组中列序号为minIndex的值为列高+项高度+间隙
474
+ this.columnHeights[minIndex] += cache[itemKey].height + gutter
475
+ }
476
+
477
+ // 获取列高数组中最大高度
478
+ this.waterfallBoxHeight = Math.max(...this.columnHeights)
479
+
480
+ // 添加项数据到数组中
481
+ this.$emit('changeList', {
482
+ ...item,
483
+ _layoutRect: cache[itemKey],
484
+ _renderKey: itemKey,
485
+ })
486
+
487
+ // 移除临时数组中已处理的数据
488
+ this.tempList.splice(0, 1)
489
+
490
+ // 如果还有数据则继续执行
491
+ if (this.tempList.length) {
492
+ this.renderData()
493
+ } else {
494
+ this.$emit('finish')
495
+ }
496
+ },
497
+ /**
498
+ * 清空数据列表
499
+ */
500
+ async clear() {
501
+ // #ifdef VUE2
502
+ this.$emit('input', [])
503
+ // #endif
504
+ // #ifdef VUE3
505
+ this.$emit('update:modelValue', [])
506
+ // #endif
507
+ this.tempList = []
508
+ await this.sleep(300)
509
+ this.$emit('clear')
510
+ },
511
+ /**
512
+ * 移除某条数据
513
+ * @param {Object} id
514
+ */
515
+ remove(id) {
516
+ let index = -1
517
+ // 同时删除父组件对应的数据
518
+ // #ifdef VUE2
519
+ index = this.value.findIndex((item) => item[this.idKey] === id)
520
+ if (index !== -1) this.$emit('input', this.value.splice(index, 1))
521
+ // #endif
522
+ // #ifdef VUE3
523
+ index = this.modelValue.findIndex((item) => item[this.idKey] === id)
524
+ if (index !== -1) this.$emit('update:modelValue', this.modelValue.splice(index, 1))
525
+ // #endif
526
+ this.$emit('remove', id)
527
+ },
528
+ },
529
+ }
530
+ </script>
531
+ <style lang="scss" scoped>
532
+ .miran-waterfall {
533
+ /* #ifndef APP-NVUE */
534
+ display: flex;
535
+ /* #endif */
536
+ flex-direction: row;
537
+ align-items: flex-start;
538
+
539
+ &__column {
540
+ /* #ifndef APP-NVUE */
541
+ display: flex;
542
+ /* #endif */
543
+ flex-direction: column;
544
+ flex: 1;
545
+ // #ifndef APP-NVUE
546
+ height: auto;
547
+ // #endif
548
+ }
549
+ }
550
+ </style>
@@ -0,0 +1,3 @@
1
+ export interface LcbWaterfallProps {
2
+ // Define the component's prop types here
3
+ }
package/global.d.ts CHANGED
@@ -33,6 +33,7 @@ declare module 'vue' {
33
33
  'lcb-user-top': (typeof import('./types/components/lcb-user-top/lcb-user-top.vue'))['default']
34
34
  'lcb-video': (typeof import('./types/components/lcb-video/lcb-video.vue'))['default']
35
35
  'lcb-vip': (typeof import('./types/components/lcb-vip/lcb-vip.vue'))['default']
36
+ 'lcb-waterfall': (typeof import('./types/components/lcb-waterfall/lcb-waterfall.vue'))['default']
36
37
  }
37
38
  }
38
39
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tplc/business",
3
- "version": "0.2.38",
3
+ "version": "0.2.39",
4
4
  "keywords": [
5
5
  "业务组件"
6
6
  ],
@@ -1,5 +1,7 @@
1
1
  declare function __VLS_template(): Readonly<{
2
2
  coverImg(props: { value: any }): any
3
+ headImg(props: { value: any }): any
4
+ userName(props: { value: any }): any
3
5
  productName(props: { value: any }): any
4
6
  subTitle(props: { value: any }): any
5
7
  price(props: { value: any }): any
@@ -21,6 +23,8 @@ declare function __VLS_template(): Readonly<{
21
23
  orderTips(props: { value: any }): any
22
24
  }> & {
23
25
  coverImg(props: { value: any }): any
26
+ headImg(props: { value: any }): any
27
+ userName(props: { value: any }): any
24
28
  productName(props: { value: any }): any
25
29
  subTitle(props: { value: any }): any
26
30
  price(props: { value: any }): any
@@ -1,6 +1,8 @@
1
1
  import { LcbProductItemProps } from './types'
2
2
  declare function __VLS_template(): Readonly<{
3
3
  coverImg(props: { value: any }): any
4
+ userName(props: { value: any }): any
5
+ headImg(props: { value: any }): any
4
6
  productName(props: { value: any }): any
5
7
  subTitle(props: { value: any }): any
6
8
  price(props: { value: any }): any
@@ -30,6 +32,8 @@ declare function __VLS_template(): Readonly<{
30
32
  contentSection(): any
31
33
  }> & {
32
34
  coverImg(props: { value: any }): any
35
+ userName(props: { value: any }): any
36
+ headImg(props: { value: any }): any
33
37
  productName(props: { value: any }): any
34
38
  subTitle(props: { value: any }): any
35
39
  price(props: { value: any }): any
@@ -77,6 +81,7 @@ declare const __VLS_component: import('vue').DefineComponent<
77
81
  titleVisible: boolean
78
82
  subTitleVisible: boolean
79
83
  priceVisible: boolean
84
+ userInfoVisible: boolean
80
85
  priceTipsVisible: boolean
81
86
  priceUnitVisible: boolean
82
87
  priceSuffixVisible: boolean
@@ -125,6 +130,7 @@ declare const __VLS_component: import('vue').DefineComponent<
125
130
  titleVisible: boolean
126
131
  subTitleVisible: boolean
127
132
  priceVisible: boolean
133
+ userInfoVisible: boolean
128
134
  priceTipsVisible: boolean
129
135
  priceUnitVisible: boolean
130
136
  priceSuffixVisible: boolean
@@ -161,6 +167,7 @@ declare const __VLS_component: import('vue').DefineComponent<
161
167
  coverImgVisible: boolean
162
168
  titleVisible: boolean
163
169
  subTitleVisible: boolean
170
+ userInfoVisible: boolean
164
171
  priceVisible: boolean
165
172
  priceUnitVisible: boolean
166
173
  priceSuffixVisible: boolean
@@ -15,6 +15,8 @@ export interface LcbProductItemProps {
15
15
  imageType?: 'square' | 'vertical' | 'horizontal'
16
16
  coverImg?: any
17
17
  productName?: any
18
+ headImg?: string
19
+ userName?: string
18
20
  subTitle?: any
19
21
  price?: any
20
22
  priceUnit?: any
@@ -37,6 +39,7 @@ export interface LcbProductItemProps {
37
39
  coverImgVisible?: boolean
38
40
  titleVisible?: boolean
39
41
  subTitleVisible?: boolean
42
+ userInfoVisible?: boolean
40
43
  priceVisible?: boolean
41
44
  priceUnitVisible?: boolean
42
45
  priceSuffixVisible?: boolean
@@ -0,0 +1,73 @@
1
+ declare const _default: import('vue').DefineComponent<
2
+ {
3
+ item: {
4
+ type: ObjectConstructor
5
+ default(): {}
6
+ }
7
+ gutter: {
8
+ type: NumberConstructor
9
+ default: number
10
+ }
11
+ animation: {
12
+ type: BooleanConstructor
13
+ default: boolean
14
+ }
15
+ skeletonHeight: {
16
+ type: NumberConstructor
17
+ default: number
18
+ }
19
+ showSkeleton: {
20
+ type: BooleanConstructor
21
+ default: boolean
22
+ }
23
+ },
24
+ any,
25
+ {
26
+ imageUrl: string
27
+ hideSkeleton: boolean
28
+ },
29
+ {},
30
+ {
31
+ onImageLoad(): void
32
+ onImageError(): void
33
+ clickItem(item: any): void
34
+ },
35
+ import('vue').ComponentOptionsMixin,
36
+ import('vue').ComponentOptionsMixin,
37
+ {},
38
+ string,
39
+ import('vue').PublicProps,
40
+ Readonly<
41
+ import('vue').ExtractPropTypes<{
42
+ item: {
43
+ type: ObjectConstructor
44
+ default(): {}
45
+ }
46
+ gutter: {
47
+ type: NumberConstructor
48
+ default: number
49
+ }
50
+ animation: {
51
+ type: BooleanConstructor
52
+ default: boolean
53
+ }
54
+ skeletonHeight: {
55
+ type: NumberConstructor
56
+ default: number
57
+ }
58
+ showSkeleton: {
59
+ type: BooleanConstructor
60
+ default: boolean
61
+ }
62
+ }>
63
+ >,
64
+ {
65
+ item: Record<string, any>
66
+ gutter: number
67
+ animation: boolean
68
+ skeletonHeight: number
69
+ showSkeleton: boolean
70
+ },
71
+ {}
72
+ >
73
+ export default _default
@@ -0,0 +1,192 @@
1
+ declare const _default: import('vue').DefineComponent<
2
+ {
3
+ value: {
4
+ type: ArrayConstructor
5
+ default: () => never[]
6
+ }
7
+ modelValue: {
8
+ type: ArrayConstructor
9
+ default: () => never[]
10
+ }
11
+ idKey: {
12
+ type: StringConstructor
13
+ default: string
14
+ }
15
+ addTime: {
16
+ type: NumberConstructor
17
+ default: number
18
+ }
19
+ columnCount: {
20
+ type: (StringConstructor | NumberConstructor)[]
21
+ default: number
22
+ }
23
+ columnGap: {
24
+ type: (StringConstructor | NumberConstructor)[]
25
+ default: number
26
+ }
27
+ leftGap: {
28
+ type: (StringConstructor | NumberConstructor)[]
29
+ default: number
30
+ }
31
+ rightGap: {
32
+ type: (StringConstructor | NumberConstructor)[]
33
+ default: number
34
+ }
35
+ showScrollbar: {
36
+ type: BooleanConstructor[]
37
+ default: boolean
38
+ }
39
+ width: {
40
+ type: (StringConstructor | NumberConstructor)[]
41
+ default: string
42
+ }
43
+ height: {
44
+ type: (StringConstructor | NumberConstructor)[]
45
+ default: string
46
+ }
47
+ },
48
+ any,
49
+ {
50
+ list1: never[]
51
+ list2: never[]
52
+ list3: never[]
53
+ list4: never[]
54
+ list5: never[]
55
+ tempList: never[]
56
+ columnWidth: number
57
+ columnHeights: never[]
58
+ },
59
+ {
60
+ valueCom(): any
61
+ copyValue(): any
62
+ columnNum(): any
63
+ gapLeftStyle(): {
64
+ width: string
65
+ }
66
+ gapRightStyle(): {
67
+ width: string
68
+ }
69
+ gapCenterStyle(): {
70
+ width: string
71
+ }
72
+ nvueWaterfallStyle(): any
73
+ },
74
+ {
75
+ /**
76
+ * 添加单位
77
+ */
78
+ addUnit: (value?: string, unit?: string) => string
79
+ /**
80
+ * 进行延时
81
+ */
82
+ sleep: (value?: number) => Promise<any>
83
+ /**
84
+ * 媒体查询
85
+ * @param {Object} selector
86
+ * @param {Object} context
87
+ */
88
+ getRect: (selector: any, context: any) => Promise<any>
89
+ /**
90
+ * 去除空格
91
+ */
92
+ trim: (str: any, pos?: string) => any
93
+ /**
94
+ * 样式转换
95
+ */
96
+ addStyle: (customStyle: any, target?: string) => any
97
+ /**
98
+ * 滚动到底部触发事件
99
+ * @param {Object} e
100
+ */
101
+ scrollToBottom: () => void
102
+ /**
103
+ * 媒体查询
104
+ */
105
+ startObserver(selector: any): Promise<any>
106
+ /**
107
+ * 拆分数据
108
+ */
109
+ splitData: () => any
110
+ /**
111
+ * 暂存未用
112
+ */
113
+ renderData(): void
114
+ /**
115
+ * 清空数据列表
116
+ */
117
+ clear(): Promise<void>
118
+ /**
119
+ * 移除某条数据
120
+ * @param {Object} id
121
+ */
122
+ remove(id: any): void
123
+ },
124
+ import('vue').ComponentOptionsMixin,
125
+ import('vue').ComponentOptionsMixin,
126
+ {},
127
+ string,
128
+ import('vue').PublicProps,
129
+ Readonly<
130
+ import('vue').ExtractPropTypes<{
131
+ value: {
132
+ type: ArrayConstructor
133
+ default: () => never[]
134
+ }
135
+ modelValue: {
136
+ type: ArrayConstructor
137
+ default: () => never[]
138
+ }
139
+ idKey: {
140
+ type: StringConstructor
141
+ default: string
142
+ }
143
+ addTime: {
144
+ type: NumberConstructor
145
+ default: number
146
+ }
147
+ columnCount: {
148
+ type: (StringConstructor | NumberConstructor)[]
149
+ default: number
150
+ }
151
+ columnGap: {
152
+ type: (StringConstructor | NumberConstructor)[]
153
+ default: number
154
+ }
155
+ leftGap: {
156
+ type: (StringConstructor | NumberConstructor)[]
157
+ default: number
158
+ }
159
+ rightGap: {
160
+ type: (StringConstructor | NumberConstructor)[]
161
+ default: number
162
+ }
163
+ showScrollbar: {
164
+ type: BooleanConstructor[]
165
+ default: boolean
166
+ }
167
+ width: {
168
+ type: (StringConstructor | NumberConstructor)[]
169
+ default: string
170
+ }
171
+ height: {
172
+ type: (StringConstructor | NumberConstructor)[]
173
+ default: string
174
+ }
175
+ }>
176
+ >,
177
+ {
178
+ value: unknown[]
179
+ modelValue: unknown[]
180
+ width: string | number
181
+ height: string | number
182
+ idKey: string
183
+ addTime: number
184
+ columnCount: string | number
185
+ columnGap: string | number
186
+ leftGap: string | number
187
+ rightGap: string | number
188
+ showScrollbar: boolean
189
+ },
190
+ {}
191
+ >
192
+ export default _default