@tplc/business 0.7.52 → 0.7.54

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,37 @@
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.7.54](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/compare/v1.0.25...v0.7.54) (2026-01-07)
6
+
7
+
8
+ ### 🐛 Bug Fixes | Bug 修复
9
+
10
+ * **wd-img:** remove unnecessary z-index class from image component ([064ed39](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/064ed3962a45987ac7f22140312659df29259a0d))
11
+
12
+ ### [0.7.53](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/compare/v0.7.51...v0.7.53) (2026-01-07)
13
+
14
+
15
+ ### 🚀 Chore | 构建/工程依赖/工具
16
+
17
+ * **release:** 0.7.52 ([ee19331](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/ee193313d84f6116acdc38630ecc010fa29fcc37))
18
+
19
+
20
+ ### ✨ Features | 新功能
21
+
22
+ * **lcb-wrapper-list:** implement pagination support with z-paging and enhance dynamic style handling in lcb-block ([f345e4e](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/f345e4e0d84231db8e72fd55fefebdf01eac3bd6))
23
+
24
+
25
+ ### 🐛 Bug Fixes | Bug 修复
26
+
27
+ * **lcb-area:** set default value for compareValues function to prevent potential errors ([4e5e48a](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/4e5e48a373ba00e491c5cd002da5792e57006aec))
28
+ * **lcb-area:** use nullish coalescing for default flex value in itemStylesCache ([421d71c](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/421d71c428f4a2f3db37169e9d5dd6ccdc1d6d07))
29
+
30
+
31
+ ### ♻️ Code Refactoring | 代码重构
32
+
33
+ * **lcb-area, lcb-button:** optimize actionProps computation and streamline data source selection ([117ce0d](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/117ce0df62ee17b25a341c4dd3b51b90a0f78bd8))
34
+ * **lcb-wrapper-list:** streamline pagination implementation and enhance style handling in lcb-block ([9bb70e5](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/9bb70e58d28cb5575760d6fad0da81dec229beab))
35
+
5
36
  ### [0.7.52](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/compare/v0.7.48...v0.7.52) (2026-01-07)
6
37
 
7
38
 
@@ -31,7 +31,7 @@ import { LcbAreaProps } from './types'
31
31
  import { getFlexStyle, transformValueUnit } from '@tplc/business/utils/transform'
32
32
  import { get } from 'lodash-es'
33
33
  import { dynamicRequest } from '../../utils/request'
34
- import { getDynamicData } from '../../utils/utils'
34
+ import { resolveActionProps, selectDynamicSource } from '../../utils/componentHelpers'
35
35
  import useDynamicData from '../../hooks/useDynamicData'
