wui-components-v2 1.1.82 → 1.1.83

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/api/page.ts CHANGED
@@ -112,3 +112,24 @@ export function deleteData(sourceId: string, code: string) {
112
112
  method: 'DELETE',
113
113
  })
114
114
  }
115
+
116
+ // 关联字段联动:通过 relValueField 下选中值请求默认值
117
+ // relValueField 下存在 extRefPageId 时请求 /v3/ltmpl/top-data,
118
+ // 否则请求 /v3/dtmpl/data
119
+ // topData 参数: { top: 1, code: 上一个表单拿到的值, sourceId: extRefPageId }
120
+ // dtmplData 参数: { code: 选中值, sourceId: 参数来源字段 sourceId }
121
+ export function topData(params: { top: number; code: any; sourceId: string }) {
122
+ return req({
123
+ url: `/v3/ltmpl/top-data`,
124
+ method: 'GET',
125
+ data: params,
126
+ })
127
+ }
128
+
129
+ export function dtmplData(params: { top: number; code: any; sourceId: string }) {
130
+ return req({
131
+ url: `/v3/dtmpl/data`,
132
+ method: 'GET',
133
+ data: params,
134
+ })
135
+ }
@@ -6,13 +6,14 @@ import type { Enums, Fields, Groups } from '../../type'
6
6
  import WuiSelectPopup from '../wui-select-popup/wui-select-popup.vue'
7
7
  import ControlTypeSupportor from '../../utils/control-type-supportor'
8
8
  import { useCompanyFieldFilter } from '../../composables/useCompanyFieldFilter'
9
+ import { useFormFieldValues } from '../../composables/useFormFieldValues'
9
10
  import customSelectPicker from '../custom-select-picker/custom-select-picker.vue'
10
11
  import CustomDatePicker from '../custom-date-picker/custom-date-picker.vue'
11
12
  import addAddressPage from '../add-address-page/add-address-page.vue'
12
13
  import userChoose from '../user-choose/user-choose.vue'
13
14
  import { generateHighResolutionID } from '../../utils/index'
14
15
  import scanInput from '../scan-input/scan-input.vue'
15
- // import { enums } from '../../api/page'
16
+ import { dtmplData, topData } from '../../api/page'
16
17
  defineOptions({
17
18
  name: 'FormControl',
18
19
  })
@@ -153,14 +154,215 @@ const shouldMonitor = computed(() => {
153
154
  .map((innerItem: any) => ({
154
155
  id: innerItem.sourceId,
155
156
  title: innerItem.title,
156
- relValueField: innerItem.relValueField.id,
157
- relValueField3: innerItem.relValueField3.id,
157
+ relValueField: innerItem.relValueField?.id,
158
+ relValueField3: innerItem.relValueField3?.id,
158
159
  }))
159
160
  console.log('shouldMonitor:', newList)
160
161
  return newList
161
162
  })
162
163
  //-e
163
164
 
