wui-components-v2 1.1.82 → 1.1.84

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,232 @@ 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.map(({ key, assignment }) => {
197
+ const rel = (item as any)[key]
198
+ // subRelValueField:兼容嵌套在 rel 字段下,或 item 同级(仅 relValueField 兼容同级)
199
+ const subRel = rel?.subRelValueField || (key === 'relValueField' ? (item as any).subRelValueField : undefined)
200
+ if (!rel || !subRel) return null
201
+ // 找到原始字段对象(props.fieldGroup.fields 中):filteredFields 每次重算会浅拷贝生成新对象,
202
+ // 只改拷贝对象会在重算时丢失 max/min,因此同时持有原始对象引用,双写以保证不丢值
203
+ const rawItem = (props.fieldGroup?.fields || []).find((f: any) => f.sourceId === item.sourceId) || item
204
+ return {
205
+ monitorKey: `${item.sourceId}__${key}`, // 唯一键,避免多 rel 字段共用 paramSourceId 相互干扰
206
+ item, // filteredFields 中的拷贝对象,直接改可立即触发模板更新
207
+ rawItem, // 原始字段对象,改它可在 filteredFields 重算后保留 max/min
208
+ itemSourceId: item.sourceId,
209
+ assignment, // 'value' | 'max' | 'min'
210
+ valueSourceKey: subRel.sourceId, // 从接口返回 fieldMap 取值所用的 key
211
+ paramSourceId: rel.sourceId || rel.id, // 请求参数来源字段
212
+ useA: !!rel.extRefPageId,
213
+ extRefPageId: rel.extRefPageId,
214
+ }
215
+ }).filter((m: any) => m && m.paramSourceId && m.valueSourceKey)
216
+ )
217
+ )
218
+ const { setFieldValue, getFieldValue } = useFormFieldValues()
219
+ // 把本表单每个字段的选中值同步到跨表单共享存储,
220
+ // 供后续表单通过 relValueField.sourceId 读取上一个表单的选中值
221
+ watch(
222
+ () => model.value,
223
+ val => {
224
+ for (const key in val) {
225
+ if (Object.prototype.hasOwnProperty.call(val, key)) {
226
+ setFieldValue(key, val[key])
227
+ }
228
+ }
229
+ },
230
+ { deep: true }
231
+ )
232
+
233
+ // 输入裁剪:对设置了 min/max 的字段,输入值超出范围时自动纠正到边界
234
+ const fieldConstraints = computed(() => {
235
+ const map: Record<string, { min?: number; max?: number }> = {}
236
+ for (const item of fields.value) {
237
+ const rawMin = item.min
238
+ const rawMax = item.max
239
+ const min = rawMin !== undefined && rawMin !== null && rawMin !== '' ? Number(rawMin) : undefined
240
+ const max = rawMax !== undefined && rawMax !== null && rawMax !== '' ? Number(rawMax) : undefined
241
+ if (min !== undefined || max !== undefined) {
242
+ map[item.sourceId] = { min, max }
243
+ }
244
+ }
245
+ return map
246
+ })
247
+ watch(
248
+ () => model.value,
249
+ val => {
250
+ for (const sourceId in fieldConstraints.value) {
251
+ const { min, max } = fieldConstraints.value[sourceId]
252
+ const v = (val as any)[sourceId]
253
+ if (v === undefined || v === null || v === '') continue
254
+ const num = Number(v)
255
+ if (isNaN(num)) continue
256
+ let fixed = num
257
+ if (min !== undefined && num < min) fixed = min
258
+ if (max !== undefined && num > max) fixed = max
259
+ if (fixed !== num) {
260
+ ;(model.value as any)[sourceId] = fixed
261
+ console.log('[subRel] 裁剪输入', sourceId, '从', v, '→', fixed, '(min=', min, 'max=', max, ')')
262
+ }
263
+ }
264
+ },
265
+ { deep: true }
266
+ )
267
+
268
+ console.log('[subRel] 命中联动项:', subRelMonitor.value)
269
+ const lastSubRelSnapshot = ref<Record<string, any>>({})
270
+ watch(
271
+ () => {
272
+ const snapshot: Record<string, any> = {}
273
+ subRelMonitor.value.forEach((m: any) => {
274
+ // 取值优先级:本表单 model > 跨页面存储 > props.entity(兜底)
275
+ const local = model.value[m.paramSourceId]
276
+ const fromStore = getFieldValue(m.paramSourceId)
277
+ const fromEntity = (props.entity as any)?.[m.paramSourceId]
278
+ const finalVal =
279
+ local !== undefined && local !== ''
280
+ ? local
281
+ : fromStore !== undefined && fromStore !== ''
282
+ ? fromStore
283
+ : fromEntity !== undefined && fromEntity !== ''
284
+ ? fromEntity
285
+ : undefined
286
+ console.log(
287
+ `[subRel] 来源取值 ${m.monitorKey} sourceId=${m.paramSourceId} | local=`,
288
+ local,
289
+ '| store=',
290
+ fromStore,
291
+ '| props.entity=',
292
+ fromEntity,
293
+ '=> 采用',
294
+ finalVal
295
+ )
296
+ snapshot[m.monitorKey] = finalVal
297
+ })
298
+ return snapshot
299
+ },
300
+ async newVal => {
301
+ for (const m of subRelMonitor.value as any[]) {
302
+ const val = newVal[m.monitorKey]
303
+ const prev = lastSubRelSnapshot.value[m.monitorKey]
304
+ lastSubRelSnapshot.value[m.monitorKey] = val
305
+ console.log(
306
+ '[subRel] 处理',
307
+ m.monitorKey,
308
+ '| assignment=',
309
+ m.assignment,
310
+ '| 参数来源sourceId=',
311
+ m.paramSourceId,
312
+ '| 取到的值val=',
313
+ val,
314
+ '| 上次值prev=',
315
+ prev
316
+ )
317
+ // 仅在选中值存在且发生变化时请求,避免无意义重复调用
318
+ if (val !== undefined && val !== null && val !== '' && val !== prev) {
319
+ try {
320
+ let api: any
321
+ let apiName: string
322
+ let param: any
323
+ if (m.useA) {
324
+ api = topData
325
+ apiName = '/v3/ltmpl/top-data'
326
+ param = { top: 1, secondCriterion: val, sourceId: m.extRefPageId }
327
+ } else {
328
+ api = dtmplData
329
+ apiName = '/v3/dtmpl/data'
330
+ param = { top: 1, secondCriterion: val, sourceId: m.paramSourceId }
331
+ }
332
+ console.log('[subRel] >>> 发起请求', apiName, '参数=', param)
333
+ const res = await api(param)
334
+ // =========================================================
335
+ // 【赋值点】接口返回结构:
336
+ // { entities: [ { fieldMap: { sourceId: 值, ... } } ] }
337
+ // 需从 entities[0].fieldMap[目标sourceId] 取出真正要填的值
338
+ // =========================================================
339
+ console.log('[subRel] <<< 接口返回 res =', res)
340
+ const entity = (res as any)?.entities?.[0]
341
+ const fieldMap = entity?.fieldMap || {}
342
+ console.log('[subRel] fieldMap =', fieldMap)
343
+ // 用 subRelValueField.sourceId 作为 key 从 fieldMap 取出默认值
344
+ const value = fieldMap[m.valueSourceKey]
345
+ console.log('[subRel] 用 key', m.valueSourceKey, '从 fieldMap 取出值 =', value)
346
+ if (value !== undefined && value !== null) {
347
+ // 根据 assignment 决定赋值位置:
348
+ // value → 当前项默认值(model[sourceId]);max → item.max;min → item.min
349
+ if (m.assignment === 'max') {
350
+ // 双写:拷贝对象立即更新模板;原始对象在 filteredFields 重算后保留
351
+ m.item.max = value
352
+ m.rawItem.max = value
353
+ console.log('[subRel] ✓ 已设置最大值 item.max =', value, '(sourceId=', m.itemSourceId, ')')
354
+ } else if (m.assignment === 'min') {
355
+ m.item.min = value
356
+ m.rawItem.min = value
357
+ console.log('[subRel] ✓ 已设置最小值 item.min =', value, '(sourceId=', m.itemSourceId, ')')
358
+ } else {
359
+ model.value[m.itemSourceId] = value
360
+ console.log('[subRel] ✓ 已赋值默认值 model[', m.itemSourceId, '] =', value)
361
+ }
362
+ } else {
363
+ console.warn('[subRel] ⚠ fieldMap 中未找到 key', m.valueSourceKey, ',未赋值')
364
+ }
365
+ } catch (e) {
366
+ console.error('[subRel] 关联默认值请求失败:', e)
367
+ }
368
+ } else {
369
+ // 没进请求分支:常见原因是 val 为空(跨页面没取到值)或值未变化
370
+ console.warn(
371
+ '[subRel] ✗ 未发起请求 ——',
372
+ val === undefined || val === null || val === ''
373
+ ? `参数来源值 val 为空(跨页面存储未取到 sourceId=${m.paramSourceId} 的值)`
374
+ : `值未发生变化(prev=${prev})`
375
+ )
376
+ }
377
+ }
378
+ },
379
+ { immediate: true }
380
+ )
381
+ //-e subRelValueField 联动
382
+
164
383
  // 存储变化后的数据,{ [fieldKey]: newValue, ... }
165
384
  const changedData = ref<Record<string, any>>({})
166
385
  const initialModel = ref<Record<string, any>>({})
@@ -351,6 +570,8 @@ console.log('clearValueMap', clearValueMap)
351
570
  <wd-input
352
571
  v-model="model[item.sourceId]"
353
572
  type="number"
573
+ :min="Number(item.min || 0)"
574
+ :max="Number(item.max || Infinity)"
354
575
  :readonly="item.disabled || item.rowEditType === 'readonly'"
355
576
  :clearable="!item.disabled"
356
577
  :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.84",
4
4
  "description": "wui 组件库",
5
5
  "author": "wgxshh",
6
6
  "license": "MIT",