@fishawack/lab-velocity 2.0.0-beta.30 → 2.0.0-beta.31

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.
@@ -6,6 +6,8 @@
6
6
  :disabled="disabled"
7
7
  v-model="content"
8
8
  :required="required"
9
+ :true-label="trueValue"
10
+ :false-label="falseValue"
9
11
  @change="handleInput"
10
12
  >
11
13
  </el-checkbox>
@@ -29,6 +31,14 @@ export default {
29
31
  type: String,
30
32
  default: "vel-checkbox",
31
33
  },
34
+ trueValue: {
35
+ type: [String, Number, Boolean],
36
+ default: true,
37
+ },
38
+ falseValue: {
39
+ type: [String, Number, Boolean],
40
+ default: false,
41
+ },
32
42
  },
33
43
 
34
44
  components: {
@@ -5,6 +5,7 @@
5
5
  </template>
6
6
 
7
7
  <el-select
8
+ ref="select"
8
9
  v-model="content"
9
10
  :class="baseClass"
10
11
  :multiple="multiple"
@@ -14,32 +15,42 @@
14
15
  :collapse-tags="collapseTags"
15
16
  :filterable="filterable"
16
17
  :value-on-clear="null"
17
- @change="handleInput"
18
- @clear="this.$emit('clear')"
19
- @blur="this.$emit('blur')"
18
+ remote-show-suffix
19
+ :remote="!!endpoint"
20
+ :remote-method="endpoint ? handleSearch : undefined"
21
+ :loading="initialLoading"
20
22
  :empty-values="[null, undefined]"
23
+ @change="handleInput"
24
+ @clear="$emit('clear')"
25
+ @blur="$emit('blur')"
26
+ @visible-change="handleVisibleChange"
21
27
  >
22
28
  <template #default>
23
29
  <slot name="default">
24
- <el-option
25
- v-if="!options[0]?.label"
26
- v-for="(label, value) in options"
27
- :key="value"
28
- :label="label"
29
- :value="castValue(value)"
30
- >
31
- </el-option>
32
- <el-option
33
- v-else
34
- v-for="option in options"
35
- :key="option.value"
36
- :label="option.label"
37
- :disabled="option.disabled"
38
- :value="castValue(option.value)"
39
- >
40
- </el-option>
30
+ <template v-if="!currentOptions[0]?.label">
31
+ <el-option
32
+ v-for="(label, value) in currentOptions"
33
+ :key="value"
34
+ :label="label"
35
+ :value="castValue(value)"
36
+ >
37
+ </el-option>
38
+ </template>
39
+ <template v-else>
40
+ <el-option
41
+ v-for="option in currentOptions"
42
+ :key="option.value"
43
+ :label="option.label"
44
+ :disabled="option.disabled"
45
+ :value="option"
46
+ >
47
+ </el-option>
48
+ </template>
41
49
  </slot>
42
50
  </template>
51
+ <template v-if="endpoint" #loading>
52
+ <div class="el-select-dropdown__loading">Loading...</div>
53
+ </template>
43
54
  </el-select>
44
55
  </XInput>
45
56
  </template>
@@ -47,12 +58,21 @@
47
58
  <script>
48
59
  import { ElSelect } from "element-plus";
49
60
  import { ElOption } from "element-plus";
61
+ import { ElNotification } from "element-plus";
50
62
  import input from "./input.js";
51
63
  import XInput from "./input.vue";
52
64
  import _ from "lodash";
65
+ import axios from "axios";
53
66
 
54
67
  export default {
68
+ components: {
69
+ XInput,
70
+ ElOption,
71
+ ElSelect,
72
+ },
73
+
55
74
  mixins: [input],
75
+
56
76
  props: {
57
77
  ...input.props,
58
78
  modelValue: {
@@ -77,21 +97,123 @@ export default {
77
97
  },
78
98
  options: {
79
99
  type: Array,
80
- default: [],
100
+ default: () => [],
81
101
  },
82
102
  multiple: {
83
103
  type: Boolean,
84
104
  default: false,
85
105
  },
106
+ endpoint: {
107
+ type: String,
108
+ default: null,
109
+ },
110
+ params: {
111
+ type: Object,
112
+ default: () => ({}),
113
+ },
114
+ labelKey: {
115
+ type: String,
116
+ default: "label",
117
+ },
118
+ valueKey: {
119
+ type: String,
120
+ default: "id",
121
+ },
122
+ searchParam: {
123
+ type: String,
124
+ default: "name",
125
+ },
126
+ perPage: {
127
+ type: Number,
128
+ default: 10,
129
+ },
86
130
  },
87
131
 
88
- components: {
89
- XInput,
90
- ElOption,
91
- ElSelect,
132
+ emits: ["change", "clear", "blur"],
133
+
134
+ data() {
135
+ return {
136
+ initialLoading: false,
137
+ loadingMore: false,
138
+ asyncOptions: [],
139
+ currentPage: 1,
140
+ hasMore: false,
141
+ searchQuery: "",
142
+ lastSearchQuery: "",
143
+ initialLoadDone: false,
144
+ scrollListener: null,
145
+ wrapScrollTop: 0,
146
+ wrapScrollLeft: 0,
147
+ };
148
+ },
149
+
150
+ computed: {
151
+ currentOptions() {
152
+ // If endpoint is provided, use async options, otherwise use static options
153
+ if (this.endpoint) {
154
+ return this.asyncOptions;
155
+ }
156
+ return this.options;
157
+ },
158
+ },
159
+
160
+ mounted() {
161
+ if (this.endpoint && this.$refs.select?.$refs?.scrollbarRef) {
162
+ const scrollbar = this.$refs.select.$refs.scrollbarRef;
163
+ const wrapRef = scrollbar.wrapRef;
164
+
165
+ this.scrollListener = () => {
166
+ if (!wrapRef) return;
167
+
168
+ const distance = 10;
169
+ const prevTop = this.wrapScrollTop;
170
+ const prevLeft = this.wrapScrollLeft;
171
+
172
+ this.wrapScrollTop = wrapRef.scrollTop;
173
+ this.wrapScrollLeft = wrapRef.scrollLeft;
174
+
175
+ const arrivedStates = {
176
+ bottom:
177
+ this.wrapScrollTop + wrapRef.clientHeight >=
178
+ wrapRef.scrollHeight - distance,
179
+ top: this.wrapScrollTop <= distance && prevTop !== 0,
180
+ right:
181
+ this.wrapScrollLeft + wrapRef.clientWidth >=
182
+ wrapRef.scrollWidth - distance &&
183
+ prevLeft !== this.wrapScrollLeft,
184
+ left: this.wrapScrollLeft <= distance && prevLeft !== 0,
185
+ };
186
+
187
+ let direction = null;
188
+ if (prevTop !== this.wrapScrollTop) {
189
+ direction = this.wrapScrollTop > prevTop ? "bottom" : "top";
190
+ }
191
+ if (prevLeft !== this.wrapScrollLeft) {
192
+ direction =
193
+ this.wrapScrollLeft > prevLeft ? "right" : "left";
194
+ }
195
+
196
+ if (direction && arrivedStates[direction]) {
197
+ this.handleEndReached(direction);
198
+ }
199
+ };
200
+
201
+ wrapRef.addEventListener("scroll", this.scrollListener);
202
+ }
203
+ },
204
+
205
+ beforeUnmount() {
206
+ if (
207
+ this.scrollListener &&
208
+ this.$refs.select?.$refs?.scrollbarRef?.wrapRef
209
+ ) {
210
+ this.$refs.select.$refs.scrollbarRef.wrapRef.removeEventListener(
211
+ "scroll",
212
+ this.scrollListener,
213
+ );
214
+ }
92
215
  },
93
216
 
94
- emits: ["change", "clear", "blur"],
95
217
  methods: {
96
218
  castValue(value) {
97
219
  if (
@@ -104,6 +226,87 @@ export default {
104
226
  }
105
227
  return value;
106
228
  },
229
+
230
+ async handleVisibleChange(visible) {
231
+ // Load data when dropdown is opened for the first time
232
+ if (visible && this.endpoint && !this.initialLoadDone) {
233
+ await this.fetchOptions(1, "");
234
+ this.initialLoadDone = true;
235
+ }
236
+ },
237
+
238
+ async handleSearch(query) {
239
+ if (!this.endpoint) return;
240
+ if (query === this.lastSearchQuery) return;
241
+
242
+ this.searchQuery = query;
243
+ this.lastSearchQuery = query;
244
+ this.currentPage = 1;
245
+ this.asyncOptions = [];
246
+ await this.fetchOptions(1, query);
247
+ },
248
+
249
+ handleEndReached(direction) {
250
+ if (!this.endpoint) return;
251
+ if (direction !== "bottom") return;
252
+ if (!this.hasMore || this.loadingMore) return;
253
+ this.fetchOptions(this.currentPage + 1, this.searchQuery);
254
+ },
255
+
256
+ async fetchOptions(page, searchQuery = "") {
257
+ const isInitialLoad = page === 1;
258
+ const loadingKey = isInitialLoad ? "initialLoading" : "loadingMore";
259
+
260
+ if (this[loadingKey]) return;
261
+
262
+ this[loadingKey] = true;
263
+
264
+ try {
265
+ const requestParams = {
266
+ ...this.params,
267
+ page,
268
+ per_page: this.perPage,
269
+ };
270
+
271
+ // Add search filter if query is provided
272
+ if (searchQuery) {
273
+ requestParams[`filter[${this.searchParam}]`] = searchQuery;
274
+ }
275
+
276
+ const response = await axios.get(this.endpoint, {
277
+ params: requestParams,
278
+ });
279
+
280
+ const data = response.data.data || [];
281
+ const meta = response.data.meta;
282
+
283
+ // Transform API data to option format
284
+ const newOptions = data.map((item) => ({
285
+ label: item[this.labelKey],
286
+ value: item[this.valueKey],
287
+ disabled: item.disabled || false,
288
+ }));
289
+
290
+ // If it's the first page, replace options, otherwise append
291
+ if (page === 1) {
292
+ this.asyncOptions = newOptions;
293
+ } else {
294
+ this.asyncOptions = [...this.asyncOptions, ...newOptions];
295
+ }
296
+
297
+ this.currentPage = page;
298
+ this.hasMore = meta && meta.current_page < meta.last_page;
299
+ } catch (error) {
300
+ console.error("Error fetching select options:", error);
301
+ ElNotification.error({
302
+ title: "Error",
303
+ message: "Failed to load options. Please try again.",
304
+ duration: 5000,
305
+ });
306
+ } finally {
307
+ this[loadingKey] = false;
308
+ }
309
+ },
107
310
  },
108
311
  };
109
312
  </script>
@@ -0,0 +1,75 @@
1
+ <template>
2
+ <el-table
3
+ :data="audits"
4
+ :default-sort="
5
+ query && {
6
+ prop: query.sort_by,
7
+ order: query.sort_dir === 'asc' ? 'ascending' : 'descending',
8
+ }
9
+ "
10
+ @sort-change="$emit('sort-change')"
11
+ >
12
+ <el-table-column label="Previous" :fit="false">
13
+ <template #default="scope">
14
+ <p
15
+ :key="key"
16
+ v-for="{ key, value } in scope?.row?.old_values"
17
+ class="truncate color-9"
18
+ >
19
+ <strong>{{ $filters.ucfirst(key) }}</strong
20
+ ><br />{{ value }}
21
+ </p>
22
+ </template>
23
+ </el-table-column>
24
+
25
+ <el-table-column label="New" :fit="false">
26
+ <template #default="scope">
27
+ <p
28
+ :key="key"
29
+ v-for="{ key, value } in scope?.row?.new_values"
30
+ class="truncate"
31
+ >
32
+ <strong>{{ $filters.ucfirst(key) }}</strong
33
+ ><br />{{ value }}
34
+ </p>
35
+ </template>
36
+ </el-table-column>
37
+
38
+ <el-table-column label="User" prop="user.email" :fit="false" />
39
+
40
+ <el-table-column label="Date" :fit="false">
41
+ <template #default="scope">
42
+ {{ $filters.calendarFormat(scope?.row?.created_at) }}
43
+ </template>
44
+ </el-table-column>
45
+ </el-table>
46
+ </template>
47
+
48
+ <script>
49
+ import { ElTable, ElTableColumn } from "element-plus";
50
+
51
+ export default {
52
+ components: {
53
+ ElTable,
54
+ ElTableColumn,
55
+ },
56
+
57
+ props: ["data", "query"],
58
+
59
+ computed: {
60
+ audits() {
61
+ return (this.data || [])
62
+ .map((d) => {
63
+ d.new_values = Object.entries(d.new_values)
64
+ .filter(([key, value]) => value)
65
+ .map(([key, value]) => ({ key, value }));
66
+ d.old_values = Object.entries(d.old_values)
67
+ .filter(([key, value]) => value)
68
+ .map(([key, value]) => ({ key, value }));
69
+ return d;
70
+ })
71
+ .reverse();
72
+ },
73
+ },
74
+ };
75
+ </script>
@@ -48,20 +48,31 @@ export default {
48
48
 
49
49
  methods: {
50
50
  async submit() {
51
+ const hold = this.form.data();
52
+
51
53
  if (this.resource.form.submit) {
52
54
  await this.resource.form.submit(this);
53
55
  } else {
54
56
  try {
57
+ this.form.populate(this.resource.form.preparation(this));
58
+
55
59
  let res = await this.form.post(
56
60
  `${this.resource.api.endpoint(this)}`,
57
61
  );
58
62
 
59
63
  this.$router.replace({
60
- name: `${this.resource.slug}.show`,
64
+ name: `${this.resource.routeName}.show`,
61
65
  params: { [this.resource.id]: res.data.id },
62
66
  });
63
67
  } catch (e) {
64
68
  console.log(e);
69
+ } finally {
70
+ if (
71
+ !this.form.successful ||
72
+ !this.form.__options.resetOnSuccess
73
+ ) {
74
+ this.form.populate(hold);
75
+ }
65
76
  }
66
77
  }
67
78
  },
@@ -43,9 +43,12 @@ export default {
43
43
  },
44
44
 
45
45
  beforeMount() {
46
- this.form = new Form(this.resource.form.fields(this), {
47
- resetOnSuccess: false,
48
- });
46
+ this.form = new Form(
47
+ { _method: "PATCH", ...this.resource.form.fields(this) },
48
+ {
49
+ resetOnSuccess: false,
50
+ },
51
+ );
49
52
  },
50
53
 
51
54
  async mounted() {
@@ -70,20 +73,31 @@ export default {
70
73
 
71
74
  methods: {
72
75
  async submit() {
76
+ const hold = this.form.data();
77
+
73
78
  if (this.resource.form.submit) {
74
79
  await this.resource.form.submit(this);
75
80
  } else {
76
81
  try {
77
- let res = await this.form.patch(
82
+ this.form.populate(this.resource.form.preparation(this));
83
+
84
+ let res = await this.form.post(
78
85
  `${this.resource.api.endpoint(this)}/${this.model.id}`,
79
86
  );
80
87
 
81
88
  this.$router.replace({
82
- name: `${this.resource.slug}.show`,
89
+ name: `${this.resource.routeName}.show`,
83
90
  params: { [this.resource.id]: res.data.id },
84
91
  });
85
92
  } catch (e) {
86
93
  console.log(e);
94
+ } finally {
95
+ if (
96
+ !this.form.successful ||
97
+ !this.form.__options.resetOnSuccess
98
+ ) {
99
+ this.form.populate(hold);
100
+ }
87
101
  }
88
102
  }
89
103
  },
@@ -3,6 +3,7 @@
3
3
  <form @submit.prevent="submit">
4
4
  <template v-for="(item, index) in resource.form.structure" :key="index">
5
5
  <component
6
+ v-if="!item.condition || item.condition(this)"
6
7
  :is="item.render ? item.render(this) : 'VelBasic'"
7
8
  v-model="form[item.key]"
8
9
  :type="item.type || 'text'"
@@ -10,6 +11,7 @@
10
11
  :name="item.key"
11
12
  :placeholder="
12
13
  item.placeholder ||
14
+ item.label ||
13
15
  item.key[0].toUpperCase() + item.key.slice(1)
14
16
  "
15
17
  :label="
@@ -11,7 +11,11 @@
11
11
  <div class="bg-0 p-3 box-shadow-1 border-r-4 mb-6">
12
12
  <VelPageHeader
13
13
  :icon="resource.icon"
14
- :title="`${model.name ?? model.id} ${model.last_name ?? ''}`"
14
+ :title="
15
+ resource.modelTitle
16
+ ? resource.modelTitle(this)
17
+ : `${model.name ?? model.id} ${model.last_name ?? ''}`
18
+ "
15
19
  >
16
20
  <template
17
21
  v-for="(rendered, index) in renderedActions"
@@ -99,7 +103,10 @@ export default {
99
103
  computed: {
100
104
  // This boolean helps determine if we are the final depth of route being rendered
101
105
  deepestRoute() {
102
- return this.depth === this.$route.matched.length;
106
+ return (
107
+ this.depth ===
108
+ this.$route.matched.filter((d) => d.components).length
109
+ );
103
110
  },
104
111
 
105
112
  // Compute rendered layout once
@@ -120,7 +127,7 @@ export default {
120
127
 
121
128
  axios
122
129
  .get(
123
- `${this.resource.api.endpoint(this)}/${this.$route.params[`${this.resource.slug}Id`]}`,
130
+ `${this.resource.api.endpoint(this)}/${this.$route.params[`${this.resource.id}`]}`,
124
131
  {
125
132
  params: this.resource.api.params.show(this),
126
133
  },
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- import { merge, kebabCase, cloneDeepWith } from "lodash";
3
+ import { merge, kebabCase, snakeCase, cloneDeepWith } from "lodash";
4
4
  import axios from "axios";
5
5
  import { h, resolveComponent } from "vue";
6
6
 
@@ -30,7 +30,9 @@ export function meta(name = "default", properties = {}) {
30
30
  label: singular,
31
31
  multiLabel: name,
32
32
  slug,
33
- id: properties.id || `${slug}Id`,
33
+ path: properties.path || `/${slug}`,
34
+ routeName: properties.routeName || slug,
35
+ id: properties.id || `${snakeCase(slug)}Id`,
34
36
  icon: `icon-${singular}`,
35
37
  api: {
36
38
  endpoint: () => `/api/${properties.slug || kebabCase(name)}`,
@@ -64,7 +66,7 @@ export function meta(name = "default", properties = {}) {
64
66
  type: "primary",
65
67
  onClick: () => {
66
68
  $router.push({
67
- name: `${resource.slug}.show`,
69
+ name: `${resource.routeName}.show`,
68
70
  params: {
69
71
  [resource.id]: model.id,
70
72
  },
@@ -84,7 +86,7 @@ export function meta(name = "default", properties = {}) {
84
86
  size: "small",
85
87
  onClick: () => {
86
88
  $router.push({
87
- name: `${resource.slug}.edit`,
89
+ name: `${resource.routeName}.edit`,
88
90
  params: {
89
91
  [resource.id]: model.id,
90
92
  },
@@ -216,7 +218,7 @@ export function meta(name = "default", properties = {}) {
216
218
  size: "large",
217
219
  onClick: () => {
218
220
  $router.push({
219
- name: `${resource.slug}.create`,
221
+ name: `${resource.routeName}.create`,
220
222
  });
221
223
  },
222
224
  },
@@ -257,7 +259,7 @@ export function meta(name = "default", properties = {}) {
257
259
  type: "primary",
258
260
  onClick: () => {
259
261
  $router.push({
260
- name: `${resource.slug}.edit`,
262
+ name: `${resource.routeName}.edit`,
261
263
  params: {
262
264
  [resource.id]: model.id,
263
265
  },
@@ -293,7 +295,7 @@ export function meta(name = "default", properties = {}) {
293
295
  );
294
296
 
295
297
  $router.push({
296
- name: `${resource.slug}.index`,
298
+ name: `${resource.routeName}.index`,
297
299
  });
298
300
  },
299
301
  },
@@ -335,6 +337,8 @@ export function meta(name = "default", properties = {}) {
335
337
  () =>
336
338
  resource.description.structure.map(
337
339
  (item, index) =>
340
+ (!item.condition ||
341
+ item.condition(props)) &&
338
342
  h(
339
343
  ElDescriptionsItem,
340
344
  {
@@ -397,6 +401,15 @@ export function columns(columns = []) {
397
401
  : (props.model?.[column.key] ?? null);
398
402
  return fields;
399
403
  }, {}),
404
+ preparation: (props) =>
405
+ columns
406
+ .filter((column) => !column.filter?.form)
407
+ .reduce((fields, column) => {
408
+ fields[column.key] = column.preparation
409
+ ? column.preparation(props)
410
+ : props.form[column.key];
411
+ return fields;
412
+ }, {}),
400
413
  structure: columns
401
414
  .filter((column) => !column.filter?.form)
402
415
  .map((column) => ({
@@ -408,25 +421,20 @@ export function columns(columns = []) {
408
421
  }
409
422
 
410
423
  // Export resource
411
- export function routes(
412
- node,
413
- name,
414
- properties = {},
415
- children = [],
416
- isChild = false,
417
- ) {
424
+ export function routes(node, name, properties = {}, children = []) {
418
425
  const resource = meta(name, properties);
419
426
 
420
427
  return [
421
428
  {
422
- path: `${isChild ? "" : "/"}${resource.slug}`,
429
+ path: resource.path,
423
430
  component: node ? "" : require("../resource/parent.vue").default,
424
- name,
431
+ name: `${resource.routeName}`,
425
432
  meta: {
426
433
  resource,
427
434
  title: resource.title,
428
435
  icon: resource.icon,
429
436
  breadcrumb: () => resource.title,
437
+ ...properties.meta,
430
438
  },
431
439
  children: [
432
440
  {
@@ -434,21 +442,21 @@ export function routes(
434
442
  component: node
435
443
  ? ""
436
444
  : require("../resource/Children/index.vue").default,
437
- name: `${resource.slug}.index`,
445
+ name: `${resource.routeName}.index`,
438
446
  },
439
447
  {
440
448
  path: "create",
441
449
  component: node
442
450
  ? ""
443
451
  : require("../resource/Children/create.vue").default,
444
- name: `${resource.slug}.create`,
452
+ name: `${resource.routeName}.create`,
445
453
  },
446
454
  {
447
455
  path: `:${resource.id}`,
448
456
  component: node
449
457
  ? ""
450
458
  : require("../resource/Children/show.vue").default,
451
- name: `${resource.slug}.show`,
459
+ name: `${resource.routeName}.show`,
452
460
  // Remove leading / for nested routes or they'll resolve to the root of the site
453
461
  children: cloneDeepWith(children, (value, key) => {
454
462
  if (
@@ -468,7 +476,7 @@ export function routes(
468
476
  component: node
469
477
  ? ""
470
478
  : require("../resource/Children/edit.vue").default,
471
- name: `${resource.slug}.edit`,
479
+ name: `${resource.routeName}.edit`,
472
480
  meta: {
473
481
  breadcrumb: ({ $route }) => $route.params[resource.id],
474
482
  },
@@ -1,4 +1,5 @@
1
1
  @import "element-plus/theme-chalk/el-date-picker";
2
+ @import "element-plus/theme-chalk/el-date-picker-panel";
2
3
 
3
4
  .vel-datepicker {
4
5
  // --el-input-height:38px;
package/index.js CHANGED
@@ -38,6 +38,7 @@ export { default as Navigation } from "./_Build/vue/components/layout/Navigation
38
38
  export { default as PageTitle } from "./_Build/vue/components/layout/pageTitle.vue";
39
39
  export { default as Alert } from "./_Build/vue/components/layout/Alert.vue";
40
40
  export { default as Tooltip } from "./_Build/vue/components/layout/Tooltip.vue";
41
+ export { default as Audit } from "./_Build/vue/components/layout/Audit.vue";
41
42
  export { default as Menu } from "./_Build/vue/components/navigation/Menu.vue";
42
43
  export { default as MenuItem } from "./_Build/vue/components/navigation/MenuItem.vue";
43
44
  export { default as MenuItemGroup } from "./_Build/vue/components/navigation/MenuItemGroup.vue";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fishawack/lab-velocity",
3
- "version": "2.0.0-beta.30",
3
+ "version": "2.0.0-beta.31",
4
4
  "description": "Avalere Health branded style system",
5
5
  "scripts": {
6
6
  "setup": "npm ci || npm i && npm run content",
@@ -54,7 +54,7 @@
54
54
  "@tiptap/starter-kit": "^2.11.2",
55
55
  "@tiptap/vue-3": "^2.11.2",
56
56
  "axios": "^1.11.0",
57
- "element-plus": "^2.7.8",
57
+ "element-plus": "^2.11.8",
58
58
  "form-backend-validation": "github:mikemellor11/form-backend-validation#master",
59
59
  "lodash": "^4.17.21",
60
60
  "quill": "^1.3.7",