vue2-client 1.14.68 → 1.14.69

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.
Files changed (62) hide show
  1. package/docs/Logic/345/207/275/346/225/260/344/275/277/347/224/250/347/233/270/345/205/263.md +0 -1
  2. package/docs//345/207/275/346/225/260/344/275/277/347/224/250/347/233/270/345/205/263.md +1 -2
  3. package/package.json +1 -1
  4. package/src/base-client/components/common/XReport/XReport.vue +72 -1
  5. package/src/base-client/components/common/XReport/XReportDemo.vue +1 -2
  6. package/src/base-client/components/common/XReport/XReportTrGroup.vue +218 -29
  7. package/src/base-client/components/common/XReport/index.md +59 -0
  8. package/src/base-client/components/common/XUploadFilesView/index.vue +485 -485
  9. package/src/base-client/components/his/XTextCard/XTextCard.vue +207 -207
  10. package/src/base-client/components/his/XTreeRows/TreeNode.vue +100 -100
  11. package/src/base-client/components/his/XTreeRows/XTreeRows.vue +197 -197
  12. package/src/base-client/components/his/threeTestOrders/editor.vue +111 -111
  13. package/src/pages/WorkflowDetail/WorkFlowDemo.vue +1 -1
  14. package/src/pages/WorkflowDetail/WorkflowPageDetail/LeaveMessage.vue +388 -388
  15. package/src/pages/WorkflowDetail/WorkflowPageDetail/WorkFlowHandle.vue +1 -1
  16. package/src/router/async/router.map.js +2 -2
  17. package/.history/public/his/editor/editor_20250606134713.html +0 -51
  18. package/.history/src/base-client/components/common/AddressSearchCombobox/AddressSearchCombobox_20250527173925.vue +0 -509
  19. package/.history/src/base-client/components/common/AddressSearchCombobox/AddressSearchCombobox_20250527174316.vue +0 -524
  20. package/.history/src/base-client/components/common/AddressSearchCombobox/AddressSearchCombobox_20250527174419.vue +0 -524
  21. package/.history/src/base-client/components/common/AddressSearchCombobox/AddressSearchCombobox_20250527174422.vue +0 -524
  22. package/.history/src/base-client/components/common/XForm/XFormItem_20250508134122.vue +0 -1320
  23. package/.history/src/base-client/components/common/XForm/XFormItem_20250527171604.vue +0 -1332
  24. package/.history/src/base-client/components/common/XForm/XFormItem_20250527171613.vue +0 -1331
  25. package/.history/src/base-client/components/common/XForm/XFormItem_20250527171703.vue +0 -1331
  26. package/.history/src/base-client/components/common/XForm/XFormItem_20250527171720.vue +0 -1331
  27. package/.history/src/base-client/components/common/XForm/XFormItem_20250527174327.vue +0 -1339
  28. package/.history/src/base-client/components/his/XList/XList_20250609135848.vue +0 -173
  29. package/.history/src/base-client/components/his/XList/XList_20250609141026.vue +0 -222
  30. package/.history/src/base-client/components/his/XList/XList_20250609141035.vue +0 -229
  31. package/.history/src/base-client/components/his/XList/XList_20250609141103.vue +0 -229
  32. package/.history/src/base-client/components/his/XList/XList_20250609141105.vue +0 -229
  33. package/.history/src/base-client/components/his/XList/XList_20250609141334.vue +0 -241
  34. package/.history/src/base-client/components/his/XList/XList_20250609141404.vue +0 -241
  35. package/.history/src/base-client/components/his/XList/XList_20250609141406.vue +0 -241
  36. package/.history/src/base-client/components/his/XList/XList_20250609141801.vue +0 -245
  37. package/.history/src/base-client/components/his/XList/XList_20250609142033.vue +0 -245
  38. package/.history/src/base-client/components/his/XList/XList_20250609142038.vue +0 -245
  39. package/.history/src/base-client/components/his/XList/XList_20250609142435.vue +0 -255
  40. package/.history/src/base-client/components/his/XList/XList_20250609142503.vue +0 -255
  41. package/.history/src/base-client/components/his/XList/XList_20250609142504.vue +0 -255
  42. package/.history/src/base-client/components/his/XList/XList_20250609143012.vue +0 -270
  43. package/.history/src/base-client/components/his/XList/XList_20250609143044.vue +0 -270
  44. package/.history/src/base-client/components/his/XList/XList_20250609143046.vue +0 -270
  45. package/.history/src/base-client/components/his/XList/XList_20250609143210.vue +0 -270
  46. package/.history/src/base-client/components/his/XList/XList_20250609144339.vue +0 -294
  47. package/.history/src/base-client/components/his/XList/XList_20250609144410.vue +0 -294
  48. package/.history/src/base-client/components/his/XList/XList_20250609144412.vue +0 -294
  49. package/.history/src/base-client/components/his/XList/XList_20250609144647.vue +0 -303
  50. package/.history/src/base-client/components/his/XList/XList_20250609144716.vue +0 -303
  51. package/.history/src/base-client/components/his/XList/XList_20250609144729.vue +0 -303
  52. package/.history/src/base-client/components/his/XList/XList_20250609151232.vue +0 -288
  53. package/.history/src/base-client/components/his/XList/XList_20250609151247.vue +0 -288
  54. package/.history/src/base-client/components/his/XList/XList_20250609151252.vue +0 -288
  55. package/.history/src/base-client/components/his/XList/XList_20250609161220.vue +0 -317
  56. package/.history/src/base-client/components/his/XList/XList_20250609161258.vue +0 -306
  57. package/.history/src/base-client/components/his/XList/XList_20250609161319.vue +0 -306
  58. package/.history/src/base-client/components/his/XList/XList_20250609161320.vue +0 -306
  59. package/Users/objecrt/af-vue2-client/src/base-client/components/his/XShiftSchedule/XShiftSchedule.vue +0 -36
  60. package/src/base-client/components/TreeList/TreeList.vue +0 -91
  61. package/src/base-client/components/TreeList/TreeNode.vue +0 -81
  62. package/src/base-client/components/common/XCardSet/XTiltle.vue +0 -191
