@fishawack/lab-velocity 2.0.0-beta.13 → 2.0.0-beta.15
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.md +129 -43
- package/_Build/vue/components/layout/Layout.vue +9 -7
- package/_Build/vue/components/layout/Table.vue +3 -1
- package/_Build/vue/modules/AuthModule/routes/PCompanies/resource.js +98 -112
- package/_Build/vue/modules/AuthModule/routes/PUsers/resource.js +44 -57
- package/_Build/vue/modules/resource/Children/edit.vue +1 -0
- package/_Build/vue/modules/resource/Children/index.vue +4 -8
- package/_Build/vue/modules/resource/Children/partials/form.vue +4 -0
- package/_Build/vue/modules/resource/Children/show.vue +6 -35
- package/_Build/vue/modules/resource/index.js +66 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -78,6 +78,17 @@ import { Auth } from "@fishawack/lab-velocity";
|
|
|
78
78
|
];
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
+
You can override the default setup for companies & user resources by passing an optional second object to the `adminRoutes` method. See the [resources](#Resources) section below for available options.
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
...Auth.Router.adminRoutes(node, {
|
|
85
|
+
userResource: {
|
|
86
|
+
label: "Custom User label",
|
|
87
|
+
api: `/api/v2/users`,
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
```
|
|
91
|
+
|
|
81
92
|
### Configure store
|
|
82
93
|
|
|
83
94
|
##### store.js
|
|
@@ -252,8 +263,8 @@ export function meta(name, properties = {}) {
|
|
|
252
263
|
pageLink: name,
|
|
253
264
|
api: `/api/${name}`,
|
|
254
265
|
permissions: {
|
|
255
|
-
create: true,
|
|
256
|
-
edit: true,
|
|
266
|
+
create: () => true,
|
|
267
|
+
edit: () => true,
|
|
257
268
|
},
|
|
258
269
|
searchable: {
|
|
259
270
|
value: "name",
|
|
@@ -264,30 +275,73 @@ export function meta(name, properties = {}) {
|
|
|
264
275
|
fields: () => ({}),
|
|
265
276
|
},
|
|
266
277
|
table: {
|
|
267
|
-
structure: [
|
|
278
|
+
structure: [
|
|
279
|
+
{
|
|
280
|
+
key: "id",
|
|
281
|
+
},
|
|
282
|
+
],
|
|
268
283
|
},
|
|
269
|
-
|
|
270
|
-
actions: [],
|
|
284
|
+
description: {
|
|
271
285
|
structure: [
|
|
272
286
|
{
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
287
|
+
key: "id",
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
},
|
|
291
|
+
index: {
|
|
292
|
+
actions: [],
|
|
293
|
+
layout: [
|
|
294
|
+
(props) => {
|
|
295
|
+
const { resource } = props;
|
|
296
|
+
|
|
297
|
+
return h(VTableSorter, {
|
|
298
|
+
key: "PIndex",
|
|
299
|
+
"json-data": {
|
|
300
|
+
...resource,
|
|
301
|
+
tableStructure: resource.table.structure,
|
|
302
|
+
},
|
|
303
|
+
defaults: resource.defaults,
|
|
304
|
+
"fixed-height": false,
|
|
305
|
+
"display-edit-action":
|
|
306
|
+
resource.permissions.create(props),
|
|
307
|
+
});
|
|
285
308
|
},
|
|
286
309
|
],
|
|
287
310
|
},
|
|
288
311
|
show: {
|
|
289
312
|
actions: [],
|
|
290
|
-
|
|
313
|
+
layout: [
|
|
314
|
+
(props) => {
|
|
315
|
+
const { resource, model } = props;
|
|
316
|
+
|
|
317
|
+
return h(
|
|
318
|
+
ElDescriptions,
|
|
319
|
+
{
|
|
320
|
+
border: true,
|
|
321
|
+
column: 1,
|
|
322
|
+
},
|
|
323
|
+
resource.description.structure.map((item, index) =>
|
|
324
|
+
h(
|
|
325
|
+
ElDescriptionsItem,
|
|
326
|
+
{
|
|
327
|
+
key: index,
|
|
328
|
+
labelWidth: "20%",
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
label: () =>
|
|
332
|
+
item.label ||
|
|
333
|
+
item.key[0].toUpperCase() +
|
|
334
|
+
item.key.slice(1),
|
|
335
|
+
default: () =>
|
|
336
|
+
item.render
|
|
337
|
+
? h(item.render(props))
|
|
338
|
+
: model?.[item.key] || "",
|
|
339
|
+
},
|
|
340
|
+
),
|
|
341
|
+
),
|
|
342
|
+
);
|
|
343
|
+
},
|
|
344
|
+
],
|
|
291
345
|
},
|
|
292
346
|
defaults: "",
|
|
293
347
|
icon: `icon-${singular}`,
|
|
@@ -297,7 +351,7 @@ export function meta(name, properties = {}) {
|
|
|
297
351
|
}
|
|
298
352
|
```
|
|
299
353
|
|
|
300
|
-
|
|
354
|
+
Layout is an array of functions that return render functions
|
|
301
355
|
|
|
302
356
|
```js
|
|
303
357
|
import { h, resolveComponent } from "vue";
|
|
@@ -305,34 +359,66 @@ import { h, resolveComponent } from "vue";
|
|
|
305
359
|
{
|
|
306
360
|
// ...
|
|
307
361
|
show: {
|
|
362
|
+
layout: [
|
|
363
|
+
({ model }) =>
|
|
364
|
+
h(resolveComponent("router-link"), {
|
|
365
|
+
class: "underline",
|
|
366
|
+
to: {
|
|
367
|
+
name: "companies.show",
|
|
368
|
+
params: { id: model.company_id },
|
|
369
|
+
},
|
|
370
|
+
text: model.company.name,
|
|
371
|
+
})
|
|
372
|
+
],
|
|
373
|
+
},
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
You can see above that the show & index route have a default render function returned for a table and description block. If you want to keep this you can grab the existing render function from the `defaultResource` export.
|
|
378
|
+
|
|
379
|
+
```js
|
|
380
|
+
import { defaultResource, meta } from "../../../resource/index.js";
|
|
381
|
+
|
|
382
|
+
{
|
|
383
|
+
// ...
|
|
384
|
+
index: {
|
|
385
|
+
layout: [
|
|
386
|
+
() => h("div", "I appear above"),
|
|
387
|
+
...defaultResource.index.layout,
|
|
388
|
+
() => h("div", "I appear below"),
|
|
389
|
+
],
|
|
390
|
+
},
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Structure arrays take objects. The objects require a key only but have other optional properties. A render function can also be passed to fully customize the rendering that happens.
|
|
395
|
+
|
|
396
|
+
```js
|
|
397
|
+
{
|
|
398
|
+
table: {
|
|
308
399
|
structure: [
|
|
309
|
-
[
|
|
310
|
-
{
|
|
311
|
-
label: "Email",
|
|
312
|
-
key: "email",
|
|
313
|
-
},
|
|
314
|
-
{
|
|
315
|
-
label: "Company",
|
|
316
|
-
render: ({ model }) =>
|
|
317
|
-
h(resolveComponent("router-link"), {
|
|
318
|
-
class: "underline",
|
|
319
|
-
to: {
|
|
320
|
-
name: "companies.show",
|
|
321
|
-
params: { id: model.company_id },
|
|
322
|
-
},
|
|
323
|
-
text: model.company.name,
|
|
324
|
-
}),
|
|
325
|
-
},
|
|
326
|
-
],
|
|
327
400
|
{
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
401
|
+
key: "name",
|
|
402
|
+
sortable: true,
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
key: "role",
|
|
406
|
+
render: () => h("div", "Custom template"),
|
|
334
407
|
},
|
|
335
408
|
],
|
|
336
409
|
},
|
|
410
|
+
form: {
|
|
411
|
+
structure: [
|
|
412
|
+
{
|
|
413
|
+
key: "name",
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
key: "provider_name",
|
|
417
|
+
label: "Provider",
|
|
418
|
+
render: ({ model }) => h("span", model?.provider_name.label ?? ""),
|
|
419
|
+
initial: ({ model }) => model?.provider_name.value ?? null,
|
|
420
|
+
}
|
|
421
|
+
]
|
|
422
|
+
}
|
|
337
423
|
}
|
|
338
424
|
```
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="vel-app">
|
|
3
3
|
<VelHeader class="justify-end-dive">
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
<router-link :to="{ name: 'index' }">
|
|
5
|
+
<GSvg
|
|
6
|
+
class="logo"
|
|
7
|
+
style="width: 180px"
|
|
8
|
+
embed
|
|
9
|
+
asis
|
|
10
|
+
:name="$store.state.auth.logo"
|
|
11
|
+
/>
|
|
12
|
+
</router-link>
|
|
11
13
|
<template #links>
|
|
12
14
|
<div class="flex items-center pr">
|
|
13
15
|
<GIcon
|
|
@@ -22,7 +22,9 @@
|
|
|
22
22
|
>
|
|
23
23
|
<!-- Support a custom render function -->
|
|
24
24
|
<template v-if="item.render" #default="scope">
|
|
25
|
-
<component
|
|
25
|
+
<component
|
|
26
|
+
:is="item.render({ model: scope.row, ...this })"
|
|
27
|
+
/>
|
|
26
28
|
</template>
|
|
27
29
|
</el-table-column>
|
|
28
30
|
</template>
|
|
@@ -6,7 +6,7 @@ import VelTableSorter from "../../../../components/layout/TableSorter.vue";
|
|
|
6
6
|
import VelRoleLegend from "../../../../components/layout/RoleLegend.vue";
|
|
7
7
|
import component from "./form.vue";
|
|
8
8
|
import userResource from "../PUsers/resource.js";
|
|
9
|
-
import { meta } from "../../../resource/index.js";
|
|
9
|
+
import { defaultResource, meta } from "../../../resource/index.js";
|
|
10
10
|
|
|
11
11
|
import { ElNotification } from "element-plus";
|
|
12
12
|
import { h } from "vue";
|
|
@@ -16,6 +16,10 @@ export default [
|
|
|
16
16
|
"companies",
|
|
17
17
|
{
|
|
18
18
|
defaults: "include=primary_contact",
|
|
19
|
+
permissions: {
|
|
20
|
+
create: ({ $store }) => $store.getters.can("write companies"),
|
|
21
|
+
edit: ({ $store }) => $store.getters.can("write companies"),
|
|
22
|
+
},
|
|
19
23
|
singular: "company",
|
|
20
24
|
icon: "icon-cases",
|
|
21
25
|
form: {
|
|
@@ -34,145 +38,127 @@ export default [
|
|
|
34
38
|
table: {
|
|
35
39
|
structure: [
|
|
36
40
|
{
|
|
37
|
-
label: "Name",
|
|
38
41
|
key: "name",
|
|
39
42
|
sortable: true,
|
|
40
43
|
},
|
|
41
44
|
{
|
|
42
|
-
label: "Total users",
|
|
43
45
|
key: "user_count",
|
|
44
|
-
|
|
46
|
+
label: "Total users",
|
|
45
47
|
width: "150",
|
|
46
48
|
},
|
|
47
49
|
{
|
|
48
|
-
|
|
49
|
-
render: (
|
|
50
|
+
key: "role",
|
|
51
|
+
render: ({ model }) =>
|
|
50
52
|
h(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
model.roles.length === 1 ? Chip : Chips,
|
|
54
|
+
model.roles.length === 1
|
|
53
55
|
? {
|
|
54
|
-
name:
|
|
55
|
-
label:
|
|
56
|
+
name: model.roles[0].name,
|
|
57
|
+
label: model.roles[0].label,
|
|
56
58
|
}
|
|
57
|
-
: { array:
|
|
59
|
+
: { array: model.roles },
|
|
58
60
|
),
|
|
59
61
|
},
|
|
60
62
|
],
|
|
61
63
|
},
|
|
62
|
-
|
|
64
|
+
description: {
|
|
63
65
|
structure: [
|
|
64
66
|
{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
key: "PIndex",
|
|
68
|
-
"json-data": {
|
|
69
|
-
...resource,
|
|
70
|
-
tableStructure: resource.table.structure,
|
|
71
|
-
},
|
|
72
|
-
defaults: resource.defaults,
|
|
73
|
-
"fixed-height": false,
|
|
74
|
-
"display-edit-action":
|
|
75
|
-
$store.getters.can("write companies"),
|
|
76
|
-
}),
|
|
67
|
+
key: "domains",
|
|
68
|
+
render: ({ model }) => h("span", model.domains.join(", ")),
|
|
77
69
|
},
|
|
78
70
|
{
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
class: "mt-5",
|
|
82
|
-
}),
|
|
71
|
+
key: "sso_enabled",
|
|
72
|
+
label: "SSO Enabled",
|
|
83
73
|
},
|
|
84
|
-
],
|
|
85
|
-
},
|
|
86
|
-
show: {
|
|
87
|
-
actions: [
|
|
88
74
|
{
|
|
75
|
+
key: "primary_contact",
|
|
76
|
+
label: "Primary Contact",
|
|
89
77
|
render: ({ model }) =>
|
|
90
|
-
model.primary_contact
|
|
91
|
-
h(
|
|
92
|
-
VelButton,
|
|
93
|
-
{
|
|
94
|
-
type: "primary",
|
|
95
|
-
async onClick() {
|
|
96
|
-
try {
|
|
97
|
-
const res = await axios.post(
|
|
98
|
-
`/api/companies/${model.id}/welcome`,
|
|
99
|
-
);
|
|
100
|
-
ElNotification({
|
|
101
|
-
title: "Success",
|
|
102
|
-
message: res.data.message,
|
|
103
|
-
type: "success",
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
model.primary_contact_contacted = true;
|
|
107
|
-
} catch (e) {
|
|
108
|
-
ElNotification({
|
|
109
|
-
title: "Warning",
|
|
110
|
-
message:
|
|
111
|
-
e.response?.data?.message ||
|
|
112
|
-
e.message,
|
|
113
|
-
type: "warning",
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
"Send welcome email",
|
|
119
|
-
),
|
|
78
|
+
h("span", model.primary_contact?.name),
|
|
120
79
|
},
|
|
121
|
-
],
|
|
122
|
-
structure: [
|
|
123
|
-
[
|
|
124
|
-
{
|
|
125
|
-
label: "Domains",
|
|
126
|
-
render: ({ model }) =>
|
|
127
|
-
h("span", model.domains.join(", ")),
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
label: "SSO Enabled",
|
|
131
|
-
key: "sso_enabled",
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
label: "Primary Contact",
|
|
135
|
-
render: ({ model }) =>
|
|
136
|
-
h("span", model.primary_contact?.name),
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
label: "Primary Contact Email",
|
|
140
|
-
render: ({ model }) =>
|
|
141
|
-
h("span", model.primary_contact?.email),
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
label: "Primary Contact Contacted",
|
|
145
|
-
render: ({ model }) =>
|
|
146
|
-
h("span", !!model.primary_contact_contacted),
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
label: "Total users",
|
|
150
|
-
key: "user_count",
|
|
151
|
-
},
|
|
152
|
-
],
|
|
153
80
|
{
|
|
81
|
+
key: "primary_contact_email",
|
|
82
|
+
label: "Primary Contact Email",
|
|
154
83
|
render: ({ model }) =>
|
|
155
|
-
h(
|
|
156
|
-
overrides: model.overrides_roles_and_permissions,
|
|
157
|
-
form: { roles: model.roles.map((d) => d.id) },
|
|
158
|
-
readonly: true,
|
|
159
|
-
}),
|
|
84
|
+
h("span", model.primary_contact?.email),
|
|
160
85
|
},
|
|
161
86
|
{
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
87
|
+
key: "primary_contact_contacted",
|
|
88
|
+
label: "Primary Contact Contacted",
|
|
89
|
+
render: ({ model }) =>
|
|
90
|
+
h("span", !!model.primary_contact_contacted),
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
label: "Total users",
|
|
94
|
+
key: "user_count",
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
index: {
|
|
99
|
+
layout: [
|
|
100
|
+
...defaultResource.index.layout,
|
|
101
|
+
() =>
|
|
102
|
+
h(VelRoleLegend, {
|
|
103
|
+
class: "mt-5",
|
|
104
|
+
}),
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
show: {
|
|
108
|
+
actions: [
|
|
109
|
+
({ model }) =>
|
|
110
|
+
model.primary_contact &&
|
|
111
|
+
h(
|
|
112
|
+
VelButton,
|
|
113
|
+
{
|
|
114
|
+
type: "primary",
|
|
115
|
+
async onClick() {
|
|
116
|
+
try {
|
|
117
|
+
const res = await axios.post(
|
|
118
|
+
`/api/companies/${model.id}/welcome`,
|
|
119
|
+
);
|
|
120
|
+
ElNotification({
|
|
121
|
+
title: "Success",
|
|
122
|
+
message: res.data.message,
|
|
123
|
+
type: "success",
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
model.primary_contact_contacted = true;
|
|
127
|
+
} catch (e) {
|
|
128
|
+
ElNotification({
|
|
129
|
+
title: "Warning",
|
|
130
|
+
message:
|
|
131
|
+
e.response?.data?.message ||
|
|
132
|
+
e.message,
|
|
133
|
+
type: "warning",
|
|
134
|
+
});
|
|
135
|
+
}
|
|
169
136
|
},
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
137
|
+
},
|
|
138
|
+
"Send welcome email",
|
|
139
|
+
),
|
|
140
|
+
],
|
|
141
|
+
layout: [
|
|
142
|
+
...defaultResource.show.layout,
|
|
143
|
+
({ model }) =>
|
|
144
|
+
h(VelFormRole, {
|
|
145
|
+
overrides: model.overrides_roles_and_permissions,
|
|
146
|
+
form: { roles: model.roles.map((d) => d.id) },
|
|
147
|
+
readonly: true,
|
|
148
|
+
}),
|
|
149
|
+
({ model, $store }) => {
|
|
150
|
+
const resource = meta(...userResource);
|
|
151
|
+
return h(VelTableSorter, {
|
|
152
|
+
key: "PIndex",
|
|
153
|
+
"json-data": {
|
|
154
|
+
...resource,
|
|
155
|
+
tableStructure: resource.table.structure,
|
|
156
|
+
},
|
|
157
|
+
defaults: `include=company&filter[company_id]=${model.id}`,
|
|
158
|
+
"fixed-height": false,
|
|
159
|
+
"display-edit-action":
|
|
160
|
+
$store.getters.can("write users"),
|
|
161
|
+
});
|
|
176
162
|
},
|
|
177
163
|
],
|
|
178
164
|
},
|
|
@@ -4,6 +4,7 @@ import Chips from "../../../../components/layout/Chips.vue";
|
|
|
4
4
|
import VelTableSorter from "../../../../components/layout/TableSorter.vue";
|
|
5
5
|
import VelRoleLegend from "../../../../components/layout/RoleLegend.vue";
|
|
6
6
|
import component from "./form.vue";
|
|
7
|
+
import { defaultResource } from "../../../resource/index.js";
|
|
7
8
|
|
|
8
9
|
import { ElMessageBox } from "element-plus";
|
|
9
10
|
import { ElNotification } from "element-plus";
|
|
@@ -27,6 +28,10 @@ export default [
|
|
|
27
28
|
searchable: {
|
|
28
29
|
value: "email",
|
|
29
30
|
},
|
|
31
|
+
permissions: {
|
|
32
|
+
create: ({ $store }) => $store.getters.can("write users"),
|
|
33
|
+
edit: ({ $store }) => $store.getters.can("write users"),
|
|
34
|
+
},
|
|
30
35
|
form: {
|
|
31
36
|
async submit({ model, form, $router, $store, method }) {
|
|
32
37
|
try {
|
|
@@ -111,19 +116,16 @@ export default [
|
|
|
111
116
|
table: {
|
|
112
117
|
structure: [
|
|
113
118
|
{
|
|
114
|
-
label: "Name",
|
|
115
119
|
key: "name",
|
|
116
120
|
sortable: true,
|
|
117
121
|
},
|
|
118
122
|
{
|
|
119
|
-
label: "Email",
|
|
120
123
|
key: "email",
|
|
121
|
-
sortable: true,
|
|
122
124
|
},
|
|
123
125
|
{
|
|
124
|
-
|
|
126
|
+
key: "company",
|
|
125
127
|
sortable: true,
|
|
126
|
-
render: (model) =>
|
|
128
|
+
render: ({ model }) =>
|
|
127
129
|
h(resolveComponent("router-link"), {
|
|
128
130
|
class: "underline",
|
|
129
131
|
to: {
|
|
@@ -134,80 +136,65 @@ export default [
|
|
|
134
136
|
}),
|
|
135
137
|
},
|
|
136
138
|
{
|
|
137
|
-
|
|
138
|
-
render: (
|
|
139
|
+
key: "role",
|
|
140
|
+
render: ({ model }) =>
|
|
139
141
|
h(
|
|
140
|
-
!
|
|
141
|
-
|
|
142
|
+
!model.overrides_roles_and_permissions ||
|
|
143
|
+
model.roles.length === 1
|
|
142
144
|
? Chip
|
|
143
145
|
: Chips,
|
|
144
|
-
!
|
|
146
|
+
!model.overrides_roles_and_permissions
|
|
145
147
|
? {
|
|
146
148
|
name: "inherited",
|
|
147
149
|
label: "Inherited",
|
|
148
150
|
}
|
|
149
|
-
:
|
|
151
|
+
: model.roles.length === 1
|
|
150
152
|
? {
|
|
151
|
-
name:
|
|
152
|
-
label:
|
|
153
|
+
name: model.roles[0].name,
|
|
154
|
+
label: model.roles[0].label,
|
|
153
155
|
}
|
|
154
|
-
: { array:
|
|
156
|
+
: { array: model.roles },
|
|
155
157
|
),
|
|
156
158
|
},
|
|
157
159
|
],
|
|
158
160
|
},
|
|
159
|
-
|
|
161
|
+
description: {
|
|
160
162
|
structure: [
|
|
161
163
|
{
|
|
162
|
-
|
|
163
|
-
h(VelTableSorter, {
|
|
164
|
-
key: "PIndex",
|
|
165
|
-
"json-data": {
|
|
166
|
-
...resource,
|
|
167
|
-
tableStructure: resource.table.structure,
|
|
168
|
-
},
|
|
169
|
-
defaults: resource.defaults,
|
|
170
|
-
"fixed-height": false,
|
|
171
|
-
"display-edit-action":
|
|
172
|
-
$store.getters.can("write users"),
|
|
173
|
-
}),
|
|
164
|
+
key: "email",
|
|
174
165
|
},
|
|
175
166
|
{
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
167
|
+
key: "company",
|
|
168
|
+
render: ({ model }) =>
|
|
169
|
+
h(resolveComponent("router-link"), {
|
|
170
|
+
class: "underline",
|
|
171
|
+
to: {
|
|
172
|
+
name: "companies.show",
|
|
173
|
+
params: { id: model.company_id },
|
|
174
|
+
},
|
|
175
|
+
text: model.company.name,
|
|
179
176
|
}),
|
|
180
177
|
},
|
|
181
178
|
],
|
|
182
179
|
},
|
|
180
|
+
index: {
|
|
181
|
+
layout: [
|
|
182
|
+
...defaultResource.index.layout,
|
|
183
|
+
() =>
|
|
184
|
+
h(VelRoleLegend, {
|
|
185
|
+
class: "mt-5",
|
|
186
|
+
}),
|
|
187
|
+
],
|
|
188
|
+
},
|
|
183
189
|
show: {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
render: ({ model }) =>
|
|
193
|
-
h(resolveComponent("router-link"), {
|
|
194
|
-
class: "underline",
|
|
195
|
-
to: {
|
|
196
|
-
name: "companies.show",
|
|
197
|
-
params: { id: model.company_id },
|
|
198
|
-
},
|
|
199
|
-
text: model.company.name,
|
|
200
|
-
}),
|
|
201
|
-
},
|
|
202
|
-
],
|
|
203
|
-
{
|
|
204
|
-
render: ({ model }) =>
|
|
205
|
-
h(VelFormRole, {
|
|
206
|
-
overrides: model.overrides_roles_and_permissions,
|
|
207
|
-
form: { roles: model.roles.map((d) => d.id) },
|
|
208
|
-
readonly: true,
|
|
209
|
-
}),
|
|
210
|
-
},
|
|
190
|
+
layout: [
|
|
191
|
+
...defaultResource.show.layout,
|
|
192
|
+
({ model }) =>
|
|
193
|
+
h(VelFormRole, {
|
|
194
|
+
overrides: model.overrides_roles_and_permissions,
|
|
195
|
+
form: { roles: model.roles.map((d) => d.id) },
|
|
196
|
+
readonly: true,
|
|
197
|
+
}),
|
|
211
198
|
],
|
|
212
199
|
},
|
|
213
200
|
},
|
|
@@ -9,22 +9,18 @@
|
|
|
9
9
|
|
|
10
10
|
<div class="flex gap items-center justify-end">
|
|
11
11
|
<template
|
|
12
|
-
v-for="(
|
|
12
|
+
v-for="(render, index) in resource.index.actions"
|
|
13
13
|
:key="index"
|
|
14
14
|
>
|
|
15
|
-
<
|
|
16
|
-
<component :is="row.render(this)" />
|
|
17
|
-
</template>
|
|
15
|
+
<component :is="render(this)" />
|
|
18
16
|
</template>
|
|
19
17
|
</div>
|
|
20
18
|
|
|
21
19
|
<template
|
|
22
|
-
v-for="(
|
|
20
|
+
v-for="(render, index) in resource.index.layout"
|
|
23
21
|
:key="index"
|
|
24
22
|
>
|
|
25
|
-
<
|
|
26
|
-
<component :is="row.render(this)" />
|
|
27
|
-
</template>
|
|
23
|
+
<component :is="render(this)" />
|
|
28
24
|
</template>
|
|
29
25
|
</div>
|
|
30
26
|
</div>
|
|
@@ -14,15 +14,13 @@
|
|
|
14
14
|
:title="`${model.name} ${model.last_name ?? ''}`"
|
|
15
15
|
>
|
|
16
16
|
<template
|
|
17
|
-
v-for="(
|
|
17
|
+
v-for="(render, index) in resource.show.actions"
|
|
18
18
|
:key="index"
|
|
19
19
|
>
|
|
20
|
-
<
|
|
21
|
-
<component :is="row.render(this)" />
|
|
22
|
-
</template>
|
|
20
|
+
<component :is="render(this)" />
|
|
23
21
|
</template>
|
|
24
22
|
<VelButton
|
|
25
|
-
v-if="resource.permissions.edit"
|
|
23
|
+
v-if="resource.permissions.edit(this)"
|
|
26
24
|
tag="a"
|
|
27
25
|
type="primary"
|
|
28
26
|
@click="
|
|
@@ -45,37 +43,13 @@
|
|
|
45
43
|
<hr class="my-3 hr-muted" />
|
|
46
44
|
|
|
47
45
|
<template
|
|
48
|
-
v-for="(
|
|
46
|
+
v-for="(render, index) in resource.show.layout"
|
|
49
47
|
:key="index"
|
|
50
48
|
>
|
|
51
|
-
<
|
|
52
|
-
<component :is="row.render(this)" />
|
|
53
|
-
</template>
|
|
54
|
-
<el-descriptions v-else border column="1">
|
|
55
|
-
<el-descriptions-item
|
|
56
|
-
v-for="(item, i) in row"
|
|
57
|
-
:key="i"
|
|
58
|
-
:label-width="'20%'"
|
|
59
|
-
>
|
|
60
|
-
<template #label>
|
|
61
|
-
{{
|
|
62
|
-
item.label ||
|
|
63
|
-
item.key[0].toUpperCase() +
|
|
64
|
-
item.key.slice(1)
|
|
65
|
-
}}
|
|
66
|
-
</template>
|
|
67
|
-
|
|
68
|
-
<template v-if="item.render">
|
|
69
|
-
<component :is="item.render(this)" />
|
|
70
|
-
</template>
|
|
71
|
-
<template v-else>
|
|
72
|
-
{{ model[item.key] }}
|
|
73
|
-
</template>
|
|
74
|
-
</el-descriptions-item>
|
|
75
|
-
</el-descriptions>
|
|
49
|
+
<component :is="render(this)" />
|
|
76
50
|
|
|
77
51
|
<hr
|
|
78
|
-
v-if="index < resource.show.
|
|
52
|
+
v-if="index < resource.show.layout.length - 1"
|
|
79
53
|
class="my-3 hr-muted"
|
|
80
54
|
/>
|
|
81
55
|
</template>
|
|
@@ -93,7 +67,6 @@
|
|
|
93
67
|
import axios from "axios";
|
|
94
68
|
import VelSpinner from "../../../components/form/Spinner.vue";
|
|
95
69
|
import VelButton from "../../../components/basic/Button.vue";
|
|
96
|
-
import { ElDescriptions, ElDescriptionsItem } from "element-plus";
|
|
97
70
|
|
|
98
71
|
export default {
|
|
99
72
|
components: {
|
|
@@ -103,8 +76,6 @@ export default {
|
|
|
103
76
|
.default,
|
|
104
77
|
VelSpinner,
|
|
105
78
|
VelButton,
|
|
106
|
-
ElDescriptions,
|
|
107
|
-
ElDescriptionsItem,
|
|
108
79
|
},
|
|
109
80
|
|
|
110
81
|
props: {
|
|
@@ -5,8 +5,11 @@ import { merge } from "lodash";
|
|
|
5
5
|
import { h } from "vue";
|
|
6
6
|
|
|
7
7
|
import VTableSorter from "../../components/layout/TableSorter.vue";
|
|
8
|
+
import { ElDescriptions, ElDescriptionsItem } from "element-plus";
|
|
8
9
|
|
|
9
|
-
export
|
|
10
|
+
export const defaultResource = meta();
|
|
11
|
+
|
|
12
|
+
export function meta(name = "default", properties = {}) {
|
|
10
13
|
const singular = properties.singular || name.slice(0, -1);
|
|
11
14
|
|
|
12
15
|
return merge(
|
|
@@ -19,8 +22,8 @@ export function meta(name, properties = {}) {
|
|
|
19
22
|
pageLink: name,
|
|
20
23
|
api: `/api/${name}`,
|
|
21
24
|
permissions: {
|
|
22
|
-
create: true,
|
|
23
|
-
edit: true,
|
|
25
|
+
create: () => true,
|
|
26
|
+
edit: () => true,
|
|
24
27
|
},
|
|
25
28
|
searchable: {
|
|
26
29
|
value: "name",
|
|
@@ -29,32 +32,76 @@ export function meta(name, properties = {}) {
|
|
|
29
32
|
form: {
|
|
30
33
|
component: null,
|
|
31
34
|
fields: () => ({}),
|
|
35
|
+
structure: [],
|
|
32
36
|
},
|
|
33
37
|
table: {
|
|
34
|
-
structure: [
|
|
38
|
+
structure: [
|
|
39
|
+
{
|
|
40
|
+
key: "id",
|
|
41
|
+
},
|
|
42
|
+
],
|
|
35
43
|
},
|
|
36
|
-
|
|
37
|
-
actions: [],
|
|
44
|
+
description: {
|
|
38
45
|
structure: [
|
|
39
46
|
{
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
key: "id",
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
index: {
|
|
52
|
+
actions: [],
|
|
53
|
+
layout: [
|
|
54
|
+
(props) => {
|
|
55
|
+
const { resource } = props;
|
|
56
|
+
|
|
57
|
+
return h(VTableSorter, {
|
|
58
|
+
key: "PIndex",
|
|
59
|
+
"json-data": {
|
|
60
|
+
...resource,
|
|
61
|
+
tableStructure: resource.table.structure,
|
|
62
|
+
},
|
|
63
|
+
defaults: resource.defaults,
|
|
64
|
+
"fixed-height": false,
|
|
65
|
+
"display-edit-action":
|
|
66
|
+
resource.permissions.create(props),
|
|
67
|
+
});
|
|
52
68
|
},
|
|
53
69
|
],
|
|
54
70
|
},
|
|
55
71
|
show: {
|
|
56
72
|
actions: [],
|
|
57
|
-
|
|
73
|
+
layout: [
|
|
74
|
+
(props) => {
|
|
75
|
+
const { resource, model } = props;
|
|
76
|
+
|
|
77
|
+
return h(
|
|
78
|
+
ElDescriptions,
|
|
79
|
+
{
|
|
80
|
+
border: true,
|
|
81
|
+
column: 1,
|
|
82
|
+
},
|
|
83
|
+
resource.description.structure.map((item, index) =>
|
|
84
|
+
h(
|
|
85
|
+
ElDescriptionsItem,
|
|
86
|
+
{
|
|
87
|
+
key: index,
|
|
88
|
+
labelWidth: "20%",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
label: () =>
|
|
92
|
+
item.label ||
|
|
93
|
+
item.key[0].toUpperCase() +
|
|
94
|
+
item.key.slice(1),
|
|
95
|
+
default: () =>
|
|
96
|
+
item.render
|
|
97
|
+
? h(item.render(props))
|
|
98
|
+
: model?.[item.key] || "",
|
|
99
|
+
},
|
|
100
|
+
),
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
},
|
|
104
|
+
],
|
|
58
105
|
},
|
|
59
106
|
defaults: "",
|
|
60
107
|
icon: `icon-${singular}`,
|