@jzt-packages/components 1.0.0
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 +68 -0
- package/src/JztBackTop/index.vue +255 -0
- package/src/JztButtonList/index.vue +88 -0
- package/src/JztChart/index.vue +95 -0
- package/src/JztCharts/index.vue +317 -0
- package/src/JztClassTabs/index.vue +156 -0
- package/src/JztDateSelect/dateSelect.vue +186 -0
- package/src/JztDateSelect/dateType.vue +54 -0
- package/src/JztDateSelect/index.ts +135 -0
- package/src/JztDateSelect/interface/index.ts +13 -0
- package/src/JztDialog/index.vue +249 -0
- package/src/JztEllipsisTooltip/index.vue +61 -0
- package/src/JztEmpty/index.vue +45 -0
- package/src/JztErrorPage/403.vue +30 -0
- package/src/JztErrorPage/404.vue +19 -0
- package/src/JztErrorPage/500.vue +18 -0
- package/src/JztErrorPage/assets/401.png +0 -0
- package/src/JztErrorPage/assets/403.png +0 -0
- package/src/JztErrorPage/assets/404.png +0 -0
- package/src/JztErrorPage/assets/500.png +0 -0
- package/src/JztErrorPage/index.scss +35 -0
- package/src/JztErrorPage/index.vue +35 -0
- package/src/JztFilePreview/components/pdfViewer.vue +221 -0
- package/src/JztFilePreview/hooks/useImageMethod.ts +256 -0
- package/src/JztFilePreview/index.scss +171 -0
- package/src/JztFilePreview/index.vue +68 -0
- package/src/JztFilePreview/interface/index.ts +18 -0
- package/src/JztFilePreview/previewFile.vue +371 -0
- package/src/JztFormGrid/README.md +520 -0
- package/src/JztFormGrid/components/formItem.vue +209 -0
- package/src/JztFormGrid/components/formItemValue.vue +384 -0
- package/src/JztFormGrid/components/showDetailForm.vue +172 -0
- package/src/JztFormGrid/index.scss +60 -0
- package/src/JztFormGrid/index.vue +513 -0
- package/src/JztFormGrid/interface/index.ts +106 -0
- package/src/JztGrid/components/GridItem.vue +68 -0
- package/src/JztGrid/index.vue +179 -0
- package/src/JztGrid/interface/index.ts +6 -0
- package/src/JztImportExcel/assets/delete.png +0 -0
- package/src/JztImportExcel/index.scss +46 -0
- package/src/JztImportExcel/index.vue +430 -0
- package/src/JztImportExcel/interface/index.ts +25 -0
- package/src/JztLabelTitle/index.vue +65 -0
- package/src/JztLeftRightMode/components/CollapseButton.vue +80 -0
- package/src/JztLeftRightMode/components/LeftCard.vue +203 -0
- package/src/JztLeftRightMode/components/LeftLayout.vue +173 -0
- package/src/JztLeftRightMode/components/RightHeader.vue +186 -0
- package/src/JztLeftRightMode/components/RightLayout.vue +235 -0
- package/src/JztLeftRightMode/components/RightTableHeader.vue +43 -0
- package/src/JztLeftRightMode/hooks/useCollapse.ts +17 -0
- package/src/JztLeftRightMode/hooks/useDefaultProps.ts +19 -0
- package/src/JztLeftRightMode/hooks/useLeftLayout.ts +201 -0
- package/src/JztLeftRightMode/hooks/useMode.ts +20 -0
- package/src/JztLeftRightMode/hooks/usePrevNext.ts +60 -0
- package/src/JztLeftRightMode/hooks/useRightLayout.ts +215 -0
- package/src/JztLeftRightMode/hooks/useSlots.ts +15 -0
- package/src/JztLeftRightMode/index.ts +3 -0
- package/src/JztLeftRightMode/index.vue +494 -0
- package/src/JztLeftRightMode/types/index.ts +457 -0
- package/src/JztLoading/fullScreen.ts +45 -0
- package/src/JztLoading/index.scss +67 -0
- package/src/JztLoading/index.vue +18 -0
- package/src/JztLogin/components/LoginFooter.vue +17 -0
- package/src/JztLogin/components/LoginForm.vue +99 -0
- package/src/JztLogin/hooks/useLogin.ts +186 -0
- package/src/JztLogin/index.scss +142 -0
- package/src/JztLogin/index.vue +31 -0
- package/src/JztLogin/interface/index.ts +47 -0
- package/src/JztNumericalRange/index.vue +81 -0
- package/src/JztPageCard/comm/datePicker.vue +151 -0
- package/src/JztPageCard/comm/details.vue +60 -0
- package/src/JztPageCard/comm/export.vue +24 -0
- package/src/JztPageCard/comm/tabs.vue +94 -0
- package/src/JztPageCard/comm/tooltip.vue +31 -0
- package/src/JztPageCard/index.vue +287 -0
- package/src/JztPagination/index.vue +70 -0
- package/src/JztProductInfo/components/imagePreview.vue +275 -0
- package/src/JztProductInfo/components/qxUnique.vue +101 -0
- package/src/JztProductInfo/components/records.vue +265 -0
- package/src/JztProductInfo/hooks/useParams.ts +143 -0
- package/src/JztProductInfo/hooks/useQxUnique.tsx +466 -0
- package/src/JztProductInfo/images/defaultProduct.png +0 -0
- package/src/JztProductInfo/index.ts +116 -0
- package/src/JztProductInfo/index.vue +108 -0
- package/src/JztProductInfo/interface/index.ts +15 -0
- package/src/JztQueryDetailTable/index.scss +100 -0
- package/src/JztQueryDetailTable/index.vue +400 -0
- package/src/JztQueryDetailTable/interface/index.ts +10 -0
- package/src/JztQueryTable/QueryTable /345/212/237/350/203/275.md" +1580 -0
- package/src/JztQueryTable/README.md +567 -0
- package/src/JztQueryTable/components/ColSetting.vue +67 -0
- package/src/JztQueryTable/components/ColumnsSetting.vue +404 -0
- package/src/JztQueryTable/components/ColumnsSetting1.vue +220 -0
- package/src/JztQueryTable/components/DeployToAccountLevelSetting.vue +351 -0
- package/src/JztQueryTable/components/Pagination.vue +54 -0
- package/src/JztQueryTable/components/TableColumn.vue +109 -0
- package/src/JztQueryTable/const.ts +1 -0
- package/src/JztQueryTable/hooks/useQueryTable.ts +194 -0
- package/src/JztQueryTable/hooks/useSelection.ts +47 -0
- package/src/JztQueryTable/hooks/useTableSetting.ts +197 -0
- package/src/JztQueryTable/hooks/useTemplate.ts +127 -0
- package/src/JztQueryTable/index.scss +91 -0
- package/src/JztQueryTable/index.vue +1445 -0
- package/src/JztQueryTable/interface/index.ts +185 -0
- package/src/JztRegionSelect/index.vue +134 -0
- package/src/JztSearchForm/components/SearchFormItem.vue +473 -0
- package/src/JztSearchForm/index.vue +530 -0
- package/src/JztSearchForm/interface/index.ts +100 -0
- package/src/JztSelectFilter/index.scss +63 -0
- package/src/JztSelectFilter/index.vue +110 -0
- package/src/JztSelectTable/index.vue +257 -0
- package/src/JztTable/index.scss +72 -0
- package/src/JztTable/index.vue +353 -0
- package/src/JztTable/interface/index.ts +1 -0
- package/src/JztTime/comm/agencySelect.vue +112 -0
- package/src/JztTime/comm/collapseRow.vue +132 -0
- package/src/JztTime/comm/dateSelect.vue +292 -0
- package/src/JztTime/comm/deptSelect.vue +193 -0
- package/src/JztTime/comm/typeSelect.vue +97 -0
- package/src/JztTime/index.ts +216 -0
- package/src/JztTime/index.vue +303 -0
- package/src/JztTime/interface/index.ts +23 -0
- package/src/JztTreeFilter/index.scss +44 -0
- package/src/JztTreeFilter/index.vue +177 -0
- package/src/JztUploadFile/interface/index.ts +21 -0
- package/src/JztUploadFile/multiple.scss +215 -0
- package/src/JztUploadFile/multiple.vue +318 -0
- package/src/JztUploadFile/single.scss +226 -0
- package/src/JztUploadFile/single.vue +274 -0
- package/src/JztUploadImg/Img.vue +294 -0
- package/src/JztUploadImg/Imgs.vue +411 -0
- package/src/JztUploadImg/index.scss +138 -0
- package/src/JztUploadImg/interface/index.ts +22 -0
- package/src/SelectIcon/index.scss +39 -0
- package/src/SelectIcon/index.vue +106 -0
- package/src/SvgIcon/index.vue +22 -0
- package/src/hooks/useAuthButtons.ts +58 -0
- package/src/hooks/useFormByUserType.ts +90 -0
- package/src/hooks/useTableEvents.ts +30 -0
- package/src/hooks/useUploadFileHook.ts +262 -0
- package/src/index.ts +91 -0
- package/src/typings/global.d.ts +101 -0
- package/src/utils/index.ts +107 -0
- package/src/utils/tree.ts +57 -0
- package/tsconfig.json +45 -0
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
# JztFormGrid 表单组件
|
|
2
|
+
|
|
3
|
+
> 一个基于 **配置驱动(Config-Driven)** 的表单组件,基于 Element Plus `el-form` 封装,通过 `queryItems` 配置数组即可渲染出完整的表单,无需手写大量 `el-form-item` 模板。
|
|
4
|
+
|
|
5
|
+
适用场景:新增 / 编辑 / 详情弹窗、查询条件区、复杂业务表单(如产品信息维护、订单录入等)。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 目录
|
|
10
|
+
|
|
11
|
+
- [一、核心特性](#一核心特性)
|
|
12
|
+
- [二、快速上手](#二快速上手)
|
|
13
|
+
- [三、组件文件结构](#三组件文件结构)
|
|
14
|
+
- [四、组件 Props(JztFormGrid)](#四组件-propsjztformgrid)
|
|
15
|
+
- [五、queryItems 表单项配置(核心)](#五queryitems-表单项配置核心)
|
|
16
|
+
- [六、el 支持的组件类型](#六el-支持的组件类型)
|
|
17
|
+
- [七、enum 枚举字典(下拉数据)](#七enum-枚举字典下拉数据)
|
|
18
|
+
- [八、cascader 级联选择](#八cascader-级联选择)
|
|
19
|
+
- [九、动态显示 / 隐藏 / 禁用](#九动态显示--隐藏--禁用)
|
|
20
|
+
- [十、表单分组](#十表单分组)
|
|
21
|
+
- [十一、操作按钮](#十一操作按钮)
|
|
22
|
+
- [十二、查看模式 vs 详情模式](#十二查看模式-vs-详情模式)
|
|
23
|
+
- [十三、暴露的方法(ref 调用)](#十三暴露的方法ref-调用)
|
|
24
|
+
- [十四、插槽](#十四插槽)
|
|
25
|
+
- [十五、自定义渲染(tsx)](#十五自定义渲染tsx)
|
|
26
|
+
- [十六、时间 / 数字范围字段](#十六时间--数字范围字段)
|
|
27
|
+
- [十七、常见踩坑点](#十七常见踩坑点)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 一、核心特性
|
|
32
|
+
|
|
33
|
+
1. **配置驱动**:一个 `queryItems` 数组描述整个表单,类型由 TypeScript 完整推导。
|
|
34
|
+
2. **支持多种控件**:`el-input`、`el-select`、`el-date-picker`、`el-cascader`、`el-tree-select`、`el-switch` 等 Element Plus 组件,以及业务自定义组件(上传、行政区划、数字范围等)。
|
|
35
|
+
3. **内置枚举字典机制**:`enum` 支持静态数组 / 异步接口请求,自动填充下拉。
|
|
36
|
+
4. **级联选择**:`cascader` 配置上级,自动联动加载下级数据,清空时递归清除。
|
|
37
|
+
5. **三种模式切换**:编辑模式(默认)/ 查看模式(`isShow`)/ 详情模式(`isDetail`),同一份配置即可复用。
|
|
38
|
+
6. **分组 & 折叠**:通过 `groupTitle + children` 实现分组卡片,支持折叠展开。
|
|
39
|
+
7. **自定义渲染(tsx)**:`render`、`labelRender` 等支持灵活的自定义内容。
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 二、快速上手
|
|
44
|
+
|
|
45
|
+
```vue
|
|
46
|
+
<template>
|
|
47
|
+
<JztFormGrid ref="formRef" :query-items="columns" :rule-form="form" />
|
|
48
|
+
<el-button type="primary" @click="onSubmit">提交</el-button>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<script setup lang="ts">
|
|
52
|
+
import { JztFormGrid } from '@jzt-spd/components'
|
|
53
|
+
import type { JztFormInstance, FormItemsProps } from '@jzt-spd/components/JztFormGrid'
|
|
54
|
+
|
|
55
|
+
const formRef = ref<JztFormInstance>()
|
|
56
|
+
const form = reactive({ name: '', status: 1, remark: '' })
|
|
57
|
+
|
|
58
|
+
const columns = ref<FormItemsProps[]>([
|
|
59
|
+
{
|
|
60
|
+
label: '姓名',
|
|
61
|
+
prop: 'name',
|
|
62
|
+
el: 'el-input',
|
|
63
|
+
span: 12,
|
|
64
|
+
rules: [{ required: true, message: '请输入姓名', trigger: 'blur' }]
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
label: '状态',
|
|
68
|
+
prop: 'status',
|
|
69
|
+
el: 'el-select',
|
|
70
|
+
span: 12,
|
|
71
|
+
enum: [
|
|
72
|
+
{ label: '启用', value: 1 },
|
|
73
|
+
{ label: '禁用', value: 0 }
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
label: '备注',
|
|
78
|
+
prop: 'remark',
|
|
79
|
+
el: 'el-input',
|
|
80
|
+
span: 24,
|
|
81
|
+
props: { type: 'textarea', rows: 3 }
|
|
82
|
+
}
|
|
83
|
+
])
|
|
84
|
+
|
|
85
|
+
const onSubmit = () => {
|
|
86
|
+
formRef.value?.onConfirm((valid, formData) => {
|
|
87
|
+
if (valid) {
|
|
88
|
+
console.log('校验通过,提交数据:', formData)
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
</script>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
> 💡 **导入约定**
|
|
96
|
+
> - 组件本身:`import { JztFormGrid } from '@jzt-spd/components'`
|
|
97
|
+
> - 类型:`import type { JztFormInstance, FormItemsProps } from '@jzt-spd/components/JztFormGrid'`
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 三、组件文件结构
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
JztFormGrid/
|
|
105
|
+
├── index.vue # 主组件:表单容器、分组渲染、数据格式化、ref 方法暴露
|
|
106
|
+
├── index.scss # 公共样式
|
|
107
|
+
├── interface/index.ts # FormItemsProps 等类型定义(最重要)
|
|
108
|
+
└── components/
|
|
109
|
+
├── formItem.vue # 单个表单项容器:决定渲染编辑态/查看态/详情态
|
|
110
|
+
├── formItemValue.vue # 编辑态:真正渲染 el-* 控件、enum 处理、级联事件
|
|
111
|
+
└── showDetailForm.vue # 详情态:左右布局的 label:value 展示
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**渲染链路**:`index.vue`(遍历 queryItems)→ `formItem.vue`(判断模式)→ `formItemValue.vue`(编辑)/ `showDetailForm.vue`(详情)。
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 四、组件 Props(JztFormGrid)
|
|
119
|
+
|
|
120
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
121
|
+
| --- | --- | --- | --- |
|
|
122
|
+
| `queryItems` | `FormItemsProps[]` | `[]` | **必填**,表单配置项数组 |
|
|
123
|
+
| `ruleForm` | `object` | `{}` | 表单数据对象(双向绑定,直接修改即生效) |
|
|
124
|
+
| `isShow` | `boolean` | `false` | 查看模式:表单项以只读文本展示 |
|
|
125
|
+
| `disabled` | `boolean` | `false` | 整个表单禁用 |
|
|
126
|
+
| `isDetail` | `boolean` | `false` | 详情模式:使用 `showDetailForm` 的 `label : value` 左右布局展示 |
|
|
127
|
+
| `labelWidth` | `number \| string` | `'auto'` | 整体 label 宽度 |
|
|
128
|
+
| `border` | `boolean` | `false` | 显示网格边框样式 |
|
|
129
|
+
| `isCard` | `boolean` | `false` | 分组卡片样式(带边框 + 浅灰背景头部) |
|
|
130
|
+
| `showCollapsed` | `boolean` | `true` | 分组是否显示折叠按钮 |
|
|
131
|
+
| `infoEmptyText` | `string` | `'-'` | 查看 / 详情模式下空值的占位符 |
|
|
132
|
+
| `showOverflowTooltip` | `boolean` | - | 详情 value 是否超长显示 Tooltip |
|
|
133
|
+
| `formProps` | `object` | - | 透传给 `el-form` 的属性 |
|
|
134
|
+
| `groupProps` | `object` | `{}` | 透传给分组标题组件 `JztLabelTitle` 的属性 |
|
|
135
|
+
|
|
136
|
+
> 三种"只读"模式的区别见 [第十二章](#十二查看模式-vs-详情模式)。
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 五、queryItems 表单项配置(核心)
|
|
141
|
+
|
|
142
|
+
每个数组元素是一个 `FormItemsProps` 对象。**最常用的字段**:
|
|
143
|
+
|
|
144
|
+
| 字段 | 类型 | 说明 |
|
|
145
|
+
| --- | --- | --- |
|
|
146
|
+
| `label` | `string` | 表单项标签 |
|
|
147
|
+
| `prop` | `string` | 绑定到 `ruleForm` 的字段名(必填) |
|
|
148
|
+
| `el` | `ElType` | 渲染的控件类型,见 [第六章](#六el-支持的组件类型) |
|
|
149
|
+
| `span` | `number` | 栅格宽度,默认 `12`(24 栅格制,12 = 一行两列) |
|
|
150
|
+
| `props` | `object` | 透传给 el 控件的属性(如 `placeholder`、`disabled`、`type` 等) |
|
|
151
|
+
| `events` | `object` | 透传给 el 控件的事件(key 为事件名,如 `change`、`focus`) |
|
|
152
|
+
| `rules` | `array` | Element Plus 校验规则 |
|
|
153
|
+
| `enum` | 见 [第七章](#七enum-枚举字典下拉数据) | 下拉 / 选项数据 |
|
|
154
|
+
| `fieldNames` | `{ label, value, children }` | 自定义选项的字段映射(接口字段非 label/value 时用) |
|
|
155
|
+
| `change` | `(scope) => void` | 控件 change 回调,`scope = { value, optionsItem }` |
|
|
156
|
+
| `hideFun` | `() => boolean` | 返回 `true` 则隐藏该项(动态) |
|
|
157
|
+
| `disabledFn` | `(form) => boolean` | 返回 `true` 则禁用该项(动态) |
|
|
158
|
+
| `slotName` | `string` | 自定义插槽名,用 `<template #slotName>` 渲染内容 |
|
|
159
|
+
| `tooltip` | `string` | label 后的感叹号提示 |
|
|
160
|
+
| `className` | `string` | 给 `el-form-item` 加 class |
|
|
161
|
+
| `formItemProps` | `object` | 透传给 `el-form-item` 的属性 |
|
|
162
|
+
| `labelWidth` | `string \| number` | 单项 label 宽度(覆盖整体设置) |
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## 六、el 支持的组件类型
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
type ElType =
|
|
170
|
+
| 'el-input' | 'el-input-number'
|
|
171
|
+
| 'el-select' | 'el-select-v2'
|
|
172
|
+
| 'el-tree-select' | 'el-cascader'
|
|
173
|
+
| 'el-date-picker' | 'el-time-picker' | 'el-time-select'
|
|
174
|
+
| 'el-switch' | 'el-slider'
|
|
175
|
+
| 'el-radio-group' | 'el-checkbox-group'
|
|
176
|
+
// 业务自定义组件
|
|
177
|
+
| 'JztUploadImg' | 'JztUploadMultipleImgs' // 单图 / 多图上传
|
|
178
|
+
| 'JztUploadSingleFile' | 'JztUploadMultipleFiles' // 单文件 / 多文件上传
|
|
179
|
+
| 'JztDictionary' // 字典组件
|
|
180
|
+
| 'JztTable' // 表格(常用于明细)
|
|
181
|
+
| 'JztRegionSelect' // 行政区划选择
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
> 当 `el` 不传或为非 `el-*` 的自定义组件时,组件会按自定义组件逻辑渲染。
|
|
185
|
+
> 常用三件套:`el-input`(输入)、`el-select`(下拉)、`el-date-picker`(日期)。
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 七、enum 枚举字典(下拉数据)
|
|
190
|
+
|
|
191
|
+
`enum` 有三种写法,**优先级**:`enum` > `options` > 通过 `provide('fromEnumMap')` 注入。
|
|
192
|
+
|
|
193
|
+
### 1. 静态数组
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
{
|
|
197
|
+
label: '状态', prop: 'status', el: 'el-select',
|
|
198
|
+
enum: [
|
|
199
|
+
{ label: '启用', value: 1 },
|
|
200
|
+
{ label: '禁用', value: 0 }
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### 2. 异步接口(函数)
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
{
|
|
209
|
+
label: '生产厂家', prop: 'manufactureId', el: 'el-select',
|
|
210
|
+
enum: async () => await getManufacturerList({ enterpriseType: 2 }),
|
|
211
|
+
fieldNames: { label: 'value', value: 'key' } // 接口字段不是 label/value 时必配
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### 3. 级联接口(接收上级值)
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
{
|
|
219
|
+
label: '供应商', prop: 'supplierId', el: 'el-select-v2',
|
|
220
|
+
enum: (val) => getSupplierDDL(val), // val = 上级 prop 的值
|
|
221
|
+
fieldNames: { label: 'name', value: 'id' }
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**配套字段:**
|
|
226
|
+
|
|
227
|
+
| 字段 | 说明 |
|
|
228
|
+
| --- | --- |
|
|
229
|
+
| `fieldNames` | `{ label, value, children }`,把接口返回字段映射成标准 label/value |
|
|
230
|
+
| `isSetFirstValue` | 接口返回后,是否自动选中第一项 |
|
|
231
|
+
| `isAwait` | 级联场景:是否等待上级有值后才请求(详情回显常用) |
|
|
232
|
+
| `enumKey` | 当多个字段共用同一份字典时,指定读取 enumMap 的 key |
|
|
233
|
+
|
|
234
|
+
> ⚠️ 异步 `enum` 函数返回值需为 `{ result: [] }` 或 `{ data: [] }` 结构(与后端约定)。
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## 八、cascader 级联选择
|
|
239
|
+
|
|
240
|
+
实现"省 → 市 → 区"或"产品大类 → 小类"的联动。
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
{
|
|
244
|
+
label: '产品大类', prop: 'productClassId', el: 'el-select',
|
|
245
|
+
cascader: ['productTypeId'], // 声明:我的下级是 productTypeId
|
|
246
|
+
enum: async () => await getProductClassListAsync(),
|
|
247
|
+
fieldNames: { value: 'productClassId', label: 'productClassName' }
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
label: '产品小类', prop: 'productTypeId', el: 'el-select',
|
|
251
|
+
isAwait: true, // 等待上级选中后再请求
|
|
252
|
+
enum: async (parentId) => {
|
|
253
|
+
// parentId = 上级 productClassId 的值
|
|
254
|
+
return await getGoodsClassToProduceClassTypeList({ ClassCode: ... })
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**联动机制:**
|
|
260
|
+
- 上级 `change` 时,自动调用下级 `enum(上级值)` 刷新下拉,并清空下级已选值。
|
|
261
|
+
- 清空上级时,会**递归清空**所有下级的值与下拉数据(适用于三级及以上)。
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## 九、动态显示 / 隐藏 / 禁用
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
// 1. 动态隐藏:返回 true 则不渲染该项
|
|
269
|
+
{
|
|
270
|
+
label: '产品外码', prop: 'productOutCode', el: 'el-input',
|
|
271
|
+
hideFun: () => mode.value === 'add'
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// 2. 动态禁用:返回 true 则禁用
|
|
275
|
+
{
|
|
276
|
+
label: '名称', prop: 'name', el: 'el-input',
|
|
277
|
+
disabledFn: (form) => form.status === 0
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 3. 通过 props.disabled 静态禁用单个控件
|
|
281
|
+
{
|
|
282
|
+
label: '型号', prop: 'model', el: 'el-input',
|
|
283
|
+
props: { disabled: true }
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
> `hideFun` 直接控制 `v-if`,隐藏的字段不会出现在 DOM 中。
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## 十、表单分组
|
|
292
|
+
|
|
293
|
+
通过 `groupTitle` + `children` 将表单拆成多个卡片分组:
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
const queryItems = reactive<FormItemsProps[]>([
|
|
297
|
+
{
|
|
298
|
+
groupTitle: '基本信息',
|
|
299
|
+
prop: 'group1', // 分组需有唯一 prop
|
|
300
|
+
children: [
|
|
301
|
+
{ label: '姓名', prop: 'name', el: 'el-input', span: 12 },
|
|
302
|
+
{ label: '年龄', prop: 'age', el: 'el-input-number', span: 12 }
|
|
303
|
+
]
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
groupTitle: '附加信息',
|
|
307
|
+
prop: 'group2',
|
|
308
|
+
children: [ /* ... */ ]
|
|
309
|
+
}
|
|
310
|
+
])
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
配合 Props:
|
|
314
|
+
- `isCard` → 卡片样式(带边框 + 灰色头部)
|
|
315
|
+
- `showCollapsed` → 头部点击可折叠 / 展开(默认开启)
|
|
316
|
+
- 不需要分组时,直接平铺配置即可(无需 `groupTitle`/`children`)。
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## 十一、操作按钮
|
|
321
|
+
|
|
322
|
+
在 `queryItems` 末尾加一个 `prop: 'submitBtn'` 的特殊项,可渲染内置的操作按钮:
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
{
|
|
326
|
+
prop: 'submitBtn',
|
|
327
|
+
span: 24,
|
|
328
|
+
btnLList: [
|
|
329
|
+
{
|
|
330
|
+
id: 'confirm', // 内置:触发校验 + 获取格式化数据
|
|
331
|
+
text: '提交',
|
|
332
|
+
type: 'primary',
|
|
333
|
+
fun: (valid, form) => { // valid: 校验结果; form: 格式化后的数据
|
|
334
|
+
if (valid) saveApi(form)
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
id: 'reset', // 内置:重置表单
|
|
339
|
+
text: '重置',
|
|
340
|
+
fun: () => console.log('已重置')
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
id: 'custom', // 自定义 id:直接拿到格式化数据
|
|
344
|
+
text: '暂存',
|
|
345
|
+
fun: (form) => draftApi(form)
|
|
346
|
+
}
|
|
347
|
+
]
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
> 按钮也可以放在组件外部,通过 `ref` 调用 `onConfirm`(见 [第十三章](#十三暴露的方法ref-调用)),更灵活。
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## 十二、查看模式 vs 详情模式
|
|
356
|
+
|
|
357
|
+
| 模式 | 触发方式 | 表现 | 适用场景 |
|
|
358
|
+
| --- | --- | --- | --- |
|
|
359
|
+
| **编辑(默认)** | - | 正常可交互控件 | 新增 / 编辑 |
|
|
360
|
+
| **查看模式** | `is-show="true"` | 控件变为只读文本,下拉值自动反显为 label | 弹窗内只读查看 |
|
|
361
|
+
| **详情模式** | `is-detail="true"` | `label : value` 左右布局(灰 label + 黑 value),更紧凑 | 详情页、打印 |
|
|
362
|
+
| **整体禁用** | `disabled="true"` | 控件保留但变灰不可操作 | 审批中等场景 |
|
|
363
|
+
|
|
364
|
+
详情模式额外配置:
|
|
365
|
+
|
|
366
|
+
```ts
|
|
367
|
+
{
|
|
368
|
+
label: '描述', prop: 'remark', el: 'el-input',
|
|
369
|
+
detailProps: {
|
|
370
|
+
showProp: 'showRemark', // 详情展示用别的字段(编辑是id,展示是name)
|
|
371
|
+
showOverflowTooltip: true // 超长 Tooltip
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
> 单项也可独立设置 `isDetail: true`,与整体 `isDetail` 取 `item.isDetail ?? isDetail`。
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## 十三、暴露的方法(ref 调用)
|
|
381
|
+
|
|
382
|
+
通过 `ref` 可调用以下方法(`defineExpose`):
|
|
383
|
+
|
|
384
|
+
| 方法 | 签名 | 说明 |
|
|
385
|
+
| --- | --- | --- |
|
|
386
|
+
| `onConfirm` | `(callback?) => Promise` | **常用**。先校验,再回调 `callback(valid, formatForm)` |
|
|
387
|
+
| `onReset` | `(callback?) => void` | 重置表单字段 + 清除校验 |
|
|
388
|
+
| `validateForm` | `() => Promise<boolean>` | 校验整个表单,返回是否通过 |
|
|
389
|
+
| `validateFields` | `(fields) => Promise<boolean>` | 校验指定字段(string 或 string[]) |
|
|
390
|
+
| `clearValidateForm` | `() => void` | 清除校验结果 |
|
|
391
|
+
| `getFormatForm` | `(form?) => object` | 拿到格式化后的数据(处理范围字段、空值,见下) |
|
|
392
|
+
| `formRef` | `FormInstance` | Element Plus `el-form` 实例,可调用任意原生方法 |
|
|
393
|
+
|
|
394
|
+
**外部提交按钮的标准写法:**
|
|
395
|
+
|
|
396
|
+
```vue
|
|
397
|
+
<template>
|
|
398
|
+
<JztFormGrid ref="formRef" :query-items="columns" :rule-form="form" />
|
|
399
|
+
<el-button type="primary" @click="onSubmit">提交</el-button>
|
|
400
|
+
<el-button @click="onReset">重置</el-button>
|
|
401
|
+
</template>
|
|
402
|
+
|
|
403
|
+
<script setup lang="ts">
|
|
404
|
+
const formRef = ref<JztFormInstance>()
|
|
405
|
+
|
|
406
|
+
const onSubmit = () => {
|
|
407
|
+
formRef.value?.onConfirm((valid, formData) => {
|
|
408
|
+
if (valid) saveApi(formData) // formData 已自动处理范围字段、空值
|
|
409
|
+
})
|
|
410
|
+
}
|
|
411
|
+
const onReset = () => {
|
|
412
|
+
formRef.value?.onReset(() => console.log('重置成功'))
|
|
413
|
+
}
|
|
414
|
+
</script>
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## 十四、插槽
|
|
420
|
+
|
|
421
|
+
| 插槽名 | 说明 |
|
|
422
|
+
| --- | --- |
|
|
423
|
+
| `footer` | 表单底部内容 |
|
|
424
|
+
| `header_right` | 分组标题右侧区域(仅 `showCollapsed=false` 时生效) |
|
|
425
|
+
| `自定义 slotName` | 在表单项配置 `slotName` 后,用 `<template #你的slotName>` 完全自定义该项内容 |
|
|
426
|
+
|
|
427
|
+
```vue
|
|
428
|
+
<JztFormGrid :query-items="[{ label: '年级', prop: 'grade', el: 'el-select', slotName: 'mySlot' }]" :rule-form="form">
|
|
429
|
+
<template #mySlot>
|
|
430
|
+
<el-radio-group v-model="form.grade">...</el-radio-group>
|
|
431
|
+
</template>
|
|
432
|
+
</JztFormGrid>
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## 十五、自定义渲染(tsx)
|
|
438
|
+
|
|
439
|
+
需要 `<script lang="tsx" setup>`。所有 render 函数都接收上下文(如 `form`),返回 VNode。
|
|
440
|
+
|
|
441
|
+
| 字段 | 作用位置 |
|
|
442
|
+
| --- | --- |
|
|
443
|
+
| `labelRender` | 自定义 label 内容 |
|
|
444
|
+
| `render` | 自定义表单项内容(编辑 + 查看通用) |
|
|
445
|
+
| `showRender` | 详情 / 查看态内容(优先级高于 `render`) |
|
|
446
|
+
| `outerRender` | 在 form-item **同层级外部**渲染 |
|
|
447
|
+
| `rightRender` | 控件右侧追加内容 |
|
|
448
|
+
| `valueLeftRender` / `valueRightRender` | 详情模式下 value 左右追加内容 |
|
|
449
|
+
|
|
450
|
+
```tsx
|
|
451
|
+
{
|
|
452
|
+
label: '年龄', prop: 'age', el: 'el-input',
|
|
453
|
+
labelRender: () => <span style="color: red">年龄:</span>,
|
|
454
|
+
render: ({ form }) => <span>男生({form.age})</span>
|
|
455
|
+
}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
## 十六、时间 / 数字范围字段
|
|
461
|
+
|
|
462
|
+
将一个**数组型控件**的值,拆 / 合到两个独立字段提交。
|
|
463
|
+
|
|
464
|
+
```ts
|
|
465
|
+
// 时间范围:UI 是一个 datetimerange,提交时拆成 startTime / endTime
|
|
466
|
+
{
|
|
467
|
+
label: '事件范围', prop: 'timeList', el: 'el-date-picker',
|
|
468
|
+
startField: 'startTime',
|
|
469
|
+
endField: 'endTime',
|
|
470
|
+
props: { type: 'datetimerange', valueFormat: 'YYYY-MM-DD HH:mm:ss' }
|
|
471
|
+
}
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
**机制(在 `index.vue` 的 `queryFrom` / `getFormatForm` 中处理):**
|
|
475
|
+
- **回显**:若 `startTime`/`endTime` 有值,自动合并为 `[startTime, endTime]` 赋给 `prop`。
|
|
476
|
+
- **提交**:`getFormatForm` 把数组拆回 `startTime`/`endTime`,并删除中间字段 `timeList`。
|
|
477
|
+
|
|
478
|
+
数字范围同理(`el: 'JztNumericalRange'` + `startField`/`endField`)。
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## 十七、常见踩坑点
|
|
483
|
+
|
|
484
|
+
1. **`ruleForm` 必须是响应式对象**:用 `reactive({})` 或 `ref({})`,否则值无法更新。直接修改 `ruleForm.xxx = yyy` 即可双向绑定,无需 `emit`。
|
|
485
|
+
|
|
486
|
+
2. **`prop` 必须唯一**:作为 `el-form-item` 的 key 与校验字段,重复会导致校验错乱。分组项也要有唯一 `prop`。
|
|
487
|
+
|
|
488
|
+
3. **接口字段不是 label/value 时务必配 `fieldNames`**:否则下拉显示空白或 value 错误。
|
|
489
|
+
|
|
490
|
+
4. **级联下级要加 `isAwait`(详情回显场景)**:否则下级在上级还没值时就请求,拿不到数据。
|
|
491
|
+
|
|
492
|
+
5. **查看 / 详情模式下,`ruleForm` 也要有对应字段的值**,否则显示空占位符(`infoEmptyText`,默认 `-`)。
|
|
493
|
+
|
|
494
|
+
6. **范围字段提交后被删除**:`timeList` 这类中间字段在 `getFormatForm` 后会被删除,只保留 `startField`/`endField`,接口以此为准。
|
|
495
|
+
|
|
496
|
+
7. **空值默认值 `GUIDNoneValue`**:后端某些字段(如供应商、机构)要求不选时传全零 GUID `00000000-0000-0000-0000-000000000000`。配置:
|
|
497
|
+
```ts
|
|
498
|
+
{ prop: 'agencyId', isSetEmptyData: true, emptyNullValue: userStore.userAgencyId }
|
|
499
|
+
```
|
|
500
|
+
- 初始化(页面):取 `emptyNullValue`
|
|
501
|
+
- 提交(结果):空值时填 `GUIDNoneValue`
|
|
502
|
+
|
|
503
|
+
8. **`queryItems` 变更会重新深拷贝**:组件内部对 `queryItems` 做了 `cloneDeep`(避免折叠状态互相影响),所以**不要在配置里放函数闭包外还能被外部修改的引用**,且动态改配置请整体替换数组。
|
|
504
|
+
|
|
505
|
+
9. **`submitBtn` 与外部按钮二选一**:内部按钮交互固定,需要自定义交互(如 loading、二次确认)时,建议用外部按钮 + `onConfirm`。
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## 附:完整真实示例参考
|
|
510
|
+
|
|
511
|
+
仓库内已有丰富示例,新同学可直接参考:
|
|
512
|
+
|
|
513
|
+
| 场景 | 参考文件 |
|
|
514
|
+
| --- | --- |
|
|
515
|
+
| **官方示例(含全配置演示)** | `spd-main-layout/src/views/assembly/form/useFormGrid/index.vue` |
|
|
516
|
+
| 复杂编辑表单(产品主信息) | `spd-basic-system/src/components/ProductForm/MainInfo.vue` |
|
|
517
|
+
| 上传文件表单 | `spd-main-layout/src/views/assembly/uploadFile/components/formFile.vue` |
|
|
518
|
+
| 弹窗新增 / 编辑 | `spd-basic-system/src/views/order/equipmentApplicationForm/components/addDialog.vue` |
|
|
519
|
+
|
|
520
|
+
> 建议先把 `useFormGrid/index.vue` 这个官方 Demo 跑起来,对照本文档逐项调试,能最快上手。
|