@tplc/business 0.7.22 → 0.7.24

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,35 @@
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.24](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/compare/v0.7.19...v0.7.24) (2025-12-21)
6
+
7
+
8
+ ### 🚀 Chore | 构建/工程依赖/工具
9
+
10
+ * **release:** 0.7.20 ([3c69dbe](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/3c69dbe444fc50cc4f470ffa316a379c739dab4a))
11
+ * **release:** 0.7.21 ([2c0db34](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/2c0db34f5ec241cc0297d68a5755c7ae7a75dcb8))
12
+ * **release:** 0.7.22 ([31f6a87](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/31f6a874afdc82a4a58a8ef610194757666144d0))
13
+ * **release:** 0.7.23 ([9c56ffd](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/9c56ffd1c060c2d81105a1049654f80081674d10))
14
+ * **release:** 1.0.16 ([6ab5880](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/6ab588048db6a5b3d58529ad239f895c989d6d03))
15
+
16
+
17
+ ### ✨ Features | 新功能
18
+
19
+ * 优化lcb ([4508077](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/45080773f73d2f9329de373d2b713eb5b644dea0))
20
+ * 优化pay ([fdead3a](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/fdead3a80ca5d7de1de0eff9745e35e2c4fca6db))
21
+ * 兼容点击 ([8027588](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/8027588f38760e3de6b01205ec982eceb91f0e86))
22
+ * 新增progress ([474bcac](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/474bcac7c08cb234e1d6945036d8ca4d64fa4f88))
23
+ * 更新wot-number ([f700988](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/f700988b15ec08eef0d1b8c140483eca8e0afed4))
24
+ * 调整数据结构 ([b6f0d24](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/b6f0d24a26d3d4dbf05ca855ed687d6819eb7c0f))
25
+
26
+ ### [0.7.23](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/compare/v1.0.16...v0.7.23) (2025-12-18)
27
+
28
+
29
+ ### ✨ Features | 新功能
30
+
31
+ * 新增progress ([474bcac](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/474bcac7c08cb234e1d6945036d8ca4d64fa4f88))
32
+ * 更新wot-number ([f700988](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/f700988b15ec08eef0d1b8c140483eca8e0afed4))
33
+
5
34
  ### [0.7.22](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/compare/v0.7.21...v0.7.22) (2025-12-17)
6
35
 
7
36
 
@@ -4,23 +4,12 @@
4
4
  v-bind="actionProps"
5
5
  :renderMode="actionProps?.jumpType ? 'button' : 'noClick'"
6
6
  >
7
- <view
8
- :style="{
9
- display: display,
10
- gap: transformValueUnit(gap),
11
- overflowX: scrollX ? 'auto' : 'hidden',
12
- ...innerStyle,
13
- }"
14
- class="h-full"
15
- >
7
+ <view :style="containerStyle" class="h-full">
16
8
  <view
17
9
  v-for="(item, index) in list"
18
10
  :key="item.id"
19
11
  class="slot-wrapper z-1"
20
- :style="{
21
- ...getStyle(index),
22
- overflowX,
23
- }"
12
+ :style="getItemStyle(index)"
24
13
  >
25
14
  <slot :item="item" />
26
15
  </view>
@@ -30,7 +19,7 @@
30
19
  </template>
31
20
 
32
21
  <script setup lang="ts">
33
- import { computed, watch } from 'vue'
22
+ import { computed, watch, ref } from 'vue'
34
23
  import { LcbAreaProps } from './types'
35
24
  import { getFlexStyle, transformValueUnit } from '@tplc/business/utils/transform'
36
25
  import { get } from 'lodash-es'
@@ -51,29 +40,66 @@ const props = withDefaults(defineProps<LcbAreaProps>(), {
51
40
  overflowX: 'initial',
52
41
  })
53
42
  const { userStore, innerDynamicData, pageInfo } = useDynamicData()