36
36
  defineOptions({
37
37
  name: 'LcbArea',
@@ -96,7 +96,7 @@ const itemStylesCache = computed(() => {
96
96
  return {
97
97
  ...commonItemStyle.value,
98
98
  gridColumn: colSpan && isGrid ? `span ${colSpan} / span ${colSpan}` : undefined,
99
- flex: isFlex ? flex || 'auto' : undefined,
99
+ flex: isFlex ? (flex ?? 'auto') : undefined,
100
100
  flexShrink: isFlex ? flexShrink : undefined,
101
101
  width: flex ? '100%' : 'auto',
102
102
  }
@@ -121,24 +121,12 @@ const getData = async () => {
121
121
 
122
122
  /** 处理跳转链接中的动态参数 */
123
123
  const actionProps = computed(() => {
124
- const jumpUrl = props.action?.jumpUrl
125
- if (!jumpUrl) {
126
- return props.action
127
- }
128
-
129
- // 优化: 提前返回,避免不必要的计算
130
- if (props.dynamicActionKey) {
131
- return get(innerDynamicData.value, props.dynamicActionKey)
132
- }
133
-
134
- // 优化: 只在 jumpUrl 存在时才调用 getDynamicData
135
-
136
- return {
137
- ...props.action,
138
- jumpUrl: getDynamicData(jumpUrl, {
139
- store: innerDynamicData.value,
140
- }),
141
- }
124
+ return resolveActionProps({
125
+ dynamicActionKey: props.dynamicActionKey,
126
+ action: props.action as any,
127
+ innerDynamicData: innerDynamicData.value,
128
+ templateStore: innerDynamicData.value,
129
+ })
142
130
  })
143
131
 
144
132
  // 优化: 移除 deep: true,只监听引用变化
@@ -176,7 +164,7 @@ const compareValues = (value = '', compareValue: string, compareType: string) =>
176
164
 
177
165
  // 优化: 缓存数据源
178
166
  const dependDataSource = computed(() => {
179
- return props.keyFromUser ? userStore?.userInfo : innerDynamicData.value
167
+ return selectDynamicSource(props.keyFromUser, userStore?.userInfo, innerDynamicData.value)
180
168
  })
181
169
 
182
170
  const showArea = computed(() => {
@@ -11,7 +11,11 @@
11
11
  @avatar="onAvatar"
12
12
  @click="handleClick"
13
13
  :customStyle="actionPaddingStyle"
14
- :renderMode="!actionProps?.jumpType && mode !== 'image' ? 'noClick' : 'button'"
14
+ :renderMode="
15
+ !actionProps?.jumpType && (mode === 'image' ? !enablePreview : true)
16
+ ? 'noClick'
17
+ : 'button'
18
+ "
15
19
  >
16
20
  <view class="!flex items-center justify-center" :style="iconGapStyle">
17
21
  <wd-icon
@@ -63,6 +67,7 @@ import { computed } from 'vue'
63
67
  import useDynamicData from '../../hooks/useDynamicData'
64
68
  import { getFlexStyle, transformValueUnit } from '../../utils/transform'
65
69
  import { getDynamicData } from '../../utils/utils'
70
+ import { resolveActionProps, selectDynamicSource } from '../../utils/componentHelpers'
66
71
  import { LcbButtonProps } from './types'
67
72
  defineOptions({
68
73
  name: 'LcbButton',
@@ -85,7 +90,7 @@ const { userStore, innerDynamicData } = useDynamicData()
85
90
 
86
91
  // 优化: 缓存 store 数据源
87
92
  const store = computed(() => {
88
- return props.keyFromUser ? userStore?.userInfo : innerDynamicData.value
93
+ return selectDynamicSource(props.keyFromUser, userStore?.userInfo, innerDynamicData.value)
89
94
  })
90
95
 
91
96
  const dynamicValue = computed(() => {
@@ -122,25 +127,13 @@ const innerValue = computed(() => {
122
127
  })
123
128
  })
124
129
 
125
- /** 处理跳转链接中的动态参数 */
126
130
  const actionProps = computed(() => {
127
- // 优化: 提前返回,避免不必要的计算
128
- if (props.dynamicActionKey) {
129
- return get(innerDynamicData.value, props.dynamicActionKey)
130
- }
131
-
132
- // 优化: 只在 jumpUrl 存在时才调用 getDynamicData
133
- const jumpUrl = props.action?.jumpUrl
134
- if (!jumpUrl) {
135
- return props.action
136
- }
137
-
138
- return {
139
- ...props.action,
140
- jumpUrl: getDynamicData(jumpUrl, {
141
- store: store.value,
142
- }),
143
- }
131
+ return resolveActionProps({
132
+ dynamicActionKey: props.dynamicActionKey,
133
+ action: props.action as any,
134
+ innerDynamicData: innerDynamicData.value,
135
+ templateStore: store.value,
136
+ })
144
137
  })
145
138
 
146
139
  const onAvatar = (headImgUrl) => {
@@ -44,7 +44,14 @@
44
44
  v-bind="inputLink"
45
45
  custom-class="!w-full !truncate !pr-2"
46
46
  :customStyle="{
47
- color: form.keywords ? 'var(--content-color)' : '#969696',
47
+ fontSize: placeholderFontSize ? `${placeholderFontSize}rpx` : undefined,
48
+ fontWeight: placeholderFontWeight,
49
+ fontFamily: placeholderFontFamily,
50
+ color: placeholderColor
51
+ ? placeholderColor
52
+ : form.keywords
53
+ ? 'var(--content-color)'
54
+ : '#969696',
48
55
  }"
49
56
  :url-params="urlParams"
50
57
  >
@@ -8,4 +8,8 @@ export interface LcbCalendarSearchProps extends LcbBlockProps {
8
8
  icon?: string
9
9
  mode?: 'link' | 'search'
10
10
  inputLink?: LcbActionViewProps
11
+ placeholderColor?: string
12
+ placeholderFontSize?: number
13
+ placeholderFontWeight?: number
14
+ placeholderFontFamily?: string
11
15
  }
@@ -146,26 +146,6 @@ const coverImgWidth = computed(() => {
146
146
  mode="heightFix"
147
147
  />
148
148
  </view>
149
- <!-- area -->
150
- <!-- && itemProps.areaOnImg -->
151
- <view
152
- v-if="
153
- itemProps.showArea &&
154
- itemAttrs.cityArea &&
155
- itemProps.areaOnImg &&
156
- !itemProps.imgBottomIcon
157
- "
158
- class="flex absolute bottom-0 left-0 z-1 gap-1 p-1 w-full box-border text-white text-3.5 items-center text-shadow-xl"
159
- :style="{
160
- color: itemProps.areaColor,
161
- fontSize: `${itemProps.areaFontSize}rpx`,
162
- }"
163
- >
164
- <wd-icon name="location"></wd-icon>
165
- <view class="line-clamp-1">
166
- {{ itemAttrs.cityArea }}
167
- </view>
168
- </view>
169
149
  <slot></slot>
170
150
  </view>
171
151
  </block>
@@ -63,6 +63,9 @@ const props = withDefaults(defineProps<LcbProductItemProps>(), {
63
63
  itemContentPl: 0,
64
64
  itemGap: 10,
65
65
  titleOnImg: false,
66
+ areaOnImg: false,
67
+ addressOnImg: false,
68
+ priceOnImg: false,
66
69
  })
67
70
  const attrs = useAttrs() as Record<string, any>
68
71
  provide('lcb-product-item-props', props)
@@ -147,6 +150,48 @@ const titleOnImgTextStyle = computed(() => {
147
150
  ...(props.productNameStyle || {}),
148
151
  }
149
152
  })
153
+
154
+ const blockedByImgBottomIcon = computed(() => !!(props.imgBottomIcon && props.imgBottomIconVisible))
155
+
156
+ const titleOnImgVisible = computed(() => {
157
+ return (
158
+ !!props.titleOnImg &&
159
+ !!props.productNameVisible &&
160
+ !!props.productName &&
161
+ !blockedByImgBottomIcon.value
162
+ )
163
+ })
164
+
165
+ const areaOnImgVisible = computed(() => {
166
+ return !!(props.areaOnImg && props.showArea && attrs.cityArea && !blockedByImgBottomIcon.value)
167
+ })
168
+
169
+ const addressOnImgVisible = computed(() => {
170
+ return !!(
171
+ props.addressOnImg &&
172
+ props.addressIntroVisible &&
173
+ !!props.addressIntro &&
174
+ !blockedByImgBottomIcon.value
175
+ )
176
+ })
177
+
178
+ const priceOnImgVisible = computed(() => {
179
+ if (!props.priceOnImg || blockedByImgBottomIcon.value) return false
180
+
181
+ const hasScribePrice = props.scribePriceVisible && isNumber(props.scribePrice)
182
+ const hasPrice = props.priceVisible && isNumber(props.price)
183
+ const hasPriceTips = props.priceTipsVisible && !!props.priceTips
184
+ return !!(hasScribePrice || hasPrice || hasPriceTips)
185
+ })
186
+
187
+ const imgOverlayVisible = computed(() => {
188
+ return (
189
+ titleOnImgVisible.value ||
190
+ areaOnImgVisible.value ||
191
+ addressOnImgVisible.value ||
192
+ priceOnImgVisible.value
193
+ )
194
+ })
150
195
  </script>
151
196
 
152
197
  <template>
@@ -169,28 +214,84 @@ const titleOnImgTextStyle = computed(() => {
169
214
  <template #default>
170
215
  <slot name="coverImgSection" />
171
216
  <view
172
- v-if="
173
- titleOnImg &&
174
- productNameVisible &&
175
- !!productName &&
176
- !(imgBottomIcon && imgBottomIconVisible)
177
- "
178
- class="absolute left-0 w-full z-1 p-1 box-border text-white text-shadow-xl bottom-0"
217
+ v-if="imgOverlayVisible"
218
+ class="absolute left-0 w-full p-1 box-border text-white text-shadow-xl bottom-0"
179
219
  :style="titleOnImgMaskStyle"
180
220
  >
181
- <slot name="productName" :value="productName">
221
+ <view class="flex flex-col gap-1 items-start">
222
+ <view v-if="titleOnImgVisible" class="w-full">
223
+ <slot name="productName" :value="productName">
224
+ <view
225
+ :class="[
226
+ productNameClass,
227
+ 'inline text-[32rpx] font-bold',
228
+ `line-clamp-${titleLineClamp}`,
229
+ 'whitespace-pre-wrap',
230
+ ]"
231
+ :style="titleOnImgTextStyle"
232
+ >
233
+ {{ productName }}
234
+ </view>
235
+ </slot>
236
+ </view>
237
+
182
238
  <view
183
- :class="[
184
- productNameClass,
185
- 'inline text-[32rpx] font-bold',
186
- `line-clamp-${titleLineClamp}`,
187
- 'whitespace-pre-wrap',
188
- ]"
189
- :style="titleOnImgTextStyle"
239
+ v-if="areaOnImgVisible || addressOnImgVisible"
240
+ class="flex gap-1 items-center w-full"
241
+ :style="{
242
+ color: areaColor,
243
+ fontSize: `${areaFontSize}rpx`,
244
+ }"
190
245
  >
191
- {{ productName }}
246
+ <wd-icon name="location"></wd-icon>
247
+ <view class="line-clamp-1">
248
+ {{ areaOnImgVisible ? attrs.cityArea : addressIntro }}
249
+ </view>
192
250
  </view>
193
- </slot>
251
+
252
+ <view v-if="priceOnImgVisible" class="flex flex-col gap-1 items-start leading-none">
253
+ <view class="flex gap-1 justify-start w-full flex-wrap">
254
+ <view
255
+ class="flex gap-[2rpx] items-end line-through"
256
+ v-if="scribePriceVisible && isNumber(scribePrice)"
257
+ >
258
+ <template
259
+ v-for="propName in ['scribePriceUnit', 'scribePrice', 'scribePriceSuffix']"
260
+ :key="propName"
261
+ >
262
+ <ItemValue :prop="propName" v-if="!!$slots?.[propName]">
263
+ <template #scribePrice="{ value }">
264
+ <slot name="scribePrice" :value="value" />
265
+ </template>
266
+ <template #scribePriceUnit="{ value }">
267
+ <slot name="scribePriceUnit" :value="value" />
268
+ </template>
269
+ <template #scribePriceSuffix="{ value }">
270
+ <slot name="scribePriceSuffix" :value="value" />
271
+ </template>
272
+ </ItemValue>
273
+ <ItemValue :prop="propName" v-else />
274
+ </template>
275
+ </view>
276
+
277
+ <view
278
+ class="flex gap-[4rpx] items-end justify-start"
279
+ v-if="priceVisible && isNumber(price)"
280
+ >
281
+ <ItemValue prop="pricePrefix" />
282
+ <ItemValue prop="priceUnit" />
283
+ <ItemValue prop="price" />
284
+ <ItemValue prop="priceSuffix" />
285
+ </view>
286
+ </view>
287
+ <ItemValue prop="priceTips" v-if="!!$slots?.priceTips">
288
+ <template #priceTips="{ value }">
289
+ <slot name="priceTips" :value="value" />
290
+ </template>
291
+ </ItemValue>
292
+ <ItemValue prop="priceTips" v-else />
293
+ </view>
294
+ </view>
194
295
  </view>
195
296
  </template>
196
297
  <template #coverImg="{ value }">
@@ -205,28 +306,84 @@ const titleOnImgTextStyle = computed(() => {
205
306
  <template #default>
206
307
  <slot name="coverImgSection" />
207
308
  <view
208
- v-if="
209
- titleOnImg &&
210
- productNameVisible &&
211
- !!productName &&
212
- !(imgBottomIcon && imgBottomIconVisible)
213
- "
214
- class="absolute left-0 w-full z-1 p-1 box-border text-white text-shadow-xl bottom-0"
309
+ v-if="imgOverlayVisible"
310
+ class="absolute left-0 w-full p-1 box-border text-white text-shadow-xl bottom-0"
215
311
  :style="titleOnImgMaskStyle"
216
312
  >
217
- <slot name="productName" :value="productName">
313
+ <view class="flex flex-col gap-1 items-start">
314
+ <view v-if="titleOnImgVisible" class="w-full">
315
+ <slot name="productName" :value="productName">
316
+ <view
317
+ :class="[
318
+ productNameClass,
319
+ 'inline text-[32rpx] font-bold',
320
+ `line-clamp-${titleLineClamp}`,
321
+ 'whitespace-pre-wrap',
322
+ ]"
323
+ :style="titleOnImgTextStyle"
324
+ >
325
+ {{ productName }}
326
+ </view>
327
+ </slot>
328
+ </view>
329
+
218
330
  <view
219
- :class="[
220
- productNameClass,
221
- 'inline text-[32rpx] font-bold',
222
- `line-clamp-${titleLineClamp}`,
223
- 'whitespace-pre-wrap',
224
- ]"
225
- :style="titleOnImgTextStyle"
331
+ v-if="areaOnImgVisible || addressOnImgVisible"
332
+ class="flex gap-1 items-center w-full"
333
+ :style="{
334
+ color: areaColor,
335
+ fontSize: `${areaFontSize}rpx`,
336
+ }"
226
337
  >
227
- {{ productName }}
338
+ <wd-icon name="location"></wd-icon>
339
+ <view class="line-clamp-1">
340
+ {{ areaOnImgVisible ? attrs.cityArea : addressIntro }}
341
+ </view>
228
342
  </view>
229
- </slot>
343
+
344
+ <view v-if="priceOnImgVisible" class="flex flex-col gap-1 items-start leading-none">
345
+ <view class="flex gap-1 justify-start w-full flex-wrap">
346
+ <view
347
+ class="flex gap-[2rpx] items-end line-through"
348
+ v-if="scribePriceVisible && isNumber(scribePrice)"
349
+ >
350
+ <template
351
+ v-for="propName in ['scribePriceUnit', 'scribePrice', 'scribePriceSuffix']"
352
+ :key="propName"
353
+ >
354
+ <ItemValue :prop="propName" v-if="!!$slots?.[propName]">
355
+ <template #scribePrice="{ value }">
356
+ <slot name="scribePrice" :value="value" />
357
+ </template>
358
+ <template #scribePriceUnit="{ value }">
359
+ <slot name="scribePriceUnit" :value="value" />
360
+ </template>
361
+ <template #scribePriceSuffix="{ value }">
362
+ <slot name="scribePriceSuffix" :value="value" />
363
+ </template>
364
+ </ItemValue>
365
+ <ItemValue :prop="propName" v-else />
366
+ </template>
367
+ </view>
368
+
369
+ <view
370
+ class="flex gap-[4rpx] items-end justify-start"
371
+ v-if="priceVisible && isNumber(price)"
372
+ >
373
+ <ItemValue prop="pricePrefix" />
374
+ <ItemValue prop="priceUnit" />
375
+ <ItemValue prop="price" />
376
+ <ItemValue prop="priceSuffix" />
377
+ </view>
378
+ </view>
379
+ <ItemValue prop="priceTips" v-if="!!$slots?.priceTips">
380
+ <template #priceTips="{ value }">
381
+ <slot name="priceTips" :value="value" />
382
+ </template>
383
+ </ItemValue>
384
+ <ItemValue prop="priceTips" v-else />
385
+ </view>
386
+ </view>
230
387
  </view>
231
388
  </template>
232
389
  </ItemValue>
@@ -274,7 +431,7 @@ const titleOnImgTextStyle = computed(() => {
274
431
  </ItemValue>
275
432
  </view>
276
433
 
277
- <ItemValue prop="addressIntro" v-if="addressIntroVisible">
434
+ <ItemValue prop="addressIntro" v-if="addressIntroVisible && !addressOnImg">
278
435
  <!-- <template #addressIntro="{ value }"> -->
279
436
  <!-- <slot name="addressIntro" :value="value" /> -->
280
437
  <!-- </template> -->
@@ -311,6 +468,7 @@ const titleOnImgTextStyle = computed(() => {
311
468
  </view>
312
469
  <view class="flex-1"></view>
313
470
  <view
471
+ v-if="!priceOnImg"
314
472
  class="flex flex-col gap-1 items-end leading-none"
315
473
  :style="{
316
474
  marginTop: transformValueUnit(itemGap),
@@ -369,13 +527,10 @@ const titleOnImgTextStyle = computed(() => {
369
527
  <slot name="itemRightSection" />
370
528
  </view>
371
529
  <slot name="itemBottomSection" />
372
- <view
373
- v-if="attrs.rightBottomIcon && attrs.rightBottomFlag"
374
- class="absolute bottom-7 right-3 z-1"
375
- >
530
+ <view v-if="attrs.rightBottomIcon && attrs.rightBottomFlag" class="absolute bottom-7 right-3">
376
531
  <wd-img :src="attrs.rightBottomIcon" width="120rpx" mode="widthFix" />
377
532
  </view>
378
- <view v-if="attrs.rightTopIcon && attrs.rightTopFlag" class="absolute top-0 right-0 z-1">
533
+ <view v-if="attrs.rightTopIcon && attrs.rightTopFlag" class="absolute top-0 right-0">
379
534
  <wd-img :src="attrs.rightTopIcon" width="120rpx" mode="widthFix" />
380
535
  </view>
381
536
  </view>
@@ -399,28 +554,84 @@ const titleOnImgTextStyle = computed(() => {
399
554
  <template #default>
400
555
  <slot name="coverImgSection" />
401
556
  <view
402
- v-if="
403
- titleOnImg &&
404
- productNameVisible &&
405
- !!productName &&
406
- !(imgBottomIcon && imgBottomIconVisible)
407
- "
408
- class="absolute left-0 w-full z-1 p-1 box-border text-white text-shadow-xl bottom-0"
557
+ v-if="imgOverlayVisible"
558
+ class="absolute left-0 w-full p-1 box-border text-white text-shadow-xl bottom-0"
409
559
  :style="titleOnImgMaskStyle"
410
560
  >
411
- <slot name="productName" :value="productName">
561
+ <view class="flex flex-col gap-1 items-start">
562
+ <view v-if="titleOnImgVisible" class="w-full">
563
+ <slot name="productName" :value="productName">
564
+ <view
565
+ :class="[
566
+ productNameClass,
567
+ 'inline text-[32rpx] font-bold',
568
+ `line-clamp-${titleLineClamp}`,
569
+ 'whitespace-pre-wrap',
570
+ ]"
571
+ :style="titleOnImgTextStyle"
572
+ >
573
+ {{ productName }}
574
+ </view>
575
+ </slot>
576
+ </view>
577
+
412
578
  <view
413
- :class="[
414
- productNameClass,
415
- 'inline text-[32rpx] font-bold',
416
- `line-clamp-${titleLineClamp}`,
417
- 'whitespace-pre-wrap',
418
- ]"
419
- :style="titleOnImgTextStyle"
579
+ v-if="areaOnImgVisible || addressOnImgVisible"
580
+ class="flex gap-1 items-center w-full"
581
+ :style="{
582
+ color: areaColor,
583
+ fontSize: `${areaFontSize}rpx`,
584
+ }"
420
585
  >
421
- {{ productName }}
586
+ <wd-icon name="location"></wd-icon>
587
+ <view class="line-clamp-1">
588
+ {{ areaOnImgVisible ? attrs.cityArea : addressIntro }}
589
+ </view>
590
+ </view>
591
+
592
+ <view v-if="priceOnImgVisible" class="flex flex-col gap-1 items-start leading-none">
593
+ <view class="flex gap-1 justify-start w-full flex-wrap">
594
+ <view
595
+ class="flex gap-[2rpx] items-end line-through"
596
+ v-if="scribePriceVisible && isNumber(scribePrice)"
597
+ >
598
+ <template
599
+ v-for="propName in ['scribePriceUnit', 'scribePrice', 'scribePriceSuffix']"
600
+ :key="propName"
601
+ >
602
+ <ItemValue :prop="propName" v-if="!!$slots?.[propName]">
603
+ <template #scribePrice="{ value }">
604
+ <slot name="scribePrice" :value="value" />
605
+ </template>
606
+ <template #scribePriceUnit="{ value }">
607
+ <slot name="scribePriceUnit" :value="value" />
608
+ </template>
609
+ <template #scribePriceSuffix="{ value }">
610
+ <slot name="scribePriceSuffix" :value="value" />
611
+ </template>
612
+ </ItemValue>
613
+ <ItemValue :prop="propName" v-else />
614
+ </template>
615
+ </view>
616
+
617
+ <view
618
+ class="flex gap-[4rpx] items-end justify-start"
619
+ v-if="priceVisible && isNumber(price)"
620
+ >
621
+ <ItemValue prop="pricePrefix" />
622
+ <ItemValue prop="priceUnit" />
623
+ <ItemValue prop="price" />
624
+ <ItemValue prop="priceSuffix" />
625
+ </view>
626
+ </view>
627
+ <ItemValue prop="priceTips" v-if="!!$slots?.priceTips">
628
+ <template #priceTips="{ value }">
629
+ <slot name="priceTips" :value="value" />
630
+ </template>
631
+ </ItemValue>
632
+ <ItemValue prop="priceTips" v-else />
422
633
  </view>
423
- </slot>
634
+ </view>
424
635
  </view>
425
636
  </template>
426
637
  <template #coverImg="{ value }">
@@ -431,28 +642,84 @@ const titleOnImgTextStyle = computed(() => {
431
642
  <template #default>
432
643
  <slot name="coverImgSection" />
433
644
  <view
434
- v-if="
435
- titleOnImg &&
436
- productNameVisible &&
437
- !!productName &&
438
- !(imgBottomIcon && imgBottomIconVisible)
439
- "
440
- class="absolute left-0 w-full z-1 p-1 box-border text-white text-shadow-xl bottom-0"
645
+ v-if="imgOverlayVisible"
646
+ class="absolute left-0 w-full p-1 box-border text-white text-shadow-xl bottom-0"
441
647
  :style="titleOnImgMaskStyle"
442
648
  >
443
- <slot name="productName" :value="productName">
649
+ <view class="flex flex-col gap-1 items-start">
650
+ <view v-if="titleOnImgVisible" class="w-full">
651
+ <slot name="productName" :value="productName">
652
+ <view
653
+ :class="[
654
+ productNameClass,
655
+ 'inline text-[32rpx] font-bold',
656
+ `line-clamp-${titleLineClamp}`,
657
+ 'whitespace-pre-wrap',
658
+ ]"
659
+ :style="titleOnImgTextStyle"
660
+ >
661
+ {{ productName }}
662
+ </view>
663
+ </slot>
664
+ </view>
665
+
444
666
  <view
445
- :class="[
446
- productNameClass,
447
- 'inline text-[32rpx] font-bold',
448
- `line-clamp-${titleLineClamp}`,
449
- 'whitespace-pre-wrap',
450
- ]"
451
- :style="titleOnImgTextStyle"
667
+ v-if="areaOnImgVisible || addressOnImgVisible"
668
+ class="flex gap-1 items-center w-full"
669
+ :style="{
670
+ color: areaColor,
671
+ fontSize: `${areaFontSize}rpx`,
672
+ }"
452
673
  >
453
- {{ productName }}
674
+ <wd-icon name="location"></wd-icon>
675
+ <view class="line-clamp-1">
676
+ {{ areaOnImgVisible ? attrs.cityArea : addressIntro }}
677
+ </view>
454
678
  </view>
455
- </slot>
679
+
680
+ <view v-if="priceOnImgVisible" class="flex flex-col gap-1 items-start leading-none">
681
+ <view class="flex gap-1 justify-start w-full flex-wrap">
682
+ <view
683
+ class="flex gap-[2rpx] items-end line-through"
684
+ v-if="scribePriceVisible && isNumber(scribePrice)"
685
+ >
686
+ <template
687
+ v-for="propName in ['scribePriceUnit', 'scribePrice', 'scribePriceSuffix']"
688
+ :key="propName"
689
+ >
690
+ <ItemValue :prop="propName" v-if="!!$slots?.[propName]">
691
+ <template #scribePrice="{ value }">
692
+ <slot name="scribePrice" :value="value" />
693
+ </template>
694
+ <template #scribePriceUnit="{ value }">
695
+ <slot name="scribePriceUnit" :value="value" />
696
+ </template>
697
+ <template #scribePriceSuffix="{ value }">
698
+ <slot name="scribePriceSuffix" :value="value" />
699
+ </template>
700
+ </ItemValue>
701
+ <ItemValue :prop="propName" v-else />
702
+ </template>
703
+ </view>
704
+
705
+ <view
706
+ class="flex gap-[4rpx] items-end justify-start"
707
+ v-if="priceVisible && isNumber(price)"
708
+ >
709
+ <ItemValue prop="pricePrefix" />
710
+ <ItemValue prop="priceUnit" />
711
+ <ItemValue prop="price" />
712
+ <ItemValue prop="priceSuffix" />
713
+ </view>
714
+ </view>
715
+ <ItemValue prop="priceTips" v-if="!!$slots?.priceTips">
716
+ <template #priceTips="{ value }">
717
+ <slot name="priceTips" :value="value" />
718
+ </template>
719
+ </ItemValue>
720
+ <ItemValue prop="priceTips" v-else />
721
+ </view>
722
+ </view>
456
723
  </view>
457
724
  </template>
458
725
  </ItemValue>
@@ -494,7 +761,7 @@ const titleOnImgTextStyle = computed(() => {
494
761
  </ItemValue>
495
762
  </view>
496
763
 
497
- <ItemValue prop="addressIntro" v-if="addressIntroVisible">
764
+ <ItemValue prop="addressIntro" v-if="addressIntroVisible && !addressOnImg">
498
765
  <!-- <template #addressIntro="{ value }">
499
766
  <slot name="addressIntro" :value="value" />
500
767
  </template> -->
@@ -529,6 +796,7 @@ const titleOnImgTextStyle = computed(() => {
529
796
  </view>
530
797
  <view class="flex-1"></view>
531
798
  <view
799
+ v-if="!priceOnImg"
532
800
  class="flex flex-col gap-1 items-end leading-none"
533
801
  :style="{
534
802
  marginTop: transformValueUnit(itemGap),
@@ -571,13 +839,10 @@ const titleOnImgTextStyle = computed(() => {
571
839
  <slot name="itemBottomSection" />
572
840
  </view>
573
841
  <slot name="itemRightSection" />
574
- <view
575
- v-if="attrs.rightBottomIcon && attrs.rightBottomFlag"
576
- class="absolute bottom-7 right-3 z-1"
577
- >
842
+ <view v-if="attrs.rightBottomIcon && attrs.rightBottomFlag" class="absolute bottom-7 right-3">
578
843
  <wd-img :src="attrs.rightBottomIcon" width="120rpx" mode="widthFix" />
579
844
  </view>
580
- <view v-if="attrs.rightTopIcon && attrs.rightTopFlag" class="absolute top-0 right-0 z-1">
845
+ <view v-if="attrs.rightTopIcon && attrs.rightTopFlag" class="absolute top-0 right-0">
581
846
  <wd-img :src="attrs.rightTopIcon" width="120rpx" mode="widthFix" />
582
847
  </view>
583
848
  </view>
@@ -16,6 +16,8 @@ export interface LcbProductItemProps {
16
16
  tagType?: TagType
17
17
  tagContentColor?: string
18
18
  addressOnImg?: boolean
19
+ // 价格块显示在图片上(包含 price / scribePrice 及其相关字段)
20
+ priceOnImg?: boolean
19
21
  tagBgColor?: string
20
22
  tagPlain?: boolean
21
23
  tagMark?: boolean
@@ -1,20 +1,16 @@
1
1
  <template>
2
- <lcb-block
3
- v-bind="$props"
4
- :custom-style="listStyle"
5
- v-if="props.pageProps?.pagingEnabled || renderList.length"
2
+ <z-paging
3
+ ref="paging"
4
+ :auto="true"
5
+ v-model="renderList"
6
+ @query="queryList"
7
+ :fixed="false"
8
+ :default-page-size="props.pageProps?.pageLimit"
9
+ :use-page-scroll="props.pageProps?.pagingUsePageScroll"
10
+ :height="props.pageProps?.pagingUsePageScroll ? undefined : props.pageProps?.pagingHeight"
11
+ v-if="props.pageProps?.pagingEnabled"
6
12
  >
7
- <z-paging
8
- ref="paging"
9
- :auto="false"
10
- v-model="renderList"
11
- @query="queryList"
12
- :fixed="false"
13
- :default-page-size="props.pageProps?.pageLimit"
14
- :use-page-scroll="props.pageProps?.pagingUsePageScroll"
15
- :height="props.pageProps?.pagingUsePageScroll ? undefined : props.pageProps?.pagingHeight"
16
- v-if="props.pageProps?.pagingEnabled"
17
- >
13
+ <lcb-block v-bind="$props" :custom-style="listStyle">
18
14
  <view
19
15
  v-for="(item, index) in renderList"
20
16
  :key="getItemKey(item, index)"
@@ -23,17 +19,17 @@
23
19
  >
24
20
  <slot :data="item" :list="list" />
25
21
  </view>
26
- </z-paging>
27
- <template v-else>
28
- <view
29
- v-for="(item, index) in renderList"
30
- :key="getItemKey(item, index)"
31
- class="flex-shrink-0"
32
- :style="itemStyle"
33
- >
34
- <slot :data="item" :list="list" />
35
- </view>
36
- </template>
22
+ </lcb-block>
23
+ </z-paging>
24
+ <lcb-block v-bind="$props" :custom-style="listStyle" v-else>
25
+ <view
26
+ v-for="(item, index) in renderList"
27
+ :key="getItemKey(item, index)"
28
+ class="flex-shrink-0"
29
+ :style="itemStyle"
30
+ >
31
+ <slot :data="item" :list="list" />
32
+ </view>
37
33
  </lcb-block>
38
34
  </template>
39
35
 
@@ -85,14 +81,9 @@ const transformStringArrayToObjectArray = (data: unknown[]): unknown[] => {
85
81
 
86
82
  const queryList = async (page: number, limit: number) => {
87
83
  pageSearch.value = { page, limit }
88
- const { data } = await dynamicRequest(
89
- props.dataSource,
90
- innerDynamicData.value,
91
- userStore?.userInfo,
92
- {
93
- pageSearch: pageSearch.value,
94
- },
95
- )
84
+ const data = await dynamicRequest(props.dataSource, innerDynamicData.value, userStore?.userInfo, {
85
+ pageSearch: pageSearch.value,
86
+ })
96
87
  paging.value?.complete?.(data)
97
88
  }
98
89
 
@@ -145,11 +136,12 @@ const itemStyle = computed(() => {
145
136
 
146
137
  // 优化:减少对象创建,使用固定的样式对象
147
138
  const listStyle = computed(() => {
139
+ const gap = transformValueUnit(props.gap)
148
140
  return {
149
141
  width: '100%',
150
142
  display: props.display,
151
143
  'flex-direction': props.flexDirection,
152
- gap: transformValueUnit(props.gap),
144
+ gap,
153
145
  'align-items': 'stretch',
154
146
  'grid-template-columns': `repeat(${props.gridColumns}, minmax(0, 1fr))`,
155
147
  'overflow-x': props.scrollX ? 'auto' : 'hidden',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tplc/business",
3
- "version": "0.7.52",
3
+ "version": "0.7.54",
4
4
  "keywords": [
5
5
  "业务组件"
6
6
  ],
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "peerDependencies": {
13
13
  "vue": ">=3.2.47",
14
- "@tplc/wot": "1.0.24"
14
+ "@tplc/wot": "1.0.25"
15
15
  },
16
16
  "engines": {
17
17
  "node": ">=18",
@@ -6,4 +6,8 @@ export interface LcbCalendarSearchProps extends LcbBlockProps {
6
6
  icon?: string
7
7
  mode?: 'link' | 'search'
8
8
  inputLink?: LcbActionViewProps
9
+ placeholderColor?: string
10
+ placeholderFontSize?: number
11
+ placeholderFontWeight?: number
12
+ placeholderFontFamily?: string
9
13
  }
@@ -120,6 +120,9 @@ declare const __VLS_component: import('vue').DefineComponent<
120
120
  itemContentPl: number
121
121
  itemGap: number
122
122
  titleOnImg: boolean
123
+ areaOnImg: boolean
124
+ addressOnImg: boolean
125
+ priceOnImg: boolean
123
126
  }
124
127
  >,
125
128
  {},
@@ -184,6 +187,9 @@ declare const __VLS_component: import('vue').DefineComponent<
184
187
  itemContentPl: number
185
188
  itemGap: number
186
189
  titleOnImg: boolean
190
+ areaOnImg: boolean
191
+ addressOnImg: boolean
192
+ priceOnImg: boolean
187
193
  }
188
194
  >
189
195
  >
@@ -204,8 +210,11 @@ declare const __VLS_component: import('vue').DefineComponent<
204
210
  itemContentPl: number
205
211
  priceUnit: any
206
212
  scribePriceUnit: any
213
+ areaOnImg: boolean
207
214
  tagOverflowWrap: boolean
208
215
  tagType: import('@tplc/wot/components/wd-tag/types').TagType
216
+ addressOnImg: boolean
217
+ priceOnImg: boolean
209
218
  tagPlain: boolean
210
219
  tagMark: boolean
211
220
  tagRound: boolean
@@ -13,6 +13,7 @@ export interface LcbProductItemProps {
13
13
  tagType?: TagType
14
14
  tagContentColor?: string
15
15
  addressOnImg?: boolean
16
+ priceOnImg?: boolean
16
17
  tagBgColor?: string
17
18
  tagPlain?: boolean
18
19
  tagMark?: boolean
@@ -0,0 +1,28 @@
1
+ type AnyRecord = Record<string, any>
2
+ /**
3
+ * 根据 keyFromUser 选择动态数据源
4
+ * - true: userStore.userInfo
5
+ * - false: innerDynamicData
6
+ */
7
+ export declare function selectDynamicSource(
8
+ keyFromUser: boolean | undefined,
9
+ userInfo: AnyRecord | undefined,
10
+ innerDynamicData: AnyRecord | undefined,
11
+ ): AnyRecord | undefined
12
+ /**
13
+ * 统一处理 actionProps:
14
+ * - dynamicActionKey 优先:从 innerDynamicData 取整段 action 配置
15
+ * - 否则对 action.jumpUrl 做动态参数替换(${xxx})
16
+ */
17
+ export declare function resolveActionProps({
18
+ dynamicActionKey,
19
+ action,
20
+ innerDynamicData,
21
+ templateStore,
22
+ }: {
23
+ dynamicActionKey?: string
24
+ action?: AnyRecord
25
+ innerDynamicData?: AnyRecord
26
+ templateStore?: AnyRecord
27
+ }): any
28
+ export {}
@@ -0,0 +1,50 @@
1
+ import { get } from 'lodash-es'
2
+ import { getDynamicData } from './utils'
3
+
4
+ type AnyRecord = Record<string, any>
5
+
6
+ /**
7
+ * 根据 keyFromUser 选择动态数据源
8
+ * - true: userStore.userInfo
9
+ * - false: innerDynamicData
10
+ */
11
+ export function selectDynamicSource(
12
+ keyFromUser: boolean | undefined,
13
+ userInfo: AnyRecord | undefined,
14
+ innerDynamicData: AnyRecord | undefined,
15
+ ) {
16
+ return (keyFromUser ? userInfo : innerDynamicData) as AnyRecord | undefined
17
+ }
18
+
19
+ /**
20
+ * 统一处理 actionProps:
21
+ * - dynamicActionKey 优先:从 innerDynamicData 取整段 action 配置
22
+ * - 否则对 action.jumpUrl 做动态参数替换(${xxx})
23
+ */
24
+ export function resolveActionProps({
25
+ dynamicActionKey,
26
+ action,
27
+ innerDynamicData,
28
+ templateStore,
29
+ }: {
30
+ dynamicActionKey?: string
31
+ action?: AnyRecord
32
+ innerDynamicData?: AnyRecord
33
+ templateStore?: AnyRecord
34
+ }) {
35
+ if (dynamicActionKey) {
36
+ return get(innerDynamicData, dynamicActionKey)
37
+ }
38
+
39
+ const jumpUrl = action?.jumpUrl
40
+ if (!jumpUrl) {
41
+ return action
42
+ }
43
+
44
+ return {
45
+ ...action,
46
+ jumpUrl: getDynamicData(jumpUrl, {
47
+ store: templateStore,
48
+ }),
49
+ }
50
+ }