@@ -11,7 +11,6 @@
11
11
  - ENV 对应 一个 org.json.JSONObject {tenantName:"租户名称",orgName:"分公司名称",applicationName:"应用名称",dbType:"数据库类型"}
12
12
  - logic 对应 LogicService 类,其中有方法
13
13
  * logic.run(logicName,param) 执行logic / logicName 执行的业务逻辑名称,param 参数 返回 org.json.JSONObject
14
- * logic.remoteRun("服务名","logicName", "param")
15
14
  - sql 对应 [SqlService]() 类,其中有方法
16
15
  * sql.query(sql别名 string,sql参数 string) 执行已经注册的sql 返回 org.json.JSONArray
17
16
  * sql.query(sql别名 string,sql参数 string,页数 int,每页行数 int) 执行已经注册的sql 返回 org.json.JSONArray
@@ -60,8 +60,7 @@ this.openDialog('xxx', 5, {}, {}, {})
60
60
  afterDelete( res (删除接口返回数据)) 删除后触发
61
61
  action(record (当前记录), id (当前记录主键), actionType (操作类型)) 操作按钮触发
62
62
  - expandedGrid:"栅格名"
63
- - json配置相关
64
- - "pageMaxSize": (int) num,
63
+
65
64
  # x-report 插槽 使用说明
66
65
 
67
66
  - init(configName,configData) 配置名称 / 报表数据
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue2-client",
3
- "version": "1.14.68",
3
+ "version": "1.14.69",
4
4
  "private": false,
