@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/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
  }
@@ -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) {
@@ -116,29 +116,61 @@ export default {
116
116
  allRuleProps: Object.keys(rule.props || {})
117
117
  })
118
118
 
119
- // 初始化列配置
120
- if (!hasProperty(props, 'columns')) {
121
- let columns = rule.props?.columns || []
122
- // Struct 组件返回的是数组对象,直接使用;如果是字符串,尝试解析为 JSON(向后兼容)
123
- if (typeof columns === 'string') {
124
- try {
125
- const parsed = JSON.parse(columns)
126
- if (Array.isArray(parsed)) {
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
- // 确保 columns 是数组
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
+ }
@@ -73,26 +73,77 @@ export default {
73
73
  // 这样可以触发 Vue 的响应式更新,即使对象属性被修改了
74
74
  const currentOptions = props.options || []
75
75
 
76
- // 找到 targetOption 在数组中的索引
77
- let targetIndex = -1
78
- if (targetOption) {
79
- targetIndex = currentOptions.findIndex(
80
- (item) =>
81
- item.value === targetOption.value ||
82
- item.id === targetOption.id ||
83
- item === targetOption
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
- const newOptions = currentOptions.map((item, index) => {
88
- // 如果是 targetOption,创建包含所有修改的新对象
89
- if (index === targetIndex && targetOption) {
90
- const newTarget = { ...targetOption }
91
- return newTarget
92
- }
93
- // 其他对象创建新引用(浅拷贝即可)
94
- return { ...item }
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)
@@ -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
  ]
@@ -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
  }