@longhongguo/form-create-ant-design-vue 3.3.4 → 3.3.7
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/auto-import.js +3 -1
- package/dist/form-create.css +432 -0
- package/dist/form-create.esm.css +432 -0
- package/dist/form-create.esm.js +2 -2
- package/dist/form-create.esm.js.map +1 -1
- package/dist/form-create.js +2 -2
- package/dist/form-create.js.map +1 -1
- package/package.json +2 -2
- package/src/components/FcEditorWrapper.vue +618 -2
- package/src/core/alias.js +3 -0
- package/src/core/api.js +26 -1
- package/src/core/manager.js +31 -0
- package/src/parsers/accTable.js +51 -19
- package/src/parsers/alert.js +17 -0
- package/src/parsers/cascader.js +69 -18
- package/src/parsers/index.js +2 -0
- package/src/parsers/input.js +61 -0
- package/src/style/index.css +432 -0
package/src/core/api.js
CHANGED
|
@@ -292,6 +292,31 @@ export default function extendApi(api, h) {
|
|
|
292
292
|
* }
|
|
293
293
|
*/
|
|
294
294
|
$validator: validator,
|
|
295
|
-
$moment: moment
|
|
295
|
+
$moment: moment,
|
|
296
|
+
/**
|
|
297
|
+
* 启用或禁用预览模式
|
|
298
|
+
* 预览模式下:
|
|
299
|
+
* - 表单 form-item 间距调小
|
|
300
|
+
* - 组件边框不展示
|
|
301
|
+
* - 字数限制等提示信息不展示
|
|
302
|
+
* - 必填标记不展示
|
|
303
|
+
*
|
|
304
|
+
* @param {boolean} enabled - 是否启用预览模式,默认为 true
|
|
305
|
+
* @returns {void}
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* // 启用预览模式
|
|
309
|
+
* this.api.previewMode(true)
|
|
310
|
+
*
|
|
311
|
+
* @example
|
|
312
|
+
* // 禁用预览模式
|
|
313
|
+
* this.api.previewMode(false)
|
|
314
|
+
*/
|
|
315
|
+
previewMode(enabled = true) {
|
|
316
|
+
// 更新 options 中的 preview 配置
|
|
317
|
+
api.updateOptions({ preview: !!enabled })
|
|
318
|
+
// 触发刷新,使样式生效
|
|
319
|
+
api.refresh()
|
|
320
|
+
}
|
|
296
321
|
}
|
|
297
322
|
}
|
package/src/core/manager.js
CHANGED
|
@@ -84,6 +84,37 @@ export default {
|
|
|
84
84
|
)
|
|
85
85
|
})
|
|
86
86
|
|
|
87
|
+
// 预览模式下:对 upload 组件设置 disabled
|
|
88
|
+
// wangEditor 组件使用只读模式(readOnly),允许复制和点击链接
|
|
89
|
+
// textarea 组件使用只读模式(readOnly),允许复制和自适应高度
|
|
90
|
+
// CusSelect 相关组件使用预览模式(通过 CSS 阻止交互,不置灰)
|
|
91
|
+
if (this.$handle.preview === true) {
|
|
92
|
+
if (ctx.rule.type === 'upload') {
|
|
93
|
+
if (ctx.prop.props) {
|
|
94
|
+
ctx.prop.props.disabled = true
|
|
95
|
+
}
|
|
96
|
+
} else if (ctx.rule.type === 'fcEditor') {
|
|
97
|
+
// wangEditor 使用只读模式,不设置 disabled
|
|
98
|
+
// 只读模式允许复制和点击链接,但禁止编辑
|
|
99
|
+
if (ctx.prop.props) {
|
|
100
|
+
ctx.prop.props.readOnly = true
|
|
101
|
+
}
|
|
102
|
+
} else if (
|
|
103
|
+
ctx.rule.type === 'input' &&
|
|
104
|
+
ctx.prop.props?.type === 'textarea'
|
|
105
|
+
) {
|
|
106
|
+
// textarea 使用只读模式,不设置 disabled
|
|
107
|
+
// 只读模式允许复制,但禁止编辑
|
|
108
|
+
if (ctx.prop.props) {
|
|
109
|
+
ctx.prop.props.readOnly = true
|
|
110
|
+
// 完全自适应高度,不限制最大高度
|
|
111
|
+
ctx.prop.props.autoSize = true
|
|
112
|
+
// 移除 rows 属性,让 autoSize 完全控制高度
|
|
113
|
+
delete ctx.prop.props.rows
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
87
118
|
// 检查父容器是否是 flex 或 space 类型,如果是,自动设置 col: false 避免被 a-col 包装
|
|
88
119
|
// 需要在合并完 col 后再检查,确保能覆盖默认值
|
|
89
120
|
if (ctx.parent && ctx.parent.rule) {
|
package/src/parsers/accTable.js
CHANGED
|
@@ -116,29 +116,61 @@ export default {
|
|
|
116
116
|
allRuleProps: Object.keys(rule.props || {})
|
|
117
117
|
})
|
|
118
118
|
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
columns = parsed
|
|
128
|
-
}
|
|
129
|
-
} catch (e) {
|
|
130
|
-
console.warn('accTable columns parse error:', e)
|
|
131
|
-
columns = []
|
|
119
|
+
// 处理列配置 - 每次都要更新,确保 width 等属性变化时能生效
|
|
120
|
+
let columns = rule.props?.columns || []
|
|
121
|
+
// Struct 组件返回的是数组对象,直接使用;如果是字符串,尝试解析为 JSON(向后兼容)
|
|
122
|
+
if (typeof columns === 'string') {
|
|
123
|
+
try {
|
|
124
|
+
const parsed = JSON.parse(columns)
|
|
125
|
+
if (Array.isArray(parsed)) {
|
|
126
|
+
columns = parsed
|
|
132
127
|
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (!Array.isArray(columns)) {
|
|
128
|
+
} catch (e) {
|
|
129
|
+
console.warn('accTable columns parse error:', e)
|
|
136
130
|
columns = []
|
|
137
131
|
}
|
|
138
|
-
// 过滤掉 hidden 为 true 的列
|
|
139
|
-
columns = columns.filter((col) => !col.hidden)
|
|
140
|
-
props.columns = columns
|
|
141
132
|
}
|
|
133
|
+
// 确保 columns 是数组
|
|
134
|
+
if (!Array.isArray(columns)) {
|
|
135
|
+
columns = []
|
|
136
|
+
}
|
|
137
|
+
// 过滤掉 hidden 为 true 的列,同时确保所有其他属性(如 width、align、fixed 等)都被保留
|
|
138
|
+
const processedColumns = columns
|
|
139
|
+
.filter((col) => !col || !col.hidden)
|
|
140
|
+
.map((col) => {
|
|
141
|
+
// 创建列对象的副本,确保所有属性都被保留(包括 width、align、fixed 等)
|
|
142
|
+
const column = { ...col }
|
|
143
|
+
|
|
144
|
+
// 处理 width 属性:确保它能够正确传递
|
|
145
|
+
// Ant Design Vue Table 支持 width 为 number 或 string
|
|
146
|
+
// 如果 width 是纯数字字符串(如 "100"),可以转换为数字以便更好的性能
|
|
147
|
+
// 如果 width 是带单位的字符串(如 "100px"),保持字符串
|
|
148
|
+
if (
|
|
149
|
+
column.width !== undefined &&
|
|
150
|
+
column.width !== null &&
|
|
151
|
+
column.width !== ''
|
|
152
|
+
) {
|
|
153
|
+
if (typeof column.width === 'string') {
|
|
154
|
+
// 如果是纯数字字符串,转换为数字
|
|
155
|
+
const numValue = Number(column.width)
|
|
156
|
+
if (
|
|
157
|
+
!isNaN(numValue) &&
|
|
158
|
+
isFinite(numValue) &&
|
|
159
|
+
column.width.trim() === String(numValue)
|
|
160
|
+
) {
|
|
161
|
+
column.width = numValue
|
|
162
|
+
}
|
|
163
|
+
// 如果不是纯数字(如 "100px"),保持字符串
|
|
164
|
+
}
|
|
165
|
+
// 如果已经是数字,直接使用
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 确保其他列属性也被保留(align、fixed、title、dataIndex 等)
|
|
169
|
+
return column
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
// 始终更新 props.columns,确保列配置变化(包括 width)时能生效
|
|
173
|
+
props.columns = processedColumns
|
|
142
174
|
|
|
143
175
|
// 初始化数据源
|
|
144
176
|
if (!hasProperty(props, 'dataSource')) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
name: 'aAlert',
|
|
3
|
+
// Alert 是非输入组件,不需要 modelField
|
|
4
|
+
mergeProp(ctx) {
|
|
5
|
+
const props = ctx.prop.props || {}
|
|
6
|
+
// 如果没有 message 但有 description,将 description 作为 message
|
|
7
|
+
// 这样 Alert 会显示为小的样式(只有 message,没有 description)
|
|
8
|
+
if ((!props.message || props.message === '') && props.description) {
|
|
9
|
+
props.message = props.description
|
|
10
|
+
delete props.description
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
render(children, ctx) {
|
|
14
|
+
// 使用默认渲染,通过 alias 映射到 aAlert 组件
|
|
15
|
+
return ctx.$render.defaultRender(ctx, children)
|
|
16
|
+
}
|
|
17
|
+
}
|
package/src/parsers/cascader.js
CHANGED
|
@@ -73,26 +73,77 @@ export default {
|
|
|
73
73
|
// 这样可以触发 Vue 的响应式更新,即使对象属性被修改了
|
|
74
74
|
const currentOptions = props.options || []
|
|
75
75
|
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
item
|
|
83
|
-
item
|
|
84
|
-
|
|
76
|
+
// 递归查找并更新选项的函数
|
|
77
|
+
// 通过 value 路径来查找,避免引用问题
|
|
78
|
+
const updateOptionRecursive = (optionsList, pathValues, depth, newChildren) => {
|
|
79
|
+
if (!pathValues || pathValues.length === 0) {
|
|
80
|
+
// 如果没有路径,直接返回深拷贝的选项
|
|
81
|
+
return optionsList.map(item => ({
|
|
82
|
+
...item,
|
|
83
|
+
children: item.children ? item.children.map(child => ({ ...child })) : undefined
|
|
84
|
+
}))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const currentValue = pathValues[depth]
|
|
88
|
+
const isLast = depth === pathValues.length - 1
|
|
89
|
+
|
|
90
|
+
return optionsList.map(item => {
|
|
91
|
+
const newItem = { ...item }
|
|
92
|
+
|
|
93
|
+
// 如果当前项匹配路径的当前层级值
|
|
94
|
+
if ((item.value !== undefined && item.value === currentValue) ||
|
|
95
|
+
(item.id !== undefined && item.id === currentValue)) {
|
|
96
|
+
if (isLast) {
|
|
97
|
+
// 是最后一个值(目标项),更新 children
|
|
98
|
+
newItem.children = newChildren ? [...newChildren] : undefined
|
|
99
|
+
newItem.loading = false
|
|
100
|
+
// 保持其他属性不变,但深拷贝 children
|
|
101
|
+
return newItem
|
|
102
|
+
} else {
|
|
103
|
+
// 不是最后一个,需要递归更新其 children
|
|
104
|
+
if (item.children && item.children.length > 0) {
|
|
105
|
+
// 递归更新子项
|
|
106
|
+
newItem.children = updateOptionRecursive(
|
|
107
|
+
item.children,
|
|
108
|
+
pathValues,
|
|
109
|
+
depth + 1,
|
|
110
|
+
newChildren
|
|
111
|
+
)
|
|
112
|
+
} else {
|
|
113
|
+
// 如果没有 children,保持 undefined
|
|
114
|
+
newItem.children = undefined
|
|
115
|
+
}
|
|
116
|
+
return newItem
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 不匹配的项,也需要递归处理其 children(以防路径在其他分支)
|
|
121
|
+
if (item.children && item.children.length > 0) {
|
|
122
|
+
newItem.children = updateOptionRecursive(
|
|
123
|
+
item.children,
|
|
124
|
+
pathValues,
|
|
125
|
+
depth,
|
|
126
|
+
newChildren
|
|
127
|
+
)
|
|
128
|
+
} else {
|
|
129
|
+
// 如果没有 children,深拷贝基本结构
|
|
130
|
+
newItem.children = undefined
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return newItem
|
|
134
|
+
})
|
|
85
135
|
}
|
|
86
136
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
137
|
+
// 从 selectedOptions 中提取 value 路径
|
|
138
|
+
const pathValues = selectedOptions.map(opt => opt.value || opt.id)
|
|
139
|
+
|
|
140
|
+
// 使用递归函数更新选项(从第0层开始)
|
|
141
|
+
const newOptions = updateOptionRecursive(
|
|
142
|
+
currentOptions,
|
|
143
|
+
pathValues,
|
|
144
|
+
0,
|
|
145
|
+
targetOption ? targetOption.children : undefined
|
|
146
|
+
)
|
|
96
147
|
|
|
97
148
|
// 完全按照 effect.fetch 的方式:使用 deepSet 在响应式对象上设置值
|
|
98
149
|
// effect.fetch 使用: deepSet(inject.getProp(), 'props.options', val)
|
package/src/parsers/index.js
CHANGED
|
@@ -17,6 +17,7 @@ import flex from './flex'
|
|
|
17
17
|
import space from './space'
|
|
18
18
|
import spin from './spin'
|
|
19
19
|
import div from './div'
|
|
20
|
+
import alert from './alert'
|
|
20
21
|
import accTable from './accTable'
|
|
21
22
|
|
|
22
23
|
export default [
|
|
@@ -39,5 +40,6 @@ export default [
|
|
|
39
40
|
space,
|
|
40
41
|
spin,
|
|
41
42
|
div,
|
|
43
|
+
alert,
|
|
42
44
|
accTable
|
|
43
45
|
]
|
package/src/parsers/input.js
CHANGED
|
@@ -26,5 +26,66 @@ export default {
|
|
|
26
26
|
password: 'aInputPassword'
|
|
27
27
|
}[type] || 'aInput'
|
|
28
28
|
return ctx.$render.vNode.make(type, ctx.prop, children)
|
|
29
|
+
},
|
|
30
|
+
mounted(ctx) {
|
|
31
|
+
// 预览模式下,为 textarea 设置自适应高度
|
|
32
|
+
if (
|
|
33
|
+
ctx.prop.props?.type === 'textarea' &&
|
|
34
|
+
ctx.prop.props?.readOnly &&
|
|
35
|
+
ctx.$handle?.preview === true
|
|
36
|
+
) {
|
|
37
|
+
const adjustTextareaHeight = (textareaEl) => {
|
|
38
|
+
if (!textareaEl) return
|
|
39
|
+
// 重置高度,然后设置为 scrollHeight
|
|
40
|
+
textareaEl.style.height = 'auto'
|
|
41
|
+
const scrollHeight = textareaEl.scrollHeight
|
|
42
|
+
if (scrollHeight > 0) {
|
|
43
|
+
textareaEl.style.height = scrollHeight + 'px'
|
|
44
|
+
}
|
|
45
|
+
textareaEl.style.overflow = 'hidden'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 延迟执行,确保 DOM 已完全渲染
|
|
49
|
+
setTimeout(() => {
|
|
50
|
+
const el = ctx.el
|
|
51
|
+
if (!el) return
|
|
52
|
+
|
|
53
|
+
// 查找 textarea 元素
|
|
54
|
+
const textarea =
|
|
55
|
+
el.querySelector('textarea.ant-input') || el.querySelector('textarea')
|
|
56
|
+
if (!textarea) return
|
|
57
|
+
|
|
58
|
+
// 首次调整高度
|
|
59
|
+
adjustTextareaHeight(textarea)
|
|
60
|
+
|
|
61
|
+
// 使用 MutationObserver 监听内容变化
|
|
62
|
+
if (typeof MutationObserver !== 'undefined') {
|
|
63
|
+
const observer = new MutationObserver(() => {
|
|
64
|
+
adjustTextareaHeight(textarea)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
observer.observe(textarea, {
|
|
68
|
+
attributes: true,
|
|
69
|
+
attributeFilter: ['value'],
|
|
70
|
+
childList: true,
|
|
71
|
+
subtree: true,
|
|
72
|
+
characterData: true
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
// 定期检查并调整(备用方案)
|
|
76
|
+
const intervalId = setInterval(() => {
|
|
77
|
+
adjustTextareaHeight(textarea)
|
|
78
|
+
}, 200)
|
|
79
|
+
|
|
80
|
+
// 在组件销毁时清理
|
|
81
|
+
if (ctx.$handle && ctx.$handle.vm) {
|
|
82
|
+
ctx.$handle.vm.$once('hook:beforeUnmount', () => {
|
|
83
|
+
observer.disconnect()
|
|
84
|
+
clearInterval(intervalId)
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}, 100)
|
|
89
|
+
}
|
|
29
90
|
}
|
|
30
91
|
}
|