@tplc/business 0.7.74 → 0.7.76

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,30 @@
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.76](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/compare/v0.7.75...v0.7.76) (2026-01-18)
6
+
7
+
8
+ ### ♻️ Code Refactoring | 代码重构
9
+
10
+ * **types:** remove unused properties from LcbGapProps and LcbAreaProps, enhance LcbBlockProps with additional positioning attributes ([3e5dd22](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/3e5dd22dca2a854fc7760ef7f824c3b23a1c59db))
11
+
12
+
13
+ ### ✨ Features | 新功能
14
+
15
+ * **lcb-form:** enhance form component with customizable title styles and conditional submit button visibility ([6b24629](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/6b246290e9a8a066353b461fd4cfdba62477c388))
16
+
17
+ ### [0.7.75](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/compare/v0.7.74...v0.7.75) (2026-01-18)
18
+
19
+
20
+ ### 🐛 Bug Fixes | Bug 修复
21
+
22
+ * **lcb-map:** replace loading GIF with a webp image and remove the old GIF file ([6f69363](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/6f693637439cc77ff651713dd3708da23c3712d0))
23
+
24
+
25
+ ### ✨ Features | 新功能
26
+
27
+ * **lcb-area:** add IS_EDITOR_MODE constant and optimize lcb-area and lcb-block components for fixed positioning and style caching ([ba958ae](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/commit/ba958ae70cdfb74068799319d2ef4226738a6065))
28
+
5
29
  ### [0.7.74](https://gitlab888.30jia.com.cn/tourism-front/zero-code-pro/compare/v0.7.73...v0.7.74) (2026-01-17)
6
30
 
7
31
 
@@ -0,0 +1,192 @@
1
+ # FormKey 功能使用说明
2
+
3
+ ## 功能概述
4
+
5
+ 该功能允许 `lcb-form` 组件将表单数据通过 `formKey` 注册到页面上下文 (`PAGE_PROVIDE_KEY`) 中,然后 `lcb-button` 组件可以通过指定 `formKeys` 在发送请求时自动携带对应表单的数据。
6
+
7
+ ## 使用方法
8
+
9
+ ### 1. 在 lcb-form 中设置 formKey
10
+
11
+ ```vue
12
+ <lcb-form
13
+ :fields="formFields"
14
+ formKey="userForm"
15
+ v-model="formData"
16
+ />
17
+ ```
18
+
19
+ - `formKey`: 表单的唯一标识符,可以是任意字符串
20
+ - 设置 `formKey` 后,表单数据会自动注册到 `PAGE_PROVIDE_KEY` 中
21
+
22
+ ### 2. 在 lcb-button 中关联 formKeys
23
+
24
+ #### 方式一:直接在 button 上设置 formKeys
25
+
26
+ ```vue
27
+ <lcb-button
28
+ text="提交"
29
+ :formKeys="['userForm']"
30
+ :action="{
31
+ jumpType: 17,
32
+ requestInfo: {
33
+ requestUrl: '/api/user/submit',
34
+ requestParams: {
35
+ type: 'create'
36
+ }
37
+ }
38
+ }"
39
+ />
40
+ ```
41
+
42
+ #### 方式二:在 action 的 requestInfo 中设置 formKeys
43
+
44
+ ```vue
45
+ <lcb-button
46
+ text="提交"
47
+ :action="{
48
+ jumpType: 17,
49
+ requestInfo: {
50
+ requestUrl: '/api/user/submit',
51
+ requestParams: {
52
+ type: 'create'
53
+ },
54
+ formKeys: ['userForm']
55
+ }
56
+ }"
57
+ />
58
+ ```
59
+
60
+ ### 3. 关联多个表单
61
+
62
+ 一个按钮可以同时关联多个表单:
63
+
64
+ ```vue
65
+ <lcb-button
66
+ text="提交"
67
+ :formKeys="['userForm', 'addressForm']"
68
+ :action="{
69
+ jumpType: 17,
70
+ requestInfo: {
71
+ requestUrl: '/api/user/submit',
72
+ requestParams: {
73
+ type: 'create'
74
+ }
75
+ }
76
+ }"
77
+ />
78
+ ```
79
+
80
+ 或在 requestInfo 中:
81
+
82
+ ```vue
83
+ <lcb-button
84
+ text="提交"
85
+ :action="{
86
+ jumpType: 17,
87
+ requestInfo: {
88
+ requestUrl: '/api/user/submit',
89
+ requestParams: {
90
+ type: 'create'
91
+ },
92
+ formKeys: ['userForm', 'addressForm']
93
+ }
94
+ }"
95
+ />
96
+ ```
97
+
98
+ ## 数据合并规则
99
+
100
+ 当按钮发送请求时,数据合并顺序为:
101
+
102
+ 1. 先使用 `requestParams` 中的原始参数
103
+ 2. 然后依次合并 `formKeys` 对应的表单数据
104
+ 3. **formKeys 的数据会覆盖 requestParams 中的同名字段**
105
+
106
+ ### 示例
107
+
108
+ 假设有以下配置:
109
+
110
+ ```javascript
111
+ // 表单数据
112
+ formData = {
113
+ name: '张三',
114
+ age: 25
115
+ }
116
+
117
+ // requestParams
118
+ requestParams = {
119
+ type: 'create',
120
+ age: 20
121
+ }
122
+ ```
123
+
124
+ 最终发送的请求参数为:
125
+
126
+ ```javascript
127
+ {
128
+ type: 'create', // 来自 requestParams
129
+ name: '张三', // 来自 formKeys
130
+ age: 25 // 来自 formKeys,覆盖了 requestParams 中的 age: 20
131
+ }
132
+ ```
133
+
134
+ ## 完整示例
135
+
136
+ ```vue
137
+ <template>
138
+ <view>
139
+ <!-- 用户信息表单 -->
140
+ <lcb-form
141
+ :fields="userFields"
142
+ formKey="userForm"
143
+ v-model="userData"
144
+ />
145
+
146
+ <!-- 地址信息表单 -->
147
+ <lcb-form
148
+ :fields="addressFields"
149
+ formKey="addressForm"
150
+ v-model="addressData"
151
+ />
152
+
153
+ <!-- 提交按钮,同时提交两个表单的数据 -->
154
+ <lcb-button
155
+ text="提交"
156
+ :formKeys="['userForm', 'addressForm']"
157
+ :action="{
158
+ jumpType: 17,
159
+ requestInfo: {
160
+ requestUrl: '/api/user/submit',
161
+ requestParams: {
162
+ submitType: 'complete'
163
+ }
164
+ }
165
+ }"
166
+ />
167
+ </view>
168
+ </template>
169
+
170
+ <script setup>
171
+ import { ref } from 'vue'
172
+
173
+ const userData = ref({})
174
+ const addressData = ref({})
175
+
176
+ const userFields = [
177
+ // ... 表单字段配置
178
+ ]
179
+
180
+ const addressFields = [
181
+ // ... 表单字段配置
182
+ ]
183
+ </script>
184
+ ```
185
+
186
+ ## 注意事项
187
+
188
+ 1. **无需表单验证**: 按钮发送请求时不会触发表单验证,直接提交数据
189
+ 2. **formKey 必须唯一**: 在同一个页面中,不同表单的 `formKey` 应该保持唯一
190
+ 3. **数据覆盖**: formKeys 的数据会覆盖 requestParams 中的同名字段
191
+ 4. **formKeys 优先级**: 如果 button 和 requestInfo 中都设置了 formKeys,button 的 formKeys 会传递给 requestInfo
192
+ 5. **数组类型**: formKeys 统一使用数组类型,即使只绑定一个表单也需要使用数组:`['formKey']`
@@ -227,8 +227,25 @@ const onActionClick = async () => {
227
227
  }
