@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.
- package/_Build/vue/components/form/Checkbox.vue +10 -0
- package/_Build/vue/components/form/Select.vue +229 -26
- package/_Build/vue/components/layout/Audit.vue +75 -0
- package/_Build/vue/modules/resource/Children/create.vue +12 -1
- package/_Build/vue/modules/resource/Children/edit.vue +19 -5
- package/_Build/vue/modules/resource/Children/partials/form.vue +2 -0
- package/_Build/vue/modules/resource/Children/show.vue +10 -3
- package/_Build/vue/modules/resource/index.js +28 -20
- package/components/_datepicker.scss +1 -0
- package/index.js +1 -0
- package/package.json +2 -2
|
@@ -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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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.
|
|
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(
|
|
47
|
-
|
|
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
|
-
|
|
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.
|
|
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="
|
|
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
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
479
|
+
name: `${resource.routeName}.edit`,
|
|
472
480
|
meta: {
|
|
473
481
|
breadcrumb: ({ $route }) => $route.params[resource.id],
|
|
474
482
|
},
|
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.
|
|
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.
|
|
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",
|