vue-2024-ui 0.0.7 → 0.0.9

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 CHANGED
@@ -10,8 +10,8 @@
10
10
  | --------- | ------------------------------------------------------------------- | ------ | -------------------------------------------------------------------------------------------- |
11
11
  | columns | 表单列数 | number | 2 |
12
12
  | footer | 控制表单底部按钮是否显示和样式设置,hidden默认是false,style控制样式 | object | ```{hidden: false, style:{ display:"flex","align-items":"center",justifyContent:"center"}``` |
13
- | submit | 表单提交按钮,增加了submitText,hidden 属性和submit方法 | object | ``` {type: "primary",submitText: "提交",hidden: false,submit: async (model) => {...}, } ``` |
14
- | reset | 表单重置按钮,增加了restText,hidden 属性和reset方法 | object | ``` {type: "primary",resetText: "重置",hidden: false,reset: () => {...}, } ``` |
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
15
  | formItems | 通过style属性设置所有items的样式 | object | ``` style:{width:100%} ``` |
16
16
 
17
17
  ## form 增加的事件
@@ -131,8 +131,8 @@
131
131
  ``` javascript
132
132
 
133
133
  <script setup>
134
- import { ref ,reactive} from "vue";
135
- const formData=reactive({
134
+ import { ref} from "vue";
135
+ const formData=ref({
136
136
  form:{
137
137
  submit: { hidden: false },
138
138
  reset: { hidden: false },
package/README.md CHANGED
@@ -36,8 +36,8 @@ app.mount('#app')
36
36
 
37
37
  ``` javascript
38
38
  <script setup>
