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 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
- # API 说明
10
- ### 所有属性和方法都在element-plus 基础上扩展,只显示增加的属性和方法,安装前必须安装element-plus,其他原有属性和方法请查询 [官方文档](https://element-plus.org/zh-CN/component/overview.html)
23
+ [表单](./README.form.md) [表格](./README.table.md)
11
24
 
12
- # form 表单增加的属性
25
+ ## 快速上手
13
26
 
14
- | 属性 | 说明 | 类型 | 默认值 |
15
- | --------- | ------------------------------------------------------------------- | ------ | -------------------------------------------------------------------------------------------- |
16
- | columns | 表单列数 | number | 2 |
17
- | footer | 控制表单底部按钮是否显示和样式设置,hidden默认是false,style控制样式 | object | ```{hidden: false, style:{ display:"flex","align-items":"center",justifyContent:"center"}``` |
18
- | submit | 表单提交按钮,增加了submitText,hidden 属性和submit方法 | object | ``` {type: "primary",submitText: "提交",hidden: false,submit: async (model) => {...}, } ``` |
19
- | reset | 表单重置按钮,增加了restText,hidden 属性和reset方法 | object | ``` {type: "primary",resetText: "重置",hidden: false,reset: () => {...}, } ``` |
20
- | formItems | 通过style属性设置所有items的样式 | object | ``` style:{width:100%} ``` |
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
- # form 增加的事件
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
- | submit | 表单提交事件 | function | formData:表单要提交的数据 |
28
- | reset | 表单重置事件 | function | callback(isCustReset=false) 默认已处理了重置事件,如要重写事件则回调时传入true后再处理 |
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
- # form 重写 slots 增加的插槽
213
+ const edit = (row, c, i, m) => {
33
214
 
34
- | 插槽名 | 说明 |
35
- | ------------ | ---------------------- |
36
- | footer | 重写表单footer功能按钮 |
37
- | footer-left | 表单左边增加按钮 |
38
- | footer-right | 表单右边增加按钮 |
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
+ ```
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue-2024-ui",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "src/components/index.js",
@@ -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.formItems, ...item.formItem }"
6
- :style="{ ...model.formItems.style, ...item?.formItem?.style }">
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
- <el-form-item style="width: 100%;" v-if="!model.form.footer.hidden">
19
- <slot name="footer">
20
- <div :style="model.form.footer.style">
21
- <slot name="footer-left"></slot>
22
- <el-button @click="model.form.submit.submit(model)" v-bind="model.form.submit"
23
- v-if="!model.form.submit.hidden">
24
- {{ model.form.submit.submitText }}
25
- </el-button>
26
- <el-button @click="model.form.reset.reset(model)" v-bind="model.form.reset"
27
- v-if="!model.form.reset.hidden">{{
28
- model.form.reset.resetText
29
- }}</el-button>
30
- <slot name="footer-right"></slot>
31
- </div>
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
- model.value.form = {
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
- border: "1px solid rgba(0, 0, 0,0.09)",
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", ...model.value.form?.footer?.style
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
- submitText: "提交",
73
+ label: "提交",
84
74
  plain: true,
85
75
  hidden: false,
86
- submit: async (model) => {
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
- resetText: "重置",
90
+ label: "重置",
102
91
  hidden: false,
103
92
  plain: true,
104
- reset: () => {
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
- model.value.formItems = {
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
- ...model.value.formItems?.style
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
- if (value.columns && value.columns > 1) {
236
- if (model.value.form.inline) {
237
- let column = columnsWidth.value * value.columns - (value.columns - 1) * marginRight.value;
238
- model.value[key].formItem.style.width = `calc(${column}px)`;
239
- } else {
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 removePx = (el) => parseFloat(el.replace("px", ""));
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).filter(a => a[1].type != "hidden")
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).filter(a => a[1].type != "hidden").map(key => [key[0], key[1].value])));
259
- //表单列数
260
- const columns = computed(() => model.value.form.columns);
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.view(row, column, $index, model)">{{
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.edit(row, column, $index, model)">{{
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.delete(row, column, $index)">{{
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, reactive, watchEffect, watch, h, createApp, ref } from 'vue';
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
- model.value.table = {
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: true,
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.control = {
116
- type: model.value?.control ? "control" : 'hidden', label: '操作', edit: false, width: 250, fixed: 'right', ...model.value?.control,
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
- view: (row, column, $index, m) => {
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
- openModal({
127
- title: '查看',
128
- ...model.value.form.dialog,
129
- slots: {
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
- }, ...model.value?.control?.view
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
- edit: (row, column, $index, m) => {
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
- openModal({
146
- title: '编辑',
147
- ...model.value.form.dialog,
148
- slots: {
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
- }, ...model.value?.control?.edit
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
- delete: (row, column, $index) => {
163
- ElMessageBox.confirm('此操作将永久删除该文件, 是否继续?', '提示', {
164
- confirmButtonText: '确定',
165
- cancelButtonText: '取消',
166
- type: 'warning'
167
- }).then(() => {
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
- }, ...model.value?.control?.delete
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 = reactive({
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 => [key[0], key[1]]))
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;
@@ -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
- app.component("g-table", gTable);
8
- app.component("g-form", gForm);
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 };