165
+ //-s subRelValueField 联动
166
+ // 当当前项存在 relValueField 且其下存在 subRelValueField 时,
167
+ // 通过 relValueField 下的 sourceId(无则回退到 id)获取表单对应选中值,
168
+ // 将其作为参数请求接口 /aa,并把返回值作为默认值赋给当前项
169
+ // 以下类型的字段不参与 subRelValueField 联动监听
170
+ const EXCLUDE_CONTROL_TYPES = [
171
+ 'relselect',
172
+ 'refselect',
173
+ 'entity-select',
174
+ 'relselect-extdis',
175
+ 'relselectvalue',
176
+ 'entity-select-value',
177
+ 'table-entity-select',
178
+ 'lab-tree-select',
179
+ 'tree-entity-select',
180
+ 'ltree-entity-select',
181
+ 'ltree-entity-select-value',
182
+ 'tree-entity-select-value',
183
+ ]
184
+ // 三类关联字段:relValueField(默认值)、relMaxValueField(最大值)、relMinValueField(最小值)
185
+ // assignment 决定把接口返回值赋到哪:value→当前项值,max→item.max,min→item.min
186
+ const REL_FIELD_CONFIG = [
187
+ { key: 'relValueField', assignment: 'value' },
188
+ { key: 'relMaxValueField', assignment: 'max' },
189
+ { key: 'relMinValueField', assignment: 'min' },
190
+ ]
191
+ const subRelMonitor = computed(() =>
192
+ fields.value
193
+ // 仅当字段类型(extControlType)不在排除列表中才参与联动监听
194
+ .filter((item: any) => !EXCLUDE_CONTROL_TYPES.includes(item.extControlType))
195
+ .flatMap((item: any) =>
196
+ REL_FIELD_CONFIG
197
+ .map(({ key, assignment }) => {
198
+ const rel = (item as any)[key]
199
+ // subRelValueField:兼容嵌套在 rel 字段下,或 item 同级(仅 relValueField 兼容同级)
200
+ const subRel =
201
+ rel?.subRelValueField ||
202
+ (key === 'relValueField' ? (item as any).subRelValueField : undefined)
203
+ if (!rel || !subRel) return null
204
+ // 找到原始字段对象(props.fieldGroup.fields 中):filteredFields 每次重算会浅拷贝生成新对象,
205
+ // 只改拷贝对象会在重算时丢失 max/min,因此同时持有原始对象引用,双写以保证不丢值
206
+ const rawItem =
207
+ (props.fieldGroup?.fields || []).find((f: any) => f.sourceId === item.sourceId) || item
208
+ return {
209
+ monitorKey: `${item.sourceId}__${key}`, // 唯一键,避免多 rel 字段共用 paramSourceId 相互干扰
210
+ item, // filteredFields 中的拷贝对象,直接改可立即触发模板更新
211
+ rawItem, // 原始字段对象,改它可在 filteredFields 重算后保留 max/min
212
+ itemSourceId: item.sourceId,
213
+ assignment, // 'value' | 'max' | 'min'
214
+ valueSourceKey: subRel.sourceId, // 从接口返回 fieldMap 取值所用的 key
215
+ paramSourceId: rel.sourceId || rel.id, // 请求参数来源字段
216
+ useA: !!rel.extRefPageId,
217
+ extRefPageId: rel.extRefPageId,
218
+ }
219
+ })
220
+ .filter((m: any) => m && m.paramSourceId && m.valueSourceKey)
221
+ )
222
+ )
223
+ const { setFieldValue, getFieldValue } = useFormFieldValues()
224
+ // 把本表单每个字段的选中值同步到跨表单共享存储,
225
+ // 供后续表单通过 relValueField.sourceId 读取上一个表单的选中值
226
+ watch(
227
+ () => model.value,
228
+ val => {
229
+ for (const key in val) {
230
+ if (Object.prototype.hasOwnProperty.call(val, key)) {
231
+ setFieldValue(key, val[key])
232
+ }
233
+ }
234
+ },
235
+ { deep: true }
236
+ )
237
+
238
+ // 输入裁剪:对设置了 min/max 的字段,输入值超出范围时自动纠正到边界
239
+ const fieldConstraints = computed(() => {
240
+ const map: Record<string, { min?: number; max?: number }> = {}
241
+ for (const item of fields.value) {
242
+ const rawMin = item.min
243
+ const rawMax = item.max
244
+ const min = rawMin !== undefined && rawMin !== null && rawMin !== '' ? Number(rawMin) : undefined
245
+ const max = rawMax !== undefined && rawMax !== null && rawMax !== '' ? Number(rawMax) : undefined
246
+ if (min !== undefined || max !== undefined) {
247
+ map[item.sourceId] = { min, max }
248
+ }
249
+ }
250
+ return map
251
+ })
252
+ watch(
253
+ () => model.value,
254
+ val => {
255
+ for (const sourceId in fieldConstraints.value) {
256
+ const { min, max } = fieldConstraints.value[sourceId]
257
+ const v = (val as any)[sourceId]
258
+ if (v === undefined || v === null || v === '') continue
259
+ const num = Number(v)
260
+ if (isNaN(num)) continue
261
+ let fixed = num
262
+ if (min !== undefined && num < min) fixed = min
263
+ if (max !== undefined && num > max) fixed = max
264
+ if (fixed !== num) {
265
+ ;(model.value as any)[sourceId] = fixed
266
+ console.log('[subRel] 裁剪输入', sourceId, '从', v, '→', fixed, '(min=', min, 'max=', max, ')')
267
+ }
268
+ }
269
+ },
270
+ { deep: true }
271
+ )
272
+
273
+ console.log('[subRel] 命中联动项:', subRelMonitor.value)
274
+ const lastSubRelSnapshot = ref<Record<string, any>>({})
275
+ watch(
276
+ () => {
277
+ const snapshot: Record<string, any> = {}
278
+ subRelMonitor.value.forEach((m: any) => {
279
+ // 取值优先级:本表单 model > 跨页面存储 > props.entity(兜底)
280
+ const local = model.value[m.paramSourceId]
281
+ const fromStore = getFieldValue(m.paramSourceId)
282
+ const fromEntity = (props.entity as any)?.[m.paramSourceId]
283
+ const finalVal =
284
+ local !== undefined && local !== '' ? local
285
+ : fromStore !== undefined && fromStore !== '' ? fromStore
286
+ : fromEntity !== undefined && fromEntity !== '' ? fromEntity
287
+ : undefined
288
+ console.log(`[subRel] 来源取值 ${m.monitorKey} sourceId=${m.paramSourceId} | local=`, local, '| store=', fromStore, '| props.entity=', fromEntity, '=> 采用', finalVal)
289
+ snapshot[m.monitorKey] = finalVal
290
+ })
291
+ return snapshot
292
+ },
293
+ async newVal => {
294
+ for (const m of subRelMonitor.value as any[]) {
295
+ const val = newVal[m.monitorKey]
296
+ const prev = lastSubRelSnapshot.value[m.monitorKey]
297
+ lastSubRelSnapshot.value[m.monitorKey] = val
298
+ console.log('[subRel] 处理', m.monitorKey,
299
+ '| assignment=', m.assignment,
300
+ '| 参数来源sourceId=', m.paramSourceId,
301
+ '| 取到的值val=', val, '| 上次值prev=', prev)
302
+ // 仅在选中值存在且发生变化时请求,避免无意义重复调用
303
+ if (val !== undefined && val !== null && val !== '' && val !== prev) {
304
+ try {
305
+ let api: any
306
+ let apiName: string
307
+ let param: any
308
+ if (m.useA) {
309
+ api = topData
310
+ apiName = '/v3/ltmpl/top-data'
311
+ param = { top: 1, code: val, sourceId: m.extRefPageId }
312
+ } else {
313
+ api = dtmplData
314
+ apiName = '/v3/dtmpl/data'
315
+ param = { top: 1, code: val, sourceId: m.paramSourceId }
316
+ }
317
+ console.log('[subRel] >>> 发起请求', apiName, '参数=', param)
318
+ const res = await api(param)
319
+ // =========================================================
320
+ // 【赋值点】接口返回结构:
321
+ // { entities: [ { fieldMap: { sourceId: 值, ... } } ] }
322
+ // 需从 entities[0].fieldMap[目标sourceId] 取出真正要填的值
323
+ // =========================================================
324
+ console.log('[subRel] <<< 接口返回 res =', res)
325
+ const entity = (res as any)?.entities?.[0]
326
+ const fieldMap = entity?.fieldMap || {}
327
+ console.log('[subRel] fieldMap =', fieldMap)
328
+ // 用 subRelValueField.sourceId 作为 key 从 fieldMap 取出默认值
329
+ const value = fieldMap[m.valueSourceKey]
330
+ console.log('[subRel] 用 key', m.valueSourceKey, '从 fieldMap 取出值 =', value)
331
+ if (value !== undefined && value !== null) {
332
+ // 根据 assignment 决定赋值位置:
333
+ // value → 当前项默认值(model[sourceId]);max → item.max;min → item.min
334
+ if (m.assignment === 'max') {
335
+ // 双写:拷贝对象立即更新模板;原始对象在 filteredFields 重算后保留
336
+ m.item.max = value
337
+ m.rawItem.max = value
338
+ console.log('[subRel] ✓ 已设置最大值 item.max =', value, '(sourceId=', m.itemSourceId, ')')
339
+ } else if (m.assignment === 'min') {
340
+ m.item.min = value
341
+ m.rawItem.min = value
342
+ console.log('[subRel] ✓ 已设置最小值 item.min =', value, '(sourceId=', m.itemSourceId, ')')
343
+ } else {
344
+ model.value[m.itemSourceId] = value
345
+ console.log('[subRel] ✓ 已赋值默认值 model[', m.itemSourceId, '] =', value)
346
+ }
347
+ } else {
348
+ console.warn('[subRel] ⚠ fieldMap 中未找到 key', m.valueSourceKey, ',未赋值')
349
+ }
350
+ } catch (e) {
351
+ console.error('[subRel] 关联默认值请求失败:', e)
352
+ }
353
+ } else {
354
+ // 没进请求分支:常见原因是 val 为空(跨页面没取到值)或值未变化
355
+ console.warn('[subRel] ✗ 未发起请求 ——',
356
+ val === undefined || val === null || val === ''
357
+ ? `参数来源值 val 为空(跨页面存储未取到 sourceId=${m.paramSourceId} 的值)`
358
+ : `值未发生变化(prev=${prev})`)
359
+ }
360
+ }
361
+ },
362
+ { immediate: true }
363
+ )
364
+ //-e subRelValueField 联动
365
+
164
366
  // 存储变化后的数据,{ [fieldKey]: newValue, ... }
