vue-2024-ui 0.0.6 → 0.0.8
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/README.form.md +283 -0
- package/README.md +214 -31
- package/README.table.md +58 -0
- package/package.json +1 -1
- package/src/components/g-form.vue +56 -76
- package/src/components/g-table.vue +98 -81
- package/src/components/index.d.ts +34 -0
- package/src/components/index.js +119 -3
package/README.form.md
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# form 表单 [表格](./README.table.md)
|
|
2
|
+
|
|
3
|
+
## FORM API 文档说明
|
|
4
|
+
|
|
5
|
+
### 所有属性和方法都在element-plus FORM 基础上扩展,只显示增加的属性和方法,安装前必须安装element-plus,其他原有属性和方法请查询 [官方文档](https://element-plus.org/zh-CN/component/form.html)
|
|
6
|
+
|
|
7
|
+
## form 表单新增属性
|
|
8
|
+
|
|
9
|
+
| 属性 | 说明 | 类型 | 默认值 |
|
|
10
|
+
| --------- | ------------------------------------------------------------------- | ------ | -------------------------------------------------------------------------------------------- |
|
|
11
|
+
| columns | 表单列数 | number | 2 |
|
|
12
|
+
| footer | 控制表单底部按钮是否显示和样式设置,hidden默认是false,style控制样式 | object | ```{hidden: false, style:{ display:"flex","align-items":"center",justifyContent:"center"}``` |
|
|
13
|
+
| submit | 表单提交按钮,增加了label,hidden 属性和click方法 | object | ``` {type: "primary",label: "提交",hidden: false,click: async (model) => {...}, } ``` |
|
|
14
|
+
| reset | 表单重置按钮,增加了label,hidden 属性和click方法 | object | ``` {type: "primary",label: "重置",hidden: false,click: () => {...}, } ``` |
|
|
15
|
+
| formItems | 通过style属性设置所有items的样式 | object | ``` style:{width:100%} ``` |
|
|
16
|
+
|
|
17
|
+
## form 增加的事件
|
|
18
|
+
|
|
19
|
+
| 事件名 | 说明 | 类型 | 参数 |
|
|
20
|
+
| ------ | ------------ | -------- | -------------------------------------------------------------------------------------- |
|
|
21
|
+
| submit | 表单提交事件 | function | formData:表单要提交的数据 |
|
|
22
|
+
| reset | 表单重置事件 | function | callback(isCustReset=false) 默认已处理了重置事件,如要重写事件则回调时传入true后再处理 |
|
|
23
|
+
|
|
24
|
+
## form 重写 slots 增加的插槽
|
|
25
|
+
|
|
26
|
+
| 插槽名 | 说明 |
|
|
27
|
+
| ------------ | ---------------------- |
|
|
28
|
+
| footer | 重写表单footer功能按钮 |
|
|
29
|
+
| footer-left | 表单左边增加按钮 |
|
|
30
|
+
| footer-right | 表单右边增加按钮 |
|
|
31
|
+
|
|
32
|
+
## formItem 重写 slots
|
|
33
|
+
|
|
34
|
+
| 插槽名 | 说明 |
|
|
35
|
+
| --------------------- | -------------------------------- |
|
|
36
|
+
| item.key | 重写表单项item |
|
|
37
|
+
| item.key-label | 重写表单项标题 |
|
|
38
|
+
| item.key-ctrl | 重写表单控件 |
|
|
39
|
+
| item.key-ctrl-options | 重写表单控件有options的字项items |
|
|
40
|
+
|
|
41
|
+
## formItem 验证 validate
|
|
42
|
+
|
|
43
|
+
| 属性名 | 说明 | 类型 |
|
|
44
|
+
| -------- | ---------------------------------------------------- | -------- |
|
|
45
|
+
| required | 必输字段 | boolean |
|
|
46
|
+
| max,min | 字符串验证最大长度和最小长度,数字验证最大值和最小值 | number |
|
|
47
|
+
| type | number,email,url 验证数字,邮箱,网址 | string |
|
|
48
|
+
| pattern | 正则验证 | regex |
|
|
49
|
+
| validate | 自定义验证 validate(rule, value, callback) | function |
|
|
50
|
+
|
|
51
|
+
## 示例
|
|
52
|
+
|
|
53
|
+
### html
|
|
54
|
+
|
|
55
|
+
``` html
|
|
56
|
+
<template>
|
|
57
|
+
<div>
|
|
58
|
+
<g-form v-model="formData">
|
|
59
|
+
<template #age-label="{ item, model, data }">
|
|
60
|
+
</template>
|
|
61
|
+
<template #age-ctrl-suffix> 岁 </template>
|
|
62
|
+
<template #birthday-ctrl-default="cell">
|
|
63
|
+
{{ cell.data.data.text + "-" }}
|
|
64
|
+
</template>
|
|
65
|
+
<template #email-ctrl-suffix="{ model }">
|
|
66
|
+
{{ model.email.label }}
|
|
67
|
+
</template>
|
|
68
|
+
|
|
69
|
+
<template #sex-ctrl="{ data }">
|
|
70
|
+
<el-radio-button :value="1">男</el-radio-button>
|
|
71
|
+
<el-radio-button :value="2">女</el-radio-button>
|
|
72
|
+
</template>
|
|
73
|
+
<template #isDisabled-ctrl="{ data }">
|
|
74
|
+
{{ data.data.value }}
|
|
75
|
+
</template>
|
|
76
|
+
<template #hobby-ctrl="{ data }">
|
|
77
|
+
<el-checkbox-button :value="1">画画</el-checkbox-button>
|
|
78
|
+
<el-checkbox-button :value="2">游戏</el-checkbox-button>
|
|
79
|
+
</template>
|
|
80
|
+
<template #switch-ctrl-active-action>
|
|
81
|
+
<span class="custom-active-action">T</span>
|
|
82
|
+
</template>
|
|
83
|
+
<template #switch-ctrl-inactive-action>
|
|
84
|
+
<span class="custom-inactive-action">F</span>
|
|
85
|
+
</template>
|
|
86
|
+
<template #select-ctrl-empty="{ data }">
|
|
87
|
+
<el-button type="primary" size="small">增加</el-button>
|
|
88
|
+
</template>
|
|
89
|
+
<template #select2-ctrl-options="{ data }">111 </template>
|
|
90
|
+
<template #select-ctrl-options="{ data }">
|
|
91
|
+
<el-option :label="data.option.label" :value="data.option.value">
|
|
92
|
+
<div style="display: flex;color:#666;border:1px solid #ccc;width:300px;align-items: center;padding:5px;">
|
|
93
|
+
<b>》》</b>
|
|
94
|
+
<span style="display: flex;align-items: center; padding:3px 10px;height: 100%;">{{
|
|
95
|
+
data.option.label
|
|
96
|
+
}}</span>
|
|
97
|
+
</div>
|
|
98
|
+
</el-option>
|
|
99
|
+
</template>
|
|
100
|
+
<template #autoComplete-ctrl-default="{ data }">
|
|
101
|
+
{{ data.data.item.value + '--' + data.data.item.label }}
|
|
102
|
+
</template>
|
|
103
|
+
<template #autoComplete-ctrl-suffix="{ data }">
|
|
104
|
+
auto
|
|
105
|
+
</template>
|
|
106
|
+
<template #autoComplete-ctrl-prefix="{ data }">
|
|
107
|
+
P
|
|
108
|
+
</template>
|
|
109
|
+
<template #transfer-ctrl-default="{ data }">
|
|
110
|
+
{{ data.data.option.label }}
|
|
111
|
+
</template>
|
|
112
|
+
<template #upload-ctrl-default="{ data }">
|
|
113
|
+
<el-button style="width:100px">+</el-button>
|
|
114
|
+
</template>
|
|
115
|
+
<template #footer-left>
|
|
116
|
+
<el-button plain type="info" @click="formData.form.inline = false; formData.form.columns = 1">inline</el-button>
|
|
117
|
+
<el-button plain type="info" @click="formData.form.inline = true; formData.form.columns = 1">一倍</el-button>
|
|
118
|
+
<el-button plain type="success" @click="formData.form.inline = true; formData.form.columns = 2">二倍</el-button>
|
|
119
|
+
</template>
|
|
120
|
+
<template #footer-right v-if="formData.form.columns !== 1">
|
|
121
|
+
<el-button plain type="warning" @click="formData.form.inline = true; formData.form.columns = 3">三倍</el-button>
|
|
122
|
+
<el-button plain type="danger" @click="formData.form.inline = true; formData.form.columns = 4">四倍</el-button>
|
|
123
|
+
</template>
|
|
124
|
+
</g-form>
|
|
125
|
+
</div>
|
|
126
|
+
</template>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### javascript
|
|
130
|
+
|
|
131
|
+
``` javascript
|
|
132
|
+
|
|
133
|
+
<script setup>
|
|
134
|
+
import { ref ,reactive} from "vue";
|
|
135
|
+
const formData=reactive({
|
|
136
|
+
form:{
|
|
137
|
+
submit: { hidden: false },
|
|
138
|
+
reset: { hidden: false },
|
|
139
|
+
rules: {
|
|
140
|
+
"email.value": [{ required: true, message: "邮箱不能为空" }, { type: "email", message: "邮箱格式不正确" }],
|
|
141
|
+
"url.value": [{ required: true, message: "官网地址不能为空" }, { type: "url", message: "URL格式不正确" }],
|
|
142
|
+
},
|
|
143
|
+
columns: 2,
|
|
144
|
+
style: { boxShadow: null ,margin:"10px"},
|
|
145
|
+
},
|
|
146
|
+
id: { label: "ID", type: "number", value: 1, disabled: true, edit: false },
|
|
147
|
+
name: {
|
|
148
|
+
label: "姓名", value: "张三", width: 100,
|
|
149
|
+
max: 5,// min: 2,
|
|
150
|
+
//required: true,
|
|
151
|
+
rules: [{ required: true, message: "行内姓名不能为空!" }],
|
|
152
|
+
columns: 2, "show-word-limit": true,
|
|
153
|
+
formItem: { style: { border: "0px solid #fff", padding: "0px" } },
|
|
154
|
+
formatter: (row, column, cellValue, index) => row.name, sortable: true,
|
|
155
|
+
decimals: 2,
|
|
156
|
+
// suffix: " ¥", prefix: '--', width: 150
|
|
157
|
+
},
|
|
158
|
+
age: { label: "年龄", type: "number", value: 18, min: 16, decimals: { num: 0, pad: true }, suffix: '岁', sortable: true },
|
|
159
|
+
email: { label: "邮箱", type: "email", value: "xg816620@163.com", width: 400 },
|
|
160
|
+
qq: { label: "QQ", type: "number", value: 123456789, edit: false },
|
|
161
|
+
url: {
|
|
162
|
+
label: "官网地址", type: "url", columns: 2, value: "https://www.baidu.com", width: 260, align: 'left'
|
|
163
|
+
},
|
|
164
|
+
address: {
|
|
165
|
+
label: "地址", columns: 2, type: "textarea", maxlength: 50, width: 120, value: "重庆巴南万达广场B座22楼1-8", required: true, validate: (rule, value, callback) => {
|
|
166
|
+
if (value.length > 50) {
|
|
167
|
+
callback("字符串长度不能超过20个");
|
|
168
|
+
} else {
|
|
169
|
+
callback();
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
formItem: { "label-position": "right" }, suffix: '#'
|
|
173
|
+
},
|
|
174
|
+
sex: {
|
|
175
|
+
label: "性别", type: "radio", value: 2, options: [
|
|
176
|
+
{ label: "男", value: 1, border: false },
|
|
177
|
+
{ label: "女", value: 2, border: false }]
|
|
178
|
+
},
|
|
179
|
+
select: {
|
|
180
|
+
label: "选择分组", type: "select", value: 2, filterable: true, remote: true,
|
|
181
|
+
change: (a) => { console.log(a); },
|
|
182
|
+
"remote-method": (query, callback) => {
|
|
183
|
+
new Promise((resolve, reject) => {
|
|
184
|
+
setTimeout(() => {
|
|
185
|
+
resolve([
|
|
186
|
+
{ label: "apple", value: 1 },
|
|
187
|
+
{ label: "xiaomi", value: 2 },
|
|
188
|
+
{ label: "huawei", value: 3 },
|
|
189
|
+
{ label: "oppo", value: 4, disabled: "disabled" }
|
|
190
|
+
].filter(a => a.label.includes(query)))
|
|
191
|
+
}, 1000);
|
|
192
|
+
}).then(a => {
|
|
193
|
+
callback(a)
|
|
194
|
+
})
|
|
195
|
+
},
|
|
196
|
+
options: [
|
|
197
|
+
{
|
|
198
|
+
label: "手机", options: [
|
|
199
|
+
{ label: "apple", value: 1 },
|
|
200
|
+
{ label: "xiaomi", value: 2 },
|
|
201
|
+
{ label: "huawei", value: 3 }
|
|
202
|
+
],
|
|
203
|
+
}, {
|
|
204
|
+
label: "电器", options: [
|
|
205
|
+
{ label: "电视", value: 4 },
|
|
206
|
+
{ label: "冰箱", value: 5 },
|
|
207
|
+
{ label: "空调", value: 6 }
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
]
|
|
211
|
+
},
|
|
212
|
+
select2: {
|
|
213
|
+
label: "选择", type: "select", value: 3, visible: false,
|
|
214
|
+
options: [
|
|
215
|
+
{ label: "apple", value: 1 },
|
|
216
|
+
{ label: "xiaomi", value: 2 },
|
|
217
|
+
{ label: "huawei", value: 3 }
|
|
218
|
+
]
|
|
219
|
+
},
|
|
220
|
+
birthday: {
|
|
221
|
+
label: "生日", type: "datetime", value: "1985-01-01", dateFormat: "YYYY年MM月DD日", width: 200,
|
|
222
|
+
//prefix: '日期',
|
|
223
|
+
suffix: '@',
|
|
224
|
+
style: { width: "100%" },
|
|
225
|
+
},
|
|
226
|
+
hobby: {
|
|
227
|
+
label: "爱好", columns: 2, type: "checkbox-group", value: [1, 2], options: [
|
|
228
|
+
{ label: "跑步", value: 1, border: false },
|
|
229
|
+
{ label: "乒乓球", value: 2, border: false },
|
|
230
|
+
{ label: "看书", value: 3, border: false },
|
|
231
|
+
{ label: "学习", value: 4, border: false },],
|
|
232
|
+
},
|
|
233
|
+
time: {
|
|
234
|
+
label: "时间", type: "time", value: new Date(1985, 1, 1, 18, 20, 33), dateFormat: "time",
|
|
235
|
+
//prefix: '日期',
|
|
236
|
+
suffix: '@', width: 150,
|
|
237
|
+
style: { width: "100%" },
|
|
238
|
+
},
|
|
239
|
+
timeSelect: { label: "下拉时间", type: "time-select", value: "08:30", dateFormat: "mm:ss" },
|
|
240
|
+
isDisabled: { label: "是否启用", type: "checkbox", value: "是", trueValue: "是", formatter: (row, column, cellValue, index) => row.isDisabled == 2 ? '是' : '否' },
|
|
241
|
+
rate: { label: "星级", type: "rate", value: 3, suffix: '星级' },
|
|
242
|
+
slider: { label: "进度", type: "slider", value: 0.3, style: { width: "90%" }, visible: false, },
|
|
243
|
+
switch: {
|
|
244
|
+
label: "开关", type: "switch", value: 3, inlinePrompt: true, visible: false,
|
|
245
|
+
activeText: "完整展示多个内容",
|
|
246
|
+
inactiveText: "多个内容"
|
|
247
|
+
},
|
|
248
|
+
autoComplete: {
|
|
249
|
+
label: "建议输入", type: "autocomplete", value: "李四", visible: false, fetchSuggestions: (a, b) =>
|
|
250
|
+
b([{ value: "三四", label: 1 }, { value: "张三", label: 2 }, { value: "三德子", label: 3 }, { value: "李四", label: 4 }]
|
|
251
|
+
.filter((c) => c.value.toString().includes(a))), onSelect: (a) => console.log(a)
|
|
252
|
+
},
|
|
253
|
+
cascader: {
|
|
254
|
+
label: "级联选择", type: "cascader", value: [1, 2, 3], visible: false, options: [
|
|
255
|
+
{ label: "父一", value: 1, children: [{ label: "父二", value: 2, children: [{ label: "父三", value: 3 }] }] },
|
|
256
|
+
{ label: "父四", value: 4 },
|
|
257
|
+
{ label: "父五", value: 5 },
|
|
258
|
+
]
|
|
259
|
+
},
|
|
260
|
+
transfer: {
|
|
261
|
+
label: "穿梭框", type: "hidden", columns: 4, value: [1, 2], titles: ['全部', "已选"], visible: false,
|
|
262
|
+
filterable: true, data: [
|
|
263
|
+
{ label: "选项一", key: 1 }, { label: "选项二", key: 2 }, { label: "选项三", key: 3 }, { label: "选项四", key: 4 },
|
|
264
|
+
{ label: "选项五", key: 5 }, { label: "选项六", key: 6 }, { label: "选项七", key: 7 }, { label: "选项八", key: 8 },
|
|
265
|
+
],
|
|
266
|
+
|
|
267
|
+
},
|
|
268
|
+
treeSelect: {
|
|
269
|
+
label: "树形选择", type: "tree-select", value: [1, 2, 3], visible: false,
|
|
270
|
+
"show-checkbox": true, data: [
|
|
271
|
+
{
|
|
272
|
+
label: "父一", value: 1, children: [
|
|
273
|
+
{ label: "父二", value: 2, children: [{ label: "父三", value: 3 }] }]
|
|
274
|
+
},
|
|
275
|
+
{ label: "父四", value: 4 },
|
|
276
|
+
{ label: "父五", value: 5 },
|
|
277
|
+
],
|
|
278
|
+
}
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
</script>
|
|
282
|
+
|
|
283
|
+
```
|
package/README.md
CHANGED
|
@@ -1,48 +1,231 @@
|
|
|
1
1
|
# vue-2024-ui
|
|
2
2
|
|
|
3
|
-
vue + element-plus
|
|
3
|
+
## vue + element-plus
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
``` javascript
|
|
8
|
+
import { createApp } from 'vue'
|
|
9
|
+
import ElementPlus from 'element-plus'
|
|
10
|
+
import 'element-plus/dist/index.css'
|
|
11
|
+
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
|
12
|
+
import App from './App.vue'
|
|
13
|
+
import gyc from 'vue-2024-ui'
|
|
14
|
+
const app = createApp(App)
|
|
15
|
+
app.use(ElementPlus, {
|
|
16
|
+
locale: zhCn,
|
|
17
|
+
})
|
|
18
|
+
app.use(gyc)
|
|
19
|
+
app.mount('#app')
|
|
4
20
|
|
|
5
|
-
```
|
|
6
|
-
npm i vue-2024-ui
|
|
7
21
|
```
|
|
8
22
|
|
|
9
|
-
|
|
10
|
-
### 所有属性和方法都在element-plus 基础上扩展,只显示增加的属性和方法,安装前必须安装element-plus,其他原有属性和方法请查询 [官方文档](https://element-plus.org/zh-CN/component/overview.html)
|
|
23
|
+
[表单](./README.form.md) [表格](./README.table.md)
|
|
11
24
|
|
|
12
|
-
|
|
25
|
+
## 快速上手
|
|
13
26
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
``` html
|
|
28
|
+
<template>
|
|
29
|
+
<div>
|
|
30
|
+
<g-table v-model="formData" @select-all="all" @load="load" @delete="del" @view="read">
|
|
31
|
+
</g-table>
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|
|
21
34
|
|
|
35
|
+
```
|
|
22
36
|
|
|
23
|
-
|
|
37
|
+
``` javascript
|
|
38
|
+
<script setup>
|
|
39
|
+
import { reactive } from "vue";
|
|
40
|
+
const formData = reactive({
|
|
41
|
+
id: { label: "ID", type: "number", value: 1, disabled: true, edit: false },
|
|
42
|
+
name: {
|
|
43
|
+
label: "姓名", value: "张三", width: 100,
|
|
44
|
+
max: 5,// min: 2,
|
|
45
|
+
//required: true,
|
|
46
|
+
rules: [{ required: true, message: "行内姓名不能为空!" }],
|
|
47
|
+
columns: 2, "show-word-limit": true,
|
|
48
|
+
formItem: { style: { border: "0px solid #fff", padding: "0px" } },
|
|
49
|
+
formatter: (row, column, cellValue, index) => row.name, sortable: true,
|
|
50
|
+
decimals: 2,
|
|
51
|
+
// suffix: " ¥", prefix: '--', width: 150
|
|
52
|
+
},
|
|
53
|
+
age: { label: "年龄", type: "number", value: 18, min: 16, decimals: { num: 0, pad: true }, suffix: '岁', sortable: true },
|
|
54
|
+
email: { label: "邮箱", type: "email", value: "xg816620@163.com", width: 400 },
|
|
55
|
+
qq: { label: "QQ", type: "number", value: 123456789, edit: false },
|
|
56
|
+
url: {
|
|
57
|
+
label: "官网地址", type: "url", columns: 2, value: "https://www.baidu.com", width: 260, align: 'left'
|
|
58
|
+
},
|
|
59
|
+
address: {
|
|
60
|
+
label: "地址", columns: 2, type: "textarea", maxlength: 50, width: 120, value: "重庆巴南万达广场B座22楼1-8", required: true, validate: (rule, value, callback) => {
|
|
61
|
+
if (value.length > 50) {
|
|
62
|
+
callback("字符串长度不能超过20个");
|
|
63
|
+
} else {
|
|
64
|
+
callback();
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
formItem: { "label-position": "right" }, suffix: '#'
|
|
68
|
+
},
|
|
69
|
+
sex: {
|
|
70
|
+
label: "性别", type: "radio", value: 2, options: [
|
|
71
|
+
{ label: "男", value: 1, border: false },
|
|
72
|
+
{ label: "女", value: 2, border: false }]
|
|
73
|
+
},
|
|
74
|
+
select: {
|
|
75
|
+
label: "选择分组", type: "select", value: 2, filterable: true, remote: true,
|
|
76
|
+
change: (a) => { console.log(a); },
|
|
77
|
+
"remote-method": (query, callback) => {
|
|
78
|
+
new Promise((resolve, reject) => {
|
|
79
|
+
setTimeout(() => {
|
|
80
|
+
resolve([
|
|
81
|
+
{ label: "apple", value: 1 },
|
|
82
|
+
{ label: "xiaomi", value: 2 },
|
|
83
|
+
{ label: "huawei", value: 3 },
|
|
84
|
+
{ label: "oppo", value: 4, disabled: "disabled" }
|
|
85
|
+
].filter(a => a.label.includes(query)))
|
|
86
|
+
}, 1000);
|
|
87
|
+
}).then(a => {
|
|
88
|
+
callback(a)
|
|
89
|
+
})
|
|
90
|
+
},
|
|
91
|
+
options: [
|
|
92
|
+
{
|
|
93
|
+
label: "手机", options: [
|
|
94
|
+
{ label: "apple", value: 1 },
|
|
95
|
+
{ label: "xiaomi", value: 2 },
|
|
96
|
+
{ label: "huawei", value: 3 }
|
|
97
|
+
],
|
|
98
|
+
}, {
|
|
99
|
+
label: "电器", options: [
|
|
100
|
+
{ label: "电视", value: 4 },
|
|
101
|
+
{ label: "冰箱", value: 5 },
|
|
102
|
+
{ label: "空调", value: 6 }
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
select2: {
|
|
108
|
+
label: "选择", type: "select", value: 3, visible: false,
|
|
109
|
+
options: [
|
|
110
|
+
{ label: "apple", value: 1 },
|
|
111
|
+
{ label: "xiaomi", value: 2 },
|
|
112
|
+
{ label: "huawei", value: 3 }
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
birthday: {
|
|
116
|
+
label: "生日", type: "datetime", value: "1985-01-01", dateFormat: "YYYY年MM月DD日", width: 200,
|
|
117
|
+
//prefix: '日期',
|
|
118
|
+
suffix: '@',
|
|
119
|
+
style: { width: "100%" },
|
|
120
|
+
},
|
|
121
|
+
hobby: {
|
|
122
|
+
label: "爱好", columns: 2, type: "checkbox-group", value: [1, 2], options: [
|
|
123
|
+
{ label: "跑步", value: 1, border: false },
|
|
124
|
+
{ label: "乒乓球", value: 2, border: false },
|
|
125
|
+
{ label: "看书", value: 3, border: false },
|
|
126
|
+
{ label: "学习", value: 4, border: false },],
|
|
127
|
+
},
|
|
128
|
+
time: {
|
|
129
|
+
label: "时间", type: "time", value: new Date(1985, 1, 1, 18, 20, 33), dateFormat: "time",
|
|
130
|
+
//prefix: '日期',
|
|
131
|
+
suffix: '@', width: 150,
|
|
132
|
+
style: { width: "100%" },
|
|
133
|
+
},
|
|
134
|
+
timeSelect: { label: "下拉时间", type: "time-select", value: "08:30", dateFormat: "mm:ss" },
|
|
135
|
+
isDisabled: { label: "是否启用", type: "checkbox", value: "是", trueValue: "是", formatter: (row, column, cellValue, index) => row.isDisabled == 2 ? '是' : '否' },
|
|
136
|
+
rate: { label: "星级", type: "rate", value: 3, suffix: '星级' },
|
|
137
|
+
slider: { label: "进度", type: "slider", value: 0.3, style: { width: "90%" }, visible: false, },
|
|
138
|
+
switch: {
|
|
139
|
+
label: "开关", type: "switch", value: 3, inlinePrompt: true, visible: false,
|
|
140
|
+
activeText: "完整展示多个内容",
|
|
141
|
+
inactiveText: "多个内容"
|
|
142
|
+
},
|
|
143
|
+
autoComplete: {
|
|
144
|
+
label: "建议输入", type: "autocomplete", value: "李四", visible: false, fetchSuggestions: (a, b) =>
|
|
145
|
+
b([{ value: "三四", label: 1 }, { value: "张三", label: 2 }, { value: "三德子", label: 3 }, { value: "李四", label: 4 }]
|
|
146
|
+
.filter((c) => c.value.toString().includes(a))), onSelect: (a) => console.log(a)
|
|
147
|
+
},
|
|
148
|
+
cascader: {
|
|
149
|
+
label: "级联选择", type: "cascader", value: [1, 2, 3], visible: false, options: [
|
|
150
|
+
{ label: "父一", value: 1, children: [{ label: "父二", value: 2, children: [{ label: "父三", value: 3 }] }] },
|
|
151
|
+
{ label: "父四", value: 4 },
|
|
152
|
+
{ label: "父五", value: 5 },
|
|
153
|
+
]
|
|
154
|
+
},
|
|
155
|
+
transfer: {
|
|
156
|
+
label: "穿梭框", type: "hidden", columns: 4, value: [1, 2], titles: ['全部', "已选"], visible: false,
|
|
157
|
+
filterable: true, data: [
|
|
158
|
+
{ label: "选项一", key: 1 }, { label: "选项二", key: 2 }, { label: "选项三", key: 3 }, { label: "选项四", key: 4 },
|
|
159
|
+
{ label: "选项五", key: 5 }, { label: "选项六", key: 6 }, { label: "选项七", key: 7 }, { label: "选项八", key: 8 },
|
|
160
|
+
],
|
|
24
161
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
162
|
+
},
|
|
163
|
+
treeSelect: {
|
|
164
|
+
label: "树形选择", type: "tree-select", value: [1, 2, 3], visible: false,
|
|
165
|
+
"show-checkbox": true, data: [
|
|
166
|
+
{
|
|
167
|
+
label: "父一", value: 1, children: [
|
|
168
|
+
{ label: "父二", value: 2, children: [{ label: "父三", value: 3 }] }]
|
|
169
|
+
},
|
|
170
|
+
{ label: "父四", value: 4 },
|
|
171
|
+
{ label: "父五", value: 5 },
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
upload: {
|
|
175
|
+
label: "上传", type: "upload", value: [], style: { width: "100%" }, visible: false,
|
|
176
|
+
action: "https://jsonplaceholder.typicode.com/posts/",
|
|
177
|
+
headers: { "Authorization": "Bearer " + "your-token" }
|
|
178
|
+
},
|
|
179
|
+
control:{
|
|
180
|
+
view:{
|
|
181
|
+
auth:()=>true
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
});
|
|
29
185
|
|
|
186
|
+
const createData = () => {
|
|
187
|
+
const list = [];
|
|
188
|
+
for (let i = 0; i < 210; i++) {
|
|
189
|
+
list.push({
|
|
190
|
+
id: i + 1,
|
|
191
|
+
name: `测试数据${i + 1}`,
|
|
192
|
+
age: Math.floor(Math.random() * 30) + 18,
|
|
193
|
+
email: `test${i + 1}@example.com`,
|
|
194
|
+
sex: (Math.floor(Math.random() * 2) + 1) == 2 ? '男' : '女',
|
|
195
|
+
select: 'xiaomi',
|
|
196
|
+
hobby: `测试数据${i + 1}`,
|
|
197
|
+
url: `https://www.example.com/test${i + 1}`,
|
|
198
|
+
birthday: new Date(2000, Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1),
|
|
199
|
+
address: `测试地址${i + 1}`,
|
|
200
|
+
rate: Math.floor(Math.random() * 5) + 1,
|
|
201
|
+
timeSelect: new Date(),
|
|
202
|
+
isDisabled: Math.floor(Math.random() * 2) + 1,
|
|
203
|
+
qq: "123"
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return list;
|
|
207
|
+
};
|
|
30
208
|
|
|
209
|
+
const del = (row, back) => {
|
|
210
|
+
back(true)
|
|
211
|
+
}
|
|
31
212
|
|
|
32
|
-
|
|
213
|
+
const edit = (row, c, i, m) => {
|
|
33
214
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
215
|
+
}
|
|
216
|
+
const read = (row, c, i, m) => {
|
|
217
|
+
}
|
|
218
|
+
formData.table = {
|
|
219
|
+
border: undefined,
|
|
220
|
+
page: { pageSize: 10, style: { justifyContent: 'flex-end' }, onChange: (a, b) => console.log(a, b) }
|
|
221
|
+
}
|
|
222
|
+
const all = (d) => console.log(d);
|
|
223
|
+
const load = (d, callback) => {
|
|
224
|
+
const { pageSize, currentPage } = d;
|
|
225
|
+
callback(createData().filter((item, index) => index >= (currentPage - 1) * pageSize && index < currentPage * pageSize), createData().length);
|
|
226
|
+
}
|
|
39
227
|
|
|
40
|
-
# formItem 重写 slots
|
|
41
228
|
|
|
42
|
-
|
|
43
|
-
| --------------------- | -------------------------------- |
|
|
44
|
-
| item.key | 重写表单项item |
|
|
45
|
-
| item.key-label | 重写表单项标题 |
|
|
46
|
-
| item.key-ctrl | 重写表单控件 |
|
|
47
|
-
| item.key-ctrl-options | 重写表单控件有options的字项items |
|
|
229
|
+
</script>
|
|
48
230
|
|
|
231
|
+
```
|
package/README.table.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# table 表格
|
|
2
|
+
[表单](./README.form.md)
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# API 说明
|
|
6
|
+
### 所有属性和方法都在element-plus 基础上扩展,只显示增加的属性和方法,安装前必须安装element-plus,其他原有属性和方法请查询 [官方文档](https://element-plus.org/zh-CN/component/table.html)
|
|
7
|
+
|
|
8
|
+
# table 表格新增属性
|
|
9
|
+
|
|
10
|
+
| 属性 | 说明 | 类型 | 默认值 |
|
|
11
|
+
| --------- | ------------------------------------------------------------------- | ------ | -------------------------------------------------------------------------------------------- |
|
|
12
|
+
| columns | 表单列数 | number | 2 |
|
|
13
|
+
| footer | 控制表单底部按钮是否显示和样式设置,hidden默认是false,style控制样式 | object | ```{hidden: false, style:{ display:"flex","align-items":"center",justifyContent:"center"}``` |
|
|
14
|
+
| submit | 表单提交按钮,增加了submitText,hidden 属性和submit方法 | object | ``` {type: "primary",submitText: "提交",hidden: false,submit: async (model) => {...}, } ``` |
|
|
15
|
+
| reset | 表单重置按钮,增加了restText,hidden 属性和reset方法 | object | ``` {type: "primary",resetText: "重置",hidden: false,reset: () => {...}, } ``` |
|
|
16
|
+
| formItems | 通过style属性设置所有items的样式 | object | ``` style:{width:100%} ``` |
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# table 增加的事件
|
|
20
|
+
|
|
21
|
+
| 事件名 | 说明 | 类型 | 参数 |
|
|
22
|
+
| ------ | ------------ | -------- | -------------------------------------------------------------------------------------- |
|
|
23
|
+
| submit | 表单提交事件 | function | formData:表单要提交的数据 |
|
|
24
|
+
| reset | 表单重置事件 | function | callback(isCustReset=false) 默认已处理了重置事件,如要重写事件则回调时传入true后再处理 |
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# table 重写 slots 增加的插槽
|
|
29
|
+
|
|
30
|
+
| 插槽名 | 说明 |
|
|
31
|
+
| ------------ | ---------------------- |
|
|
32
|
+
| footer | 重写表单footer功能按钮 |
|
|
33
|
+
| footer-left | 表单左边增加按钮 |
|
|
34
|
+
| footer-right | 表单右边增加按钮 |
|
|
35
|
+
|
|
36
|
+
# tablecolumn 重写 slots
|
|
37
|
+
|
|
38
|
+
| 插槽名 | 说明 |
|
|
39
|
+
| --------------------- | -------------------------------- |
|
|
40
|
+
| item.key | 重写表单项item |
|
|
41
|
+
| item.key-label | 重写表单项标题 |
|
|
42
|
+
| item.key-ctrl | 重写表单控件 |
|
|
43
|
+
| item.key-ctrl-options | 重写表单控件有options的字项items |
|
|
44
|
+
|
|
45
|
+
# 示例
|
|
46
|
+
### html
|
|
47
|
+
```
|
|
48
|
+
<template>
|
|
49
|
+
|
|
50
|
+
</template>
|
|
51
|
+
```
|
|
52
|
+
### javascript
|
|
53
|
+
```
|
|
54
|
+
<script setup>
|
|
55
|
+
|
|
56
|
+
</script>
|
|
57
|
+
```
|
|
58
|
+
|
package/package.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
<el-form ref="gform" :model="model" v-bind="model.form" class="g-form-container">
|
|
3
3
|
<slot>
|
|
4
4
|
<el-form-item :label="item.label" :prop="`${key}.value`" :rules="getItemRulesByItem(item)"
|
|
5
|
-
v-for="(item, key) of items" :key="key" v-bind="{ ...model
|
|
6
|
-
:style="{ ...model
|
|
5
|
+
v-for="(item, key) of items" :key="key" v-bind="{ ...model?.formItems, ...item?.formItem }"
|
|
6
|
+
:style="{ ...model?.formItems?.style, ...item?.formItem?.style }">
|
|
7
7
|
<slot :name="key" :item :model>
|
|
8
8
|
<g-ctrl v-model="model[key].value" :item :ctrlKey="key" :model class="g-ctrl-container">
|
|
9
9
|
<template v-for="(slot, slotKey, index) in $slots" :key="index" #[slotKey]="data">
|
|
@@ -15,29 +15,28 @@
|
|
|
15
15
|
<slot :name="`${key}-label`" :data :item :model>{{ item.label }}</slot>
|
|
16
16
|
</template>
|
|
17
17
|
</el-form-item>
|
|
18
|
-
<
|
|
19
|
-
<
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
</slot>
|
|
33
|
-
</el-form-item>
|
|
18
|
+
<slot name="footer" v-if="!model.form.footer.hidden">
|
|
19
|
+
<div :style="model.form.footer.style">
|
|
20
|
+
<slot name="footer-left"></slot>
|
|
21
|
+
<el-button @click="model.form.submit.click(model)" v-bind="model.form.submit"
|
|
22
|
+
v-if="!model?.form?.submit?.hidden">
|
|
23
|
+
{{ model?.form?.submit?.label }}
|
|
24
|
+
</el-button>
|
|
25
|
+
<el-button @click="model.form.reset.click(model)" v-bind="model.form.reset"
|
|
26
|
+
v-if="!model?.form?.reset?.hidden">{{
|
|
27
|
+
model?.form?.reset?.label
|
|
28
|
+
}}</el-button>
|
|
29
|
+
<slot name="footer-right"></slot>
|
|
30
|
+
</div>
|
|
31
|
+
</slot>
|
|
34
32
|
</slot>
|
|
35
33
|
</el-form>
|
|
36
34
|
</template>
|
|
37
35
|
|
|
38
36
|
<script setup>
|
|
39
|
-
import { computed, onMounted, ref, useAttrs, watch } from "vue";
|
|
37
|
+
import { computed, inject, onMounted, ref, useAttrs, watch, watchEffect } from "vue";
|
|
40
38
|
import gCtrl from "./g-ctrl.vue";
|
|
39
|
+
import { convertToPx, mergeObjects } from "./index";
|
|
41
40
|
defineOptions({
|
|
42
41
|
name: "g-form",
|
|
43
42
|
inheritAttrs: false
|
|
@@ -45,10 +44,10 @@ defineOptions({
|
|
|
45
44
|
const model = defineModel({ required: true });
|
|
46
45
|
const emits = defineEmits(["submit", "reset"]);
|
|
47
46
|
const gform = ref(null);
|
|
48
|
-
const columnsWidth = ref(0);
|
|
49
47
|
let marginRight = ref(32);
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
const options = inject('options');
|
|
49
|
+
|
|
50
|
+
const defaultFormAttrs = {
|
|
52
51
|
inline: true,
|
|
53
52
|
type: "hidden",
|
|
54
53
|
"scroll-to-error": true,
|
|
@@ -56,34 +55,25 @@ model.value.form = {
|
|
|
56
55
|
"status-icon": true,
|
|
57
56
|
"scroll-into-view-options": true,
|
|
58
57
|
columns: 2,
|
|
59
|
-
...model.value.form,
|
|
60
58
|
style: {
|
|
61
59
|
width: "600px",
|
|
62
|
-
|
|
63
|
-
borderRadius: "10px",
|
|
64
|
-
padding: "0px",
|
|
65
|
-
paddingTop: "20px",
|
|
66
|
-
margin: "0px",
|
|
67
|
-
boxShadow: "0 0 5px rgba(0, 0, 0, 0.1)",
|
|
68
|
-
...model.value.form?.style,
|
|
69
|
-
...useAttrs().style
|
|
60
|
+
...useAttrs()?.style
|
|
70
61
|
},
|
|
71
62
|
// 表单底部属性
|
|
72
63
|
footer: {
|
|
73
64
|
hidden: false,
|
|
74
|
-
...model.value.form?.footer,
|
|
75
65
|
style: {
|
|
76
|
-
display: "flex", width: "100%", "align-items": "center", justifyContent: "center", margin: "5px",
|
|
66
|
+
display: "flex", width: "100%", "align-items": "center", justifyContent: "center", margin: "5px",
|
|
77
67
|
},
|
|
78
68
|
},
|
|
79
69
|
//表单提交按钮属性
|
|
80
70
|
submit: {
|
|
81
71
|
type: "primary",
|
|
82
72
|
disabled: false,
|
|
83
|
-
|
|
73
|
+
label: "提交",
|
|
84
74
|
plain: true,
|
|
85
75
|
hidden: false,
|
|
86
|
-
|
|
76
|
+
click: async (model) => {
|
|
87
77
|
if (!gform.value) return
|
|
88
78
|
await gform.value.validate((valid, fields) => {
|
|
89
79
|
if (valid) {
|
|
@@ -93,15 +83,14 @@ model.value.form = {
|
|
|
93
83
|
}
|
|
94
84
|
})
|
|
95
85
|
},
|
|
96
|
-
...model.value.form?.submit
|
|
97
86
|
},
|
|
98
87
|
//表单重置按钮属性
|
|
99
88
|
reset: {
|
|
100
89
|
disabled: false,
|
|
101
|
-
|
|
90
|
+
label: "重置",
|
|
102
91
|
hidden: false,
|
|
103
92
|
plain: true,
|
|
104
|
-
|
|
93
|
+
click: () => {
|
|
105
94
|
if (!gform.value) return
|
|
106
95
|
let isreset = true;
|
|
107
96
|
emits("reset", (flag) => {
|
|
@@ -111,22 +100,24 @@ model.value.form = {
|
|
|
111
100
|
gform.value.resetFields();
|
|
112
101
|
}
|
|
113
102
|
},
|
|
114
|
-
...model.value.form?.reset
|
|
115
103
|
},
|
|
116
|
-
dialog: {
|
|
117
|
-
width: 635,
|
|
118
|
-
...model.value.form?.dialog
|
|
119
|
-
}
|
|
120
104
|
};
|
|
121
|
-
|
|
122
|
-
|
|
105
|
+
model.value.form = mergeObjects(
|
|
106
|
+
defaultFormAttrs,
|
|
107
|
+
options?.form,
|
|
108
|
+
model.value?.form
|
|
109
|
+
);
|
|
110
|
+
const defaultFormItemsAttrs = {
|
|
123
111
|
type: "hidden",
|
|
124
112
|
style: {
|
|
125
113
|
width: "100%",
|
|
126
|
-
|
|
127
|
-
},
|
|
128
|
-
...model.value.formItems,
|
|
114
|
+
}
|
|
129
115
|
};
|
|
116
|
+
model.value.formItems = mergeObjects(
|
|
117
|
+
defaultFormItemsAttrs,
|
|
118
|
+
options?.formItems,
|
|
119
|
+
model?.formItems
|
|
120
|
+
);
|
|
130
121
|
|
|
131
122
|
//获取表单项验证规则
|
|
132
123
|
const getItemRulesByItem = (item) => {
|
|
@@ -213,49 +204,38 @@ const getItemRulesByItem = (item) => {
|
|
|
213
204
|
return itemRules;
|
|
214
205
|
}
|
|
215
206
|
|
|
216
|
-
onMounted(() => {
|
|
217
|
-
load();
|
|
218
|
-
})
|
|
207
|
+
onMounted(() => { load(); })
|
|
219
208
|
|
|
220
209
|
const load = () => {
|
|
221
210
|
if (!model.value.form.inline) {
|
|
222
211
|
model.value.form.columns = 1;
|
|
223
212
|
}
|
|
224
|
-
if (model.value.form.style.width) {
|
|
225
|
-
columnsWidth.value = removePx(model.value.form.style.width) / model.value.form.columns;
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
model.value.form.style.width = "600px";
|
|
229
|
-
}
|
|
230
|
-
model.value.form.dialog.width = +model.value?.form?.style?.width.replace("px", "")+35+"px";
|
|
231
|
-
console.log(model.value.form.dialog.width);
|
|
232
213
|
for (const [key, value] of Object.entries(items.value)) {
|
|
233
|
-
model.value[key].formItem = { ...model.value[key].formItem, ...value.formItem }
|
|
214
|
+
model.value[key].formItem = { ...model.value[key].formItem, ...value.formItem };
|
|
234
215
|
model.value[key].formItem.style = { ...model.value[key].formItem.style, ...value.formItem.style };
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
model.value[key].formItem.style.width = `calc(${columnsWidth.value - marginRight.value}px)`;
|
|
241
|
-
}
|
|
242
|
-
} else {
|
|
243
|
-
model.value[key].formItem.style.width = `calc(${columnsWidth.value - marginRight.value}px)`;
|
|
244
|
-
}
|
|
216
|
+
|
|
217
|
+
const columnWidth = value.columns && value.columns > 1 ?
|
|
218
|
+
columnsWidth.value * value.columns - (value.columns - 1) * marginRight.value :
|
|
219
|
+
columnsWidth.value - marginRight.value;
|
|
220
|
+
model.value[key].formItem.style.width = `calc(${columnWidth}px)`;
|
|
245
221
|
}
|
|
246
|
-
}
|
|
222
|
+
};
|
|
247
223
|
|
|
248
224
|
watch(() => model.value.form.columns, () => load());
|
|
249
225
|
watch(() => model.value.form.inline, () => load());
|
|
250
226
|
|
|
251
|
-
const
|
|
227
|
+
const columnsWidth = computed(() => convertToPx(model.value.form.style.width) / model.value.form.columns);
|
|
228
|
+
console.log(columnsWidth.value);
|
|
252
229
|
//表单项
|
|
253
|
-
const items = computed(() => Object.fromEntries(Object.entries(model.value)
|
|
230
|
+
const items = computed(() => Object.fromEntries(Object.entries(model.value)
|
|
231
|
+
.filter(a => a[1].type != "hidden")
|
|
254
232
|
.filter(a => (a[1].edit !== false))
|
|
255
233
|
.filter(a => (a[1].type !== "control"))
|
|
256
234
|
.map(key => [key[0], key[1]])));
|
|
257
235
|
//表单提交对象
|
|
258
|
-
const formInfo = computed(() => Object.fromEntries(Object.entries(model.value)
|
|
259
|
-
|
|
260
|
-
|
|
236
|
+
const formInfo = computed(() => Object.fromEntries(Object.entries(model.value)
|
|
237
|
+
.filter(a => a[1].type != "hidden")
|
|
238
|
+
.filter(a => a[1].type != "control")
|
|
239
|
+
.map(key => [key[0], key[1].value])));
|
|
240
|
+
|
|
261
241
|
</script>
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<slot name="control-view" :row :column :$index
|
|
17
17
|
v-if="model.control.view.auth(row, column, $index)">
|
|
18
18
|
<el-button v-bind="model.control.view"
|
|
19
|
-
@click="model.control.view.
|
|
19
|
+
@click="model.control.view.click(row, column, $index, model)">{{
|
|
20
20
|
model.control.view.label
|
|
21
21
|
}}</el-button>
|
|
22
22
|
</slot>
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
<slot name="control-edit" :row :column :$index
|
|
25
25
|
v-if="model.control.edit.auth(row, column, $index)">
|
|
26
26
|
<el-button v-bind="model.control.edit"
|
|
27
|
-
@click="model.control.edit.
|
|
27
|
+
@click="model.control.edit.click(row, column, $index, model)">{{
|
|
28
28
|
model.control.edit.label
|
|
29
29
|
}}</el-button>
|
|
30
30
|
</slot>
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
<slot name="control-delete" :row :column :$index
|
|
33
33
|
v-if="model.control.delete.auth(row, column, $index)">
|
|
34
34
|
<el-button v-bind="model.control.delete"
|
|
35
|
-
@click="model.control.delete.
|
|
35
|
+
@click="model.control.delete.click(row, column, $index)">{{
|
|
36
36
|
model.control.delete.label }}</el-button>
|
|
37
37
|
</slot>
|
|
38
38
|
<slot name="control-right" :row :column :$index> </slot>
|
|
@@ -64,12 +64,10 @@
|
|
|
64
64
|
|
|
65
65
|
<script setup>
|
|
66
66
|
import dayjs from 'dayjs';
|
|
67
|
-
import ElementPlus from 'element-plus'
|
|
68
67
|
import { ElMessageBox } from 'element-plus';
|
|
69
|
-
import { computed, onMounted,
|
|
70
|
-
import { ElDialog } from 'element-plus';
|
|
71
|
-
//import gyc from "./index"
|
|
68
|
+
import { computed, onMounted, ref, watchEffect, h, inject } from 'vue';
|
|
72
69
|
import GForm from './g-form.vue';
|
|
70
|
+
import { openModal, mergeObjects,convertToPx } from './index';
|
|
73
71
|
|
|
74
72
|
defineOptions({
|
|
75
73
|
name: "g-table",
|
|
@@ -78,9 +76,10 @@ defineOptions({
|
|
|
78
76
|
|
|
79
77
|
const emits = defineEmits(['load', 'view', "edit", "delete"]);
|
|
80
78
|
const model = defineModel({ required: true });
|
|
79
|
+
const options = inject('options');
|
|
81
80
|
|
|
82
81
|
//table default attrs
|
|
83
|
-
|
|
82
|
+
const defaultTableAttrs = {
|
|
84
83
|
data: [],
|
|
85
84
|
border: true,
|
|
86
85
|
stripe: true,
|
|
@@ -88,7 +87,6 @@ model.value.table = {
|
|
|
88
87
|
fit: false,
|
|
89
88
|
type: "hidden",
|
|
90
89
|
height: "calc(100vh - 55px)",
|
|
91
|
-
...model.value.table,
|
|
92
90
|
load: () => {
|
|
93
91
|
let { currentPage, pageSize } = model.value.table.page;
|
|
94
92
|
emits('load', { currentPage, pageSize }, (data, total) => {
|
|
@@ -97,128 +95,144 @@ model.value.table = {
|
|
|
97
95
|
});
|
|
98
96
|
},
|
|
99
97
|
page: {
|
|
100
|
-
background:
|
|
98
|
+
background: "red",
|
|
101
99
|
total: 0,
|
|
102
100
|
pageSize: 20,
|
|
103
101
|
currentPage: 1,
|
|
104
102
|
pageSizes: [5, 10, 20, 30, 40, 50],
|
|
105
103
|
layout: 'total, sizes, prev, pager, next, jumper',
|
|
106
|
-
...model.value.table?.page,
|
|
107
104
|
style: {
|
|
108
105
|
display: 'flex',
|
|
109
106
|
justifyContent: 'flex-end',
|
|
110
107
|
padding: "10px",
|
|
111
|
-
...model.value.table?.page?.style
|
|
112
108
|
},
|
|
113
109
|
}
|
|
114
|
-
}
|
|
115
|
-
model.value.
|
|
116
|
-
|
|
110
|
+
};
|
|
111
|
+
model.value.table = mergeObjects(
|
|
112
|
+
defaultTableAttrs,
|
|
113
|
+
options?.table,
|
|
114
|
+
model.value.table
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const defaultControl = {
|
|
118
|
+
type: 'control', label: '操作', edit: false, width: 250, fixed: 'right',
|
|
117
119
|
style: { ...model.value?.control?.style },
|
|
120
|
+
add: {
|
|
121
|
+
label: '新增', text: false, type: 'primary',
|
|
122
|
+
dialog: {
|
|
123
|
+
title: '新增',
|
|
124
|
+
popMode: "dialog",
|
|
125
|
+
slots: {
|
|
126
|
+
default: () => h(GForm, { modelValue: model.value }),
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
auth: () => {
|
|
130
|
+
return true;
|
|
131
|
+
},
|
|
132
|
+
click: (row, column, $index, m) => {
|
|
133
|
+
model.value.form.disabled = undefined;
|
|
134
|
+
emits("add", row, column, $index, m);
|
|
135
|
+
Object.keys(row).forEach(key => {
|
|
136
|
+
if (model.value[key]) {
|
|
137
|
+
model.value[key].value = row[key];
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
openModal(model.value.control.add.dialog, model.value?.control?.add?.dialog?.popMode);
|
|
141
|
+
},
|
|
142
|
+
},
|
|
118
143
|
view: {
|
|
119
144
|
label: '查看', text: true, type: 'success',
|
|
145
|
+
dialog: {
|
|
146
|
+
title: '查看',
|
|
147
|
+
popMode: "dialog",
|
|
148
|
+
slots: {
|
|
149
|
+
default: () => h(GForm, { modelValue: model.value }),
|
|
150
|
+
},
|
|
151
|
+
},
|
|
120
152
|
auth: (row, column, $index) => {
|
|
121
153
|
return false;
|
|
122
154
|
},
|
|
123
|
-
|
|
155
|
+
click: (row, column, $index, m) => {
|
|
156
|
+
let w = model.value?.form?.style?.width || 600;
|
|
157
|
+
if (model.value.control.view.dialog.popMode == "drawer") {
|
|
158
|
+
model.value.control.view.dialog.size = convertToPx(w) + 45;
|
|
159
|
+
} else {
|
|
160
|
+
model.value.control.view.dialog.width = convertToPx(w) + 35;
|
|
161
|
+
}
|
|
124
162
|
model.value.form.disabled = true;
|
|
125
163
|
emits("view", row, column, $index, m);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// 自定义插槽内容
|
|
131
|
-
default: () => h(GForm, { modelValue: model.value }),
|
|
132
|
-
...model.value?.form?.dialog?.slots
|
|
133
|
-
},
|
|
164
|
+
Object.keys(row).forEach(key => {
|
|
165
|
+
if (model.value[key]) {
|
|
166
|
+
model.value[key].value = row[key];
|
|
167
|
+
}
|
|
134
168
|
});
|
|
135
|
-
|
|
169
|
+
openModal(model.value.control.view.dialog, model.value?.control?.view?.dialog?.popMode);
|
|
170
|
+
},
|
|
136
171
|
},
|
|
137
172
|
edit: {
|
|
138
173
|
label: '编辑', text: true, type: 'primary',
|
|
174
|
+
dialog: {
|
|
175
|
+
title: '编辑',
|
|
176
|
+
popMode: "dialog",
|
|
177
|
+
slots: {
|
|
178
|
+
default: () => h(GForm, { modelValue: model.value }),
|
|
179
|
+
},
|
|
180
|
+
},
|
|
139
181
|
auth: (row, column, $index) => {
|
|
140
182
|
return true;
|
|
141
183
|
},
|
|
142
|
-
|
|
184
|
+
click: (row, column, $index, m) => {
|
|
143
185
|
model.value.form.disabled = undefined;
|
|
186
|
+
let w = model.value?.form?.style?.width || 600;
|
|
187
|
+
if (model.value.control.edit.dialog.popMode == "drawer") {
|
|
188
|
+
model.value.control.edit.dialog.size = convertToPx(w) + 45;
|
|
189
|
+
} else {
|
|
190
|
+
model.value.control.edit.dialog.width = convertToPx(w) + 35;
|
|
191
|
+
}
|
|
144
192
|
emits("edit", row, column, $index, m);
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
// 自定义插槽内容
|
|
150
|
-
default: () => h(GForm, { modelValue: model.value }),
|
|
151
|
-
...model.value?.form?.dialog?.slots
|
|
152
|
-
},
|
|
193
|
+
Object.keys(row).forEach(key => {
|
|
194
|
+
if (model.value[key]) {
|
|
195
|
+
model.value[key].value = row[key];
|
|
196
|
+
}
|
|
153
197
|
});
|
|
154
|
-
|
|
155
|
-
},
|
|
198
|
+
openModal(model.value?.control?.edit?.dialog, model.value?.control?.edit?.dialog?.popMode);
|
|
199
|
+
},
|
|
156
200
|
},
|
|
157
201
|
delete: {
|
|
158
202
|
text: true, label: '删除', type: 'danger',
|
|
159
203
|
auth: (row, column, $index) => {
|
|
160
204
|
return true;
|
|
161
205
|
},
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
206
|
+
dialog: {
|
|
207
|
+
message: '此操作将永久删除该文件, 是否继续?', title: '提示', options: { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }
|
|
208
|
+
},
|
|
209
|
+
click: (row, column, $index) => {
|
|
210
|
+
let { message, title, options } = model.value.control.delete.dialog;
|
|
211
|
+
ElMessageBox.confirm(message, title, options).then(() => {
|
|
168
212
|
emits("delete", { row, column, $index }, (res) => {
|
|
169
213
|
if (res) {
|
|
170
214
|
model.value.table.data.splice($index, 1);
|
|
171
|
-
console.log(model.value.table.data)
|
|
172
215
|
}
|
|
173
216
|
})
|
|
174
217
|
}).catch(() => { });
|
|
175
218
|
|
|
176
|
-
},
|
|
219
|
+
},
|
|
177
220
|
},
|
|
178
221
|
};
|
|
179
222
|
|
|
223
|
+
model.value.control = mergeObjects(
|
|
224
|
+
defaultControl,
|
|
225
|
+
options?.control,
|
|
226
|
+
model.value?.control
|
|
227
|
+
);
|
|
228
|
+
|
|
180
229
|
//table column default attrs
|
|
181
|
-
const itemValue =
|
|
230
|
+
const itemValue = ref({
|
|
182
231
|
width: 100, align: "center", formatter: (row, column, cellValue, index) => {
|
|
183
232
|
return cellValue;
|
|
184
233
|
}
|
|
185
234
|
});
|
|
186
235
|
|
|
187
|
-
// 用 createApp 方法封装打开模态窗的方法
|
|
188
|
-
const openModal = (props = {}) => {
|
|
189
|
-
const el = document.createElement('div');
|
|
190
|
-
const app = createApp({
|
|
191
|
-
setup() {
|
|
192
|
-
const visible = ref(true);
|
|
193
|
-
const closeModal = () => {
|
|
194
|
-
visible.value = false;
|
|
195
|
-
app.unmount(); // 关闭对话框时卸载组件
|
|
196
|
-
delete el.remove(); // 关闭对话框时删除元素
|
|
197
|
-
};
|
|
198
|
-
return () => h(ElDialog, {
|
|
199
|
-
modelValue: visible.value,
|
|
200
|
-
'onUpdate:modelValue': closeModal,
|
|
201
|
-
draggable: true,
|
|
202
|
-
"append-to-body": true,
|
|
203
|
-
"v-if": visible.value,
|
|
204
|
-
width:model.value.form.dialog.width,
|
|
205
|
-
...props
|
|
206
|
-
},
|
|
207
|
-
() => [
|
|
208
|
-
props.slots.header ? h('div', { slot: 'header' }, props.slots.header()) : null,
|
|
209
|
-
// 传递名为 'default' 的插槽
|
|
210
|
-
props.slots.default ? h(props.slots.default()) : null,
|
|
211
|
-
// 传递名为 'footer' 的插槽
|
|
212
|
-
props.slots.footer ? h('div', { slot: 'footer' }, props.slots.footer()) : null,
|
|
213
|
-
// 可以继续添加其他插槽...
|
|
214
|
-
]
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
app.use(ElementPlus).mount(el);
|
|
219
|
-
document.body.appendChild(el);
|
|
220
|
-
};
|
|
221
|
-
|
|
222
236
|
//扩展formatter方法
|
|
223
237
|
const formatterExtends = (item, str) => {
|
|
224
238
|
let trueValue = str;
|
|
@@ -271,7 +285,10 @@ const tableColumns = computed(() => Object.fromEntries(
|
|
|
271
285
|
Object.entries(model.value)
|
|
272
286
|
.filter(a => a[1].type != "hidden")
|
|
273
287
|
.filter(a => a[1].visible !== false)
|
|
274
|
-
.map(key =>
|
|
288
|
+
.map(key => {
|
|
289
|
+
key[1].sort = key[1].sort || 0;
|
|
290
|
+
return [key[0], key[1]]
|
|
291
|
+
}).sort((a, b) => a[1].sort - b[1].sort))
|
|
275
292
|
);
|
|
276
293
|
</script>
|
|
277
294
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { App } from 'vue';
|
|
2
|
+
import { Component } from 'vue';
|
|
3
|
+
import { ElDialogProps, ElDrawerProps } from 'element-plus';
|
|
4
|
+
|
|
5
|
+
declare module 'vue' {
|
|
6
|
+
interface ComponentCustomProperties {
|
|
7
|
+
openModel: typeof openModal;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface GlobalOptions {
|
|
12
|
+
// 在这里定义全局选项的类型
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface OpenModalProps {
|
|
16
|
+
slots?: {
|
|
17
|
+
default?: () => any;
|
|
18
|
+
header?: () => any;
|
|
19
|
+
footer?: () => any;
|
|
20
|
+
};
|
|
21
|
+
// 在这里定义其他传递给openModal函数的属性
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function install(app: App, options?: GlobalOptions): void;
|
|
25
|
+
export function openModal(props?: OpenModalProps, popMode?: 'dialog' | 'drawer'): void;
|
|
26
|
+
export function convertToPx(value: string | number, referenceSize?: number): number;
|
|
27
|
+
export function mergeObjects<T extends object>(...objects: T[]): T;
|
|
28
|
+
|
|
29
|
+
declare const components: {
|
|
30
|
+
gTable: Component;
|
|
31
|
+
gForm: Component;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default components;
|
package/src/components/index.js
CHANGED
|
@@ -1,10 +1,126 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import ElementPlus from 'element-plus'
|
|
4
|
+
import { ElDialog, ElDrawer } from "element-plus";
|
|
1
5
|
import gTable from "./g-table.vue";
|
|
2
6
|
import gForm from "./g-form.vue";
|
|
7
|
+
import { createApp, h, provide, readonly, ref } from "vue";
|
|
8
|
+
const globalOptions = {};
|
|
3
9
|
|
|
10
|
+
const components = [
|
|
11
|
+
gTable,
|
|
12
|
+
gForm
|
|
13
|
+
]
|
|
4
14
|
|
|
5
15
|
export default {
|
|
6
|
-
install(app, options) {
|
|
7
|
-
|
|
8
|
-
|
|
16
|
+
install(app, options = {}) {
|
|
17
|
+
Object.assign(globalOptions, options);
|
|
18
|
+
for (const component of components) {
|
|
19
|
+
app.component(component.name, component);
|
|
20
|
+
}
|
|
21
|
+
app.provide("options", readonly(options));
|
|
22
|
+
app.config.globalProperties.openModel = openModal;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const openModal = (props = {}, popMode = "dialog") => {
|
|
26
|
+
const el = document.createElement('div');
|
|
27
|
+
const app = createApp({
|
|
28
|
+
setup() {
|
|
29
|
+
const visible = ref(true);
|
|
30
|
+
const closeModal = () => {
|
|
31
|
+
visible.value = false;
|
|
32
|
+
app.unmount(); // 关闭对话框时卸载组件
|
|
33
|
+
delete el.remove(); // 关闭对话框时删除元素
|
|
34
|
+
};
|
|
35
|
+
provide("options", globalOptions);
|
|
36
|
+
return () => h(popMode == "dialog" ? ElDialog : popMode == "drawer" ? ElDrawer : null, {
|
|
37
|
+
modelValue: visible.value,
|
|
38
|
+
'onUpdate:modelValue': closeModal,
|
|
39
|
+
draggable: true,
|
|
40
|
+
"append-to-body": true,
|
|
41
|
+
...props
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
default: props.slots?.default ? () => h('div', {}, props.slots.default()) : null,
|
|
45
|
+
header: props.slots?.header ? () => h('div', {}, props.slots.header()) : null,
|
|
46
|
+
footer: props.slots?.footer ? () => h('div', {}, props.slots.footer()) : null
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
for (const component of components) {
|
|
52
|
+
app.component(component.name, component);
|
|
53
|
+
}
|
|
54
|
+
app.use(ElementPlus).mount(el);
|
|
55
|
+
document.body.appendChild(el);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
function convertToPx(value, referenceSize = window.innerWidth) {
|
|
59
|
+
// 如果已经是px,直接返回
|
|
60
|
+
if (typeof value === 'number') {
|
|
61
|
+
return value;
|
|
62
|
+
}
|
|
63
|
+
if (value.endsWith('px')) {
|
|
64
|
+
return parseFloat(value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 处理vh和vw
|
|
68
|
+
const viewportHeight = window.innerHeight;
|
|
69
|
+
const viewportWidth = window.innerWidth;
|
|
70
|
+
if (value.endsWith('vh')) {
|
|
71
|
+
return (viewportHeight * parseFloat(value) / 100);
|
|
72
|
+
}
|
|
73
|
+
if (value.endsWith('vw')) {
|
|
74
|
+
return (viewportWidth * parseFloat(value) / 100);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 处理%值,需要提供参考大小
|
|
78
|
+
if (value.endsWith('%')) {
|
|
79
|
+
if (typeof referenceSize !== 'number') {
|
|
80
|
+
throw new Error('Reference size is required to convert % to px');
|
|
81
|
+
}
|
|
82
|
+
return (referenceSize * parseFloat(value) / 100);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 处理calc表达式
|
|
86
|
+
if (value.startsWith('calc(') && value.endsWith(')')) {
|
|
87
|
+
const calcExpr = value.slice(5, -1).trim();
|
|
88
|
+
// 使用一个安全的eval替代方案来解析calc表达式
|
|
89
|
+
function safeEval(expr) {
|
|
90
|
+
// 只允许数字、加减乘除运算符、小括号和空格
|
|
91
|
+
if (/[0-9+\-*/().\s]+$/.test(expr)) {
|
|
92
|
+
return new Function('return ' + expr)();
|
|
93
|
+
} else {
|
|
94
|
+
throw new Error('Invalid calc expression');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// 在这里,我们假设calc表达式中可能包含其他单位,因此递归调用parseValueToPx
|
|
98
|
+
const result = safeEval(calcExpr.replace(/([0-9.]+)([a-z%]+)/g, (match, number, unit) => {
|
|
99
|
+
return convertToPx(number + unit, referenceSize);
|
|
100
|
+
}));
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 如果无法识别单位,抛出错误
|
|
105
|
+
throw new Error('Unsupported unit in value: ' + value);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
function mergeObjects(...objects) {
|
|
110
|
+
const result = ref({});
|
|
111
|
+
for (const obj of objects) {
|
|
112
|
+
if (obj && typeof obj === 'object') {
|
|
113
|
+
for (const key in obj) {
|
|
114
|
+
if (obj.hasOwnProperty(key)) {
|
|
115
|
+
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
|
|
116
|
+
result.value[key] = mergeObjects(result.value[key], obj[key]);
|
|
117
|
+
} else {
|
|
118
|
+
result.value[key] = obj[key];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
9
123
|
}
|
|
124
|
+
return result.value;
|
|
10
125
|
}
|
|
126
|
+
export { openModal, convertToPx, mergeObjects };
|