5
5
  "scripts": {
6
6
  "serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint",
@@ -247,7 +247,17 @@ export default {
247
247
  this.$emit('updateImg', data)
248
248
  },
249
249
  // 导出数据,某些外部需要统一控制数据的变动
250
- exportData () {
250
+ exportData (skipValidation = false) {
251
+ // 如果不跳过校验,先进行必填字段校验
252
+ if (!skipValidation) {
253
+ const validation = this.validateRequiredFields()
254
+ if (!validation.isValid) {
255
+ const errorMessages = validation.errors.map(error => error.message).join(';')
256
+ console.warn(`数据导出警告:存在必填字段未填写 - ${errorMessages}`)
257
+ // 在导出时只警告,不阻止导出
258
+ }
259
+ }
260
+
251
261
  // 获取当前修改后的数据
252
262
  let tempData
253
263
  if (this.activeConfig === undefined || this.activeConfig === null) {
@@ -296,8 +306,68 @@ export default {
296
306
  }
297
307
  }
298
308
  },
309
+ // 验证必填字段
310
+ validateRequiredFields () {
311
+ const errors = []
312
+ const config = this.type === 'display' ? this.originalConfig : this.activeConfig
313
+
314
+ if (!config || !config.columns) {
315
+ return { isValid: true, errors: [] }
316
+ }
317
+
318
+ // 遍历所有配置项检查必填字段
319
+ config.columns.forEach((row, rowIndex) => {
320
+ row.forEach((cell, cellIndex) => {
321
+ if (cell.required && cell.dataIndex) {
322
+ let value
323
+ if (cell.dataIndex.indexOf('@@@') !== -1) {
324
+ // 处理深层嵌套数据
325
+ // const arr = cell.dataIndex.split('@@@')
326
+ value = config.tempData && config.tempData[cell.dataIndex]
327
+ } else {
328
+ value = config.data[cell.dataIndex]
329
+ }
330
+
331
+ // 检查值是否为空
332
+ if (!value || (typeof value === 'string' && value.trim() === '')) {
333
+ const message = cell.requiredMessage || this.getDefaultRequiredMessage(cell.type)
334
+ errors.push({
335
+ dataIndex: cell.dataIndex,
336
+ message: message,
337
+ rowIndex: rowIndex,
338
+ cellIndex: cellIndex,
339
+ type: cell.type
340
+ })
341
+ }
342
+ }
343
+ })
344
+ })
345
+
346
+ return {
347
+ isValid: errors.length === 0,
348
+ errors: errors
349
+ }
350
+ },
351
+ // 获取默认的必填提示信息
352
+ getDefaultRequiredMessage (fieldType) {
353
+ const defaultMessages = {
354
+ datePicker: '请选择日期',
355
+ timePicker: '请选择日期时间',
356
+ input: '请填写此字段',
357
+ inputs: '请填写此字段'
358
+ }
359
+ return defaultMessages[fieldType] || '此字段为必填项'
360
+ },
299
361
  // 正常的保存方法,当前修改内容会直接全部导出到外部
300
362
  saveConfig () {
363
+ // 先进行必填字段校验
364
+ const validation = this.validateRequiredFields()
365
+ if (!validation.isValid) {
366
+ const errorMessages = validation.errors.map(error => error.message).join(';')
367
+ this.$message.error(`保存失败:${errorMessages}`)
368
+ return false
369
+ }
370
+
301
371
  if (this.activeConfig === undefined || this.activeConfig === null) {
302
372
  return this.originalConfig.data
303
373
  } else {
@@ -306,6 +376,7 @@ export default {
306
376
  this.changeDeepObject(this.activeConfig.data, key, this.activeConfig.tempData[key])
307
377
  })
308
378
  this.$emit('saveConfig', this.$refs.XReportDesign.activatedConfig)
379
+ return true
309
380
  }
310
381
  },
311
382
  // 通过@@@分割临时变量,找到对应的key,并修改它的值
@@ -4,9 +4,8 @@
4
4
  @updateImg="updateImg"
5
5
  ref="main"
6
6
  :use-oss-for-img="false"
7
- config-name="ceshi1"
7
+ config-name="用户户内通气点火工艺流程"
8
8
  server-name="af-system"
9
- :config-data="{aaa:'data:image/png;base64,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115LdHotwujfv7+JFURlIIGUgLUeH3/8sfd7qH306dPHdO+KykECSQjWgyMNGLy9du1a7/exbfT0009X71WFIYEkxFoP37Z2wHocc8wxUb9+/VSfqDAkkARgPWhnp3CXxHpQwGP//f7775/e4kQqSCAJoO7x1ltvRR9++KH3ewjODzzwQBOca+dg5SGBeGKr5s8//7x35gqwHoMGDTJ7z0XlIYF4QLvHmjVrTGD+ySefeL/PWg8EwgwwUXlIIB7QsctmqBdffNF7xyBgPQYMGGB2DorKRAIpAtZjyZIl0dNPP212Dfpircd5550Xde7cOcUVijSRQIpAYE6r+TvvvJPofTb2OOGEE1JamcgCCcSBPXl2woQJXhuWLFgP9nyQ2pX1qGwkkH8B12rZsmXRU089lailBEjnXnDBBdGxxx6b0upEVkgg/8L69evNEdWTJk1KFJhTKWcYw4UXXqjMVREovDLwIs9oR+E2IGs1Z86caNy4caZr1xc2RO2+++7R8OHD1ZRYBCw03Qg8gDgHnfvFURh5G4MkgWwFHxxTSp588snErhU9VwxjIPZQS3txEAj3mfuNO8qkSYSCBWZrQKgzQVoigbQAcXDwDb1WSV0rAnN6rbAenPkh3HCvub+4sl988YU5T6Wmpua/Oy7pPEAoCIYZxjx8QiCBtIAP7P33348eeeSRaPHixYnei2tAYE5Luzp2i4NASKEztaXlvxmfRLcC8dvhhx9upr8ccsghptiKaJhlnOX9lUD+hrjj008/jcaOHRt9/fXXid7LB8YHedlll5kYRPjBPd+WlbaTXGgKZVsBYuH+nn322eY0riz31CiLFf0v7qDe8e677yZ6L4E5/vK1116rUaIJsS6WCysWWn2mT5+eedarzQvEDn977rnnEscdgGtFOwkvtbP7Y4fiUWvygQdRbW2t2ZmZJW1eIPi9r7/+evTwww8njjsIHGklueaaazRnNyEIhDNVkgy9YCJ+1pmtNi0QG5Tfd999iRoRwWat8Il9xoiKf2Izhr5gQXbbbbfM6yRtViCI44MPPojuv//+RIOnLQSOzMEdOHBgLvL1lYbdY+MLDySC86zTvW1SILZSfvfddycOygFBkM698sorjV8skoNAigXoLbGHDmVNmxMIHboUpkjn0sKeNCi3J9PedNNNaidpBaVYEAqGWdOmBGKPSnvwwQej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'}"
10
9
  :show-img-in-cell="true"
11
10
  :edit-mode="false"
12
11
  :show-save-button="false"
@@ -37,6 +37,22 @@
37
37
  {{ deserializeFunctionAndRun(cell.customFunction, configData[cell.dataIndex], config) }}
38
38
  </template>
39
39
  </template>
40
+ <template v-else-if="cell.type === 'datePicker'">
41
+ <template v-if="cell.customFunction === undefined">
42
+ {{ getDeepObject(configData, cell.dataIndex) }}
43
+ </template>
44
+ <template v-else>
45
+ {{ deserializeFunctionAndRun(cell.customFunction, configData[cell.dataIndex], config) }}
46
+ </template>
47
+ </template>
48
+ <template v-else-if="cell.type === 'timePicker'">
49
+ <template v-if="cell.customFunction === undefined">
50
+ {{ getDeepObject(configData, cell.dataIndex) }}
51
+ </template>
52
+ <template v-else>
53
+ {{ deserializeFunctionAndRun(cell.customFunction, configData[cell.dataIndex], config) }}
54
+ </template>
55
+ </template>
40
56
  <template v-else-if="cell.type === 'inputs'">
41
57
  <template v-if="cell.customFunction === undefined">
42
58
  {{ showSubRowValue(cell) }}
@@ -131,6 +147,26 @@
131
147
  }}
