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
|
-
|
|
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
|
|
157
|
-
relValueField3: innerItem.relValueField3
|
|
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
|
-
|
|
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
|
+
}
|
package/composables/useTabbar.ts
CHANGED
|
@@ -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])
|