@longhongguo/form-create-ant-design-vue 3.3.18 → 3.3.20

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@longhongguo/form-create-ant-design-vue",
3
- "version": "3.3.18",
3
+ "version": "3.3.20",
4
4
  "description": "AntDesignVue版本低代码表单|FormCreate 是一个可以通过 JSON 生成具有动态渲染、数据收集、验证和提交功能的低代码表单生成组件。支持6个UI框架,适配移动端,并且支持生成任何 Vue 组件。内置20种常用表单组件和自定义组件,再复杂的表单都可以轻松搞定。",
5
5
  "main": "./dist/form-create.min.js",
6
6
  "module": "./dist/form-create.esm.js",
@@ -0,0 +1,305 @@
1
+ <template>
2
+ <a-button
3
+ :type="buttonType"
4
+ :loading="loading"
5
+ :disabled="disabled"
6
+ @click="handleExport"
7
+ >
8
+ <slot>{{ text || '导出Excel' }}</slot>
9
+ </a-button>
10
+ </template>
11
+
12
+ <script>
13
+ import { defineComponent } from 'vue'
14
+ import { message } from 'ant-design-vue'
15
+
16
+ export default defineComponent({
17
+ name: 'ExportExcel',
18
+ props: {
19
+ // form-create 注入的对象
20
+ formCreateInject: {
21
+ type: Object,
22
+ default: null
23
+ },
24
+ // 按钮类型
25
+ buttonType: {
26
+ type: String,
27
+ default: 'primary',
28
+ validator(value) {
29
+ return [
30
+ 'primary',
31
+ 'ghost',
32
+ 'dashed',
33
+ 'link',
34
+ 'text',
35
+ 'default'
36
+ ].includes(value)
37
+ }
38
+ },
39
+ // 按钮文字
40
+ text: {
41
+ type: String,
42
+ default: '导出Excel'
43
+ },
44
+ // 是否禁用
45
+ disabled: {
46
+ type: Boolean,
47
+ default: false
48
+ },
49
+ // 导出接口配置
50
+ action: {
51
+ type: String,
52
+ default: ''
53
+ },
54
+ // 请求方法
55
+ method: {
56
+ type: String,
57
+ default: 'post'
58
+ },
59
+ // 请求数据
60
+ data: {
61
+ type: Object,
62
+ default: () => ({})
63
+ },
64
+ // 查询参数
65
+ query: {
66
+ type: Object,
67
+ default: () => ({})
68
+ },
69
+ // 请求头
70
+ headers: {
71
+ type: Object,
72
+ default: () => ({})
73
+ },
74
+ // 是否携带凭证
75
+ withCredentials: {
76
+ type: Boolean,
77
+ default: false
78
+ },
79
+ // 文件名(如果不提供,将从响应头获取)
80
+ fileName: {
81
+ type: String,
82
+ default: ''
83
+ }
84
+ },
85
+ data() {
86
+ return {
87
+ loading: false
88
+ }
89
+ },
90
+ methods: {
91
+ async handleExport() {
92
+ if (this.loading || this.disabled) {
93
+ return
94
+ }
95
+
96
+ if (!this.action) {
97
+ message.warning('请配置导出接口地址')
98
+ return
99
+ }
100
+
101
+ this.loading = true
102
+
103
+ try {
104
+ // 使用自定义的fetch来处理blob响应
105
+ const result = await this.fetchBlob({
106
+ action: this.action,
107
+ method: this.method,
108
+ data: this.data,
109
+ query: this.query,
110
+ headers: this.headers,
111
+ withCredentials: this.withCredentials
112
+ })
113
+
114
+ // 获取文件名
115
+ let fileName =
116
+ this.fileName || result.fileName || `${new Date().getTime()}.xlsx`
117
+ if (!fileName.endsWith('.xlsx') && !fileName.endsWith('.xls')) {
118
+ fileName = `${fileName}.xlsx`
119
+ }
120
+
121
+ // 导出文件
122
+ this.exportFile(fileName, result.blob)
123
+
124
+ message.success('导出成功')
125
+ } catch (error) {
126
+ console.error('导出失败:', error)
127
+ message.error(error.message || '导出失败,请稍后重试')
128
+ } finally {
129
+ this.loading = false
130
+ }
131
+ },
132
+
133
+ fetchBlob(config) {
134
+ return new Promise((resolve, reject) => {
135
+ const xhr = new XMLHttpRequest()
136
+ let action = config.action || ''
137
+ let fileName = ''
138
+
139
+ // 处理查询参数
140
+ if (config.query) {
141
+ const query =
142
+ typeof config.query === 'string'
143
+ ? config.query
144
+ : Object.keys(config.query).reduce((acc, key) => {
145
+ acc[key] =
146
+ config.query[key] === null ||
147
+ config.query[key] === undefined
148
+ ? ''
149
+ : config.query[key]
150
+ return acc
151
+ }, {})
152
+ const queryString = new URLSearchParams(query).toString()
153
+ if (queryString) {
154
+ if (action.includes('?')) {
155
+ action += `&${queryString}`
156
+ } else {
157
+ action += `?${queryString}`
158
+ }
159
+ }
160
+ }
161
+
162
+ xhr.onerror = () => {
163
+ reject(new Error('请求失败'))
164
+ }
165
+
166
+ xhr.onload = () => {
167
+ if (xhr.status < 200 || xhr.status >= 300) {
168
+ // 检查是否是JSON错误响应
169
+ const contentType = xhr.getResponseHeader('content-type') || ''
170
+ if (contentType.includes('application/json')) {
171
+ // 如果是JSON类型,读取blob内容并解析
172
+ const reader = new FileReader()
173
+ reader.onload = (e) => {
174
+ try {
175
+ const obj = JSON.parse(e.target.result)
176
+ reject(new Error(obj.message || '导出失败'))
177
+ } catch (e) {
178
+ reject(new Error(`导出失败: ${xhr.status}`))
179
+ }
180
+ }
181
+ reader.readAsText(xhr.response)
182
+ return
183
+ } else {
184
+ reject(new Error(`导出失败: ${xhr.status}`))
185
+ }
186
+ return
187
+ }
188
+
189
+ // 获取响应头中的文件名
190
+ const contentDisposition = xhr.getResponseHeader(
191
+ 'content-disposition'
192
+ )
193
+ if (contentDisposition) {
194
+ const fileNameMatch = contentDisposition.match(
195
+ /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/
196
+ )
197
+ if (fileNameMatch && fileNameMatch[1]) {
198
+ fileName = fileNameMatch[1].replace(/['"]/g, '')
199
+ fileName = decodeURIComponent(fileName)
200
+ }
201
+ }
202
+
203
+ // 检查blob的type是否为JSON
204
+ const blob = xhr.response
205
+ if (blob.type && blob.type.includes('application/json')) {
206
+ // 如果是JSON类型,读取并解析
207
+ const reader = new FileReader()
208
+ reader.onload = (e) => {
209
+ try {
210
+ const obj = JSON.parse(e.target.result)
211
+ reject(new Error(obj.message || '导出失败'))
212
+ } catch (e) {
213
+ reject(new Error('导出失败'))
214
+ }
215
+ }
216
+ reader.readAsText(blob)
217
+ return
218
+ }
219
+
220
+ // 返回blob和文件名
221
+ resolve({
222
+ blob: xhr.response,
223
+ fileName: fileName
224
+ })
225
+ }
226
+
227
+ xhr.open(config.method || 'get', action, true)
228
+
229
+ // 设置响应类型为blob
230
+ xhr.responseType = 'blob'
231
+
232
+ // 设置请求头
233
+ try {
234
+ if (window.parent && window.parent.window) {
235
+ const authorizationKey =
236
+ window.parent.window.localStorage.getItem('authorizationKey')
237
+ const authorizationValue =
238
+ window.parent.window.localStorage.getItem('authorizationValue')
239
+ if (authorizationKey && authorizationValue) {
240
+ xhr.setRequestHeader(authorizationKey, authorizationValue)
241
+ }
242
+ } else {
243
+ const authorizationKey =
244
+ window.localStorage.getItem('authorizationKey')
245
+ const authorizationValue =
246
+ window.localStorage.getItem('authorizationValue')
247
+ if (authorizationKey && authorizationValue) {
248
+ xhr.setRequestHeader(authorizationKey, authorizationValue)
249
+ }
250
+ }
251
+ } catch (e) {
252
+ console.warn('获取token失败', e)
253
+ }
254
+
255
+ const headers = config.headers || {}
256
+ Object.keys(headers).forEach((item) => {
257
+ if (headers[item] != null) {
258
+ xhr.setRequestHeader(item, headers[item])
259
+ }
260
+ })
261
+
262
+ if (config.withCredentials && 'withCredentials' in xhr) {
263
+ xhr.withCredentials = true
264
+ }
265
+
266
+ // 处理请求体
267
+ if (config.data) {
268
+ const formData = new FormData()
269
+ Object.keys(config.data).forEach((key) => {
270
+ formData.append(key, config.data[key])
271
+ })
272
+ xhr.send(formData)
273
+ } else {
274
+ xhr.send()
275
+ }
276
+ })
277
+ },
278
+ /**
279
+ * 导出文件
280
+ * @param {string} fileName 文件名
281
+ * @param {Blob} content Blob对象
282
+ */
283
+ exportFile(fileName, content) {
284
+ const blob = new Blob([content], {
285
+ type: 'application/vnd.ms-excel'
286
+ })
287
+
288
+ if ('download' in document.createElement('a')) {
289
+ // 非IE下载
290
+ const elink = document.createElement('a')
291
+ elink.download = fileName
292
+ elink.style.display = 'none'
293
+ elink.href = URL.createObjectURL(blob)
294
+ document.body.appendChild(elink)
295
+ elink.click()
296
+ URL.revokeObjectURL(elink.href) // 释放URL 对象
297
+ document.body.removeChild(elink)
298
+ } else {
299
+ // IE10+下载
300
+ navigator.msSaveBlob(blob, fileName)
301
+ }
302
+ }
303
+ }
304
+ })
305
+ </script>
@@ -214,6 +214,31 @@ export default defineComponent({
214
214
  }
215
215
  },
216
216
  beforeUnmount() {
217
+ // 清理内容变化事件监听器
218
+ if (this._editor && this._editor._fcChangeHandlers) {
219
+ this._editor._fcChangeHandlers.forEach((item) => {
220
+ if (item.type === 'eventHook' && item.handler) {
221
+ // 从事件钩子中移除
222
+ if (
223
+ this._editor.txt &&
224
+ this._editor.txt.eventHooks &&
225
+ this._editor.txt.eventHooks.changeEvents
226
+ ) {
227
+ const index = this._editor.txt.eventHooks.changeEvents.indexOf(
228
+ item.handler
229
+ )
230
+ if (index > -1) {
231
+ this._editor.txt.eventHooks.changeEvents.splice(index, 1)
232
+ }
233
+ }
234
+ } else if (item.element && item.handler) {
235
+ // 移除 DOM 事件监听器
236
+ item.element.removeEventListener(item.event, item.handler, true)
237
+ }
238
+ })
239
+ this._editor._fcChangeHandlers = null
240
+ }
241
+
217
242
  // 清理粘贴事件监听器
218
243
  if (this._pasteHandler && this._pasteHandlerContainer) {
219
244
  this._pasteHandlerContainer.removeEventListener(
@@ -322,6 +347,189 @@ export default defineComponent({
322
347
 
323
348
  // 保存编辑器引用
324
349
  this._editor = editor
350
+ console.log('[FcEditorWrapper] initEditor called', {
351
+ editor,
352
+ hasConfig: !!editor.config
353
+ })
354
+
355
+ // 监听编辑器内容变化,实时触发更新以便校验能及时响应
356
+ // 方法1: 在 config 中设置 onchange(必须在 editor.create() 之前)
357
+ if (editor.config) {
358
+ const originalOnchange = editor.config.onchange
359
+ console.log('[FcEditorWrapper] Setting config.onchange', {
360
+ hasOriginalOnchange: !!originalOnchange
361
+ })
362
+ editor.config.onchange = (html) => {
363
+ console.log('[FcEditorWrapper] config.onchange triggered', {
364
+ html: html ? html.substring(0, 50) + '...' : html
365
+ })
366
+ // 调用原有的 onchange(如果有)
367
+ if (originalOnchange && typeof originalOnchange === 'function') {
368
+ originalOnchange(html)
369
+ }
370
+ // 触发 modelValue 更新,这样 form-create 可以实时触发校验
371
+ console.log('[FcEditorWrapper] Emitting update:modelValue', {
372
+ html: html ? html.substring(0, 50) + '...' : html
373
+ })
374
+ this.$emit('update:modelValue', html)
375
+
376
+ // 尝试触发校验重新执行
377
+ this.$nextTick(() => {
378
+ if (
379
+ this.formCreateInject &&
380
+ this.formCreateInject.api &&
381
+ this.formCreateInject.field
382
+ ) {
383
+ const api = this.formCreateInject.api
384
+ const field = this.formCreateInject.field
385
+ console.log(
386
+ '[FcEditorWrapper] Attempting to trigger validation',
387
+ {
388
+ field,
389
+ hasValidateField: typeof api.validateField === 'function'
390
+ }
391
+ )
392
+
393
+ // 如果值不为空,尝试重新校验该字段
394
+ const isEmpty =
395
+ !html ||
396
+ !html.trim() ||
397
+ html === '<p><br></p>' ||
398
+ html === '<p></p>'
399
+ if (!isEmpty && typeof api.validateField === 'function') {
400
+ // 重新校验字段,这会根据当前值重新执行校验规则
401
+ api.validateField(field).catch(() => {
402
+ // 校验失败是正常的,不需要处理
403
+ })
404
+ console.log(
405
+ '[FcEditorWrapper] validateField called for field:',
406
+ field
407
+ )
408
+ } else if (
409
+ isEmpty &&
410
+ typeof api.clearValidateState === 'function'
411
+ ) {
412
+ // 如果值为空,清除校验状态(但这可能不是我们想要的,因为可能还是需要显示错误)
413
+ // api.clearValidateState([field])
414
+ }
415
+ } else {
416
+ console.log('[FcEditorWrapper] Cannot trigger validation', {
417
+ hasInject: !!this.formCreateInject,
418
+ hasApi: !!(this.formCreateInject && this.formCreateInject.api),
419
+ hasField: !!(
420
+ this.formCreateInject && this.formCreateInject.field
421
+ ),
422
+ injectKeys: this.formCreateInject
423
+ ? Object.keys(this.formCreateInject)
424
+ : []
425
+ })
426
+ }
427
+ })
428
+ }
429
+ }
430
+
431
+ // 方法2: 在编辑器创建后,也监听 DOM 事件作为备用方案
432
+ // 这样可以确保即使 config.onchange 不生效,也能触发更新
433
+ this.$nextTick(() => {
434
+ // 等待编辑器完全创建后再设置
435
+ setTimeout(() => {
436
+ console.log('[FcEditorWrapper] Setting up DOM event listeners', {
437
+ hasEditor: !!editor,
438
+ hasTxt: !!(editor && editor.txt),
439
+ editorId: editor && editor.id
440
+ })
441
+ if (editor && editor.txt) {
442
+ // 获取文本容器
443
+ let textContainer = null
444
+ if (editor.$textElem && editor.$textElem[0]) {
445
+ textContainer = editor.$textElem[0]
446
+ console.log('[FcEditorWrapper] Found textContainer via $textElem')
447
+ } else if (editor.id) {
448
+ const editorEl = document.getElementById(editor.id)
449
+ if (editorEl) {
450
+ textContainer = editorEl.querySelector('.w-e-text')
451
+ console.log(
452
+ '[FcEditorWrapper] Found textContainer via querySelector',
453
+ { editorId: editor.id, found: !!textContainer }
454
+ )
455
+ }
456
+ }
457
+
458
+ if (textContainer) {
459
+ // 监听 input 事件
460
+ const handleInput = () => {
461
+ const html = editor.txt.html()
462
+ console.log(
463
+ '[FcEditorWrapper] DOM event triggered (input/keyup/paste)',
464
+ { html: html ? html.substring(0, 50) + '...' : html }
465
+ )
466
+ this.$emit('update:modelValue', html)
467
+ }
468
+
469
+ // 监听多种事件以确保捕获所有内容变化
470
+ textContainer.addEventListener('input', handleInput)
471
+ textContainer.addEventListener('keyup', handleInput)
472
+ textContainer.addEventListener('paste', handleInput, true)
473
+ console.log('[FcEditorWrapper] Added DOM event listeners', {
474
+ textContainer
475
+ })
476
+
477
+ // 保存事件处理器引用,以便清理
478
+ if (!editor._fcChangeHandlers) {
479
+ editor._fcChangeHandlers = []
480
+ }
481
+ editor._fcChangeHandlers.push({
482
+ element: textContainer,
483
+ event: 'input',
484
+ handler: handleInput
485
+ })
486
+ editor._fcChangeHandlers.push({
487
+ element: textContainer,
488
+ event: 'keyup',
489
+ handler: handleInput
490
+ })
491
+ editor._fcChangeHandlers.push({
492
+ element: textContainer,
493
+ event: 'paste',
494
+ handler: handleInput
495
+ })
496
+
497
+ // 如果编辑器支持事件钩子,也使用它
498
+ if (editor.txt.eventHooks && editor.txt.eventHooks.changeEvents) {
499
+ console.log('[FcEditorWrapper] Found eventHooks.changeEvents', {
500
+ count: editor.txt.eventHooks.changeEvents.length
501
+ })
502
+ const changeHandler = () => {
503
+ const html = editor.txt.html()
504
+ console.log(
505
+ '[FcEditorWrapper] eventHook changeEvents triggered',
506
+ { html: html ? html.substring(0, 50) + '...' : html }
507
+ )
508
+ this.$emit('update:modelValue', html)
509
+ }
510
+ editor.txt.eventHooks.changeEvents.push(changeHandler)
511
+ console.log('[FcEditorWrapper] Added eventHook handler', {
512
+ newCount: editor.txt.eventHooks.changeEvents.length
513
+ })
514
+
515
+ if (!editor._fcChangeHandlers) {
516
+ editor._fcChangeHandlers = []
517
+ }
518
+ editor._fcChangeHandlers.push({
519
+ type: 'eventHook',
520
+ handler: changeHandler
521
+ })
522
+ } else {
523
+ console.log(
524
+ '[FcEditorWrapper] No eventHooks.changeEvents found'
525
+ )
526
+ }
527
+ } else {
528
+ console.warn('[FcEditorWrapper] Could not find textContainer')
529
+ }
530
+ }
531
+ }, 100)
532
+ })
325
533
 
326
534
  // 如果设置了只读模式,配置编辑器为只读
327
535
  // 使用多重延迟和监听,确保编辑器完全创建后再设置
@@ -9,6 +9,7 @@ import CusUserSelect from './CusUserSelect/index.vue'
9
9
  import TableForm from './tableForm/TableForm.vue'
10
10
  import TableFormView from './tableForm/TableFormView.vue'
11
11
  import TableFormColumnView from './tableForm/TableFormColumnView.vue'
12
+ import ExportExcel from './ExportExcel.vue'
12
13
 
13
14
  export default [
14
15
  upload,
@@ -21,5 +22,6 @@ export default [
21
22
  CusUserSelect,
22
23
  TableForm,
23
24
  TableFormView,
24
- TableFormColumnView
25
+ TableFormColumnView,
26
+ ExportExcel
25
27
  ]
package/src/core/alias.js CHANGED
@@ -47,5 +47,6 @@ export default {
47
47
  aCard: PRE + 'Card',
48
48
  Card: PRE + 'Card',
49
49
  accTable: PRE + 'Table',
50
- accTableTable: PRE + 'Table'
50
+ accTableTable: PRE + 'Table',
51
+ exportExcel: 'ExportExcel'
51
52
  }
@@ -1,9 +1,7 @@
1
1
  import { hasProperty } from '@form-create/utils/lib/type'
2
- import { deepCopy } from '@form-create/utils/lib/deepextend'
3
- import { h } from 'vue'
4
2
 
5
3
  // 简单的 getValue 函数,根据路径字符串获取嵌套对象的值
6
- function getValue(obj, path) {
4
+ const getValue = (obj, path) => {
7
5
  if (!obj || !path) return undefined
8
6
  const keys = path.split('.')
9
7
  let value = obj
@@ -0,0 +1,3 @@
1
+ export default {
2
+ name: 'exportExcel'
3
+ }
@@ -21,6 +21,7 @@ import alert from './alert'
21
21
  import card from './card'
22
22
  import accTable from './accTable'
23
23
  import tableForm from './tableForm'
24
+ import exportExcel from './exportExcel'
24
25
 
25
26
  export default [
26
27
  checkbox,
@@ -45,5 +46,6 @@ export default [
45
46
  alert,
46
47
  card,
47
48
  accTable,
48
- tableForm
49
+ tableForm,
50
+ exportExcel
49
51
  ]
@@ -1,4 +1,3 @@
1
1
  export default {
2
2
  name: 'tableForm'
3
3
  }
4
-