132
148
  </template>
133
149
  </template>
150
+ <template v-else-if="cell.type === 'datePicker'">
151
+ <template v-if="cell.customFunction === undefined">
152
+ {{ getDeepObject(configData.arr[inputColumnsDefinitionIndex], cell.dataIndex) }}
153
+ </template>
154
+ <template v-else>
155
+ {{
156
+ deserializeFunctionAndRun(cell.customFunction, configData.arr[inputColumnsDefinitionIndex][cell.dataIndex], config)
157
+ }}
158
+ </template>
159
+ </template>
160
+ <template v-else-if="cell.type === 'timePicker'">
161
+ <template v-if="cell.customFunction === undefined">
162
+ {{ getDeepObject(configData.arr[inputColumnsDefinitionIndex], cell.dataIndex) }}
163
+ </template>
164
+ <template v-else>
165
+ {{
166
+ deserializeFunctionAndRun(cell.customFunction, configData.arr[inputColumnsDefinitionIndex][cell.dataIndex], config)
167
+ }}
168
+ </template>
169
+ </template>
134
170
  <template v-else-if="cell.type === 'inputs'">
135
171
  <template v-if="cell.customFunction === undefined">
136
172
  {{ getDeepObject(configData.arr[inputColumnsDefinitionIndex], cell.dataIndex) }}
@@ -178,39 +214,61 @@
178
214
  >{{ cell.text || '确认' }}
179
215
  </a-button>
180
216
  </template>