165
367
  const changedData = ref<Record<string, any>>({})
166
368
  const initialModel = ref<Record<string, any>>({})
@@ -351,6 +553,8 @@ console.log('clearValueMap', clearValueMap)
351
553
  <wd-input
352
554
  v-model="model[item.sourceId]"
353
555
  type="number"
556
+ :min="Number(item.min || 0)"
557
+ :max="Number(item.max || Infinity)"
354
558
  :readonly="item.disabled || item.rowEditType === 'readonly'"
355
559
  :clearable="!item.disabled"
356
560
  :placeholder="`请输入${item.title}`"
@@ -26,7 +26,14 @@ const { activeTabbar, getTabbarItemValue, setTabbarItemActive, tabbarList } = us
26
26
 
27
27
  function handleTabbarChange({ value }: { value: string }) {
28
28
  setTabbarItemActive(value)
29
- router.pushTab({ name: value })
29
+ const item = tabbarList.value.find(item => item.name === value)
30
+ if (item && item.query && item.pagePath) {
31
+ const params = Object.entries(item.query).map(([k, v]) => `${k}=${v}`).join('&')
32
+ uni.navigateTo({ url: `/${item.pagePath}?${params}` })
33
+ }
34
+ else {
35
+ router.pushTab({ name: value })
36
+ }
30
37
  }