228
228
  break
229
229
  case '17':
230
- if ('requestInfo' in props && props.requestInfo)
231
- await uni.$lcb.http.post(props.requestInfo.requestUrl, props.requestInfo.requestParams)
230
+ if ('requestInfo' in props && props.requestInfo) {
231
+ // 准备请求参数
232
+ let finalParams = { ...props.requestInfo.requestParams }
233
+
234
+ // 如果有 formKeys,从 PAGE_PROVIDE_KEY 中获取表单数据并合并(formKeys 数据覆盖 requestParams)
235
+ if (props.requestInfo.formKeys && Array.isArray(props.requestInfo.formKeys)) {
236
+ props.requestInfo.formKeys.forEach((key) => {
237
+ const formData = pageInfo.value?.[key]
238
+ if (formData) {
239
+ finalParams = {
240
+ ...finalParams,
241
+ ...formData,
242
+ }
243
+ }
244
+ })
245
+ }
246
+
247
+ await uni.$lcb.http.post(props.requestInfo.requestUrl, finalParams)
248
+ }
232
249
  /** 请求之后刷新schemaPage */
233
250
  if ('requestInfo' in props && props.requestInfo?.refreshSchemaPage) {
234
251
  uni.$emit('refreshSchemaPage')
@@ -23,6 +23,8 @@ export type LcbActionViewProps = {
23
23
  requestParams: Record<string, any>
24
24
  /** 请求之后刷新schemapage */
25
25
  refreshSchemaPage?: boolean
26
+ /** 绑定的表单 keys,请求时会将对应表单数据合并到 requestParams */
27
+ formKeys?: string[]
26
28
  }
27
29
  submitRequestInfo?: {
28
30
  requestUrl: string
@@ -26,7 +26,7 @@
26
26
  </template>
27
27
 
28
28
  <script setup lang="ts">
29
- import { computed, watch } from 'vue'
29
+ import { computed, watch, shallowRef } from 'vue'
30
30
  import { LcbAreaProps } from './types'
31
31
  import { getFlexStyle, transformValueUnit } from '@tplc/business/utils/transform'
32
32
  import { get } from 'lodash-es'
@@ -48,65 +48,84 @@ const props = withDefaults(defineProps<LcbAreaProps>(), {
48
48
  })
49
49
  const { userStore, innerDynamicData, pageInfo } = useDynamicData()
50
50
 
51
- // 缓存 gap 的单位转换
52
- const gapValue = computed(() => transformValueUnit(props.gap))
51
+ // 优化:使用 shallowRef 缓存 flex 样式
52
+ const itemAlignStyleCache = shallowRef<Record<string, any> | null>(null)
53
+ const cachedItemAlign = shallowRef<string>('')
53
54
 
54
- // 优化容器样式计算,合并为一个 computed
55
- const containerStyle = computed(() => {
56
- const baseStyle = {
57
- display: props.display,
58
- gap: gapValue.value,
59
- overflowX: (props.scrollX ? 'auto' : 'hidden') as 'auto' | 'hidden',
55
+ const getItemAlignStyle = () => {
56
+ const align = props.itemAlign
57
+ if (!align) return {}
58
+ if (align === cachedItemAlign.value && itemAlignStyleCache.value) {
59
+ return itemAlignStyleCache.value
60
60
  }
61
+ cachedItemAlign.value = align
62
+ itemAlignStyleCache.value = getFlexStyle(align)
63
+ return itemAlignStyleCache.value
64
+ }
65
+
66
+ // 优化:缓存 gap 和容器样式计算
67
+ const containerStyle = computed(() => {
68
+ const gapValue = transformValueUnit(props.gap)
69
+ const overflowX = (props.scrollX ? 'auto' : 'hidden') as 'auto' | 'hidden'
61
70
 
62
71
  if (props.display === 'grid') {
63
72
  return {
64
- ...baseStyle,
73
+ display: 'grid',
74
+ gap: gapValue,
75
+ overflowX,
65
76
  gridTemplateColumns: `repeat(${props.gridColumns}, minmax(0, 1fr))`,
66
77
  }
67
78
  }
68
79
 
69
80
  return {
70
- ...baseStyle,
81
+ display: 'flex',
82
+ gap: gapValue,
83
+ overflowX,
71
84
  flexDirection: props.displayFlex,
72
85
  alignItems: 'stretch',
73
86
  }
74
87
  })
75
88
 
76
- // 缓存 itemAlign flex 样式,避免每个 item 都重复计算
77
- const itemAlignStyle = computed(() => getFlexStyle(props.itemAlign))
78
-
79
- // 优化: 缓存公共样式部分
80
- const commonItemStyle = computed(() => ({
81
- ...itemAlignStyle.value,
82
- overflowX: props.overflowX,
83
- }))
84
-
85
- // 优化: 缓存每个 item 的样式,避免重复计算
89
+ // 优化: 使用 Map 缓存每个 item 的样式,只在依赖变化时重新计算
86
90
  const itemStylesCache = computed(() => {
91
+ if (!props.list?.length) return new Map<number, Record<string, any>>()
92
+
87
93
  const isGrid = props.display === 'grid'
88
94
  const isFlex = props.display === 'flex'
89
95
  const flexShrink = props.flexShrink ?? 1
96
+ const itemAlignStyle = getItemAlignStyle()
97
+ const overflowX = props.overflowX
98
+
99
+ const cache = new Map<number, Record<string, any>>()
90
100
 
91
- return (
92
- props.list?.map((_, index) => {
93
- const flex = props.areaFlexs?.[index]?.flex ?? 1
94
- const colSpan = props.areaItems?.[index]?.colSpan
95
-
96
- return {
97
- ...commonItemStyle.value,
98
- gridColumn: colSpan && isGrid ? `span ${colSpan} / span ${colSpan}` : undefined,
99
- flex: isFlex ? (flex ?? 'auto') : undefined,
100
- flexShrink: isFlex ? flexShrink : undefined,
101
- width: flex ? '100%' : 'auto',
102
- }
103
- }) || []
104
- )
101
+ for (let i = 0; i < props.list.length; i++) {
102
+ const flex = props.areaFlexs?.[i]?.flex ?? 1
103
+ const colSpan = props.areaItems?.[i]?.colSpan
104
+
105
+ const style: Record<string, any> = {
106
+ ...itemAlignStyle,
107
+ overflowX,
108
+ }
109
+
110
+ if (isGrid && colSpan) {
111
+ style.gridColumn = `span ${colSpan} / span ${colSpan}`
112
+ }
113
+
114
+ if (isFlex) {
115
+ style.flex = flex ?? 'auto'
116
+ style.flexShrink = flexShrink
117
+ style.width = flex ? '100%' : 'auto'
118
+ }
119
+
120
+ cache.set(i, style)
121
+ }
122
+
123
+ return cache
105
124
  })
106
125
 
107
- // 优化: 使用缓存的样式
126
+ // 优化: 直接从 Map 获取缓存的样式
108
127
  const getItemStyle = (index: number) => {
109
- return itemStylesCache.value[index] || commonItemStyle.value
128
+ return itemStylesCache.value.get(index) || { overflowX: props.overflowX }
110
129
  }
111
130
 
112
131
  const getData = async () => {
@@ -140,26 +159,20 @@ watch(
140
159
  },
141
160
  )
142
161
 
143
- // 优化: 提取比较逻辑,减少重复代码
162
+ // 优化: 提取比较逻辑,减少重复代码 - 使用静态对象减少创建
163
+ const compareOperations = {
164
+ '=': (value: any, compareValue: string) => `${value}` === compareValue,
165
+ '>=': (value: any, compareValue: string) => value >= compareValue,
166
+ '<=': (value: any, compareValue: string) => value <= compareValue,
167
+ '>': (value: any, compareValue: string) => value > compareValue,
168
+ '<': (value: any, compareValue: string) => value < compareValue,
169
+ '!=': (value: any, compareValue: string) => value !== compareValue,
170
+ includes: (value: any, compareValue: string) => value?.includes?.(compareValue) ?? false,
171
+ } as const
172
+
144
173
  const compareValues = (value = '', compareValue: string, compareType: string) => {
145
- switch (compareType) {
146
- case '=':
147
- return `${value}` === compareValue
148
- case '>=':
149
- return value >= compareValue
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 'includes':
159
- return value?.includes?.(compareValue) ?? false
160
- default:
161
- return `${value}` === compareValue
162
- }
174
+ const operation = compareOperations[compareType as keyof typeof compareOperations]
175
+ return operation ? operation(value, compareValue) : `${value}` === compareValue
163
176
  }
164
177
 
165
178
  // 优化: 缓存数据源
@@ -23,7 +23,6 @@ export interface LcbAreaProps extends LcbBlockProps {
23
23
  keyFromUser?: boolean
24
24
  reverse?: boolean
25
25
  dependKeyCompareValue?: string
26
- position?: 'relative' | 'absolute'
27
26
  compareType?: '=' | '>=' | '<=' | '>' | '<' | '!=' | 'includes'
28
27
  dataSource?: DataSource
29
28
  action?: LcbActionViewProps
@@ -1,19 +1,16 @@
1
1
  <template>
2
- <view
3
- :style="getFinalStyle"
4
- class="box-border overflow-hidden relative"
5
- :class="props.customClass"
6
- >
2
+ <view :style="getFinalStyle" class="box-border overflow-hidden" :class="props.customClass">
7
3
  <slot />
8
4
  </view>
9
5
  </template>
10
6
 
11
7
  <script setup lang="ts">
12
8
  import { get } from 'lodash-es'
13
- import { computed } from 'vue'
9
+ import { computed, inject, ref, shallowRef } from 'vue'
14
10
  import useDynamicData from '../../hooks/useDynamicData'
15
11
  import { getFlexStyle, transformValueUnit } from '../../utils/transform'
16
12
  import { LcbBlockInnerProps } from './types'
13
+ import { IS_EDITOR_MODE } from '../../constants'
17
14
  defineOptions({
18
15
  name: 'LcbBlock',
19
16
  options: {
@@ -32,19 +29,42 @@ const props = withDefaults(defineProps<LcbBlockInnerProps>(), {
32
29
  position: 'relative',
33
30
  })
34
31
  const { userStore, innerDynamicData } = useDynamicData()
32
+ const isEditorMode = inject(IS_EDITOR_MODE, ref(false))
33
+
34
+ // 使用 shallowRef 缓存 flex 样式,避免重复计算
35
+ const flexStyleCache = shallowRef<Record<string, any> | null>(null)
36
+ const cachedAlign = shallowRef<string>('')
37
+
38
+ const getFlexStyleCached = (align: string | undefined) => {
39
+ if (!align) return {}
40
+ if (align === cachedAlign.value && flexStyleCache.value) {
41
+ return flexStyleCache.value
42
+ }
43
+ cachedAlign.value = align
44
+ flexStyleCache.value = getFlexStyle(align)
45
+ return flexStyleCache.value
46
+ }
47
+
35
48
  const dynamicBgImage = computed(() => {
36
- return (
37
- (props.dynamicBgImage && get(innerDynamicData.value, props.dynamicBgImage)) ||
38
- props.backgroundImage
39
- )
49
+ // 优化:如果没有动态背景图,直接返回静态背景图
50
+ if (!props.dynamicBgImage) return props.backgroundImage
51
+ return get(innerDynamicData.value, props.dynamicBgImage) || props.backgroundImage
40
52
  })
41
- // 透明度+颜色
53
+
54
+ // 透明度+颜色 - 优化:减少字符串拼接
42
55
  const innerBackgroundColor = computed(() => {
43
- if (!props.backgroundColor) return ''
44
- if (props.backgroundColor.length === 7) {
45
- return props.backgroundColor + Math.floor(props.opacity * 255).toString(16)
56
+ const bgColor = props.backgroundColor
57
+ if (!bgColor) return ''
58
+ // 如果已经是 8 位(包含透明度),直接返回
59
+ if (bgColor.length === 9) return bgColor
60
+ // 如果是 7 位,添加透明度
61
+ if (bgColor.length === 7) {
62
+ const alpha = Math.floor(props.opacity * 255)
63
+ .toString(16)
64
+ .padStart(2, '0')
65
+ return bgColor + alpha
46
66
  }
47
- return props.backgroundColor
67
+ return bgColor
48
68
  })
49
69
 
50
70
  const getInnerStyle = computed(() => {
@@ -58,46 +78,44 @@ const getInnerStyle = computed(() => {
58
78
  return {}
59
79
  })
60
80
 
61
- // 缓存 padding 计算结果,减少 transformValueUnit 调用次数
62
- const paddingStyle = computed(() => {
63
- const top = transformValueUnit(props.paddingTop || props.paddingVertical || 0)
64
- const right = transformValueUnit(props.paddingRight || props.paddingHorizontal || 0)
65
- const bottom = transformValueUnit(props.paddingBottom || props.paddingVertical || 0)
66
- const left = transformValueUnit(props.paddingLeft || props.paddingHorizontal || 0)
67
- return `${top} ${right} ${bottom} ${left}`
68
- })
81
+ // 优化:合并 padding/margin/borderRadius 计算,减少函数调用
82
+ const spacingStyles = computed(() => {
83
+ const pTop = props.paddingTop ?? props.paddingVertical ?? 0
84
+ const pRight = props.paddingRight ?? props.paddingHorizontal ?? 0
85
+ const pBottom = props.paddingBottom ?? props.paddingVertical ?? 0
86
+ const pLeft = props.paddingLeft ?? props.paddingHorizontal ?? 0
69
87
 
70
- // 缓存 margin 计算结果
71
- const marginStyle = computed(() => {
72
- const top = transformValueUnit(props.floatUp ? -props.floatUp : props.marginTop || 0)
73
- const right = transformValueUnit(props.marginRight || props.marginHorizontal || 0)
74
- const bottom = transformValueUnit(props.marginBottom || 0)
75
- const left = transformValueUnit(props.marginLeft || props.marginHorizontal || 0)
76
- return `${top} ${right} ${bottom} ${left}`
77
- })
88
+ const mTop = props.floatUp ? -props.floatUp : (props.marginTop ?? 0)
89
+ const mRight = props.marginRight ?? props.marginHorizontal ?? 0
90
+ const mBottom = props.marginBottom ?? 0
91
+ const mLeft = props.marginLeft ?? props.marginHorizontal ?? 0
92
+
93
+ const topR = props.topRadius ?? props.radius
94
+ const bottomR = props.bottomRadius ?? props.radius
78
95
 
79
- // 缓存 borderRadius 计算结果,避免重复计算
80
- const borderRadiusStyle = computed(() => {
81
- const topR = props.topRadius || props.radius
82
- const bottomR = props.bottomRadius || props.radius
83
- const topValue = transformValueUnit(topR)
84
- const bottomValue = transformValueUnit(bottomR)
85
- return `${topValue} ${topValue} ${bottomValue} ${bottomValue}`
96
+ return {
97
+ padding: `${transformValueUnit(pTop)} ${transformValueUnit(pRight)} ${transformValueUnit(pBottom)} ${transformValueUnit(pLeft)}`,
98
+ margin: `${transformValueUnit(mTop)} ${transformValueUnit(mRight)} ${transformValueUnit(mBottom)} ${transformValueUnit(mLeft)}`,
99
+ borderRadius:
100
+ topR || bottomR
101
+ ? `${transformValueUnit(topR)} ${transformValueUnit(topR)} ${transformValueUnit(bottomR)} ${transformValueUnit(bottomR)}`
102
+ : undefined,
103
+ }
86
104
  })
87
105
 
88
- // 优化 background 字符串拼接
106
+ // 优化:background 字符串拼接
89
107
  const backgroundStyle = computed(() => {
90
108
  const bgImage = dynamicBgImage.value
91
109
  const bgColor = innerBackgroundColor.value || 'transparent'
92
110
  if (!bgImage) return bgColor
111
+ // 使用模板字符串减少拼接次数
93
112
  return `${props.backgroundPosition} / ${props.backgroundSize} ${props.backgroundRepeat} url('${bgImage}'), ${bgColor}`
94
113
  })
95
114
 
96
115
  const getFinalStyle = computed(() => {
97
- return {
98
- width: props.position === 'absolute' ? '100%' : '',
99
- padding: paddingStyle.value,
100
- borderRadius: borderRadiusStyle.value,
116
+ // 优化:减少对象创建和条件判断
117
+ const style: Record<string, any> = {
118
+ ...spacingStyles.value,
101
119
  color: props.color,
102
120
  background: backgroundStyle.value,
103
121
  fontSize: transformValueUnit(props.fontSize),
@@ -106,14 +124,49 @@ const getFinalStyle = computed(() => {
106
124
  zIndex: props.zIndex,
107
125
  position: props.position,
108
126
  borderWidth: transformValueUnit(props.borderWidth),
109
- boxShadow:
110
- props.shadowColor && props.shadowSize
111
- ? `0px 0px ${props.blurSize}px ${props.shadowSize}px ${props.shadowColor}`
112
- : '',
113
127
  textAlign: props.textAlign,
114
- margin: marginStyle.value,
128
+ }
129
+
130
+ // 优化:只在需要时添加 boxShadow
131
+ if (props.shadowColor && props.shadowSize) {
132
+ style.boxShadow = `0px 0px ${props.blurSize}px ${props.shadowSize}px ${props.shadowColor}`
133
+ }
134
+
135
+ const actualPosition = props.position
136
+
137
+ // 优化:只在编辑器模式下输出调试信息
138
+ if (isEditorMode.value && (actualPosition === 'fixed' || actualPosition === 'absolute')) {
139
+ console.log('[lcb-block] Position Debug:', {
140
+ propsPosition: props.position,
141
+ actualPosition,
142
+ top: props.top,
143
+ left: props.left,
144
+ right: props.right,
145
+ bottom: props.bottom,
146
+ zIndex: props.zIndex,
147
+ width: props.width,
148
+ height: props.height,
149
+ })
150
+ }
151
+
152
+ // 处理定位相关的样式 - 优化:合并条件判断
153
+ if (actualPosition === 'absolute' || actualPosition === 'fixed') {
154
+ if (props.top !== undefined) style.top = transformValueUnit(props.top)
155
+ if (props.left !== undefined) style.left = transformValueUnit(props.left)
156
+ if (props.right !== undefined) style.right = transformValueUnit(props.right)
157
+ if (props.bottom !== undefined) style.bottom = transformValueUnit(props.bottom)
158
+ if (props.width !== undefined) style.width = transformValueUnit(props.width)
159
+ if (props.height !== undefined) style.height = transformValueUnit(props.height)
160
+ } else if (actualPosition === 'relative') {
161
+ if (props.top !== undefined) style.top = transformValueUnit(props.top)
162
+ if (props.left !== undefined) style.left = transformValueUnit(props.left)
163
+ }
164
+
165
+ // 优化:最后再合并其他样式,减少对象展开次数
166
+ return {
167
+ ...style,
115
168
  ...props.customStyle,
116
- ...getFlexStyle(props.align),
169
+ ...getFlexStyleCached(props.align),
117
170
  ...getInnerStyle.value,
118
171
  }
119
172
  })
@@ -30,7 +30,7 @@ export interface LcbBlockProps {
30
30
  marginLeft?: number
31
31
  marginRight?: number
32
32
  zIndex?: number
33
- position?: 'relative' | 'absolute'
33
+ position?: 'relative' | 'absolute' | 'fixed'
34
34
  color?: string
35
35
  fontSize?: number
36
36
  fontWeight?: number
@@ -59,6 +59,13 @@ export interface LcbBlockProps {
59
59
  // 动态样式集合
60
60
  dynamicStyleOptions?: DynamicOptions
61
61
  dynamicBgImage?: string
62
+ // fixed/absolute 定位相关
63
+ top?: number
64
+ left?: number
65
+ right?: number
66
+ bottom?: number
67
+ width?: number
68
+ height?: number
62
69
  }
63
70
  export interface LcbBlockInnerProps extends LcbBlockProps {
64
71
  [key: string]: any
@@ -128,12 +128,25 @@ const innerValue = computed(() => {
128
128
  })
129
129
 
130
130
  const actionProps = computed(() => {
131
- return resolveActionProps({
131
+ const resolvedProps = resolveActionProps({
132
132
  dynamicActionKey: props.dynamicActionKey,
133
133
  action: props.action as any,
134
134
  innerDynamicData: innerDynamicData.value,
135
135
  templateStore: store.value,
136
136
  })
137
+
138
+ // 如果 button 有 formKeys,传递给 action 的 requestInfo
139
+ if (props.formKeys && resolvedProps.requestInfo) {
140
+ return {
141
+ ...resolvedProps,
142
+ requestInfo: {
143
+ ...resolvedProps.requestInfo,
144
+ formKeys: props.formKeys,
145
+ },
146
+ }
147
+ }
148
+
149
+ return resolvedProps
137
150
  })
138
151
 
139
152
  const onAvatar = (headImgUrl) => {
@@ -40,4 +40,6 @@ export interface LcbButtonProps extends LcbBlockProps {
40
40
  lineClamp?: number
41
41
  progressProps?: ExtractPropTypes<typeof progressProps>
42
42
  fontFamily?: string
43
+ // 绑定的表单 keys,请求时会将对应表单数据一并提交
44
+ formKeys?: string[]
43
45
  }
@@ -6,7 +6,6 @@
6
6
  <wd-cell
7
7
  v-for="field in fields"
8
8
  :key="field.entryFormFieldConfigId"
9
- :title="field.fieldCustomName"
10
9
  :required="field.requiredFlag"
11
10
  :prop="field.field"
12
11
  :title-width="!vertical ? '230rpx' : '100%'"
@@ -22,6 +21,11 @@
22
21
  : []
23
22
  "
24
23
  >
24
+ <template #title>
25
+ <view :style="titleStyle">
26
+ {{ field.fieldCustomName }}
27
+ </view>
28
+ </template>
25
29
  <wd-input
26
30
  v-if="field.frontInputType === 'input'"
27
31
  :placeholder="field.frontPlaceholder || t('请输入') + field.fieldCustomName"
@@ -155,7 +159,7 @@
155
159
  </wd-cell-group>
156
160
  <view
157
161
  :class="{ 'bottom-fixed': bottomFixed, 'mt-3': !bottomFixed }"
158
- v-if="submitText || agreementType"
162
+ v-if="(showSubmitButton && submitText) || agreementType"
159
163
  >
160
164
  <view class="text-center text-3 mt-3" v-if="agreementType && fields">
161
165
  <wd-checkbox v-model="form.agreement">
@@ -177,7 +181,7 @@
177
181
  block
178
182
  size="large"
179
183
  custom-class="!w-90% mt-3 !mx-auto"
180
- v-if="fields && submitText"
184
+ v-if="fields && submitText && showSubmitButton"
181
185
  >
182
186
  {{ t(submitText) }}
183
187
  </wd-button>
@@ -188,12 +192,14 @@
188
192
  </template>
189
193
 
190
194
  <script setup lang="ts">
191
- import { ref, watch } from 'vue'
195
+ import { ref, watch, computed, inject, Ref } from 'vue'
192
196
  import { LcbFormField, LcbFormProps } from './types'
193
197
  import { customUpload } from '../../utils/utils'
194
198
  import { useTranslate } from '@tplc/wot'
195
199
  import { DateTimeType } from '@tplc/wot/types/components/wd-datetime-picker-view/types'
196
200
  import dayjs from 'dayjs/esm'
201
+ import { transformValueUnit } from '../../utils/transform'
202
+ import { PAGE_PROVIDE_KEY } from '../../constants'
197
203
  defineOptions({
198
204
  name: 'LcbForm',
199
205
  options: {
@@ -214,8 +220,29 @@ const props = withDefaults(defineProps<LcbFormProps>(), {
214
220
  submitText: '提交',
215
221
  affirmText: '同意',
216
222
  bottomFixed: true,
223
+ showSubmitButton: true,
217
224
  vertical: false,
225
+ titleFontSize: 28,
226
+ titleFontWeight: 'normal',
227
+ })
228
+
229
+ // 注入页面上下文
230
+ const pageInfo = inject(PAGE_PROVIDE_KEY) as Ref<Record<string, any>>
231
+
232
+ // 计算 title 样式
233
+ const titleStyle = computed(() => {
234
+ return {
235
+ color: props.titleColor,
236
+ fontSize: transformValueUnit(props.titleFontSize),
237
+ fontWeight: props.titleFontWeight,
238
+ fontFamily: props.titleFontFamily,
239
+ }
218
240
  })
241
+
242
+ defineSlots<{
243
+ title(props: { field: LcbFormField }): any
244
+ }>()
245
+
219
246
  const fields = ref<LcbFormField[]>(props.fields)
220
247
  const customInputs = ref<Record<string, string>>({})
221
248
  const customField = 'custom'
@@ -356,8 +383,12 @@ watch(
356
383
  form,
357
384
  (newVal) => {
358
385
  formData.value = newVal
386
+ // 如果设置了 formKey,将表单数据同步到 PAGE_PROVIDE_KEY
387
+ if (props.formKey && pageInfo.value) {
388
+ pageInfo.value[props.formKey] = newVal
389
+ }
359
390
  },
360
- { deep: true },
391
+ { deep: true, immediate: true },
361
392
  )
362
393
  </script>
363
394
 
@@ -80,5 +80,14 @@ export interface LcbFormProps extends LcbBlockProps {
80
80
  affirmText?: string
81
81
  // 底部悬浮
82
82
  bottomFixed?: boolean
83
+ // 是否显示提交按钮
84
+ showSubmitButton?: boolean
83
85
  vertical?: boolean
86
+ // 标题样式配置
87
+ titleColor?: string
88
+ titleFontSize?: number
89
+ titleFontWeight?: string | number
90
+ titleFontFamily?: string
91
+ // 表单唯一标识,用于在 PAGE_PROVIDE_KEY 中注册表单数据
92
+ formKey?: string
84
93
  }
@@ -1,7 +1,6 @@
1
1
  import { LcbBlockProps } from '../lcb-block/types'
2
2
 
3
3
  export interface LcbGapProps extends LcbBlockProps {
4
- height?: number | string
5
4
  safeAreaBottom?: boolean
6
5
  bgColor?: string
7
6
  safeAreaTop?: boolean
package/constants.ts CHANGED
@@ -15,3 +15,5 @@ export const PAGE_DYNAMIC_DATA = 'page_dynamic_data'
15
15
  export const WRAPPER_ITEM_KEY = 'wrapper_item_key'
16
16
  /** 是否显示tabbar */
17
17
  export const SHOW_TABBAR = 'show_tabbar'
18
+ /** 是否在编辑器模式 */
19
+ export const IS_EDITOR_MODE = 'is_editor_mode'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tplc/business",
3
- "version": "0.7.74",
3
+ "version": "0.7.76",
4
4
  "keywords": [
5
5
  "业务组件"
6
6
  ],
@@ -21,6 +21,8 @@ export type LcbActionViewProps = {
21
21
  requestParams: Record<string, any>
22
22
  /** 请求之后刷新schemapage */
23
23
  refreshSchemaPage?: boolean
24
+ /** 绑定的表单 keys,请求时会将对应表单数据合并到 requestParams */
25
+ formKeys?: string[]
24
26
  }
25
27
  submitRequestInfo?: {
26
28
  requestUrl: string
@@ -23,7 +23,6 @@ export interface LcbAreaProps extends LcbBlockProps {
23
23
  keyFromUser?: boolean
24
24
  reverse?: boolean
25
25
  dependKeyCompareValue?: string
26
- position?: 'relative' | 'absolute'
27
26
  compareType?: '=' | '>=' | '<=' | '>' | '<' | '!=' | 'includes'
28
27
  dataSource?: DataSource
29
28
  action?: LcbActionViewProps
@@ -29,7 +29,7 @@ export interface LcbBlockProps {
29
29
  marginLeft?: number
30
30
  marginRight?: number
31
31
  zIndex?: number
32
- position?: 'relative' | 'absolute'
32
+ position?: 'relative' | 'absolute' | 'fixed'
33
33
  color?: string
34
34
  fontSize?: number
35
35
  fontWeight?: number
@@ -56,6 +56,12 @@ export interface LcbBlockProps {
56
56
  | 'bottom-right'
57
57
  dynamicStyleOptions?: DynamicOptions
58
58
  dynamicBgImage?: string
59
+ top?: number
60
+ left?: number
61
+ right?: number
62
+ bottom?: number
63
+ width?: number
64
+ height?: number
59
65
  }
60
66
  export interface LcbBlockInnerProps extends LcbBlockProps {
61
67
  [key: string]: any
@@ -36,4 +36,5 @@ export interface LcbButtonProps extends LcbBlockProps {
36
36
  lineClamp?: number
37
37
  progressProps?: ExtractPropTypes<typeof progressProps>
38
38
  fontFamily?: string
39
+ formKeys?: string[]
39
40
  }
@@ -1,16 +1,24 @@
1
- import { LcbFormProps } from './types'
1
+ import { LcbFormField, LcbFormProps } from './types'
2
2
  declare let __VLS_typeProps: LcbFormProps
3
3
  type __VLS_PublicProps = {
4
4
  modelValue?: Record<string, any>
5
5
  } & typeof __VLS_typeProps
6
- declare const _default: import('vue').DefineComponent<
6
+ declare function __VLS_template(): Readonly<{
7
+ title(props: { field: LcbFormField }): any
8
+ }> & {
9
+ title(props: { field: LcbFormField }): any
10
+ }
11
+ declare const __VLS_component: import('vue').DefineComponent<
7
12
  __VLS_WithDefaults<
8
13
  __VLS_TypePropsToOption<__VLS_PublicProps>,
9
14
  {
10
15
  submitText: string
11
16
  affirmText: string
12
17
  bottomFixed: boolean
18
+ showSubmitButton: boolean
13
19
  vertical: boolean
20
+ titleFontSize: number
21
+ titleFontWeight: string
14
22
  }
15
23
  >,
16
24
  {},
@@ -32,7 +40,10 @@ declare const _default: import('vue').DefineComponent<
32
40
  submitText: string
33
41
  affirmText: string
34
42
  bottomFixed: boolean
43
+ showSubmitButton: boolean
35
44
  vertical: boolean
45
+ titleFontSize: number
46
+ titleFontWeight: string
36
47
  }
37
48
  >
38
49
  >
@@ -41,12 +52,19 @@ declare const _default: import('vue').DefineComponent<
41
52
  },
42
53
  {
43
54
  vertical: boolean
55
+ titleFontSize: number
56
+ titleFontWeight: string | number
44
57
  submitText: string
45
58
  affirmText: string
46
59
  bottomFixed: boolean
60
+ showSubmitButton: boolean
47
61
  },
48
62
  {}
49
63
  >
64
+ declare const _default: __VLS_WithTemplateSlots<
65
+ typeof __VLS_component,
66
+ ReturnType<typeof __VLS_template>
67
+ >
50
68
  export default _default
51
69
  type __VLS_WithDefaults<P, D> = {
52
70
  [K in keyof Pick<P, keyof P>]: K extends keyof D
@@ -60,6 +78,11 @@ type __VLS_WithDefaults<P, D> = {
60
78
  type __VLS_Prettify<T> = {
61
79
  [K in keyof T]: T[K]
62
80
  } & {}
81
+ type __VLS_WithTemplateSlots<T, S> = T & {
82
+ new (): {
83
+ $slots: S
84
+ }
85
+ }
63
86
  type __VLS_NonUndefinedable<T> = T extends undefined ? never : T
64
87
  type __VLS_TypePropsToOption<T> = {
65
88
  [K in keyof T]-?: {} extends Pick<T, K>
@@ -78,5 +78,11 @@ export interface LcbFormProps extends LcbBlockProps {
78
78
  agreementType?: string
79
79
  affirmText?: string
80
80
  bottomFixed?: boolean
81
+ showSubmitButton?: boolean
81
82
  vertical?: boolean
83
+ titleColor?: string
84
+ titleFontSize?: number
85
+ titleFontWeight?: string | number
86
+ titleFontFamily?: string
87
+ formKey?: string
82
88
  }
@@ -1,6 +1,5 @@
1
1
  import { LcbBlockProps } from '../lcb-block/types'
2
2
  export interface LcbGapProps extends LcbBlockProps {
3
- height?: number | string
4
3
  safeAreaBottom?: boolean
5
4
  bgColor?: string
6
5
  safeAreaTop?: boolean
@@ -20,13 +20,19 @@ declare const __VLS_component: import('vue').DefineComponent<
20
20
  >,
21
21
  {
22
22
  mode: 'map' | 'list'
23
+ width: number
24
+ height: number
23
25
  backgroundColor: string
24
26
  color: string
25
27
  customStyle: Record<string, any>
26
28
  customClass: string
27
29
  zIndex: number
30
+ left: number
31
+ top: number
32
+ bottom: number
33
+ right: number
28
34
  radius: number
29
- position: 'relative' | 'absolute'
35
+ position: 'relative' | 'absolute' | 'fixed'
30
36
  backgroundImage: string
31
37
  border: boolean
32
38
  imageRadius: number
@@ -85,6 +85,7 @@ declare const _default: import('vue').DefineComponent<
85
85
  requestUrl: string
86
86
  requestParams: Record<string, any>
87
87
  refreshSchemaPage?: boolean
88
+ formKeys?: string[]
88
89
  }
89
90
  submitRequestInfo?: {
90
91
  requestUrl: string
@@ -194,6 +195,7 @@ declare const _default: import('vue').DefineComponent<
194
195
  requestUrl: string
195
196
  requestParams: Record<string, any>
196
197
  refreshSchemaPage?: boolean
198
+ formKeys?: string[]
197
199
  }
198
200
  submitRequestInfo?: {
199
201
  requestUrl: string
@@ -280,6 +282,7 @@ declare const _default: import('vue').DefineComponent<
280
282
  requestUrl: string
281
283
  requestParams: Record<string, any>
282
284
  refreshSchemaPage?: boolean
285
+ formKeys?: string[]
283
286
  }
284
287
  submitRequestInfo?: {
285
288
  requestUrl: string
@@ -366,6 +369,7 @@ declare const _default: import('vue').DefineComponent<
366
369
  requestUrl: string
367
370
  requestParams: Record<string, any>
368
371
  refreshSchemaPage?: boolean
372
+ formKeys?: string[]
369
373
  }
370
374
  submitRequestInfo?: {
371
375
  requestUrl: string
@@ -451,6 +455,7 @@ declare const _default: import('vue').DefineComponent<
451
455
  requestUrl: string
452
456
  requestParams: Record<string, any>
453
457
  refreshSchemaPage?: boolean
458
+ formKeys?: string[]
454
459
  }
455
460
  submitRequestInfo?: {
456
461
  requestUrl: string
@@ -542,6 +547,7 @@ declare const _default: import('vue').DefineComponent<
542
547
  requestUrl: string
543
548
  requestParams: Record<string, any>
544
549
  refreshSchemaPage?: boolean
550
+ formKeys?: string[]
545
551
  }
546
552
  submitRequestInfo?: {
547
553
  requestUrl: string
@@ -627,6 +633,7 @@ declare const _default: import('vue').DefineComponent<
627
633
  requestUrl: string
628
634
  requestParams: Record<string, any>
629
635
  refreshSchemaPage?: boolean
636
+ formKeys?: string[]
630
637
  }
631
638
  submitRequestInfo?: {
632
639
  requestUrl: string
@@ -712,6 +719,7 @@ declare const _default: import('vue').DefineComponent<
712
719
  requestUrl: string
713
720
  requestParams: Record<string, any>
714
721
  refreshSchemaPage?: boolean
722
+ formKeys?: string[]
715
723
  }
716
724
  submitRequestInfo?: {
717
725
  requestUrl: string
@@ -801,6 +809,7 @@ declare const _default: import('vue').DefineComponent<
801
809
  requestUrl: string
802
810
  requestParams: Record<string, any>
803
811
  refreshSchemaPage?: boolean
812
+ formKeys?: string[]
804
813
  }
805
814
  submitRequestInfo?: {
806
815
  requestUrl: string
@@ -887,6 +896,7 @@ declare const _default: import('vue').DefineComponent<
887
896
  requestUrl: string
888
897
  requestParams: Record<string, any>
889
898
  refreshSchemaPage?: boolean
899
+ formKeys?: string[]
890
900
  }
891
901
  submitRequestInfo?: {
892
902
  requestUrl: string
@@ -15,3 +15,5 @@ export declare const PAGE_DYNAMIC_DATA = 'page_dynamic_data'
15
15
  export declare const WRAPPER_ITEM_KEY = 'wrapper_item_key'
16
16
  /** 是否显示tabbar */
17
17
  export declare const SHOW_TABBAR = 'show_tabbar'
18
+ /** 是否在编辑器模式 */
19
+ export declare const IS_EDITOR_MODE = 'is_editor_mode'