217
+ <template v-else-if="cell.type === 'datePicker'">
218
+ <div>
219
+ <a-date-picker
220
+ @change="handleDatePickerChange($event, cell.dataIndex, cell)"
221
+ :value="formatDateValue(configData[cell.dataIndex])"
222
+ format="YYYY-MM-DD"
223
+ :style="'width:' + (cell.inputWidth ? cell.inputWidth : '100') + '%'"
224
+ :disabled="cell.inputReadOnly===true"
225
+ :class="{'required-field-error': isFieldRequired(cell) && !configData[cell.dataIndex]}"/>
226
+ <div v-if="isFieldRequired(cell) && !configData[cell.dataIndex]" class="required-message">
227
+ {{ getRequiredMessage(cell, 'datePicker') }}
228
+ </div>
229
+ </div>
230
+ </template>
231
+ <template v-else-if="cell.type === 'timePicker'">
232
+ <div>
233
+ <a-date-picker
234
+ @change="handleTimePickerChange($event, cell.dataIndex, cell)"
235
+ :value="formatDateValue(configData[cell.dataIndex])"
236
+ format="YYYY-MM-DD HH:mm:ss"
237
+ show-time
238
+ :style="'width:' + (cell.inputWidth ? cell.inputWidth : '100') + '%'"
239
+ :disabled="cell.inputReadOnly===true"
240
+ :class="{'required-field-error': isFieldRequired(cell) && !configData[cell.dataIndex]}"/>
241
+ <div v-if="isFieldRequired(cell) && !configData[cell.dataIndex]" class="required-message">
242
+ {{ getRequiredMessage(cell, 'timePicker') }}
243
+ </div>
244
+ </div>
245
+ </template>
181
246
  <template v-else-if="cell.type === 'signature'">
182
247
  <img :src="configData[cell.dataIndex]" alt="签名加载失败" style="max-height: 2rem">
183
248
  <a-button v-if="!configData[cell.dataIndex]" type="dashed" >需要在手机端签名 </a-button>
184
249
  </template>
185
250
  <template v-else-if="cell.type === 'input'">
186
- <template v-if="cell.inputReadOnly === true">
251
+ <div>
187
252
  <template v-if="cell.dataIndex.indexOf('@@@') !== -1">
188
253
  <a-input
189
- @change="handleInputDeepChange($event, cell.dataIndex)"
254
+ @change="handleInputDeepChange($event, cell.dataIndex, cell)"
190
255
  v-model="config.tempData[cell.dataIndex]"
191
256
  :style="'width:' + (cell.inputWidth ? cell.inputWidth : '100') + '%'"
192
- :disabled="true"/>
257
+ :disabled="cell.inputReadOnly === true"
258
+ :class="{'required-field-error': isFieldRequired(cell) && !config.tempData[cell.dataIndex]}"/>
193
259
  </template>
194
260
  <template v-else>
195
261
  <a-input
262
+ @change="handleInputChange($event, cell.dataIndex, cell)"
196
263
  v-model="configData[cell.dataIndex]"
197
264
  :style="'width:' + (cell.inputWidth ? cell.inputWidth : '100') + '%'"
198
- :disabled="true"/>
265
+ :disabled="cell.inputReadOnly === true"
266
+ :class="{'required-field-error': isFieldRequired(cell) && !configData[cell.dataIndex]}"/>
199
267
  </template>
200
- </template>
201
- <template v-else>
202
- <template v-if="cell.dataIndex.indexOf('@@@') !== -1">
203
- <a-input
204
- @change="handleInputDeepChange($event, cell.dataIndex)"
205
- v-model="config.tempData[cell.dataIndex]"
206
- :style="'width:' + (cell.inputWidth ? cell.inputWidth : '100') + '%'"/>
207
- </template>
208
- <template v-else>
209
- <a-input
210
- v-model="configData[cell.dataIndex]"
211
- :style="'width:' + (cell.inputWidth ? cell.inputWidth : '100') + '%'"/>
212
- </template>
213
- </template>
268
+ <div v-if="isFieldRequired(cell) && !getInputValue(cell)" class="required-message">
269
+ {{ getRequiredMessage(cell, 'input') }}
270
+ </div>
271
+ </div>
214
272
  </template>
215
273
  <template v-else-if="cell.type === 'inputs'">
216
274
  <template v-if="cell.inputReadOnly === true">
@@ -219,7 +277,7 @@
219
277
  <span class="inputsDivItemLabel">{{ displayFormatStartText(cell.format) }}</span>
220
278
  <template v-if="cell.dataIndex.indexOf('@@@') !== -1">
221
279
  <a-input
222
- @change="handleInputDeepChange($event, cell.dataIndex)"
280
+ @change="handleInputDeepChange($event, cell.dataIndex, cell)"
223
281
  v-model="config.tempData[cell.dataIndex][index]"
224
282
  :style="'width:' + (cell.inputWidth ? cell.inputWidth : '100') + '%'"
225
283
  :disabled="true"/>
@@ -240,7 +298,7 @@
240
298
  <span class="inputsDivItemLabel">{{ displayFormatStartText(cell.format) }}</span>
241
299
  <template v-if="cell.dataIndex.indexOf('@@@') !== -1">
242
300
  <a-input