31
38
 
32
39
  onMounted(() => {
@@ -0,0 +1,73 @@
1
+ /**
2
+ * 跨表单、跨页面共享字段选中值。
3
+ * 按 sourceId 存储,供后续表单/页面通过 relValueField.sourceId
4
+ * 读取上一个表单/页面的选中值。
5
+ * 使用本地持久化存储(uni.setStorageSync / localStorage),
6
+ * 跨组件实例、跨页面、跨路由均有效,不依赖 JS 实例是否重建。
7
+ */
8
+ const STORAGE_KEY = 'FORM_FIELD_VALUES'
9
+
10
+ function readStore(): Record<string, any> {
11
+ try {
12
+ // uni-app 环境
13
+ if (typeof uni !== 'undefined' && uni.getStorageSync) {
14
+ return uni.getStorageSync(STORAGE_KEY) || {}
15
+ }
16
+ } catch (e) {
17
+ // ignore
18
+ }
19
+ try {
20
+ // H5 / 浏览器回退
21
+ if (typeof localStorage !== 'undefined') {
22
+ const raw = localStorage.getItem(STORAGE_KEY)
23
+ return raw ? JSON.parse(raw) : {}
24
+ }
25
+ } catch (e) {
26
+ // ignore
27
+ }
28
+ return {}
29
+ }
30
+
31
+ function writeStore(data: Record<string, any>) {
32
+ try {
33
+ if (typeof uni !== 'undefined' && uni.setStorageSync) {
34
+ uni.setStorageSync(STORAGE_KEY, data)
35
+ return
36
+ }
37
+ } catch (e) {
38
+ // ignore
39
+ }
40
+ try {
41
+ if (typeof localStorage !== 'undefined') {
42
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(data))
43
+ }
44
+ } catch (e) {
45
+ // ignore
46
+ }
47
+ }
48
+
49
+ export function useFormFieldValues() {
50
+ function setFieldValue(sourceId: string, value: any) {
51
+ if (sourceId == null) return
52
+ const data = readStore()
53
+ data[sourceId] = value
54
+ writeStore(data)
55
+ console.log('[store] set', sourceId, '=', value)
56
+ }
57
+ function getFieldValue(sourceId: string) {
58
+ if (sourceId == null) return undefined
59
+ const v = readStore()[sourceId]
60
+ console.log('[store] get', sourceId, '=>', v, '| 当前完整存储:', readStore())
61
+ return v
62
+ }
63
+ function removeFieldValue(sourceId: string) {
64
+ if (sourceId == null) return
65
+ const data = readStore()
66
+ delete data[sourceId]
67
+ writeStore(data)
68
+ }
69
+ function clearAll() {
70
+ writeStore({})
71
+ }
72
+ return { setFieldValue, getFieldValue, removeFieldValue, clearAll }
73
+ }
@@ -8,6 +8,8 @@ export interface TabbarItem {
8
8
  icon: string
9
9
  activeIcon?: string // 激活状态自定义图标路径
10
10
  inactiveIcon?: string // 未激活状态自定义图标路径
11
+ query?: Record<string, string> // 跳转时携带的 URL 参数
12
+ pagePath?: string // 页面路径,配合 query 使用
11
13
  }
12
14
  export function useTabbar(tabbarItems: TabbarItem[]) {
13
15
  const tabbarList = ref<TabbarItem[]>([...tabbarItems])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wui-components-v2",
3
- "version": "1.1.82",
3
+ "version": "1.1.83",
4
4
  "description": "wui 组件库",
5
5
  "author": "wgxshh",
6
6
  "license": "MIT",