39
- import { reactive } from "vue";
40
- const formData = reactive({
39
+ import { ref } from "vue";
40
+ const formData = ref({
41
41
  id: { label: "ID", type: "number", value: 1, disabled: true, edit: false },
42
42
  name: {
43
43
  label: "姓名", value: "张三", width: 100,
@@ -215,7 +215,7 @@ const edit = (row, c, i, m) => {
215
215
  }
216
216
  const read = (row, c, i, m) => {
217
217
  }
218
- formData.table = {
218
+ formData.value.table = {
219
219
  border: undefined,
220
220
  page: { pageSize: 10, style: { justifyContent: 'flex-end' }, onChange: (a, b) => console.log(a, b) }
221
221
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue-2024-ui",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "src/components/index.js",
@@ -37,7 +37,8 @@
37
37
  </slot>
38
38
  </el-checkbox-group>
39
39
  <!--select-->
40
- <el-select v-model="ctrlValue" v-bind="selectModel" v-else-if="ctrlType == 'select'">
40
+ <el-select v-model="ctrlValue" :placeholder="itemInfo.label" v-bind="selectModel"
41
+ v-else-if="ctrlType == 'select'">
41
42
  <template #default v-if="itemInfo?.options?.some(a => a.options)">
42
43
  <slot :name="`${ctrlKey}-ctrl`" :options="itemInfo.options">
43
44
  <el-option-group v-for="group in itemInfo.options" :label="group.label">
@@ -61,24 +62,28 @@
61
62
  </template>
62
63
  </el-select>
63
64
  <!--tree-select-->
64
- <el-tree-select v-model="ctrlValue" v-bind="itemInfo" v-else-if="ctrlType == 'tree-select'">
65
+ <el-tree-select v-model="ctrlValue" :placeholder="itemInfo.label" v-bind="itemInfo"
66
+ v-else-if="ctrlType == 'tree-select'">
65
67
  <template v-for="(slot, index) in slots()" :key="index" #[slot]="data">
66
68
  <slot :name="`${ctrlKey}-ctrl-${slot}`" :data></slot>
67
69
  </template>
68
70
  </el-tree-select>
69
71
  <!--time-->
70
- <el-time-picker v-model="ctrlValue" arrow-control v-bind="itemInfo" v-else-if="ctrlType == 'time'" />
72
+ <el-time-picker v-model="ctrlValue" arrow-control v-bind="itemInfo" :placeholder="itemInfo.label"
73
+ v-else-if="ctrlType == 'time'" />
71
74
  <!--time-select-->
72
- <el-time-select v-model="ctrlValue" start="06:30" step="00:15" end="23:30" v-bind="itemInfo"
73
- v-else-if="ctrlType == 'time-select'" />
75
+ <el-time-select v-model="ctrlValue" start="06:30" step="00:15" end="23:30" :placeholder="itemInfo.label"
76
+ v-bind="itemInfo" v-else-if="ctrlType == 'time-select'" />
74
77
  <!--date-->
75
- <el-date-picker v-model="ctrlValue" type="date" v-bind="itemInfo" v-else-if="ctrlType == 'date'">
78
+ <el-date-picker v-model="ctrlValue" type="date" :placeholder="itemInfo.label" v-bind="itemInfo"
79
+ v-else-if="ctrlType == 'date'">
76
80
  <template v-for="(slot, index) in slots()" :key="index" #[slot]="data">
77
81
  <slot :name="`${ctrlKey}-ctrl-${slot}`" :data></slot>
78
82
  </template>
79
83
  </el-date-picker>
80
84
  <!--datetime-->
81
- <el-date-picker v-model="ctrlValue" type="datetime" v-bind="itemInfo" v-else-if="ctrlType == 'datetime'">
85
+ <el-date-picker v-model="ctrlValue" type="datetime" :placeholder="itemInfo.label" v-bind="itemInfo"
86
+ v-else-if="ctrlType == 'datetime'">
82
87
  <template v-for="(slot, index) in slots()" :key="index" #[slot]="data">
83
88
  <slot :name="`${ctrlKey}-ctrl-${slot}`" :data></slot>
84
89
  </template>
@@ -114,20 +119,21 @@
114
119
  </template>
115
120
  </el-upload>
116
121
  <!--autocomplete-->
117
- <el-autocomplete v-model="ctrlValue" v-bind="itemInfo" v-else-if="ctrlType == 'autocomplete'">
122
+ <el-autocomplete v-model="ctrlValue" v-bind="itemInfo" :placeholder="itemInfo.label"
123
+ v-else-if="ctrlType == 'autocomplete'">
118
124
  <template v-for="(slot, index) in slots()" :key="index" #[slot]="data">
119
125
  <slot :name="`${ctrlKey}-ctrl-${slot}`" :data></slot>
120
126
  </template>
121
127
  </el-autocomplete>
122
128
  <!--input-number-->
123
- <el-input-number v-model="ctrlValue" :min="0" :max="100000000" v-bind="itemInfo"
129
+ <el-input-number v-model="ctrlValue" :min="0" :max="100000000" :placeholder="itemInfo.label" v-bind="itemInfo"
124
130
  v-else-if="ctrlType == 'input-number'">
125
131
  <template v-for="(slot, index) in slots()" :key="index" #[slot]="data">
126
132
  <slot :name="`${ctrlKey}-ctrl-${slot}`" :data></slot>
127
133
  </template>
128
134
  </el-input-number>
129
135
  <!--input-->
130
- <el-input v-model="ctrlValue" show-word-limit :maxlength="itemInfo.max || 25" :clearable="true"
136
+ <el-input v-model="ctrlValue" :placeholder="itemInfo.label" :maxlength="itemInfo.max || 25" :clearable="true"
131
137
  v-bind="itemInfo" v-else>
132
138
  <template v-for="(slot, index) in slots()" :key="index" #[slot]="data">
133
139
  <slot :name="`${ctrlKey}-ctrl-${slot}`" :data></slot>
@@ -139,8 +145,10 @@
139
145
  <script setup>
140
146
  defineOptions({ inheritAttrs: false });
141
147
  import { onMounted, useAttrs, useSlots } from "vue";
148
+ import { filterObject } from ".";
142
149
  const ctrlValue = defineModel({ required: true });
143
- const itemInfo = useAttrs().item;
150
+ const itemInfo = filterObject(useAttrs().item, k => k != 'value');
151
+ delete itemInfo.formatter;
144
152
  const ctrlKey = useAttrs().ctrlKey;
145
153
  const ctrlType = itemInfo.type;
146
154
  const model = useAttrs().model;
@@ -1,9 +1,11 @@
1
1
  <template>
2
- <el-form ref="gform" :model="model" v-bind="model.form" class="g-form-container">
2
+ <el-form ref="gform" :model="model"
3
+ v-bind="filterObject(model.form, k => !['columns', 'footer', 'submit', 'reset', 'slots', 'type', 'sort'].includes(k))"
4
+ class="g-form-container">
3
5
  <slot>
4
6
  <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 }">
7
+ v-for="(item, key) of items" :key="key" v-bind="{ ...model?.formItems, ...item?.formItem }"
8
+ :style="{ ...model?.formItems?.style, ...item?.formItem?.style }">
7
9
  <slot :name="key" :item :model>
8
10
  <g-ctrl v-model="model[key].value" :item :ctrlKey="key" :model class="g-ctrl-container">
9
11
  <template v-for="(slot, slotKey, index) in $slots" :key="index" #[slotKey]="data">
@@ -15,41 +17,41 @@
15
17
  <slot :name="`${key}-label`" :data :item :model>{{ item.label }}</slot>
16
18
  </template>
17
19
  </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>
20
+ <slot name="footer" v-if="!model.form.footer.hidden">
21
+ <div :style="model.form.footer.style">
22
+ <slot name="footer-left"></slot>
23
+ <el-button @click="model.form.submit.click(formInfo, model)"
24
+ v-bind="filterObject(model.form.submit, k => !['click'].includes(k))"
25
+ v-if="!model?.form?.submit?.hidden">
26
+ {{ model?.form?.submit?.label }}
27
+ </el-button>
28
+ <el-button @click="model.form.reset.click(model)"
29
+ v-bind="filterObject(model.form.reset, k => !['click'].includes(k))"
30
+ v-if="!model?.form?.reset?.hidden">{{
31
+ model?.form?.reset?.label
32
+ }}</el-button>
33
+ <slot name="footer-right"></slot>
34
+ </div>
35
+ </slot>
34
36
  </slot>
35
37
  </el-form>
36
38
  </template>
37
39
 
38
40
  <script setup>
39
- import { computed, inject, onMounted, ref, useAttrs, watch } from "vue";
41
+ import { computed, getCurrentInstance, inject, onBeforeUnmount, onMounted, ref, useAttrs, watch } from "vue";
40
42
  import gCtrl from "./g-ctrl.vue";
43
+ import { convertToPx, mergeObjects, filterObject } from "./index";
41
44
  defineOptions({
42
45
  name: "g-form",
43
46
  inheritAttrs: false
44
47
  })
45
48
  const model = defineModel({ required: true });
46
- const emits = defineEmits(["submit", "reset"]);
49
+ const emits = defineEmits(["usubmit", "reset"]);
47
50
  const gform = ref(null);
48
- const columnsWidth = ref(0);
49
51
  let marginRight = ref(32);
50
- const options=inject('options');
51
- //合并表单默认属性
52
- model.value.form = {
52
+ const options = inject('options');
53
+
54
+ const defaultFormAttrs = {
53
55
  inline: true,
54
56
  type: "hidden",
55
57
  "scroll-to-error": true,
@@ -57,58 +59,43 @@ model.value.form = {
57
59
  "status-icon": true,
58
60
  "scroll-into-view-options": true,
59
61
  columns: 2,
60
- ...options?.form,
61
- ...model.value.form,
62
62
  style: {
63
63
  width: "600px",
64
- border: "1px solid rgba(0, 0, 0,0.09)",
65
- borderRadius: "10px",
66
- padding: "0px",
67
- paddingTop: "20px",
68
- margin: "0px",
69
- boxShadow: "0 0 5px rgba(0, 0, 0, 0.1)",
70
- ...options?.form?.style,
71
- ...model.value.form?.style,
72
- ...useAttrs().style
64
+ ...useAttrs()?.style
73
65
  },
74
66
  // 表单底部属性
75
67
  footer: {
76
68
  hidden: false,
77
- ...options?.form?.footer,
78
- ...model.value.form?.footer,
79
69
  style: {
80
- display: "flex", width: "100%", "align-items": "center", justifyContent: "center", margin: "5px",
81
- ...options?.form?.footer?.style,
82
- ...model.value.form?.footer?.style
70
+ display: "flex", width: "100%", "align-items": "center", justifyContent: "center", margin: "5px",
83
71
  },
84
72
  },
85
73
  //表单提交按钮属性
86
74
  submit: {
87
75
  type: "primary",
88
76
  disabled: false,
89
- submitText: "提交",
77
+ label: "提交",
90
78
  plain: true,
91
79
  hidden: false,
92
- ...options?.form?.submit,
93
- submit: async (model) => {
80
+ click: async (formInfo, model) => {
81
+ emits("usubmit", formInfo, model);
94
82
  if (!gform.value) return
95
83
  await gform.value.validate((valid, fields) => {
96
84
  if (valid) {
97
- emits("submit", formInfo.value, model);
85
+ console.log("提交");
98
86
  } else {
99
- console.log('error submit!', fields);
87
+ console.log('表单验证不通过!', fields);
100
88
  }
101
89
  })
102
90
  },
103
- ...model.value.form?.submit
104
91
  },
105
92
  //表单重置按钮属性
106
93
  reset: {
107
94
  disabled: false,
108
- resetText: "重置",
95
+ label: "重置",
109
96
  hidden: false,
110
97
  plain: true,
111
- reset: () => {
98
+ click: () => {
112
99
  if (!gform.value) return
113
100
  let isreset = true;
114
101
  emits("reset", (flag) => {
@@ -118,25 +105,25 @@ model.value.form = {
118
105
  gform.value.resetFields();
119
106
  }
120
107
  },
121
- ...options?.form?.reset,
122
- ...model.value.form?.reset
123
108
  },
124
- dialog: {
125
- width: 635,
126
- ...options?.form?.dialog,
127
- ...model.value.form?.dialog
128
- }
109
+ slots: {}
129
110
  };
130
- //合并表单项默认属性
131
- model.value.formItems = {
111
+ model.value.form = mergeObjects(
112
+ defaultFormAttrs,
113
+ options?.form,
114
+ model.value?.form
115
+ );
116
+ const defaultFormItemsAttrs = {
132
117
  type: "hidden",
133
118
  style: {
134
119
  width: "100%",
135
- ...options?.formItems?.style,
136
- ...model.value.formItems?.style
137
- },
138
- ...model.value.formItems,
120
+ }
139
121
  };
122
+ model.value.formItems = mergeObjects(
123
+ defaultFormItemsAttrs,
124
+ options?.formItems,
125
+ model?.formItems
126
+ );
140
127
 
141
128
  //获取表单项验证规则
142
129
  const getItemRulesByItem = (item) => {
@@ -231,40 +218,35 @@ const load = () => {
231
218
  if (!model.value.form.inline) {
232
219
  model.value.form.columns = 1;
233
220
  }
234
- if (model.value.form.style.width) {
235
- columnsWidth.value = removePx(model.value.form.style.width) / model.value.form.columns;
236
- }
237
- else {
238
- model.value.form.style.width = "600px";
239
- }
240
- model.value.form.dialog.width = +model.value?.form?.style?.width.replace("px", "") + 35 + "px";
241
221
  for (const [key, value] of Object.entries(items.value)) {
242
- model.value[key].formItem = { ...model.value[key].formItem, ...value.formItem }
222
+ model.value[key].formItem = { ...model.value[key].formItem, ...value.formItem };
243
223
  model.value[key].formItem.style = { ...model.value[key].formItem.style, ...value.formItem.style };
244
- if (value.columns && value.columns > 1) {
245
- if (model.value.form.inline) {
246
- let column = columnsWidth.value * value.columns - (value.columns - 1) * marginRight.value;
247
- model.value[key].formItem.style.width = `calc(${column}px)`;
248
- } else {
249
- model.value[key].formItem.style.width = `calc(${columnsWidth.value - marginRight.value}px)`;
250
- }
251
- } else {
252
- model.value[key].formItem.style.width = `calc(${columnsWidth.value - marginRight.value}px)`;
253
- }
224
+
225
+ const columnWidth = value.columns && value.columns > 1 ?
226
+ columnsWidth.value * value.columns - (value.columns - 1) * marginRight.value :
227
+ columnsWidth.value - marginRight.value;
228
+ model.value[key].formItem.style.width = `calc(${columnWidth}px)`;
254
229
  }
255
- }
230
+ };
231
+
232
+ onBeforeUnmount(() => {
233
+ console.log('destroy');
234
+ })
256
235
 
257
236
  watch(() => model.value.form.columns, () => load());
258
237
  watch(() => model.value.form.inline, () => load());
259
238
 
260
- const removePx = (el) => parseFloat(el.replace("px", ""));
239
+ const columnsWidth = computed(() => convertToPx(model.value.form.style.width) / model.value.form.columns);
261
240
  //表单项
262
- const items = computed(() => Object.fromEntries(Object.entries(model.value).filter(a => a[1].type != "hidden")
241
+ const items = computed(() => Object.fromEntries(Object.entries(model.value)
242
+ .filter(a => a[1].type != "hidden")
263
243
  .filter(a => (a[1].edit !== false))
264
244
  .filter(a => (a[1].type !== "control"))
265
245
  .map(key => [key[0], key[1]])));
266
246
  //表单提交对象
267
- const formInfo = computed(() => Object.fromEntries(Object.entries(model.value).filter(a => a[1].type != "hidden").map(key => [key[0], key[1].value])));
268
- //表单列数
269
- const columns = computed(() => model.value.form.columns);
247
+ const formInfo = computed(() => Object.fromEntries(Object.entries(model.value)
248
+ .filter(a => a[1].type != "hidden")
249
+ .filter(a => a[1].type != "control")
250
+ .map(key => [key[0], key[1].value])));
251
+
270
252
  </script>
@@ -0,0 +1,31 @@
1
+ <template>
2
+ <template v-for="(item, key) of getQueryModel(props.more)" :key="key">
3
+ <slot :name="`query-item-${key}`" :item :model>
4
+ <el-form-item :label="item.label" v-bind="{ ...model.queryItems, ...item?.search }">
5
+ <slot :name="'query-' + key" :item :model>
6
+ <g-ctrl v-model="queryModel[key]" :item :ctrlKey="key" :model>
7
+ <template v-for="(slot, slotKey, index) in $slots" :key="index" #[slotKey]="data">
8
+ <slot :name="slotKey" :data :item :model></slot>
9
+ </template>
10
+ </g-ctrl>
11
+ </slot>
12
+ <template #label>
13
+ <slot :name="'query-label-' + key" :item :model></slot>
14
+ </template>
15
+ </el-form-item>
16
+ </slot>
17
+ </template>
18
+ </template>
19
+
20
+ <script setup>
21
+ import gCtrl from './g-ctrl.vue';
22
+ import { computed, ref } from "vue";
23
+ const model = defineModel({ required: true });
24
+ const props = defineProps(["more"]);
25
+ const queryModel = defineModel("info", { required: true });
26
+ const getQueryModel = (flag) => computed(() =>
27
+ Object.fromEntries(Object.entries(model.value)
28
+ .filter(a => a[1].search && a[1].search.more === flag).map(key => [key[0], key[1]]))).value;
29
+ </script>
30
+
31
+ <style lang="scss" scoped></style>
@@ -0,0 +1,217 @@
1
+ <template>
2
+ <div class="g-query-container" style="position: relative;">
3
+ <el-form v-bind="filterObject(model.query, k => !['query', 'more', 'reset'].includes(k))">
4
+ <g-query-item v-model="model" v-model:info="queryModel" :more="undefined">
5
+ <template v-for="(slot, slotKey, index) in $slots" :key="index" #[slotKey]="data">
6
+ <slot :name="slotKey" :model></slot>
7
+ </template>
8
+ </g-query-item>
9
+ <el-form-item>
10
+ <el-button v-bind="model.query.query" @click="model.query.query.click()">
11
+ {{ model.query.query.label }}
12
+ </el-button>
13
+ <el-button v-bind="model.query.reset" @click="model.query.reset.click()">
14
+ {{ model.query.reset.label }}
15
+ </el-button>
16
+ <el-button v-bind="model.query.more" @click="model.query.more.click()"
17
+ v-if="model.query.more.mode !== 3">
18
+ {{ model.query.more.label }}
19
+ </el-button>
20
+ <el-popover v-bind="model.query.more.dialog" v-if="model.query.more.mode == 3">
21
+ <template #reference>
22
+ <el-button v-bind="model.query.more">
23
+ {{ model.query.more.label }}
24
+ </el-button>
25
+ </template>
26
+ <template #default>
27
+ <el-form v-bind="filterObject(model.query, k => !['query', 'more', 'reset'].includes(k))">
28
+ <g-query-item v-model="model" v-model:info="queryModel" :more="true">
29
+ <template v-for="(slot, slotKey, index) in $slots" :key="index" #[slotKey]="data">
30
+ <slot :name="slotKey" :model></slot>
31
+ </template>
32
+ </g-query-item>
33
+ </el-form>
34
+ </template>
35
+ </el-popover>
36
+ <slot name="btns"></slot>
37
+ </el-form-item>
38
+ </el-form>
39
+ <Transition :duration="300" name="nested">
40
+ <div v-if="isShowMore && !model.query.more.hidden && model.query.more.mode == 1" class="outer">
41
+ <div class="inner">
42
+ <el-form v-bind="filterObject(model.query, k => !['query', 'more', 'reset'].includes(k))"
43
+ v-show="isShowMore" v-if="model.query.more.mode == 1">
44
+ <g-query-item v-model="model" v-model:info="queryModel" :more="true">
45
+ <template v-for="(slot, slotKey, index) in $slots" :key="index" #[slotKey]="data">
46
+ <slot :name="slotKey" :model></slot>
47
+ </template>
48
+ </g-query-item>
49
+ </el-form>
50
+ </div>
51
+ </div>
52
+ </Transition>
53
+ <el-drawer v-model="isShowMore" title="更多查询" :show-close="false" v-bind="model.query.more.dialog"
54
+ v-if="isShowMore && !model.query.more.hidden && model.query.more.mode == 2">
55
+ <el-form v-bind="filterObject(model.query, k => !['query', 'more', 'reset'].includes(k))">
56
+ <g-query-item v-model="model" v-model:info="queryModel" :more="true">
57
+ <template v-for="(slot, slotKey, index) in $slots" :key="index" #[slotKey]="data">
58
+ <slot :name="slotKey" :model></slot>
59
+ </template>
60
+ </g-query-item>
61
+ </el-form>
62
+ <template #footer v-if="!model.query.more.dialog.footer.hidden">
63
+ <div v-bind="model.query.more.dialog.footer">
64
+ <el-button v-bind="model.query.query" @click="model.query.query.click()">
65
+ {{ model.query.query.label }}
66
+ </el-button>
67
+ <el-button v-bind="model.query.reset" @click="model.query.reset.click()">
68
+ {{ model.query.reset.label }}
69
+ </el-button>
70
+ </div>
71
+ </template>
72
+ </el-drawer>
73
+ </div>
74
+ </template>
75
+
76
+ <script setup>
77
+ import { ref, inject } from 'vue'
78
+ import gQueryItem from './g-query-item.vue';
79
+ import { mergeObjects, filterObject } from './index';
80
+ const model = defineModel({ required: true });
81
+ const emits = defineEmits(['query', 'reset']);
82
+ const options = inject('options');
83
+ const isShowMore = ref(false);
84
+ let initValues = Object.fromEntries(Object.entries(model.value).filter(a => a[1].search).map(([key, val]) => [key, val.value]));
85
+ const queryModel = ref(initValues);
86
+ const defaultQueryAttrs = {
87
+ hidden: false,
88
+ type: "hidden",
89
+ labelWidth: 100,
90
+ inline: true,
91
+ style: { display: "flex", flex: 1, "flex-wrap": "wrap" },
92
+ more: {
93
+ mode: 2,
94
+ hidden: false,
95
+ type: "primary",
96
+ label: "更多",
97
+ click: () => {
98
+ isShowMore.value = !isShowMore.value;
99
+ },
100
+ },
101
+ reset: {
102
+ label: "重置",
103
+ type: "primary",
104
+ hidden: false,
105
+ plain: false,
106
+ click: () => {
107
+ queryModel.value = ref(initValues);
108
+ emits('reset', queryModel.value);
109
+ },
110
+ },
111
+ query: {
112
+ hidden: false,
113
+ type: "primary",
114
+ label: "查询",
115
+ click: () => {
116
+ emits('query', queryModel.value);
117
+ isShowMore.value = false;
118
+ }
119
+ }
120
+ }
121
+
122
+ // 查询表单属性
123
+ model.value.query = mergeObjects(defaultQueryAttrs, options?.query, model.value.query);
124
+
125
+ // 表单 查询更多 属性 drawer 方式2 和 popover 方式3
126
+ model.value.query.more.dialog = model.value.query.more.mode == 2 ? mergeObjects({
127
+ title: "更多查询", showClose: false, size: '30%', direction: "ttb", footer: {
128
+ hidden: false,
129
+ style: { textAlign: 'center' }
130
+ }
131
+ }, model.value.query.more.dialog) : model.value.query.more.mode == 3 ? mergeObjects({
132
+ placement: "top-start", title: "更多查询", width: 1200, trigger: "click"
133
+ }, model.value.query.more.dialog) : {};
134
+
135
+
136
+ // 表单查询项属性
137
+ const defaultQueryItemsAttrs = {
138
+ type: "hidden",
139
+ labelPosition: 'right',
140
+ style: {
141
+ width: "250px"
142
+ }
143
+ };
144
+ model.value.queryItems = mergeObjects(defaultQueryItemsAttrs, options?.queryItems, model.value.queryItems);
145
+
146
+ </script>
147
+
148
+ <style scoped>
149
+ .g-query-container {
150
+ display: flex;
151
+ flex: 1;
152
+ flex-direction: column;
153
+ padding: 10px;
154
+ box-sizing: border-box;
155
+ }
156
+
157
+ .outer {
158
+ /* position: absolute; */
159
+ min-height: 100px;
160
+ border-radius: 10px;
161
+ background-color: rgba(255, 255, 255, 0.8);
162
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
163
+ top: 60px;
164
+ padding: 10px;
165
+ width: 100%;
166
+ box-sizing: border-box;
167
+ }
168
+
169
+ .inner {
170
+ border-radius: 0;
171
+ min-height: 100px;
172
+ border: 1px solid #f2f2f2;
173
+ padding: 20px;
174
+ box-sizing: border-box;
175
+ width: 100%;
176
+ background-color: rgba(255, 255, 255, 1);
177
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
178
+ }
179
+
180
+ .nested-enter-active,
181
+ .nested-leave-active {
182
+ transition: all 0.3s ease-in-out;
183
+ }
184
+
185
+ /* delay leave of parent element */
186
+ .nested-leave-active {
187
+ transition-delay: 0.25s;
188
+ }
189
+
190
+ .nested-enter-from,
191
+ .nested-leave-to {
192
+ transform: translateY(-100px);
193
+ opacity: 0;
194
+ }
195
+
196
+ /* we can also transition nested elements using nested selectors */
197
+ .nested-enter-active .inner,
198
+ .nested-leave-active .inner {
199
+ transition: all 0.3s ease-in-out;
200
+ }
201
+
202
+ /* delay enter of nested element */
203
+ .nested-enter-active .inner {
204
+ transition-delay: 0.25s;
205
+ }
206
+
207
+ .nested-enter-from .inner,
208
+ .nested-leave-to .inner {
209
+ transform: translateY(-100px);
210
+ /*
211
+ Hack around a Chrome 96 bug in handling nested opacity transitions.
212
+ This is not needed in other browsers or Chrome 99+ where the bug
213
+ has been fixed.
214
+ */
215
+ opacity: 0.001;
216
+ }
217
+ </style>
@@ -1,8 +1,32 @@
1
1
  <template>
2
- <el-table class="g-table-container" v-bind="{ ...$attrs, ...model.table }">
2
+ <div class="table-query-container">
3
+ <slot name="query">
4
+ <g-query v-model="model" @query="query" @reset="reset">
5
+ <template v-for="(slot, slotKey, index) in $slots" :key="index" #[slotKey]="data">
6
+ <slot :name="slotKey" :model></slot>
7
+ </template>
8
+ <template #btns>
9
+ <slot name="btns">
10
+ <div class="btns" v-if="model.control.add.auth()">
11
+ <slot name="control-add-left"></slot>
12
+ <slot name="control-add">
13
+ <el-button v-bind="model.control.add" @click="model.control.add.click(model)">{{
14
+ model.control.add.label
15
+ }}</el-button>
16
+ </slot>
17
+ <slot name="control-add-right"></slot>
18
+ </div>
19
+ </slot>
20
+
21
+ </template>
22
+ </g-query>
23
+ </slot>
24
+
25
+ </div>
26
+ <el-table class="g-table-container" v-bind="{ ...$attrs, ...filterObject(model.table, k => k !== 'page') }">
3
27
  <slot>
4
28
  <el-table-column :prop="key" v-for="(value, key) of tableColumns" :key="key"
5
- v-bind="{ ...itemValue, ...value }" header-align="center">
29
+ v-bind="{ ...model.tableColumns, ...value }" header-align="center">
6
30
  <template #default="{ row, column, $index }"
7
31
  v-if="value.type != 'selection' && value.type != 'index' && value.type != 'expand'">
8
32
  <slot :name="`${key}`" :row :column :$index>
@@ -16,7 +40,7 @@
16
40
  <slot name="control-view" :row :column :$index
17
41
  v-if="model.control.view.auth(row, column, $index)">
18
42
  <el-button v-bind="model.control.view"
19
- @click="model.control.view.view(row, column, $index, model)">{{
43
+ @click="model.control.view.click(row, column, $index, model)">{{
20
44
  model.control.view.label
21
45
  }}</el-button>
22
46
  </slot>
@@ -24,7 +48,7 @@
24
48
  <slot name="control-edit" :row :column :$index
25
49
  v-if="model.control.edit.auth(row, column, $index)">
26
50
  <el-button v-bind="model.control.edit"
27
- @click="model.control.edit.edit(row, column, $index, model)">{{
51
+ @click="model.control.edit.click(row, column, $index, model)">{{
28
52
  model.control.edit.label
29
53
  }}</el-button>
30
54
  </slot>
@@ -32,7 +56,7 @@
32
56
  <slot name="control-delete" :row :column :$index
33
57
  v-if="model.control.delete.auth(row, column, $index)">
34
58
  <el-button v-bind="model.control.delete"
35
- @click="model.control.delete.delete(row, column, $index)">{{
59
+ @click="model.control.delete.click(row, column, $index)">{{
36
60
  model.control.delete.label }}</el-button>
37
61
  </slot>
38
62
  <slot name="control-right" :row :column :$index> </slot>
@@ -65,148 +89,216 @@
65
89
  <script setup>
66
90
  import dayjs from 'dayjs';
67
91
  import { ElMessageBox } from 'element-plus';
68
- import { computed, onMounted, reactive, watchEffect, h ,inject} from 'vue';
92
+ import { computed, onMounted, watchEffect, h, inject, ref, defineAsyncComponent, shallowRef } from 'vue';
69
93
  import GForm from './g-form.vue';
70
- import { openModal } from './index';
94
+ import gQuery from './g-query.vue';
95
+ import { openModal, mergeObjects, convertToPx } from './index';
96
+ import { filterObject } from '.';
71
97
 
72
98
  defineOptions({
73
99
  name: "g-table",
74
100
  inheritAttrs: false
75
101
  })
76
102
 
77
- const emits = defineEmits(['load', 'view', "edit", "delete"]);
103
+ const emits = defineEmits(['load', 'view', "edit", "delete", 'add']);
78
104
  const model = defineModel({ required: true });
79
105
  const options = inject('options');
106
+ const queryInfo = ref({});
80
107
 
81
108
  //table default attrs
82
- model.value.table = {
109
+ const defaultTableAttrs = {
83
110
  data: [],
84
111
  border: true,
85
112
  stripe: true,
86
113
  emptyText: '暂无数据',
87
114
  fit: false,
88
115
  type: "hidden",
89
- height: "calc(100vh - 55px)",
90
- ...options?.table,
91
- ...model.value.table,
116
+ height: "calc(100vh - 65px)",
92
117
  load: () => {
93
118
  let { currentPage, pageSize } = model.value.table.page;
94
- emits('load', { currentPage, pageSize }, (data, total) => {
119
+ emits('load', { currentPage, pageSize, ...queryInfo.value }, (data, total) => {
95
120
  model.value.table.data = data;
96
121
  model.value.table.page.total = total;
97
122
  });
98
123
  },
99
124
  page: {
100
- background: true,
125
+ background: "red",
101
126
  total: 0,
102
127
  pageSize: 20,
103
128
  currentPage: 1,
104
129
  pageSizes: [5, 10, 20, 30, 40, 50],
105
130
  layout: 'total, sizes, prev, pager, next, jumper',
106
- ...options?.table?.page,
107
- ...model.value.table?.page,
108
131
  style: {
109
132
  display: 'flex',
110
133
  justifyContent: 'flex-end',
111
134
  padding: "10px",
112
- ...model.value.table?.page?.style
113
135
  },
114
136
  }
115
- }
137
+ };
138
+ model.value.table = mergeObjects(
139
+ defaultTableAttrs,
140
+ options?.table,
141
+ model.value.table
142
+ );
116
143
 
117
- model.value.form = {
118
- type: 'hidden',
119
- ...options?.form,
120
- ...model.value.form,
121
- dialog: {
122
- width: 635,
123
- ...model.value.form?.dialog,
124
- style: {
125
- ...model.value.form?.dialog?.style
126
- }
127
- }
128
- }
129
- model.value.control = {
130
- type: 'control', label: '操作', edit: false, width: 250, fixed: 'right',...options?.control, ...model.value?.control,
144
+ let closeModel = ref(() => { });
145
+ const editModel = defineAsyncComponent(() => import('./g-form.vue'));
146
+ const defaultControl = {
147
+ type: 'control', label: '操作', edit: false, width: 250, fixed: 'right',
131
148
  style: { ...model.value?.control?.style },
149
+ add: {
150
+ label: '新增', text: false, type: 'primary',
151
+ style: {
152
+ marginLeft: "10px",
153
+ },
154
+ dialog: {
155
+ title: '新增',
156
+ mode: "drawer",
157
+ slots: {
158
+ default: () => h(GForm, { modelValue: model.value }, { ...model.value.form.slots }),
159
+ },
160
+ },
161
+ auth: () => true,
162
+ click: (m) => {
163
+ model.value.form = {
164
+ ...model.value.form,
165
+ type: 'hidden',
166
+ disabled: undefined,
167
+ };
168
+ let w = model.value?.form?.style?.width || 600;
169
+ if (model.value.control.add.dialog.mode == "drawer") {
170
+ model.value.control.add.dialog.size = convertToPx(w) + 45;
171
+ } else {
172
+ model.value.control.add.dialog.width = convertToPx(w) + 35;
173
+ }
174
+ emits("add", m);
175
+ Object.keys(initFormData).forEach(key => {
176
+ if (model.value[key]) {
177
+ model.value[key].value = initFormData[key];
178
+ }
179
+ });
180
+ model.value.form.close = openModal(model.value.control.add.dialog, model.value?.control?.add?.dialog?.mode);
181
+ },
182
+ },
132
183
  view: {
133
184
  label: '查看', text: true, type: 'success',
185
+ dialog: {
186
+ title: '查看',
187
+ mode: "dialog",
188
+ slots: {
189
+ default: () => h(GForm, { modelValue: model.value }, { ...model.value.form.slots }),
190
+ },
191
+ },
134
192
  auth: (row, column, $index) => {
135
193
  return false;
136
194
  },
137
- view: (row, column, $index, m) => {
138
- model.value.form.disabled = true;
195
+ click: (row, column, $index, m) => {
196
+ model.value.form = {
197
+ ...model.value.form,
198
+ type: 'hidden',
199
+ disabled: true,
200
+ };
201
+ let w = model.value?.form?.style?.width || 600;
202
+ if (model.value.control.view.dialog.mode == "drawer") {
203
+ model.value.control.view.dialog.size = convertToPx(w) + 45;
204
+ } else {
205
+ model.value.control.view.dialog.width = convertToPx(w) + 35;
206
+ }
139
207
  emits("view", row, column, $index, m);
140
- model.value.form.dialog.width = +(model.value?.form?.style?.width?.replace("px", "")) + 35 || 635;
141
- openModal({
142
- title: '查看',
143
- width: model.value.form?.dialog?.width,
144
- ...model.value.form.dialog,
145
- slots: {
146
- // 自定义插槽内容
147
- default: () => h(GForm, { modelValue: model.value }),
148
- ...model.value?.form?.dialog?.slots
149
- },
208
+ Object.keys(row).forEach(key => {
209
+ if (model.value[key]) {
210
+ model.value[key].value = row[key];
211
+ }
150
212
  });
213
+ model.value.form.close = openModal(model.value.control.view.dialog, model.value?.control?.view?.dialog?.mode);
151
214
  },
152
- ...options?.control?.view,
153
- ...model.value?.control?.view
154
215
  },
155
216
  edit: {
156
217
  label: '编辑', text: true, type: 'primary',
218
+ dialog: {
219
+ title: '编辑',
220
+ mode: "dialog",
221
+ slots: {
222
+ default: () => {
223
+ model.value.form.onUsubmit = () => {
224
+ if (closeModel.value)
225
+ closeModel.value();
226
+ }
227
+ return h(editModel, { modelValue: model.value, ...model.value.form }, { ...model.value.form.slots })
228
+ },
229
+ },
230
+ },
157
231
  auth: (row, column, $index) => {
158
232
  return true;
159
233
  },
160
- edit: (row, column, $index, m) => {
161
- model.value.form.disabled = undefined;
234
+ click: (row, column, $index, m) => {
235
+ model.value.form = {
236
+ ...model.value.form,
237
+ type: 'hidden',
238
+ disabled: undefined,
239
+ };
240
+ let w = model.value?.form?.style?.width || 600;
241
+ if (model.value.control.edit.dialog.mode == "drawer") {
242
+ model.value.control.edit.dialog.size = convertToPx(w) + 45;
243
+ } else {
244
+ model.value.control.edit.dialog.width = convertToPx(w) + 35;
245
+ }
162
246
  emits("edit", row, column, $index, m);
163
- model.value.form.dialog.width = +(model.value?.form?.style?.width?.replace("px", "")) + 35 || 635;
164
- openModal({
165
- title: '编辑',
166
- width: model.value.form?.dialog?.width,
167
- ...model.value.form.dialog,
168
- slots: {
169
- // 自定义插槽内容
170
- default: () => h(GForm, { modelValue: model.value }),
171
- ...model.value?.form?.dialog?.slots
172
- },
247
+ Object.keys(row).forEach(key => {
248
+ if (model.value[key]) {
249
+ model.value[key].value = row[key];
250
+ }
173
251
  });
174
-
175
- },
176
- ...options?.control?.edit,
177
- ...model.value?.control?.edit
252
+ closeModel.value = openModal(model.value?.control?.edit?.dialog, model.value?.control?.edit?.dialog?.mode);
253
+ },
178
254
  },
179
255
  delete: {
180
256
  text: true, label: '删除', type: 'danger',
181
257
  auth: (row, column, $index) => {
182
258
  return true;
183
259
  },
184
- delete: (row, column, $index) => {
185
- ElMessageBox.confirm('此操作将永久删除该文件, 是否继续?', '提示', {
186
- confirmButtonText: '确定',
187
- cancelButtonText: '取消',
188
- type: 'warning'
189
- }).then(() => {
260
+ dialog: {
261
+ message: '此操作将永久删除该文件, 是否继续?', title: '提示', options: { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }
262
+ },
263
+ click: (row, column, $index) => {
264
+ let { message, title, options } = model.value.control.delete.dialog;
265
+ ElMessageBox.confirm(message, title, options).then(() => {
190
266
  emits("delete", { row, column, $index }, (res) => {
191
267
  if (res) {
192
268
  model.value.table.data.splice($index, 1);
193
- console.log(model.value.table.data)
269
+ model.value.table.page.total--;
194
270
  }
195
271
  })
196
272
  }).catch(() => { });
197
273
 
198
- },
199
- ...options?.control?.delete,
200
- ...model.value?.control?.delete
274
+ },
201
275
  },
202
276
  };
203
277
 
204
- //table column default attrs
205
- const itemValue = reactive({
206
- width: 100, align: "center", formatter: (row, column, cellValue, index) => {
278
+ model.value.control = mergeObjects(
279
+ defaultControl,
280
+ options?.control,
281
+ model.value?.control
282
+ );
283
+
284
+ const defaultTableColumnsAttrs = {
285
+ type: "hidden",
286
+ width: 120,
287
+ align: "center",
288
+ formatter: (row, column, cellValue, index) => {
207
289
  return cellValue;
208
290
  }
209
- });
291
+ };
292
+
293
+ model.value.tableColumns = mergeObjects(
294
+ defaultTableColumnsAttrs,
295
+ options?.tableColumns,
296
+ model.value?.tableColumns
297
+ );
298
+
299
+ const initFormData = Object.fromEntries(Object.entries(filterObject(model.value, (k, val) => {
300
+ return (val.type != 'control' && val.edit !== false && val.type != 'hidden');
301
+ })).map(([key, val]) => [key, val.value]));
210
302
 
211
303
  //扩展formatter方法
212
304
  const formatterExtends = (item, str) => {
@@ -247,6 +339,17 @@ const formatterExtends = (item, str) => {
247
339
  return trueValue
248
340
  }
249
341
 
342
+ const reset = (q) => {
343
+ console.log(q);
344
+ queryInfo.value = q.value;
345
+ //model.value.table.load();
346
+ }
347
+
348
+ const query = (q) => {
349
+ queryInfo.value = q;
350
+ //model.value.table.load();
351
+ }
352
+
250
353
  onMounted(() => {
251
354
 
252
355
  });
@@ -260,8 +363,9 @@ const tableColumns = computed(() => Object.fromEntries(
260
363
  Object.entries(model.value)
261
364
  .filter(a => a[1].type != "hidden")
262
365
  .filter(a => a[1].visible !== false)
263
- .map(key => [key[0], key[1]]))
366
+ .map(key => {
367
+ key[1].sort = key[1].sort || 0;
368
+ return [key[0], key[1]]
369
+ }).sort((a, b) => a[1].sort - b[1].sort))
264
370
  );
265
- </script>
266
-
267
- <style lang="scss" scoped></style>
371
+ </script>
@@ -1,19 +1,37 @@
1
- import { App } from 'vue';
2
-
3
- interface ModalProps {
4
- slots: {
5
- header?: () => JSX.Element;
6
- default?: () => JSX.Element;
7
- footer?: () => JSX.Element;
8
- [key: string]: (() => JSX.Element) | undefined;
1
+ import { Component, VNode } from 'vue';
2
+
3
+ declare const ElementPlus: any; // 假设ElementPlus已经有了类型声明
4
+ declare const ElDialog: Component;
5
+ declare const ElDrawer: Component;
6
+
7
+ export interface GlobalOptions {
8
+ // 这里定义全局选项的类型
9
+ }
10
+
11
+ export interface OpenModalProps {
12
+ slots?: {
13
+ default?: (a: any) => VNode;
14
+ header?: (p: any) => VNode;
15
+ footer?: (p: any) => VNode;
9
16
  };
10
- [key: string]: any;
17
+ // 其他props的类型定义
11
18
  }
12
19
 
13
- declare const _default: {
14
- install(app: App, options?: any): void;
20
+ export function openModal(props?: OpenModalProps, mode?: 'dialog' | 'drawer'): () => void;
21
+
22
+ export function convertToPx(value: string | number, referenceSize?: number): number;
23
+
24
+ export function mergeObjects<T>(...objects: T[]): T;
25
+
26
+ export function filterObject<T>(obj: T, filter?: (key: keyof T) => boolean): T;
27
+
28
+ export const components: {
29
+ gTable: Component;
30
+ gForm: Component;
31
+ // 其他组件的类型定义
15
32
  };
16
33
 
34
+ declare const _default: {
35
+ install: (app: any, options?: GlobalOptions) => void;
36
+ };
17
37
  export default _default;
18
-
19
- export declare const openModal: (props?: ModalProps, comType?: 'dialog' | 'drawer') => void;
@@ -4,46 +4,140 @@ import ElementPlus from 'element-plus'
4
4
  import { ElDialog, ElDrawer } from "element-plus";
5
5
  import gTable from "./g-table.vue";
6
6
  import gForm from "./g-form.vue";
7
- import { createApp, h, provide, reactive, readonly, ref } from "vue";
8
- const goptions={};
7
+ import { createApp, h, provide, readonly, ref } from "vue";
8
+ const globalOptions = {};
9
+
10
+ const components = [
11
+ gTable,
12
+ gForm
13
+ ]
9
14
 
10
15
  export default {
11
16
  install(app, options = {}) {
12
- Object.assign(goptions, options);
13
- app.component("g-table", gTable);
14
- app.component("g-form", gForm);
17
+ Object.assign(globalOptions, options);
18
+ for (const component of components) {
19
+ app.component(component.name, component);
20
+ }
15
21
  app.provide("options", readonly(options));
22
+ app.config.globalProperties.openModel = openModal;
16
23
  }
17
24
  }
18
- const openModal = (props = {}, comType = "dialog") => {
25
+ const openModal = (props = {}, mode = "dialog") => {
19
26
  const el = document.createElement('div');
20
- const app = createApp({
27
+ let closeModal = () => { };
28
+ const visible = ref(true);
29
+ let modalApp = createApp({
21
30
  setup() {
22
- const visible = ref(true);
23
- const closeModal = () => {
31
+ closeModal = () => {
24
32
  visible.value = false;
25
- app.unmount(); // 关闭对话框时卸载组件
33
+ modalApp.unmount(); // 关闭对话框时卸载组件
26
34
  delete el.remove(); // 关闭对话框时删除元素
35
+ modalApp = null;
27
36
  };
28
- provide("options", goptions);
29
- return () => h(comType == "dialog" ? ElDialog : comType == "drawer" ? ElDrawer : null, {
37
+ provide("options", globalOptions);
38
+ return () => h(mode == "dialog" ? ElDialog : mode == "drawer" ? ElDrawer : null, {
30
39
  modelValue: visible.value,
31
40
  'onUpdate:modelValue': closeModal,
32
41
  draggable: true,
33
42
  "append-to-body": true,
34
- "v-if": visible.value,
35
43
  ...props
36
44
  },
37
- () => [
38
- props.slots.header ? h('div', { slot: 'header' }, props.slots.header()) : null,
39
- props.slots.default ? h(props.slots.default()) : null,
40
- props.slots.footer ? h('div', { slot: 'footer' }, props.slots.footer()) : null,
41
- ]
45
+ {
46
+ default: props.slots?.default ? (a) => h('div', {}, props.slots.default(a)) : null,
47
+ header: props.slots?.header ? (p) => h('div', {}, props.slots.header(p)) : null,
48
+ footer: props.slots?.footer ? (p) => h('div', {}, props.slots.footer(p)) : null
49
+ }
42
50
  );
43
51
  }
44
52
  });
45
- app.use(ElementPlus).mount(el);
53
+ for (const component of components) {
54
+ modalApp.component(component.name, component);
55
+ }
56
+ modalApp.use(ElementPlus).mount(el);
46
57
  document.body.appendChild(el);
58
+ return closeModal;
47
59
  };
48
60
 
49
- export { openModal };
61
+ function convertToPx(value, referenceSize = window.innerWidth) {
62
+ // 如果已经是px,直接返回
63
+ if (typeof value === 'number') {
64
+ return value;
65
+ }
66
+ if (value.endsWith('px')) {
67
+ return parseFloat(value);
68
+ }
69
+
70
+ // 处理vh和vw
71
+ const viewportHeight = window.innerHeight;
72
+ const viewportWidth = window.innerWidth;
73
+ if (value.endsWith('vh')) {
74
+ return (viewportHeight * parseFloat(value) / 100);
75
+ }
76
+ if (value.endsWith('vw')) {
77
+ return (viewportWidth * parseFloat(value) / 100);
78
+ }
79
+
80
+ // 处理%值,需要提供参考大小
81
+ if (value.endsWith('%')) {
82
+ if (typeof referenceSize !== 'number') {
83
+ throw new Error('Reference size is required to convert % to px');
84
+ }
85
+ return (referenceSize * parseFloat(value) / 100);
86
+ }
87
+
88
+ // 处理calc表达式
89
+ if (value.startsWith('calc(') && value.endsWith(')')) {
90
+ const calcExpr = value.slice(5, -1).trim();
91
+ // 使用一个安全的eval替代方案来解析calc表达式
92
+ function safeEval(expr) {
93
+ // 只允许数字、加减乘除运算符、小括号和空格
94
+ if (/[0-9+\-*/().\s]+$/.test(expr)) {
95
+ return new Function('return ' + expr)();
96
+ } else {
97
+ throw new Error('Invalid calc expression');
98
+ }
99
+ }
100
+ // 在这里,我们假设calc表达式中可能包含其他单位,因此递归调用parseValueToPx
101
+ const result = safeEval(calcExpr.replace(/([0-9.]+)([a-z%]+)/g, (match, number, unit) => {
102
+ return convertToPx(number + unit, referenceSize);
103
+ }));
104
+ return result;
105
+ }
106
+
107
+ // 如果无法识别单位,抛出错误
108
+ throw new Error('Unsupported unit in value: ' + value);
109
+ }
110
+
111
+
112
+ function mergeObjects(...objects) {
113
+ const result = ref({});
114
+ for (const obj of objects) {
115
+ if (obj && typeof obj === 'object') {
116
+ for (const key in obj) {
117
+ if (obj.hasOwnProperty(key)) {
118
+ if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
119
+ result.value[key] = mergeObjects(result.value[key], obj[key]);
120
+ } else {
121
+ result.value[key] = obj[key];
122
+ }
123
+ }
124
+ }
125
+ }
126
+ }
127
+ return result.value;
128
+ }
129
+
130
+
131
+ function filterObject(obj, filter = (key, value) => filter(key, value)) {
132
+ const result = {};
133
+ for (const key in obj) {
134
+ if (obj.hasOwnProperty(key)) {
135
+ if (filter(key, obj[key])) {
136
+ result[key] = obj[key];
137
+ }
138
+ }
139
+ }
140
+ return result;
141
+ }
142
+
143
+ export { openModal, convertToPx, mergeObjects, filterObject };