@aspire-ui/element-component-pro 1.0.25 → 1.0.26
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/dist/ProTableForm/CellEditor.vue.d.ts +29 -0
- package/dist/ProTableForm/ProTableForm.vue.d.ts +122 -102
- package/dist/ProTableForm/index.d.ts +4 -2
- package/dist/ProTableForm/types.d.ts +1 -69
- package/dist/ProTableForm/useProTableForm.d.ts +55 -0
- package/dist/element-component-pro.es.js +1430 -1254
- package/dist/element-component-pro.es.js.map +1 -1
- package/dist/element-component-pro.umd.js +2 -2
- package/dist/element-component-pro.umd.js.map +1 -1
- package/dist/index.d.ts +445 -282
- package/dist/style.css +1 -1
- package/dist/types/index.d.ts +222 -0
- package/package.json +1 -1
- package/src/ProTable/ProTable.vue +0 -1
- package/src/ProTableForm/CellEditor.vue +188 -0
- package/src/ProTableForm/ProTableForm.vue +348 -452
- package/src/ProTableForm/TableFormCell.vue +46 -0
- package/src/ProTableForm/index.ts +6 -7
- package/src/ProTableForm/types.ts +12 -72
- package/src/ProTableForm/useProTableForm.ts +442 -0
- package/src/index.ts +6 -4
- package/src/types/index.ts +235 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-form-item
|
|
3
|
+
:prop="prop"
|
|
4
|
+
class="ecp-pro-table-form__cell-item"
|
|
5
|
+
>
|
|
6
|
+
<!-- slot 列:完全透传给父级 -->
|
|
7
|
+
<slot
|
|
8
|
+
v-if="col.component === 'slot' && col.slotName"
|
|
9
|
+
:name="'cell-' + col.slotName"
|
|
10
|
+
:column="col"
|
|
11
|
+
:row="row"
|
|
12
|
+
:value="value"
|
|
13
|
+
:update-value="updateValue"
|
|
14
|
+
/>
|
|
15
|
+
<!-- 内置组件 -->
|
|
16
|
+
<component
|
|
17
|
+
v-else
|
|
18
|
+
:is="cellComponent(col)"
|
|
19
|
+
:value="value"
|
|
20
|
+
v-bind="cellBind(col)"
|
|
21
|
+
:placeholder="col.placeholder || placeholder"
|
|
22
|
+
@input="updateValue"
|
|
23
|
+
/>
|
|
24
|
+
</el-form-item>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup lang="ts">
|
|
28
|
+
import type { ProTableFormColumn } from './types'
|
|
29
|
+
|
|
30
|
+
interface Props {
|
|
31
|
+
col: ProTableFormColumn
|
|
32
|
+
row: TableRow
|
|
33
|
+
prop: string
|
|
34
|
+
value: unknown
|
|
35
|
+
updateValue: (v: unknown) => void
|
|
36
|
+
cellComponent: (col: ProTableFormColumn) => unknown
|
|
37
|
+
cellBind: (col: ProTableFormColumn) => Record<string, unknown>
|
|
38
|
+
placeholder?: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
defineProps<Props>()
|
|
42
|
+
|
|
43
|
+
type TableRow =
|
|
44
|
+
| { _type: 'fixed'; rowKey: string; rowLabel: string }
|
|
45
|
+
| { _type: 'competitor'; _index: number }
|
|
46
|
+
</script>
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import ProTableForm from './ProTableForm.vue'
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} from './types'
|
|
2
|
+
import { useProTableForm } from './useProTableForm'
|
|
3
|
+
|
|
4
|
+
export { ProTableForm, useProTableForm }
|
|
5
|
+
export type { ProTableFormColumn, ProTableFormColumnChild } from '../types'
|
|
6
|
+
export type { ProTableFormBuiltInComponent, ProTableFormProps, ProTableFormActionType } from '../types'
|
|
7
|
+
|
|
9
8
|
export default ProTableForm
|
|
@@ -1,72 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* component 为 slot 时必填,对应插槽名为 `cell-{slotName}`(如 slotName: 'score' → #cell-score)
|
|
15
|
-
*/
|
|
16
|
-
slotName?: string
|
|
17
|
-
placeholder?: string
|
|
18
|
-
width?: number
|
|
19
|
-
minWidth?: number
|
|
20
|
-
/**
|
|
21
|
-
* 透传给单元格组件。`component === 'formatted-number'` 时在此传入
|
|
22
|
-
* `integerDigits`、`decimalPlaces`、`rounding`、`inputLimit` 等(与 FormattedNumberInput / ProForm 一致)。
|
|
23
|
-
*/
|
|
24
|
-
componentProps?: Record<string, unknown>
|
|
25
|
-
/** 覆盖该列默认必填规则(与 Element 表单 rules 一致) */
|
|
26
|
-
rules?: unknown[]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** 固定行(首列为文案,不可删) */
|
|
30
|
-
export interface ProTableFormFixedRow {
|
|
31
|
-
rowKey: string
|
|
32
|
-
label: string
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** 操作列配置(showActionColumn 为 true 时生效) */
|
|
36
|
-
export interface ProTableFormActionColumn {
|
|
37
|
-
width?: number
|
|
38
|
-
minWidth?: number
|
|
39
|
-
align?: 'left' | 'center' | 'right'
|
|
40
|
-
fixed?: boolean | 'left' | 'right'
|
|
41
|
-
/** 表头文案,使用默认「新增」按钮时显示在按钮左侧,可留空 */
|
|
42
|
-
title?: string
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface ProTableFormProps {
|
|
46
|
-
modelValue?: Record<string, unknown>
|
|
47
|
-
columns: ProTableFormColumn[]
|
|
48
|
-
fixedRows: ProTableFormFixedRow[]
|
|
49
|
-
competitorsKey?: string
|
|
50
|
-
/** 友商行「名称」字段,默认 name */
|
|
51
|
-
competitorNameKey?: string
|
|
52
|
-
firstColumnTitle?: string
|
|
53
|
-
competitorNamePlaceholder?: string
|
|
54
|
-
metricPlaceholder?: string
|
|
55
|
-
addCompetitorText?: string
|
|
56
|
-
minCompetitors?: number
|
|
57
|
-
rules?: Record<string, unknown>
|
|
58
|
-
labelWidth?: string
|
|
59
|
-
bordered?: boolean
|
|
60
|
-
firstColMinWidth?: number
|
|
61
|
-
/**
|
|
62
|
-
* 是否展示首列(维度/友商)。存在 fixedRows 时强制为 true。
|
|
63
|
-
* 无固定行且为 false 时隐藏首列,需在 columns 中自行配置名称等字段。
|
|
64
|
-
*/
|
|
65
|
-
showFirstColumn?: boolean
|
|
66
|
-
/** 是否展示操作列,默认 true */
|
|
67
|
-
showActionColumn?: boolean
|
|
68
|
-
/** 操作列宽度(showActionColumn 时) */
|
|
69
|
-
actionWidth?: number
|
|
70
|
-
/** 操作列详细配置 */
|
|
71
|
-
actionColumn?: ProTableFormActionColumn
|
|
72
|
-
}
|
|
1
|
+
// Re-export all ProTableForm types from the centralized types registry
|
|
2
|
+
export type {
|
|
3
|
+
ProTableFormColumn,
|
|
4
|
+
ProTableFormColumnChild,
|
|
5
|
+
ProTableFormActionColumn,
|
|
6
|
+
ProTableFormProps,
|
|
7
|
+
ProTableFormBuiltInComponent,
|
|
8
|
+
TableActionButton,
|
|
9
|
+
TableActionRender,
|
|
10
|
+
ProTableFormActionType,
|
|
11
|
+
ProTableFormRowType,
|
|
12
|
+
} from '../types'
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import { computed, ref } from 'vue'
|
|
2
|
+
import type { Ref, ComputedRef } from 'vue'
|
|
3
|
+
import type {
|
|
4
|
+
ProTableFormActionType,
|
|
5
|
+
ProTableFormColumn,
|
|
6
|
+
ProTableFormColumnChild,
|
|
7
|
+
ProTableFormProps,
|
|
8
|
+
FormInstance,
|
|
9
|
+
} from '../types'
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Types
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
export interface ProTableFormOptions {
|
|
16
|
+
props: Readonly<ProTableFormProps>
|
|
17
|
+
emit: (event: 'update:modelValue', value: Record<string, unknown>[]) => void
|
|
18
|
+
/** 新增行事件 */
|
|
19
|
+
emitAddRow?: () => void
|
|
20
|
+
/** 删除行事件 */
|
|
21
|
+
emitRemoveRow?: (index: number) => void
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** useProTableForm 返回 */
|
|
25
|
+
export interface UseProTableFormReturn {
|
|
26
|
+
// --- 注册 ---
|
|
27
|
+
register: (formAction: ProTableFormActionType) => void
|
|
28
|
+
|
|
29
|
+
// --- el-form 绑定 ---
|
|
30
|
+
currentModelValue: ComputedRef<Record<string, unknown>[]>
|
|
31
|
+
formModelRef: ComputedRef<{ rows: Record<string, unknown>[] }>
|
|
32
|
+
mergedRules: ComputedRef<Record<string, unknown[]>>
|
|
33
|
+
|
|
34
|
+
// --- el-table 绑定 ---
|
|
35
|
+
tableRows: ComputedRef<TableRow[]>
|
|
36
|
+
rowKeyFn: (row: TableRow) => string
|
|
37
|
+
spanMethodAdapter: (params: {
|
|
38
|
+
row: TableRow
|
|
39
|
+
column: { property: string; label: string }
|
|
40
|
+
rowIndex: number
|
|
41
|
+
columnIndex: number
|
|
42
|
+
}) => [number, number] | { rowspan: number; colspan: number } | void
|
|
43
|
+
allLeafColumnKeys: ComputedRef<string[]>
|
|
44
|
+
|
|
45
|
+
// --- ref ---
|
|
46
|
+
formRef: Ref<FormInstance | null>
|
|
47
|
+
/** 获取 el-form 实例 */
|
|
48
|
+
getFormRef: () => FormInstance | null
|
|
49
|
+
|
|
50
|
+
// --- 单元格渲染辅助 ---
|
|
51
|
+
cellComponent: (col: ProTableFormColumn | ProTableFormColumnChild) => unknown
|
|
52
|
+
cellBind: (col: ProTableFormColumn | ProTableFormColumnChild) => Record<string, unknown>
|
|
53
|
+
slotUpdateHandler: (slotProps: { row: TableRow }, col: ProTableFormColumn, childKey?: string) => (v: unknown) => void
|
|
54
|
+
getCellProp: (tableRow: TableRow, col: ProTableFormColumn, childKey?: string) => string
|
|
55
|
+
|
|
56
|
+
// --- 单元格值读写 ---
|
|
57
|
+
getCellValue: (row: TableRow, col: ProTableFormColumn, childKey?: string) => unknown
|
|
58
|
+
setCellValue: (tableRow: TableRow, col: ProTableFormColumn, childKey: string | undefined, val: unknown) => void
|
|
59
|
+
|
|
60
|
+
// --- 行操作 ---
|
|
61
|
+
handleAddRow: () => void
|
|
62
|
+
handleRemoveRow: (index: number) => void
|
|
63
|
+
|
|
64
|
+
// --- el-form 实例方法 ---
|
|
65
|
+
validate: () => Promise<boolean>
|
|
66
|
+
clearValidate: (propsArg?: string | string[]) => void
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Composable
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
type TableRow = { _index: number }
|
|
74
|
+
|
|
75
|
+
export function useProTableForm(options: ProTableFormOptions): UseProTableFormReturn {
|
|
76
|
+
const { props, emit, emitAddRow, emitRemoveRow } = options
|
|
77
|
+
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// 注册机制(与 useForm 一致)
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
const formActionRef = ref<ProTableFormActionType | null>(null)
|
|
83
|
+
|
|
84
|
+
const register = (action: ProTableFormActionType) => {
|
|
85
|
+
formActionRef.value = action
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// el-form ref
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
const formRef = ref<FormInstance | null>(null)
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// 受控 / 非受控双轨(与 ProForm 一致)
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
const controlledModelValue = computed(() => props.modelValue)
|
|
99
|
+
const isControlled = computed(() => controlledModelValue.value !== undefined)
|
|
100
|
+
const currentModelValue = computed<Record<string, unknown>[]>(() => {
|
|
101
|
+
return isControlled.value ? (controlledModelValue.value ?? []) : []
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 传给 el-form :model 的包装对象。
|
|
106
|
+
* el-form 要求 model 为 Object,直接传入数组会触发 Element UI 的 prop type 校验报错。
|
|
107
|
+
* 这里包装为 { rows: [...] },el-form-item 的 prop 路径相应改为 rows.${index}.${key}。
|
|
108
|
+
*/
|
|
109
|
+
const formModelRef = computed(() => ({ rows: currentModelValue.value }))
|
|
110
|
+
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Table rows
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
const tableRows = computed<TableRow[]>(() => {
|
|
116
|
+
return currentModelValue.value.map((item, i) => ({ ...item, _index: i }))
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
function rowKeyFn(row: TableRow) {
|
|
120
|
+
return `r-${row._index}`
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// 工具函数
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
function cloneRows(): Record<string, unknown>[] {
|
|
128
|
+
return JSON.parse(JSON.stringify(currentModelValue.value)) as Record<string, unknown>[]
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function emptyRow(): Record<string, unknown> {
|
|
132
|
+
const o: Record<string, unknown> = {}
|
|
133
|
+
for (const c of props.columns) {
|
|
134
|
+
if (c.children && c.children.length > 0) {
|
|
135
|
+
for (const child of c.children) {
|
|
136
|
+
o[child.key] = ''
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
o[c.key] = ''
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return o
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// Value getters / setters
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
function emitNext(next: Record<string, unknown>[]) {
|
|
150
|
+
emit('update:modelValue', next)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function getCell(row: TableRow): Record<string, unknown> {
|
|
154
|
+
return currentModelValue.value[row._index] ?? {}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function getCellValue(row: TableRow, col: ProTableFormColumn, childKey?: string): unknown {
|
|
158
|
+
if (childKey) {
|
|
159
|
+
const colVal = getCell(row)[col.key]
|
|
160
|
+
// Support both nested ({ economy: { costReduction: '' } }) and flat ({ costReduction: '' }) models
|
|
161
|
+
if (colVal && typeof colVal === 'object') {
|
|
162
|
+
return (colVal as Record<string, unknown>)[childKey] ?? ''
|
|
163
|
+
}
|
|
164
|
+
return getCell(row)[childKey] ?? ''
|
|
165
|
+
}
|
|
166
|
+
return getCell(row)[col.key] ?? ''
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function setCellValue(row: TableRow, col: ProTableFormColumn, childKey: string | undefined, val: unknown) {
|
|
170
|
+
const next = cloneRows()
|
|
171
|
+
const target = { ...next[row._index] }
|
|
172
|
+
if (childKey) {
|
|
173
|
+
const colVal = target[col.key]
|
|
174
|
+
// Support both nested and flat models for grouped columns
|
|
175
|
+
if (colVal && typeof colVal === 'object') {
|
|
176
|
+
const nested = { ...(colVal as Record<string, unknown>) }
|
|
177
|
+
nested[childKey] = val
|
|
178
|
+
target[col.key] = nested
|
|
179
|
+
} else {
|
|
180
|
+
target[childKey] = val
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
target[col.key] = val
|
|
184
|
+
}
|
|
185
|
+
next[row._index] = target
|
|
186
|
+
emitNext(next)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
// Row operations
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
|
|
193
|
+
function handleAddRow() {
|
|
194
|
+
emitAddRow?.()
|
|
195
|
+
const next = cloneRows()
|
|
196
|
+
next.push(emptyRow())
|
|
197
|
+
emitNext(next)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function handleRemoveRow(index: number) {
|
|
201
|
+
if (!canDeleteRow.value) return
|
|
202
|
+
emitRemoveRow?.(index)
|
|
203
|
+
const next = cloneRows()
|
|
204
|
+
next.splice(index, 1)
|
|
205
|
+
emitNext(next)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const canDeleteRow = computed(() => {
|
|
209
|
+
return currentModelValue.value.length > (props.minRows ?? 0)
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// Prop paths
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
function getCellProp(tableRow: TableRow, col: ProTableFormColumn, childKey?: string): string {
|
|
217
|
+
// Use the leaf key directly so the prop path matches the flat data model
|
|
218
|
+
// (e.g. rows.0.costReduction, not rows.0.economy.costReduction)
|
|
219
|
+
return childKey
|
|
220
|
+
? `rows.${tableRow._index}.${childKey}`
|
|
221
|
+
: `rows.${tableRow._index}.${col.key}`
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
// Validation rules
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
|
|
228
|
+
// Stable rules object — we mutate it in place and only replace the reference
|
|
229
|
+
// when the top-level keys or their array identities actually change.
|
|
230
|
+
const rulesResult: Record<string, unknown[]> = {}
|
|
231
|
+
|
|
232
|
+
/** Signature of a rules array (compares content, not function identity) */
|
|
233
|
+
function rulesSig(arr: unknown[]): string {
|
|
234
|
+
return JSON.stringify(arr.map((r) => {
|
|
235
|
+
if (r == null || typeof r !== 'object') return String(r)
|
|
236
|
+
const o = r as Record<string, unknown>
|
|
237
|
+
return {
|
|
238
|
+
required: o.required,
|
|
239
|
+
type: o.type,
|
|
240
|
+
message: o.message,
|
|
241
|
+
pattern: String(o.pattern ?? ''),
|
|
242
|
+
min: o.min,
|
|
243
|
+
max: o.max,
|
|
244
|
+
trigger: Array.isArray(o.trigger) ? [...o.trigger] : o.trigger,
|
|
245
|
+
hasValidator: typeof o.validator === 'function',
|
|
246
|
+
hasAsyncValidator: typeof o.asyncValidator === 'function',
|
|
247
|
+
}
|
|
248
|
+
}))
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const mergedRules = computed(() => {
|
|
252
|
+
const req = (title: string): unknown[] => [
|
|
253
|
+
{ required: true, message: `请输入${title}`, trigger: 'change' },
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
let anyChange = false
|
|
257
|
+
|
|
258
|
+
// Build the new rules snapshot
|
|
259
|
+
const newSnapshot: Record<string, unknown[]> = {}
|
|
260
|
+
|
|
261
|
+
currentModelValue.value.forEach((row, i) => {
|
|
262
|
+
for (const col of props.columns) {
|
|
263
|
+
const process = (key: string, colOrChild: ProTableFormColumn | ProTableFormColumnChild, value: unknown) => {
|
|
264
|
+
const path = `rows.${i}.${key}`
|
|
265
|
+
let rules: unknown[] = []
|
|
266
|
+
|
|
267
|
+
if ('dynamicRules' in colOrChild && colOrChild.dynamicRules !== undefined) {
|
|
268
|
+
rules = typeof colOrChild.dynamicRules === 'function'
|
|
269
|
+
? colOrChild.dynamicRules({ row, value, column: colOrChild as never }) as unknown[]
|
|
270
|
+
: (colOrChild.dynamicRules as unknown[])
|
|
271
|
+
} else if ('rules' in colOrChild && colOrChild.rules !== undefined) {
|
|
272
|
+
rules = colOrChild.rules as unknown[]
|
|
273
|
+
} else if (colOrChild.required) {
|
|
274
|
+
rules = req(colOrChild.title)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Only create a new array reference if content actually changed
|
|
278
|
+
const sig = rulesSig(rules)
|
|
279
|
+
const prevArr = rulesResult[path]
|
|
280
|
+
const prevSig = (prevArr as unknown as { __sig?: string } & unknown[])?.__sig
|
|
281
|
+
const arr = (prevSig === sig && prevArr) ? prevArr : rules
|
|
282
|
+
;(arr as unknown as { __sig: string } & unknown[]).__sig = sig
|
|
283
|
+
newSnapshot[path] = arr
|
|
284
|
+
if (arr !== prevArr) anyChange = true
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (col.children && col.children.length > 0) {
|
|
288
|
+
for (const child of col.children) {
|
|
289
|
+
const value = getCellValue({ _index: i } as TableRow, col, child.key)
|
|
290
|
+
process(child.key, child, value)
|
|
291
|
+
}
|
|
292
|
+
} else {
|
|
293
|
+
const value = getCellValue({ _index: i } as TableRow, col)
|
|
294
|
+
process(col.key, col, value)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
// Check props.rules keys
|
|
300
|
+
const extraRules = props.rules || {}
|
|
301
|
+
for (const key of Object.keys(extraRules)) {
|
|
302
|
+
const arr = extraRules[key] as unknown[]
|
|
303
|
+
newSnapshot[key] = arr
|
|
304
|
+
if (arr !== rulesResult[key]) anyChange = true
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Only replace rulesResult when something actually changed
|
|
308
|
+
if (anyChange || Object.keys(newSnapshot).length !== Object.keys(rulesResult).length) {
|
|
309
|
+
Object.keys(rulesResult).forEach((k) => { delete rulesResult[k] })
|
|
310
|
+
Object.assign(rulesResult, newSnapshot)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return rulesResult as Record<string, unknown[]>
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
// ---------------------------------------------------------------------------
|
|
317
|
+
// el-form 实例方法
|
|
318
|
+
// ---------------------------------------------------------------------------
|
|
319
|
+
|
|
320
|
+
function validate(): Promise<boolean> {
|
|
321
|
+
return new Promise((resolve) => {
|
|
322
|
+
const f = formRef.value
|
|
323
|
+
if (!f || typeof f.validate !== 'function') {
|
|
324
|
+
resolve(true)
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
f.validate((valid: boolean) => resolve(valid))
|
|
328
|
+
})
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function clearValidate(propsArg?: string | string[]) {
|
|
332
|
+
formRef.value?.clearValidate?.(propsArg)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ---------------------------------------------------------------------------
|
|
336
|
+
// Cell rendering helpers
|
|
337
|
+
// ---------------------------------------------------------------------------
|
|
338
|
+
|
|
339
|
+
function cellComponent(col: ProTableFormColumn | ProTableFormColumnChild): unknown {
|
|
340
|
+
return col.component === 'formatted-number' ? 'ecp-formatted-number-input' : 'el-input'
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function cellBind(col: ProTableFormColumn | ProTableFormColumnChild): Record<string, unknown> {
|
|
344
|
+
const cp = col.componentProps || {}
|
|
345
|
+
// Separate event handlers (onXxx) from regular props, so they don't get v-bound
|
|
346
|
+
// as fake attrs and bypass the @update flow. Match Vue camelCase convention.
|
|
347
|
+
const result: Record<string, unknown> = {}
|
|
348
|
+
for (const [key, val] of Object.entries(cp)) {
|
|
349
|
+
if (/^on[A-Za-z]/.test(key) && typeof val === 'function') continue
|
|
350
|
+
result[key] = val
|
|
351
|
+
}
|
|
352
|
+
if (col.component === 'formatted-number') {
|
|
353
|
+
return { integerDigits: 5, decimalPlaces: 6, rounding: 'round', inputLimit: true, ...result }
|
|
354
|
+
}
|
|
355
|
+
return result
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function slotUpdateHandler(slotProps: { row: TableRow }, col: ProTableFormColumn, childKey?: string) {
|
|
359
|
+
return (v: unknown) => setCellValue(slotProps.row, col, childKey, v)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ---------------------------------------------------------------------------
|
|
363
|
+
// Span method
|
|
364
|
+
// ---------------------------------------------------------------------------
|
|
365
|
+
|
|
366
|
+
const allLeafColumnKeys = computed<string[]>(() => {
|
|
367
|
+
const keys: string[] = []
|
|
368
|
+
for (const col of props.columns) {
|
|
369
|
+
if (col.children && col.children.length > 0) {
|
|
370
|
+
for (const child of col.children) {
|
|
371
|
+
keys.push(`${col.key}.${child.key}`)
|
|
372
|
+
}
|
|
373
|
+
} else if (!col.hideInTable) {
|
|
374
|
+
keys.push(col.key)
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return keys
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
const spanMethodAdapter = ({
|
|
381
|
+
rowIndex,
|
|
382
|
+
column,
|
|
383
|
+
columnIndex,
|
|
384
|
+
row,
|
|
385
|
+
}: {
|
|
386
|
+
row: TableRow
|
|
387
|
+
column: { property: string; label: string }
|
|
388
|
+
rowIndex: number
|
|
389
|
+
columnIndex: number
|
|
390
|
+
}): [number, number] | { rowspan: number; colspan: number } | void => {
|
|
391
|
+
if (!props.spanMethod) return
|
|
392
|
+
const colKey = allLeafColumnKeys.value[columnIndex]
|
|
393
|
+
return props.spanMethod({
|
|
394
|
+
row,
|
|
395
|
+
column: { ...column, property: colKey ?? column.property },
|
|
396
|
+
rowIndex,
|
|
397
|
+
columnIndex,
|
|
398
|
+
})
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ---------------------------------------------------------------------------
|
|
402
|
+
// Return
|
|
403
|
+
// ---------------------------------------------------------------------------
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
// 注册
|
|
407
|
+
register,
|
|
408
|
+
|
|
409
|
+
// el-form 绑定(formModelRef 包装了数组,满足 el-form :model Object 类型要求)
|
|
410
|
+
formModelRef,
|
|
411
|
+
currentModelValue,
|
|
412
|
+
mergedRules,
|
|
413
|
+
|
|
414
|
+
// el-table 绑定
|
|
415
|
+
tableRows,
|
|
416
|
+
rowKeyFn,
|
|
417
|
+
spanMethodAdapter,
|
|
418
|
+
allLeafColumnKeys,
|
|
419
|
+
|
|
420
|
+
// ref
|
|
421
|
+
formRef,
|
|
422
|
+
getFormRef: () => formRef.value,
|
|
423
|
+
|
|
424
|
+
// 单元格渲染
|
|
425
|
+
cellComponent,
|
|
426
|
+
cellBind,
|
|
427
|
+
slotUpdateHandler,
|
|
428
|
+
getCellProp,
|
|
429
|
+
|
|
430
|
+
// 单元格值读写
|
|
431
|
+
getCellValue,
|
|
432
|
+
setCellValue,
|
|
433
|
+
|
|
434
|
+
// 行操作
|
|
435
|
+
handleAddRow,
|
|
436
|
+
handleRemoveRow,
|
|
437
|
+
|
|
438
|
+
// el-form 实例方法
|
|
439
|
+
validate,
|
|
440
|
+
clearValidate,
|
|
441
|
+
}
|
|
442
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import ProDescriptions from './ProDescriptions'
|
|
|
5
5
|
import CollapseContainer from './CollapseContainer'
|
|
6
6
|
import { ProTableForm } from './ProTableForm'
|
|
7
7
|
import { useForm } from './ProForm/useForm'
|
|
8
|
+
import { useProTableForm } from './ProTableForm/useProTableForm'
|
|
8
9
|
import { useDescription } from './ProDescriptions/useDescription'
|
|
9
10
|
import { useProTable } from './ProTable/useProTable'
|
|
10
11
|
import { useComponentSetting } from './useComponentSetting'
|
|
@@ -13,13 +14,14 @@ export { ProForm, ProFormItem, FormActions, FormattedNumberInput, useForm }
|
|
|
13
14
|
export { ProTable, useProTable, TableAction }
|
|
14
15
|
export { ProDescriptions, useDescription }
|
|
15
16
|
export { CollapseContainer }
|
|
16
|
-
export { ProTableForm }
|
|
17
|
+
export { ProTableForm, useProTableForm }
|
|
17
18
|
export type {
|
|
18
19
|
ProTableFormColumn,
|
|
19
|
-
|
|
20
|
+
ProTableFormColumnChild,
|
|
20
21
|
ProTableFormProps,
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
ProTableFormBuiltInComponent,
|
|
23
|
+
ProTableFormActionType,
|
|
24
|
+
} from './types'
|
|
23
25
|
export { useComponentSetting }
|
|
24
26
|
export type { UseComponentSettingReturn } from './useComponentSetting'
|
|
25
27
|
export type { UseProTableReturn, UseProTablePropsReactive } from './ProTable/useProTable'
|