@aspire-ui/element-component-pro 1.0.26 → 1.0.27

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.
@@ -0,0 +1,207 @@
1
+ # ProTableForm 对比表表单
2
+
3
+ 用于在统一指标列下录入多个行信息,支持**内置组件**(input / 千分位数字 / select / switch 等)、**列级插槽**与**操作列完全由外部实现**。
4
+
5
+ ## 数据模型(v-model)
6
+
7
+ `modelValue` 直接为数组:
8
+
9
+ ```ts
10
+ [
11
+ { [col.key]: ..., [col.key]: ... },
12
+ { [col.key]: ..., [col.key]: ... },
13
+ ]
14
+ ```
15
+
16
+ ## 列配置 `columns`
17
+
18
+ `columns` 为 `ProTableFormColumn[]` 类型,支持多级表头(通过 `children` 嵌套)。
19
+
20
+ ### `ProTableFormColumn` 字段说明
21
+
22
+ | 字段 | 类型 | 说明 |
23
+ |------|------|------|
24
+ | `key` | `string` | 字段名,对应 model 中对象字段名 |
25
+ | `title` | `string` | 表头文字 |
26
+ | `required` | `boolean` | 是否必填(自动生成必填规则) |
27
+ | `component` | `ProTableFormBuiltInComponent` | 单元格渲染方式,见下方内置组件列表 |
28
+ | `componentProps` | `Record<string, unknown>` | 透传给单元格组件的配置参数 |
29
+ | `slotName` | `string` | `component === 'slot'` 时必填,对应父组件插槽 **`#cell-{slotName}`** |
30
+ | `render` | `ProTableFormColumnRender` | 自定义渲染函数,返回 string(文本展示,参与校验)或 VNode(自定义组件,直接渲染) |
31
+ | `rules` | `unknown[]` | 覆盖该列默认必填规则(Element 表单 rules) |
32
+ | `minWidth` / `width` | `number` | 列宽 |
33
+ | `align` | `'left' \| 'center' \| 'right'` | 单元格对齐 |
34
+ | `headerAlign` | `'left' \| 'center' \| 'right'` | 表头对齐 |
35
+ | `fixed` | `boolean \| 'left' \| 'right'` | 固定列 |
36
+ | `cellStyle` | `Record<string, string \| number>` | 单元格样式 |
37
+ | `headerCellStyle` | `Record<string, string \| number>` | 表头单元格样式 |
38
+ | `cellClassName` | `string` | 单元格 className |
39
+ | `headerCellClassName` | `string` | 表头单元格 className |
40
+ | `sortable` | `boolean` | 是否可排序 |
41
+ | `resizable` | `boolean` | 是否可拖拽调整宽度(默认 true) |
42
+ | `hideInTable` | `boolean` | 是否隐藏该列 |
43
+ | `children` | `ProTableFormColumn[]` | 多级表头子列 |
44
+
45
+ ### `ProTableFormBuiltInComponent` 内置组件类型
46
+
47
+ | component 值 | 渲染组件 | 特殊说明 |
48
+ |---|---|---|
49
+ | `input` | `el-input` | 默认,可不写 |
50
+ | `input-number` | `el-input-number` | - |
51
+ | `formatted-number` | `FormattedNumberInput` | 千分位数字,默认整数位 5、小数位 6、舍入 round、inputLimit true;可通过 `componentProps` 覆盖 |
52
+ | `select` | `el-select` | `componentProps.options` 传入选项数组 `Array<{ label, value }>` |
53
+ | `checkbox` | `el-checkbox-group` | `componentProps.options` 传入选项数组,多选 |
54
+ | `radio` | `el-radio-group` | `componentProps.options` 传入选项数组,单选 |
55
+ | `date-picker` | `el-date-picker` | - |
56
+ | `date-range` | `el-date-picker[type=range]` | 默认 format `yyyy-MM-dd`,默认占位符「开始日期」/「结束日期」 |
57
+ | `switch` | `el-switch` | - |
58
+ | `cascader` | `el-cascader` | `componentProps.options` 传入树形选项数组 |
59
+ | `api-select` | `ApiSelect` | 远程 API 加载选项,`componentProps` 支持 `api`、`labelField`、`valueField` |
60
+ | `tree-select` | `TreeSelect` | 树形下拉,`componentProps` 支持 `data`、`props` |
61
+ | `slot` | 自定义插槽 | 配合 `slotName`,通过 `#cell-{slotName}` 插槽完全自定义单元格内容 |
62
+
63
+ > `select` / `checkbox` / `radio` 的选项统一通过 `componentProps.options` 传入:
64
+ > ```ts
65
+ > componentProps: {
66
+ > options: [
67
+ > { label: '启用', value: 1 },
68
+ > { label: '禁用', value: 0 },
69
+ > ],
70
+ > }
71
+ > ```
72
+
73
+ ## 表头插槽(按列)
74
+
75
+ - **`#header-{column.key}`**:自定义某一列表头,作用域参数 `{ column }`。
76
+ - 不传则使用默认标题与必填星号。
77
+
78
+ ## 自定义单元格插槽(`component: 'slot'`)
79
+
80
+ 插槽名:**`cell-{slotName}`**
81
+
82
+ 作用域参数:
83
+
84
+ | 属性 | 说明 |
85
+ |------|------|
86
+ | `column` | 当前列配置 |
87
+ | `row` | 当前行(`_index: number`) |
88
+ | `index` | 行索引 |
89
+ | `value` | 当前单元格值 |
90
+ | `updateValue` | `(v: unknown) => void` 写回表单 |
91
+
92
+ ## 操作列
93
+
94
+ 操作列完全由外部通过 **`#action`** 作用域插槽实现,ProTableForm 不内置任何操作列 UI。
95
+
96
+ 作用域参数:
97
+
98
+ | 属性 | 说明 |
99
+ |------|------|
100
+ | `addRow` | 新增一行 |
101
+ | `removeRow` | 删除指定行,签名 `(index: number) => void` |
102
+
103
+ ```vue
104
+ <ProTableForm>
105
+ <template #action="{ addRow, removeRow }">
106
+ <el-table-column width="120" fixed="right" align="center">
107
+ <template #header>
108
+ <el-button type="text" @click="addRow">+新增</el-button>
109
+ </template>
110
+ <template #default="slotProps">
111
+ <el-button type="text" size="small" @click="removeRow(slotProps.$index)">删除</el-button>
112
+ </template>
113
+ </el-table-column>
114
+ </template>
115
+ </ProTableForm>
116
+ ```
117
+
118
+ ## 校验规则
119
+
120
+ ProTableForm 复用 `el-form` 的校验体系,支持三种配置方式,优先级:**列级 `rules` > 列级 `required` > 全局 `rules` > 表单级 rules**。
121
+
122
+ ### 三种配置方式
123
+
124
+ **方式一:`required: true`(最常用)**
125
+
126
+ 自动生成必填规则,无需手动编写:
127
+
128
+ ```ts
129
+ { key: 'name', title: '名称', required: true }
130
+ ```
131
+
132
+ **方式二:列级 `rules`(覆盖 `required` 默认规则)**
133
+
134
+ 传入 Element UI 标准 rules 数组,支持正则、数值范围、自定义 validator 等:
135
+
136
+ ```ts
137
+ {
138
+ key: 'email',
139
+ title: '邮箱',
140
+ required: true,
141
+ rules: [
142
+ { required: true, message: '请输入邮箱' },
143
+ { pattern: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, message: '邮箱格式不正确' },
144
+ ],
145
+ }
146
+ ```
147
+
148
+ ```ts
149
+ // 数值范围示例
150
+ {
151
+ key: 'price',
152
+ title: '价格',
153
+ component: 'input-number',
154
+ rules: [
155
+ { required: true, message: '请输入价格' },
156
+ { type: 'number', min: 0, message: '价格不能为负数' },
157
+ ],
158
+ }
159
+ ```
160
+
161
+ **方式三:全局 `rules`(统一规则,适用于所有同名 key)**
162
+
163
+ ```ts
164
+ <ProTableForm
165
+ :columns="columns"
166
+ :rules="{
167
+ inputNumber: [{ type: 'number', min: 0, message: '数量不能为负数' }],
168
+ }"
169
+ />
170
+ ```
171
+
172
+ ## 组件实例 `defineExpose`
173
+
174
+ | 方法/属性 | 说明 |
175
+ |------|------|
176
+ | `validate()` | 表单校验,返回 Promise\<boolean\> |
177
+ | `clearValidate(props?)` | 清除校验 |
178
+ | `addRow()` | 新增一行 |
179
+ | `removeRow(index)` | 删除指定行 |
180
+ | `getRows()` | 获取行数组 |
181
+ | `getRowCount()` | 获取行数 |
182
+ | `getTable()` | 获取 el-table 实例 |
183
+ | `getModelValue()` | 获取整个 modelValue |
184
+ | `setModelValue(val)` | 设置整个 modelValue |
185
+ | `getFormRef()` | 获取 el-form 实例 |
186
+
187
+ ## 其它 Props
188
+
189
+ - **rules**:合并进 `el-form` 的全局规则。
190
+ - **metricPlaceholder**:指标列(默认 `input`)的 placeholder,默认 `"请输入"`
191
+ - **minRows**:最少行数,初始化时自动补足
192
+ - **bordered**:是否显示边框,默认 `true`
193
+ - **stripe**:是否斑马纹,默认 `false`
194
+ - **size**:单元格尺寸,默认 `medium`
195
+
196
+ ## 示例
197
+
198
+ | 示例 | 说明 |
199
+ |------|------|
200
+ | `examples/pages/ProTableFormPage/Basic.vue` | 基础用法 + 自定义操作列 |
201
+ | `examples/pages/ProTableFormPage/BuiltInComponents.vue` | 全部 12 种内置组件一览 |
202
+ | `examples/pages/ProTableFormPage/Rules.vue` | 三种校验规则配置方式 |
203
+ | `examples/pages/ProTableFormPage/MultiHeader.vue` | 多级表头(children 嵌套) |
204
+ | `examples/pages/ProTableFormPage/MixedData.vue` | 混用:展示列(render) + 可编辑列 |
205
+ | `examples/pages/ProTableFormPage/Combo.vue` | 与 `el-form` 组合提交分段校验 |
206
+
207
+ 示例站:`/protable?tab=basic`、`/protable?tab=builtIn`、`/protable?tab=rules`、`/protable?tab=multiHeader`、`/protable?tab=mixed`、`/protable?tab=combo`
package/docs/image.png ADDED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aspire-ui/element-component-pro",
3
- "version": "1.0.26",
3
+ "version": "1.0.27",
4
4
  "description": "Element UI 二次封装组件库,基于 Vue 2.7 + TypeScript + setup 语法糖,实现 VbenAdmin 风格的 Pro 组件",