243
- @change="handleInputDeepChange($event, cell.dataIndex)"
301
+ @change="handleInputDeepChange($event, cell.dataIndex, cell)"
244
302
  v-model="config.tempData[cell.dataIndex][index]"
245
303
  :style="'width:' + (cell.inputWidth ? cell.inputWidth : '100') + '%'"/>
246
304
  </template>
@@ -337,6 +395,35 @@
337
395
  >{{ cell.text || '确认' }}
338
396
  </a-button>
339
397
  </template>
398
+ <template v-else-if="cell.type === 'datePicker'">
399
+ <div>
400
+ <a-date-picker
401
+ @change="handleArrayDatePickerChange($event, inputColumnsDefinitionIndex, cell.dataIndex, cell)"
402
+ :value="formatDateValue(configData.arr[inputColumnsDefinitionIndex][cell.dataIndex])"
403
+ format="YYYY-MM-DD"
404
+ :style="'width:' + (cell.inputWidth ? cell.inputWidth : '100') + '%'"
405
+ :disabled="cell.inputReadOnly === true"
406
+ :class="{'required-field-error': isFieldRequired(cell) && !configData.arr[inputColumnsDefinitionIndex][cell.dataIndex]}"/>
407
+ <div v-if="isFieldRequired(cell) && !configData.arr[inputColumnsDefinitionIndex][cell.dataIndex]" class="required-message">
408
+ {{ getRequiredMessage(cell, 'datePicker') }}
409
+ </div>
410
+ </div>
411
+ </template>
412
+ <template v-else-if="cell.type === 'timePicker'">
413
+ <div>
414
+ <a-date-picker
415
+ @change="handleArrayTimePickerChange($event, inputColumnsDefinitionIndex, cell.dataIndex, cell)"
416
+ :value="formatDateValue(configData.arr[inputColumnsDefinitionIndex][cell.dataIndex])"
417
+ format="YYYY-MM-DD HH:mm:ss"
418
+ show-time
419
+ :style="'width:' + (cell.inputWidth ? cell.inputWidth : '100') + '%'"
420
+ :disabled="cell.inputReadOnly === true"
421
+ :class="{'required-field-error': isFieldRequired(cell) && !configData.arr[inputColumnsDefinitionIndex][cell.dataIndex]}"/>
422
+ <div v-if="isFieldRequired(cell) && !configData.arr[inputColumnsDefinitionIndex][cell.dataIndex]" class="required-message">
423
+ {{ getRequiredMessage(cell, 'timePicker') }}
424
+ </div>
425
+ </div>
426
+ </template>
340
427
  <template v-else-if="cell.type === 'signature'">
341
428
  <img :src="configData[cell.dataIndex]" alt="签名加载失败" style="max-height: 2rem">
342
429
  <a-button v-if="!configData[cell.dataIndex]" type="dashed" >需要在手机端签名</a-button>
@@ -345,17 +432,17 @@
345
432
  {{ configData.arr[inputColumnsDefinitionIndex][cell.dataIndex] }}
346
433
  </template>
347
434
  <template v-else-if="cell.type === 'input'">
348
- <template v-if="cell.inputReadOnly === true">
435
+ <div>
349
436
  <a-input
437
+ @change="handleArrayInputChange($event, inputColumnsDefinitionIndex, cell.dataIndex, cell)"
350
438
  v-model="configData.arr[inputColumnsDefinitionIndex][cell.dataIndex]"
351
439
  :style="'width:' + (cell.inputWidth ? cell.inputWidth : '100') + '%'"
352
- :disabled="true"/>
353
- </template>
354
- <template v-else>
355
- <a-input
356
- v-model="configData.arr[inputColumnsDefinitionIndex][cell.dataIndex]"
357
- :style="'width:' + (cell.inputWidth ? cell.inputWidth : '100') + '%'"/>
358
- </template>
440
+ :disabled="cell.inputReadOnly === true"
441
+ :class="{'required-field-error': isFieldRequired(cell) && !configData.arr[inputColumnsDefinitionIndex][cell.dataIndex]}"/>
442
+ <div v-if="isFieldRequired(cell) && !configData.arr[inputColumnsDefinitionIndex][cell.dataIndex]" class="required-message">
443
+ {{ getRequiredMessage(cell, 'input') }}
444
+ </div>
445
+ </div>
359
446
  </template>
360
447
  <template v-else-if="cell.type === 'inputs'">
361
448
  <template v-if="cell.inputReadOnly === true">
@@ -379,6 +466,7 @@
379
466
  import Upload from '@vue2-client/base-client/components/common/Upload'