54
- const innerStyle = computed(() => {
43
+
44
+ // 缓存 gap 的单位转换
45
+ const gapValue = computed(() => transformValueUnit(props.gap))
46
+
47
+ // 优化容器样式计算,合并为一个 computed
48
+ const containerStyle = computed(() => {
49
+ const baseStyle = {
50
+ display: props.display,
51
+ gap: gapValue.value,
52
+ overflowX: (props.scrollX ? 'auto' : 'hidden') as 'auto' | 'hidden',
53
+ }
54
+
55
55
  if (props.display === 'grid') {
56
56
  return {
57
+ ...baseStyle,
57
58
  gridTemplateColumns: `repeat(${props.gridColumns}, minmax(0, 1fr))`,
58
59
  }
59
60
  }
61
+
60
62
  return {
63
+ ...baseStyle,
61
64
  flexDirection: props.displayFlex,
62
65
  alignItems: 'stretch',
63
66
  }
64
67
  })
65
- const getStyle = (index: number) => {
66
- const flex = props.areaFlexs?.[index]?.flex ?? 1
67
- return {
68
- ...getFlexStyle(props.itemAlign),
69
- gridColumn:
70
- props.areaItems?.[index]?.colSpan && props.display === 'grid'
71
- ? `span ${props.areaItems?.[index]?.colSpan} / span ${props.areaItems?.[index]?.colSpan}`
72
- : undefined,
73
- flex: props.display === 'flex' ? flex || 'auto' : undefined,
74
- flexShrink: props.display === 'flex' ? (props.flexShrink ?? 1) : undefined,
75
- width: flex ? '100%' : 'auto',
76
- }
68
+
69
+ // 缓存 itemAlign flex 样式,避免每个 item 都重复计算
70
+ const itemAlignStyle = computed(() => getFlexStyle(props.itemAlign))
71
+
72
+ // 优化: 缓存公共样式部分
73
+ const commonItemStyle = computed(() => ({
74
+ ...itemAlignStyle.value,
75
+ overflowX: props.overflowX,
76
+ }))
77
+
78
+ // 优化: 缓存每个 item 的样式,避免重复计算
79
+ const itemStylesCache = computed(() => {
80
+ const isGrid = props.display === 'grid'
81
+ const isFlex = props.display === 'flex'
82
+ const flexShrink = props.flexShrink ?? 1
83
+
84
+ return (
85
+ props.list?.map((_, index) => {
86
+ const flex = props.areaFlexs?.[index]?.flex ?? 1
87
+ const colSpan = props.areaItems?.[index]?.colSpan
88
+
89
+ return {
90
+ ...commonItemStyle.value,
91
+ gridColumn: colSpan && isGrid ? `span ${colSpan} / span ${colSpan}` : undefined,
92
+ flex: isFlex ? flex || 'auto' : undefined,
93
+ flexShrink: isFlex ? flexShrink : undefined,
94
+ width: flex ? '100%' : 'auto',
95
+ }
96
+ }) || []
97
+ )
98
+ })
99
+
100
+ // 优化: 使用缓存的样式
101
+ const getItemStyle = (index: number) => {
102
+ return itemStylesCache.value[index] || commonItemStyle.value
77
103
  }
78
104
 
79
105
  const getData = async () => {
@@ -88,16 +114,26 @@ const getData = async () => {
88
114
 
89
115
  /** 处理跳转链接中的动态参数 */
90
116
  const actionProps = computed(() => {
117
+ // 优化: 提前返回,避免不必要的计算
91
118
  if (props.dynamicActionKey) {
92
119
  return get(innerDynamicData.value, props.dynamicActionKey)
93
120
  }
121
+
122
+ // 优化: 只在 jumpUrl 存在时才调用 getDynamicData
123
+ const jumpUrl = props.action?.jumpUrl
124
+ if (!jumpUrl) {
125
+ return props.action
126
+ }
127
+
94
128
  return {
95
129
  ...props.action,
96
- jumpUrl: getDynamicData(props.action?.jumpUrl, {
130
+ jumpUrl: getDynamicData(jumpUrl, {
97
131
  store: innerDynamicData.value,
98
132
  }),
99
133
  }
100
134
  })
135
+
136
+ // 优化: 移除 deep: true,只监听引用变化
101
137
  watch(
102
138
  () => props.dataSource,
103
139
  () => {
@@ -105,43 +141,55 @@ watch(
105
141
  },
106
142
  {
107
143
  immediate: true,
108
- deep: true,
109
144
  },
110
145
  )
146
+
147
+ // 优化: 提取比较逻辑,减少重复代码
148
+ const compareValues = (value: any, compareValue: string, compareType: string) => {
149
+ switch (compareType) {
150
+ case '=':
151
+ return `${value}` === compareValue
152
+ case '>=':
153
+ return value >= compareValue
154
+ case '<=':
155
+ return value <= compareValue
156
+ case '>':
157
+ return value > compareValue
158
+ case '<':
159
+ return value < compareValue
160
+ case '!=':
161
+ return value !== compareValue
162
+ case 'includes':
163
+ return value?.includes?.(compareValue) ?? false
164
+ default:
165
+ return `${value}` === compareValue
166
+ }
167
+ }
168
+
169
+ // 优化: 缓存数据源
170
+ const dependDataSource = computed(() => {
171
+ return props.keyFromUser ? userStore?.userInfo : innerDynamicData.value
172
+ })
173
+
111
174
  const showArea = computed(() => {
112
- if (props.dependKey) {
113
- const value = get(
114
- props.keyFromUser ? userStore?.userInfo : innerDynamicData.value,
115
- props.dependKey,
116
- )
117
-
118
- if (props.dependKeyCompareValue) {
119
- const compareValue = `${props.dependKeyCompareValue}`
120
- switch (props.compareType) {
121
- case '=':
122
- return `${value}` === compareValue
123
- case '>=':
124
- return value >= compareValue
125
- case '<=':
126
- return value <= compareValue
127
- case '>':
128
- return value > compareValue
129
- case '<':
130
- return value < compareValue
131
- case '!=':
132
- return value !== compareValue
133
- case 'includes':
134
- return value.includes(compareValue)
135
- default:
136
- return `${value}` === compareValue
137
- }
138
- }
139
- if (value === undefined) {
140
- return false
141
- }
142
- return props.reverse ? !value : Boolean(value)
175
+ // 优化: 提前返回,避免不必要的计算
176
+ if (!props.dependKey) {
177
+ return true
178
+ }
179
+
180
+ const value = get(dependDataSource.value, props.dependKey)
181
+
182
+ // 优化: 合并 undefined 检查
183
+ if (value === undefined) {
184
+ return false
185
+ }
186
+
187
+ if (props.dependKeyCompareValue) {
188
+ const compareValue = `${props.dependKeyCompareValue}`
189
+ return compareValues(value, compareValue, props.compareType || '=')
143
190
  }
144
- return true
191
+
192
+ return props.reverse ? !value : Boolean(value)
145
193
  })
146
194
  </script>
147
195
 
@@ -1,27 +1,8 @@
1
1
  <template>
2
2
  <view
3
- :style="{
4
- width: position === 'absolute' ? '100%' : '',
5
- padding: `${transformValueUnit(paddingTop || paddingVertical || 0)} ${transformValueUnit(paddingRight || paddingHorizontal || 0)} ${transformValueUnit(paddingBottom || paddingVertical || 0)} ${transformValueUnit(paddingLeft || paddingHorizontal || 0)}`,
6
- borderRadius: `${transformValueUnit(topRadius || radius)} ${transformValueUnit(topRadius || radius)} ${transformValueUnit(bottomRadius || radius)} ${transformValueUnit(bottomRadius || radius)}`,
7
- color,
8
- background: `${backgroundPosition} / ${backgroundSize} ${backgroundRepeat} url('${dynamicBgImage}'), ${innerBackgroundColor || 'transparent'}`,
9
- fontSize: transformValueUnit(fontSize),
10
- fontWeight,
11
- borderColor,
12
- zIndex,
13
- position,
14
- borderWidth: transformValueUnit(borderWidth),
15
- boxShadow:
16
- shadowColor && shadowSize ? `0px 0px ${blurSize}px ${shadowSize}px ${shadowColor}` : '',
17
- textAlign,
18
- margin: `${transformValueUnit(floatUp ? -floatUp : marginTop || 0)} ${transformValueUnit(marginRight || marginHorizontal || 0)} ${transformValueUnit(marginBottom || 0)} ${transformValueUnit(marginLeft || marginHorizontal || 0)}`,
19
- ...customStyle,
20
- ...getFlexStyle(align),
21
- ...getInnerStyle,
22
- }"
3
+ :style="getFinalStyle"
23
4
  class="box-border overflow-hidden relative"
24
- :class="customClass"
5
+ :class="props.customClass"
25
6
  >
26
7
  <slot />
27
8
  </view>
@@ -69,11 +50,11 @@ const innerBackgroundColor = computed(() => {
69
50
  const getInnerStyle = computed(() => {
70
51
  const innerStyle = Object.keys(props.dynamicStyleOptions || {}).reduce(
71
52
  (acc, key) => {
72
- const currentOptions = props.dynamicStyleOptions[key]
53
+ const currentOptions = props.dynamicStyleOptions?.[key]
73
54
  if (key) {
74
55
  const data = get(
75
- currentOptions.keyFrom === 'user' ? userStore?.userInfo : innerDynamicData.value,
76
- currentOptions.dynamicKey,
56
+ currentOptions?.keyFrom === 'user' ? userStore?.userInfo : innerDynamicData.value,
57
+ currentOptions?.dynamicKey || '',
77
58
  )
78
59
  if (data) {
79
60
  acc[key] = data
@@ -86,4 +67,64 @@ const getInnerStyle = computed(() => {
86
67
  )
87
68
  return innerStyle
88
69
  })
70
+
71
+ // 缓存 padding 计算结果,减少 transformValueUnit 调用次数
72
+ const paddingStyle = computed(() => {
73
+ const top = transformValueUnit(props.paddingTop || props.paddingVertical || 0)
74
+ const right = transformValueUnit(props.paddingRight || props.paddingHorizontal || 0)
75
+ const bottom = transformValueUnit(props.paddingBottom || props.paddingVertical || 0)
76
+ const left = transformValueUnit(props.paddingLeft || props.paddingHorizontal || 0)
77
+ return `${top} ${right} ${bottom} ${left}`
78
+ })
79
+
80
+ // 缓存 margin 计算结果
81
+ const marginStyle = computed(() => {
82
+ const top = transformValueUnit(props.floatUp ? -props.floatUp : props.marginTop || 0)
83
+ const right = transformValueUnit(props.marginRight || props.marginHorizontal || 0)
84
+ const bottom = transformValueUnit(props.marginBottom || 0)
85
+ const left = transformValueUnit(props.marginLeft || props.marginHorizontal || 0)
86
+ return `${top} ${right} ${bottom} ${left}`
87
+ })
88
+
89
+ // 缓存 borderRadius 计算结果,避免重复计算
90
+ const borderRadiusStyle = computed(() => {
91
+ const topR = props.topRadius || props.radius
92
+ const bottomR = props.bottomRadius || props.radius
93
+ const topValue = transformValueUnit(topR)
94
+ const bottomValue = transformValueUnit(bottomR)
95
+ return `${topValue} ${topValue} ${bottomValue} ${bottomValue}`
96
+ })
97
+
98
+ // 优化 background 字符串拼接
99
+ const backgroundStyle = computed(() => {
100
+ const bgImage = dynamicBgImage.value
101
+ const bgColor = innerBackgroundColor.value || 'transparent'
102
+ if (!bgImage) return bgColor
103
+ return `${props.backgroundPosition} / ${props.backgroundSize} ${props.backgroundRepeat} url('${bgImage}'), ${bgColor}`
104
+ })
105
+
106
+ const getFinalStyle = computed(() => {
107
+ return {
108
+ width: props.position === 'absolute' ? '100%' : '',
109
+ padding: paddingStyle.value,
110
+ borderRadius: borderRadiusStyle.value,
111
+ color: props.color,
112
+ background: backgroundStyle.value,
113
+ fontSize: transformValueUnit(props.fontSize),
114
+ fontWeight: props.fontWeight,
115
+ borderColor: props.borderColor,
116
+ zIndex: props.zIndex,
117
+ position: props.position,
118
+ borderWidth: transformValueUnit(props.borderWidth),
119
+ boxShadow:
120
+ props.shadowColor && props.shadowSize
121
+ ? `0px 0px ${props.blurSize}px ${props.shadowSize}px ${props.shadowColor}`
122
+ : '',
123
+ textAlign: props.textAlign,
124
+ margin: marginStyle.value,
125
+ ...props.customStyle,
126
+ ...getFlexStyle(props.align),
127
+ ...getInnerStyle.value,
128
+ }
129
+ })
89
130
  </script>
@@ -1,29 +1,19 @@
1
1
  <template>
2
2
  <template v-if="mode === 'noStyle'">{{ innerValue }}</template>
3
- <view
4
- class="w-full h-full"
5
- :style="{
6
- ...innerStyle,
7
- }"
8
- v-else-if="dynamicKey && hideWhenDynamicKeyNotExist ? dynamicValue : true"
9
- >
3
+ <view class="w-full h-full" :style="wrapperStyle" v-else-if="shouldRender">
10
4
  <lcb-block
11
- v-bind="styleOptions[0]"
5
+ v-bind="blockProps"
12
6
  :customClass="`${customClass} border-solid`"
13
- :customStyle="{
14
- width: props.fillWidth ? '100%' : 'fit-content',
15
- height: props.fillHeight ? '100%' : 'fit-content',
16
- ...innerItemStyle,
17
- }"
7
+ :customStyle="blockCustomStyle"
18
8
  >
19
9
  <lcb-action-view
20
10
  v-bind="actionProps"
21
11
  @avatar="onAvatar"
22
12
  @click="handleClick"
23
- :customStyle="styleOptions[1]"
13
+ :customStyle="actionPaddingStyle"
24
14
  :renderMode="!actionProps?.jumpType && mode !== 'image' ? 'noClick' : 'button'"
25
15
  >
26
- <view class="!flex items-center justify-center" :style="`gap: ${iconGap}rpx`">
16
+ <view class="!flex items-center justify-center" :style="iconGapStyle">
27
17
  <wd-icon
28
18
  v-if="icon"
29
19
  :name="icon"
@@ -47,6 +37,9 @@
47
37
  v-if="innerValue"
48
38
  />
49
39
  </template>
40
+ <template v-else-if="mode === 'progress'">
41
+ <wd-progress :value="innerValue" v-bind="progressProps" />
42
+ </template>
50
43
  <text
51
44
  v-else
52
45
  style="line-height: 1.54"
@@ -87,6 +80,7 @@ const props = withDefaults(defineProps<LcbButtonProps>(), {
87
80
  })
88
81
  const { userStore, innerDynamicData } = useDynamicData()
89
82
 
83
+ // 优化: 缓存 store 数据源
90
84
  const store = computed(() => {
91
85
  return props.keyFromUser ? userStore?.userInfo : innerDynamicData.value
92
86
  })
@@ -99,6 +93,14 @@ const dynamicValue = computed(() => {
99
93
  return value
100
94
  })
101
95
 
96
+ // 优化: 提取条件渲染逻辑,避免在模板中重复计算
97
+ const shouldRender = computed(() => {
98
+ if (!props.dynamicKey || !props.hideWhenDynamicKeyNotExist) {
99
+ return true
100
+ }
101
+ return !!dynamicValue.value
102
+ })
103
+
102
104
  const handleClick = () => {
103
105
  if (props.mode === 'image' && innerValue.value && props.enablePreview) {
104
106
  uni.previewImage({
@@ -119,16 +121,25 @@ const innerValue = computed(() => {
119
121
 
120
122
  /** 处理跳转链接中的动态参数 */
121
123
  const actionProps = computed(() => {
124
+ // 优化: 提前返回,避免不必要的计算
122
125
  if (props.dynamicActionKey) {
123
126
  return get(innerDynamicData.value, props.dynamicActionKey)
124
127
  }
128
+
129
+ // 优化: 只在 jumpUrl 存在时才调用 getDynamicData
130
+ const jumpUrl = props.action?.jumpUrl
131
+ if (!jumpUrl) {
132
+ return props.action
133
+ }
134
+
125
135
  return {
126
136
  ...props.action,
127
- jumpUrl: getDynamicData(props.action?.jumpUrl, {
137
+ jumpUrl: getDynamicData(jumpUrl, {
128
138
  store: store.value,
129
139
  }),
130
140
  }
131
141
  })
142
+
132
143
  const onAvatar = (headImgUrl) => {
133
144
  userStore?.updateUser(
134
145
  {
@@ -137,6 +148,8 @@ const onAvatar = (headImgUrl) => {
137
148
  true,
138
149
  )
139
150
  }
151
+
152
+ // 优化: 缓存样式对象,避免在模板中创建新对象
140
153
  const innerStyle = computed(() => {
141
154
  return getFlexStyle(props.align)
142
155
  })
@@ -145,7 +158,29 @@ const innerItemStyle = computed(() => {
145
158
  return getFlexStyle(props.itemAlign)
146
159
  })
147
160
 
148
- const styleOptions = computed<[Record<string, any>, string]>(() => {
161
+ // 优化: wrapper 样式单独提取
162
+ const wrapperStyle = computed(() => innerStyle.value)
163
+
164
+ // 优化: 将 iconGap 样式单独提取,避免模板字符串
165
+ const iconGapStyle = computed(() => `gap: ${props.iconGap}rpx`)
166
+
167
+ // 优化: 将 block customStyle 单独提取
168
+ const blockCustomStyle = computed(() => ({
169
+ width: props.fillWidth ? '100%' : 'fit-content',
170
+ height: props.fillHeight ? '100%' : 'fit-content',
171
+ ...innerItemStyle.value,
172
+ }))
173
+
174
+ // 优化: 分离 padding 计算和 block props,避免解构整个 props
175
+ const actionPaddingStyle = computed(() => {
176
+ const top = transformValueUnit(props.paddingTop || props.paddingVertical)
177
+ const right = transformValueUnit(props.paddingRight || props.paddingHorizontal)
178
+ const bottom = transformValueUnit(props.paddingBottom || props.paddingVertical)
179
+ const left = transformValueUnit(props.paddingLeft || props.paddingHorizontal)
180
+ return `width: 100%; padding:${top} ${right} ${bottom} ${left}`
181
+ })
182
+
183
+ const blockProps = computed(() => {
149
184
  const {
150
185
  paddingVertical,
151
186
  paddingHorizontal,
@@ -155,9 +190,7 @@ const styleOptions = computed<[Record<string, any>, string]>(() => {
155
190
  paddingRight,
156
191
  ...other
157
192
  } = props
158
-
159
- const actionStyle = `width: 100%; padding:${transformValueUnit(paddingTop || paddingVertical)} ${transformValueUnit(paddingRight || paddingHorizontal)} ${transformValueUnit(paddingBottom || paddingVertical)} ${transformValueUnit(paddingLeft || paddingHorizontal)}`
160
- return [other, actionStyle]
193
+ return other
161
194
  })
162
195
  </script>
163
196
  <style lang="scss" scoped>
@@ -1,10 +1,12 @@
1
+ import { progressProps } from '@tplc/wot/components/wd-progress/types'
1
2
  import { LcbActionViewProps } from '../lcb-action-view/types'
2
3
  import { LcbBlockProps } from '../lcb-block/types'
4
+ import { ExtractPropTypes } from 'vue'
3
5
 
4
6
  export interface LcbButtonProps extends LcbBlockProps {
5
7
  text: string
6
8
  action?: LcbActionViewProps
7
- mode: 'image' | 'text' | 'noStyle' | 'qrcode'
9
+ mode: 'image' | 'text' | 'noStyle' | 'qrcode' | 'progress'
8
10
  url?: string
9
11
  imageWidth?: number
10
12
  imageHeight?: number
@@ -36,4 +38,5 @@ export interface LcbButtonProps extends LcbBlockProps {
36
38
  // 动态action key
37
39
  dynamicActionKey?: string
38
40
  lineClamp?: number
41
+ progressProps?: ExtractPropTypes<typeof progressProps>
39
42
  }
@@ -2,11 +2,9 @@
2
2
  <lcb-block v-bind="$props" :custom-style="listStyle" v-if="renderList.length">
3
3
  <view
4
4
  v-for="(item, index) in renderList"
5
- :key="index"
5
+ :key="getItemKey(item, index)"
6
6
  class="flex-shrink-0"
7
- :style="{
8
- width: childrenAutoWidth ? 'auto' : width ? transformValueUnit(width) : '100%',
9
- }"
7
+ :style="itemStyle"
10
8
  >
11
9
  <slot :data="item" :list="list" />
12
10
  </view>
@@ -15,7 +13,7 @@
15
13
 
16
14
  <script setup lang="ts">
17
15
  import { transformValueUnit } from '@tplc/business/utils/transform'
18
- import { computed, ref, watchEffect } from 'vue'
16
+ import { computed, ref, watch, shallowRef } from 'vue'
19
17
  import useDynamicData from '../../hooks/useDynamicData'
20
18
  import { dynamicRequest } from '../../utils/request'
21
19
  import { LcbWrapperListProps } from './types'
@@ -32,9 +30,16 @@ const props = withDefaults(defineProps<LcbWrapperListProps>(), {
32
30
  flexDirection: 'column',
33
31
  })
34
32
  const { userStore, innerDynamicData } = useDynamicData()
35
- const renderList = ref<unknown[]>([{ walletAmount: 1 }])
33
+ // 使用 shallowRef 优化大数组的响应式性能
34
+ const renderList = shallowRef<unknown[]>([])
35
+ const isLoading = ref(false)
36
36
 
37
+ // 优化:检查是否需要转换,避免不必要的遍历
37
38
  const transformStringArrayToObjectArray = (data: unknown[]): unknown[] => {
39
+ // 如果第一个元素已经是对象,假设整个数组都是对象
40
+ if (data.length > 0 && typeof data[0] === 'object' && data[0] !== null) {
41
+ return data
42
+ }
38
43
  return data.map((item) => {
39
44
  if (typeof item === 'string') {
40
45
  return { label: item }
@@ -43,31 +48,68 @@ const transformStringArrayToObjectArray = (data: unknown[]): unknown[] => {
43
48
  })
44
49
  }
45
50
 
46
- watchEffect(async () => {
47
- let data: unknown[] = []
48
- const dynamicData = await dynamicRequest(
49
- props.dataSource,
50
- innerDynamicData.value,
51
- userStore?.userInfo,
52
- )
53
- if (dynamicData) {
54
- data = dynamicData
51
+ // 优化:使用 watch 替代 watchEffect,明确依赖并添加防抖和加载状态
52
+ let loadingTimer: ReturnType<typeof setTimeout> | null = null
53
+ watch(
54
+ () => [props.dataSource, innerDynamicData.value, userStore?.userInfo] as const,
55
+ async ([dataSource, dynamicData, userInfo]) => {
56
+ // 防抖处理,避免频繁请求
57
+ if (loadingTimer) {
58
+ clearTimeout(loadingTimer)
59
+ }
60
+
61
+ loadingTimer = setTimeout(async () => {
62
+ // 防止重复请求
63
+ if (isLoading.value) return
64
+
65
+ isLoading.value = true
66
+ try {
67
+ let data: unknown[] = []
68
+ const result = await dynamicRequest(dataSource, dynamicData, userInfo)
69
+ if (result) {
70
+ data = result
71
+ }
72
+ // 使用 shallowRef 时需要重新赋值整个数组
73
+ renderList.value = Array.isArray(data) ? transformStringArrayToObjectArray(data) : []
74
+ } finally {
75
+ isLoading.value = false
76
+ }
77
+ }, 100) // 100ms 防抖
78
+ },
79
+ { immediate: true },
80
+ )
81
+
82
+ // 优化:缓存 key 获取逻辑,减少属性访问
83
+ const getItemKey = (item: any, index: number) => {
84
+ return item?.id ?? item?.key ?? index
85
+ }
86
+
87
+ // 优化:提取 gap 计算,避免在 listStyle 中重复计算
88
+ const gapValue = computed(() => transformValueUnit(props.gap))
89
+
90
+ // 优化:简化条件判断逻辑
91
+ const itemStyle = computed(() => {
92
+ let width = '100%'
93
+ if (props.childrenAutoWidth) {
94
+ width = 'auto'
95
+ } else if (props.width) {
96
+ width = transformValueUnit(props.width)
55
97
  }
56
- // 转换字符串数组为对象数组的辅助函数
57
- renderList.value = Array.isArray(data) ? transformStringArrayToObjectArray(data) : []
98
+ return { width }
58
99
  })
100
+
101
+ // 优化:减少对象创建,使用固定的样式对象
59
102
  const listStyle = computed(() => {
60
- const style = {
103
+ return {
61
104
  width: '100%',
62
105
  display: props.display,
63
106
  'flex-direction': props.flexDirection,
64
- gap: transformValueUnit(props.gap),
107
+ gap: gapValue.value,
65
108
  'align-items': 'stretch',
66
109
  'grid-template-columns': `repeat(${props.gridColumns}, minmax(0, 1fr))`,
67
110
  'overflow-x': props.scrollX ? 'auto' : 'hidden',
68
111
  'white-space': props.scrollX ? 'nowrap' : 'normal',
69
112
  }
70
- return style
71
113
  })
72
114
  </script>
73
115
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tplc/business",
3
- "version": "0.7.22",
3
+ "version": "0.7.24",
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.15"
14
+ "@tplc/wot": "1.0.16"
15
15
  },
16
16
  "engines": {
17
17
  "node": ">=18",
@@ -1,9 +1,11 @@
1
+ import { progressProps } from '@tplc/wot/components/wd-progress/types'
1
2
  import { LcbActionViewProps } from '../lcb-action-view/types'
2
3
  import { LcbBlockProps } from '../lcb-block/types'
4
+ import { ExtractPropTypes } from 'vue'
3
5
  export interface LcbButtonProps extends LcbBlockProps {
4
6
  text: string
5
7
  action?: LcbActionViewProps
6
- mode: 'image' | 'text' | 'noStyle' | 'qrcode'
8
+ mode: 'image' | 'text' | 'noStyle' | 'qrcode' | 'progress'
7
9
  url?: string
8
10
  imageWidth?: number
9
11
  imageHeight?: number
@@ -32,4 +34,5 @@ export interface LcbButtonProps extends LcbBlockProps {
32
34
  | 'bottom-right'
33
35
  dynamicActionKey?: string
34
36
  lineClamp?: number
37
+ progressProps?: ExtractPropTypes<typeof progressProps>
35
38
  }