5
5
  "type": "module",
6
6
  "main": "./dist/element-component-pro.umd.js",
@@ -16,7 +16,8 @@
16
16
  },
17
17
  "files": [
18
18
  "dist",
19
- "src"
19
+ "src",
20
+ "docs"
20
21
  ],
21
22
  "scripts": {
22
23
  "dev": "vite",
@@ -173,7 +173,7 @@ defineExpose({
173
173
  })
174
174
  </script>
175
175
 
176
- <style scoped>
176
+ <style>
177
177
  .ecp-collapse-container {
178
178
  background: #fff;
179
179
  border-radius: 14px;
@@ -333,7 +333,7 @@ watch(() => effectiveProps.value.collapseOptions?.defaultExpand, (value) => {
333
333
  })
334
334
  </script>
335
335
 
336
- <style scoped>
336
+ <style>
337
337
  .ecp-pro-descriptions {
338
338
  width: 100%;
339
339
  box-sizing: border-box;
@@ -58,7 +58,7 @@ defineEmits<{
58
58
  }>()
59
59
  </script>
60
60
 
61
- <style scoped>
61
+ <style>
62
62
  /* .ecp-form-actions {
63
63
  } */
64
64
  .ecp-form-actions {
@@ -494,7 +494,7 @@ watch(() => controlledModelValue.value, (value) => {
494
494
  watch(() => [props.schemas, props.initialValues], syncSchemas, { deep: true })
495
495
  </script>
496
496
 
497
- <style scoped>
497
+ <style>
498
498
  .ecp-pro-form {
499
499
  padding: 16px;
500
500
  position: relative;
@@ -331,7 +331,7 @@ const renderComponent = computed(() => {
331
331
  })
332
332
  </script>
333
333
 
334
- <style scoped>
334
+ <style>
335
335
  .ecp-pro-form-item__colon {
336
336
  margin-right: 2px;
337
337
  }
@@ -241,7 +241,7 @@ watch(() => props.params, () => {
241
241
  }, { deep: true })
242
242
  </script>
243
243
 
244
- <style scoped>
244
+ <style>
245
245
  .ecp-tree-select {
246
246
  position: relative;
247
247
  width: 100%;
@@ -706,7 +706,7 @@ watch(() => props.dataSource, () => {
706
706
  watch(() => props.loading, (v) => { loading.value = v ?? false })
707
707
  </script>
708
708
 
709
- <style scoped>
709
+ <style>
710
710
  .ecp-pro-table {
711
711
  padding: 16px;
712
712
  background: #fff;
@@ -175,7 +175,7 @@ const handleDropdownCommand = (index: number | string) => {
175
175
  }
176
176
  </script>
177
177
 
178
- <style scoped>
178
+ <style>
179
179
  .ecp-table-action {
180
180
  display: inline-flex;
181
181
  align-items: center;
@@ -5,7 +5,7 @@
5
5
  :value="value"
6
6
  v-bind="cellBind"
7
7
  :placeholder="placeholder"
8
- v-on="mergedEvents('change')"
8
+ v-on="mergedEvents('change', 'clear')"
9
9
  >
10
10
  <el-option
11
11
  v-for="opt in options"
@@ -85,7 +85,7 @@ import { computed } from 'vue'
85
85
  import ApiSelect from '../ProForm/ApiSelect.vue'
86
86
  import TreeSelect from '../ProForm/TreeSelect.vue'
87
87
  import FormattedNumberInput from '../ProForm/FormattedNumberInput.vue'
88
- import type { ProTableFormColumn, ProTableFormColumnChild } from '../types'
88
+ import type { ProTableFormColumn, ProTableFormColumnChild, ProTableFormActionType } from '../types'
89
89
 
90
90
  const props = defineProps<{
91
91
  col: ProTableFormColumn | ProTableFormColumnChild
@@ -94,6 +94,8 @@ const props = defineProps<{
94
94
  row?: Record<string, unknown>
95
95
  size?: 'medium' | 'small' | 'large'
96
96
  placeholder?: string
97
+ /** ProTableForm 操作实例 */
98
+ action?: ProTableFormActionType
97
99
  }>()
98
100
 
99
101
  const emit = defineEmits<{ (e: 'update', v: unknown): void }>()
@@ -124,7 +126,7 @@ const cellBind = computed<Record<string, unknown>>(() => {
124
126
 
125
127
  const cp = props.col.componentProps
126
128
  if (typeof cp === 'function') {
127
- dynamicProps = cp({ row: props.row ?? {}, value: props.value, column: props.col as any })
129
+ dynamicProps = cp({ row: props.row ?? {}, value: props.value, column: props.col as any, action: props.action as any })
128
130
  } else {
129
131
  staticProps = cp ?? {}
130
132
  }
@@ -156,7 +158,7 @@ const cellEvents = computed<Record<string, (...args: unknown[]) => unknown>>(()
156
158
 
157
159
  const cp = props.col.componentProps
158
160
  if (typeof cp === 'function') {
159
- dynamicProps = cp({ row: props.row ?? {}, value: props.value, column: props.col as any })
161
+ dynamicProps = cp({ row: props.row ?? {}, value: props.value, column: props.col as any, action: props.action as any })
160
162
  } else {
161
163
  staticProps = cp ?? {}
162
164
  }
@@ -174,15 +176,17 @@ const cellEvents = computed<Record<string, (...args: unknown[]) => unknown>>(()
174
176
  /**
175
177
  * Merges cellEvents (from componentProps) with the internal emit bridge.
176
178
  * componentProps listeners fire first, then emit('update') updates the row value.
179
+ * clear 事件特殊处理:用户 handler 先执行,再 emit(undefined) 清空值。
177
180
  */
178
- const mergedEvents = (internalEvent: 'input' | 'change') => {
179
- const userHandler = cellEvents.value[internalEvent]
180
- return {
181
- ...cellEvents.value,
182
- [internalEvent]: (arg: unknown) => {
181
+ const mergedEvents = (...internalEvents: Array<'input' | 'change' | 'clear'>) => {
182
+ const handlers: Record<string, (...args: unknown[]) => void> = {}
183
+ for (const internalEvent of internalEvents) {
184
+ const userHandler = cellEvents.value[internalEvent]
185
+ handlers[internalEvent] = (arg: unknown) => {
183
186
  if (userHandler) userHandler(arg)
184
- emit('update', arg)
185
- },
187
+ emit('update', internalEvent === 'clear' ? undefined : arg)
188
+ }
186
189
  }
190
+ return handlers
187
191
  }
188
192
  </script>
@@ -4,7 +4,6 @@
4
4
  ref="formRef"
5
5
  :model="formModelRef"
6
6
  :rules="mergedRules"
7
- :validate-on-rule-change="false"
8
7
  :label-width="labelWidth"
9
8
  :size="formSize"
10
9
  :label-position="labelPosition"
@@ -127,6 +126,7 @@
127
126
  :row="slotProps.row"
128
127
  :size="size"
129
128
  :placeholder="col.placeholder || metricPlaceholder"
129
+ :action="action"
130
130
  @update="setCellValue(slotProps.row, col, child.key, $event)"
131
131
  />
132
132
  </el-form-item>
@@ -205,6 +205,7 @@
205
205
  :row="slotProps.row"
206
206
  :size="size"
207
207
  :placeholder="col.placeholder || metricPlaceholder"
208
+ :action="action"
208
209
  @update="setCellValue(slotProps.row, col, undefined, $event)"
209
210
  />
210
211
  </el-form-item>
@@ -417,7 +418,7 @@ defineExpose(action)
417
418
  emit('register', action)
418
419
  </script>
419
420
 
420
- <style scoped>
421
+ <style>
421
422
  .ecp-pro-table-form__form :deep(.el-form-item) {
422
423
  margin-bottom: 0;
423
424
  }
@@ -347,11 +347,12 @@ export interface ProTableFormColumn {
347
347
  * `integerDigits`、`decimalPlaces`、`rounding`、`inputLimit` 等(与 FormattedNumberInput 一致)。
348
348
  * 支持函数形式,参数为 { row, value, column },返回要合并到组件上的属性。
349
349
  */
350
- componentProps?: Record<string, unknown> | ((params: {
351
- row: Record<string, unknown>
352
- value: unknown
353
- column: ProTableFormColumn
354
- }) => Record<string, unknown>)
350
+ /**
351
+ * 透传给单元格组件。`component === 'formatted-number'` 时在此传入
352
+ * `integerDigits`、`decimalPlaces`、`rounding`、`inputLimit` 等。
353
+ * 支持函数形式,参数为 { row, value, column, action },返回要合并到组件上的属性。
354
+ */
355
+ componentProps?: Record<string, unknown> | ((params: CellComponentPropsParams) => Record<string, unknown>)
355
356
  /** 动态校验规则 */
356
357
  dynamicRules?: unknown[] | ((params: {
357
358
  row: Record<string, unknown>
@@ -406,13 +407,9 @@ export interface ProTableFormColumnChild {
406
407
  /**
407
408
  * 透传给单元格组件。`component === 'formatted-number'` 时在此传入
408
409
  * `integerDigits`、`decimalPlaces`、`rounding`、`inputLimit` 等。
409
- * 支持函数形式,参数为 { row, value, column },返回要合并到组件上的属性。
410
+ * 支持函数形式,参数为 { row, value, column, action },返回要合并到组件上的属性。
410
411
  */
411
- componentProps?: Record<string, unknown> | ((params: {
412
- row: Record<string, unknown>
413
- value: unknown
414
- column: ProTableFormColumnChild
415
- }) => Record<string, unknown>)
412
+ componentProps?: Record<string, unknown> | ((params: CellComponentPropsParams) => Record<string, unknown>)
416
413
  rules?: unknown[]
417
414
  /** 动态校验规则 */
418
415
  dynamicRules?: unknown[] | ((params: {
@@ -520,3 +517,12 @@ export interface ProTableFormActionType {
520
517
  getFormRef: () => FormInstance | null
521
518
  }
522
519
 
520
+ /** componentProps 函数的参数类型 */
521
+ export interface CellComponentPropsParams {
522
+ row: Record<string, unknown>
523
+ value: unknown
524
+ column: ProTableFormColumn | ProTableFormColumnChild
525
+ /** ProTableForm 操作实例,可调用 validate、addRow、removeRow 等 */
526
+ action: ProTableFormActionType
527
+ }
528
+