380
467
  import { formatDate } from '@vue2-client/utils/util'
381
468
  import { nanoid } from 'nanoid'
469
+ import moment from 'moment'
382
470
 
383
471
  export default {
384
472
  name: 'XReportTrGroup',
@@ -582,9 +670,35 @@ export default {
582
670
  return result
583
671
  },
584
672
  // 表格中数据key含有@@@,需要手动触发更新
585
- handleInputDeepChange () {
673
+ handleInputDeepChange (event, dataIndex, cell = null) {
674
+ // 如果字段必填且有值,触发重新渲染以移除错误样式
675
+ if (cell && this.isFieldRequired(cell) && event.target.value) {
676
+ this.$forceUpdate()
677
+ }
586
678
  this.$forceUpdate()
587
679
  },
680
+ // 处理普通input变化
681
+ handleInputChange (event, dataIndex, cell = null) {
682
+ // 如果字段必填且有值,触发重新渲染以移除错误样式
683
+ if (cell && this.isFieldRequired(cell) && event.target.value) {
684
+ this.$forceUpdate()
685
+ }
686
+ },
687
+ // 处理动态行中input变化
688
+ handleArrayInputChange (event, arrayIndex, dataIndex, cell = null) {
689
+ // 如果字段必填且有值,触发重新渲染以移除错误样式
690
+ if (cell && this.isFieldRequired(cell) && event.target.value) {
691
+ this.$forceUpdate()
692
+ }
693
+ },
694
+ // 获取input的值,用于必填校验
695
+ getInputValue (cell) {
696
+ if (cell.dataIndex.indexOf('@@@') !== -1) {
697
+ return this.config.tempData[cell.dataIndex]
698
+ } else {
699
+ return this.configData[cell.dataIndex]
700
+ }
701
+ },
588
702
  // 路径中含有@@@的key,将其解析,并返回其数据
589
703
  getDeepObject (obj, strPath) {
590
704
  const arr = strPath.split('@@@')
@@ -610,6 +724,69 @@ export default {
610
724
  }
611
725
  this.configData = Object.assign({}, this.configData)
612
726
  },
727
+ // 通用的日期时间选择器变化处理方法
728
+ handleDateTimePickerChange (date, dataIndex, format, arrayIndex = null, cell = null) {
729
+ const dateStr = date ? moment(date).format(format) : ''
730
+
731
+ if (arrayIndex !== null) {
732
+ // 处理动态行中的日期时间选择器
733
+ this.configData.arr[arrayIndex][dataIndex] = dateStr
734
+ } else if (dataIndex.indexOf('@@@') !== -1) {
735
+ // 处理深层嵌套的数据
736
+ this.handleInputDeepChange({ target: { value: dateStr } }, dataIndex, cell)
737
+ } else {
738
+ // 处理普通的数据
739
+ this.configData[dataIndex] = dateStr
740
+ }
741
+
742
+ // 如果字段必填且有值,触发重新渲染以移除错误样式
743
+ if (cell && this.isFieldRequired(cell) && dateStr) {
744
+ this.$forceUpdate()
745
+ }
746
+
747
+ this.configData = Object.assign({}, this.configData)
748
+ },
749
+ // 日期选择器变化处理(YYYY-MM-DD格式)
750
+ handleDatePickerChange (date, dataIndex, cell = null) {
751
+ this.handleDateTimePickerChange(date, dataIndex, 'YYYY-MM-DD', null, cell)
752
+ },
753
+ // 时间选择器变化处理(YYYY-MM-DD HH:mm:ss格式)
754
+ handleTimePickerChange (date, dataIndex, cell = null) {
755
+ this.handleDateTimePickerChange(date, dataIndex, 'YYYY-MM-DD HH:mm:ss', null, cell)
756
+ },
757
+ // 动态行中日期选择器变化处理
758
+ handleArrayDatePickerChange (date, arrayIndex, dataIndex, cell = null) {
759
+ this.handleDateTimePickerChange(date, dataIndex, 'YYYY-MM-DD', arrayIndex, cell)
760
+ },
761
+ // 动态行中时间选择器变化处理
762
+ handleArrayTimePickerChange (date, arrayIndex, dataIndex, cell = null) {
763
+ this.handleDateTimePickerChange(date, dataIndex, 'YYYY-MM-DD HH:mm:ss', arrayIndex, cell)
764
+ },
765
+ // 判断字段是否必填
766
+ isFieldRequired (cell) {
767
+ return cell && cell.required === true
768
+ },
769
+ // 获取必填字段的提示信息
770
+ getRequiredMessage (cell, fieldType) {
771
+ if (cell.requiredMessage) {
772
+ return cell.requiredMessage
773
+ }
774
+
775
+ // 默认提示信息
776
+ const defaultMessages = {
777
+ datePicker: '请选择日期',
778
+ timePicker: '请选择日期时间',
779
+ input: '请填写此字段'
780
+ }
781
+
782
+ return defaultMessages[fieldType] || '此字段为必填项'
783
+ },
784
+ // 格式化日期值,用于日期选择器的value
785
+ formatDateValue (dateStr) {
786
+ if (!dateStr) return null
787
+ const date = moment(dateStr)
788
+ return date.isValid() ? date : null
789
+ },
613
790
  // 反序列化函数并执行
614
791
  deserializeFunctionAndRun (functionStr, value) {
615
792
  // eslint-disable-next-line no-eval
@@ -813,4 +990,16 @@ export default {
813
990
  border-bottom: 1px solid #000;
814
991
  padding: 8px;
815
992
  }
993
+
994
+ .required-field-error {
995
+ border-color: #ff4d4f !important;
996
+ box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.2) !important;
997
+ }
998
+
999
+ .required-message {
1000
+ color: #ff4d4f;
1001
+ font-size: 12px;
1002
+ margin-top: 4px;
1003
+ line-height: 1.2;
1004
+ }
816
1005
  </style>
@@ -36,9 +36,68 @@ export default {
36
36
  ```vue
37
37
  <XReport :config-name="'test_tableConfig'" :activated-slot-name="'test_tableConfig_slot'"/>
38
38
  ```
39
+
40
+ ## 支持的表单项目类型
41
+
42
+ | 类型 | 说明 | 输出格式 | 备注 |
43
+ |-----------------|------|-------|-----------|
44
+ | input | 文本输入框 | 字符串 | 标准文本输入 |
45
+ | datePicker| 日期选择器 | YYYY-MM-DD | 日期格式 |
46
+ | timePicker | 时间选择器 | YYYY-MM-DD HH:mm:ss | 日期时间格式 |
47
+ | curDateInput | 当前日期按钮 | YYYY-MM-DD HH:mm:ss | 点击获取当前时间|
48
+ | signature | 签名框 | 图片URL | 需要在手机端操作|
49
+ | images | 图片上传 | 图片数组 | 支持多图片上传|
50
+
51
+ ## 配置例子
52
+
53
+ ### 日期选择器配置
54
+ ```json
55
+ {
56
+ "type": "datePicker",
57
+ "dataIndex": "selectedDate",
58
+ "inputWidth": "100%",
59
+ "inputReadOnly": false,
60
+ "required": true,
61
+ "requiredMessage": "请选择操作日期"
62
+ }
63
+ ```
64
+
65
+ ### 时间选择器配置
66
+ ```json
67
+ {
68
+ "type": "timePicker",
69
+ "dataIndex": "selectedDateTime",
70
+ "inputWidth": "100%",
71
+ "inputReadOnly": false,
72
+ "required": true,
73
+ "requiredMessage": "请选择具体时间"
74
+ }
75
+ ```
76
+
77
+ ### 必填字段配置说明
78
+
79
+ | 参数 | 说明 | 类型 | 默认值 |
80
+ |-----------------|------|-------|-----------|
81
+ | required | 是否必填 | Boolean | false |
82
+ | requiredMessage| 必填提示信息 | String | 根据字段类型自动生成 |
83
+
84
+ **默认提示信息:**
85
+ - datePicker: "请选择日期"
86
+ - timePicker: "请选择日期时间"
87
+ - input: "请填写此字段"
88
+ - 其他类型: "此字段为必填项"
89
+
90
+ **必填校验特性:**
91
+ - 必填字段为空时会显示红色边框和错误提示
92
+ - 保存时会自动校验所有必填字段,校验失败会阻止保存并显示错误信息
93
+ - 导出数据时会进行校验提醒,但不会阻止导出
94
+
39
95
  ## 注意事项
40
96
 
41
97
  > 在某些情况下,比如手机端,只需要输入表格中一部分的内容。
42
98
  > 可以将这部分内容作为插槽,并在activatedSlotName中填写插槽名。
43
99
  > 则会在设计页面仅展示插槽中的输入项,并在预览窗口中,根据configName中的配置,
44
100
  > 渲染出来完整的表格
101
+
102
+ > 新增的datePicker和timePicker支持在普通行、动态行中使用,
103
+ > 会自动格式化为指定的日期时间格式并保存到配置数据中。支持必填校验功能。