@hostlink/nuxt-light 1.1.8 → 1.2.1
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/dist/module.json +1 -1
- package/dist/runtime/components/l-app-main.vue +86 -23
- package/dist/runtime/components/l-btn.vue +3 -14
- package/dist/runtime/components/l-card.vue +94 -10
- package/dist/runtime/components/l-fav-menu.vue +26 -0
- package/dist/runtime/components/l-login.vue +3 -3
- package/dist/runtime/components/l-menu.vue +4 -3
- package/dist/runtime/components/l-small-box.vue +4 -4
- package/dist/runtime/components/l-tabs.vue +4 -2
- package/dist/runtime/components/l-user-eventlog.vue +29 -0
- package/dist/runtime/components/l-user-overview.vue +79 -0
- package/dist/runtime/components/l-user-userlog.vue +13 -0
- package/dist/runtime/formkit/Select.vue +1 -1
- package/dist/runtime/index.d.ts +38 -2
- package/dist/runtime/index.mjs +14 -0
- package/dist/runtime/pages/System/menu/index.vue +136 -99
- package/dist/runtime/pages/User/_user_id/view.vue +28 -42
- package/dist/runtime/pages/User/profile.vue +47 -24
- package/dist/runtime/plugin.mjs +4 -2
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { useRoute } from 'vue-router';
|
|
3
|
+
import { useLight, q, m } from "#imports";
|
|
4
|
+
import { useQuasar, Loading, Dialog } from 'quasar';
|
|
4
5
|
import { useI18n } from 'vue-i18n';
|
|
5
|
-
import { q, m, f } from '../';
|
|
6
6
|
import { ref, computed, reactive, provide, watch } from 'vue';
|
|
7
7
|
import { useRuntimeConfig } from 'nuxt/app';
|
|
8
8
|
|
|
9
|
+
Loading.show()
|
|
9
10
|
//download system value
|
|
10
11
|
/* import { download } from './../lib/SystemValue'
|
|
11
12
|
await download();
|
|
@@ -20,15 +21,18 @@ const tt = await q({
|
|
|
20
21
|
app: ["menus", "viewAsMode", "languages",
|
|
21
22
|
"copyrightYear",
|
|
22
23
|
"copyrightName",
|
|
24
|
+
"hasFavorite",
|
|
23
25
|
{ i18nMessages: ["name", "value"] }],
|
|
24
|
-
my: ['username', 'first_name', 'last_name', 'roles', "styles", "language",
|
|
26
|
+
my: ['username', 'first_name', 'last_name', 'roles', "styles", "language", "permissions", { myFavorites: ["label", "path"] }],
|
|
25
27
|
})
|
|
26
28
|
|
|
27
29
|
let app = tt.app
|
|
28
|
-
let my = tt.my
|
|
30
|
+
let my = reactive(tt.my)
|
|
29
31
|
|
|
30
32
|
const light = useLight();
|
|
31
33
|
light.init(my.styles);
|
|
34
|
+
//set permission
|
|
35
|
+
light.setPermissions(my.permissions);
|
|
32
36
|
|
|
33
37
|
quasar.dark.set(light.isDarkMode());
|
|
34
38
|
|
|
@@ -36,7 +40,8 @@ const i18n = useI18n();
|
|
|
36
40
|
i18n.locale = my.language || 'en';
|
|
37
41
|
|
|
38
42
|
let system = null
|
|
39
|
-
|
|
43
|
+
|
|
44
|
+
if (light.isGranted("system.storage")) {
|
|
40
45
|
system = await q("system", ["diskFreeSpace", "diskUsageSpace", "diskTotalSpace", "diskFreeSpacePercent"]);
|
|
41
46
|
}
|
|
42
47
|
|
|
@@ -65,6 +70,7 @@ const toggleRightDrawer = () => {
|
|
|
65
70
|
let showViewAs = false;
|
|
66
71
|
if (my && my.roles.indexOf('Administrators') != -1) {
|
|
67
72
|
showViewAs = true;
|
|
73
|
+
light.isAdmin = true;
|
|
68
74
|
}
|
|
69
75
|
|
|
70
76
|
const menuOverlayHeader = ref(false)
|
|
@@ -188,19 +194,64 @@ const containerStyle = computed(() => {
|
|
|
188
194
|
}
|
|
189
195
|
})
|
|
190
196
|
|
|
191
|
-
const
|
|
197
|
+
const route = useRoute()
|
|
198
|
+
|
|
199
|
+
const reloadMyFavorites = async () => {
|
|
200
|
+
const data = await q({
|
|
201
|
+
my: {
|
|
202
|
+
myFavorites: {
|
|
203
|
+
label: true,
|
|
204
|
+
path: true,
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
my.myFavorites = data.my.myFavorites;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const onToggleFav = async () => {
|
|
192
212
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
} else {
|
|
198
|
-
fullscreen.value = true;
|
|
213
|
+
if (isFav.value) {
|
|
214
|
+
await m("removeMyFavorite", {
|
|
215
|
+
path: route.fullPath,
|
|
216
|
+
})
|
|
199
217
|
|
|
200
|
-
|
|
218
|
+
//reload my.myFavorites
|
|
219
|
+
reloadMyFavorites();
|
|
220
|
+
|
|
221
|
+
return;
|
|
201
222
|
}
|
|
223
|
+
|
|
224
|
+
Dialog.create({
|
|
225
|
+
title: 'Add to favorite',
|
|
226
|
+
message: 'Enter favorite label',
|
|
227
|
+
prompt: {
|
|
228
|
+
//get window title, remove - and replace with space
|
|
229
|
+
model: route.name.replace(/-/g, ' '),
|
|
230
|
+
},
|
|
231
|
+
cancel: true,
|
|
232
|
+
persistent: true,
|
|
233
|
+
}).onOk(async (data) => {
|
|
234
|
+
if (data === '') return;
|
|
235
|
+
if (await m("addMyFavorite", {
|
|
236
|
+
path: route.fullPath,
|
|
237
|
+
label: data,
|
|
238
|
+
})) {
|
|
239
|
+
reloadMyFavorites();
|
|
240
|
+
|
|
241
|
+
}
|
|
242
|
+
})
|
|
202
243
|
}
|
|
203
244
|
|
|
245
|
+
const isFav = computed(() => {
|
|
246
|
+
//check my.myFavorites
|
|
247
|
+
let fav = my.myFavorites.find((item) => {
|
|
248
|
+
return item.path == route.fullPath;
|
|
249
|
+
})
|
|
250
|
+
return fav;
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
Loading.hide()
|
|
254
|
+
|
|
204
255
|
</script>
|
|
205
256
|
|
|
206
257
|
<template>
|
|
@@ -218,26 +269,33 @@ const onFullscreen = () => {
|
|
|
218
269
|
|
|
219
270
|
<q-space />
|
|
220
271
|
|
|
221
|
-
<q-btn :icon="
|
|
222
|
-
|
|
272
|
+
<q-btn :icon="isFav ? 'favorite' : 'sym_o_favorite'" round flat dense class="q-mr-xs" @click="onToggleFav"
|
|
273
|
+
v-if="app.hasFavorite">
|
|
274
|
+
</q-btn>
|
|
223
275
|
|
|
276
|
+
<q-btn :icon="$q.fullscreen.isActive ? 'fullscreen_exit' : 'fullscreen'" round flat dense class="q-mr-xs"
|
|
277
|
+
@click="$q.fullscreen.toggle()">
|
|
224
278
|
</q-btn>
|
|
225
279
|
|
|
226
|
-
<q-btn v-if="languages.length > 1"
|
|
280
|
+
<q-btn v-if="languages.length > 1" round flat icon="language" class="q-mr-xs">
|
|
281
|
+
<q-tooltip>
|
|
282
|
+
{{ my.language }}
|
|
283
|
+
</q-tooltip>
|
|
227
284
|
<q-menu>
|
|
228
285
|
<q-list>
|
|
229
|
-
<q-item v-for="language
|
|
230
|
-
@click="onChangeLocale(language.value)">
|
|
286
|
+
<q-item v-for=" language in languages " :key="language.value" v-close-popup clickable
|
|
287
|
+
@click="onChangeLocale(language.value)" :active="language.value == my.language">
|
|
231
288
|
<q-item-section>
|
|
232
289
|
<q-item-label>{{ language.name }}</q-item-label>
|
|
233
290
|
</q-item-section>
|
|
234
291
|
</q-item>
|
|
235
292
|
</q-list>
|
|
236
293
|
</q-menu>
|
|
294
|
+
|
|
295
|
+
|
|
237
296
|
</q-btn>
|
|
238
297
|
|
|
239
|
-
<q-btn icon="sym_o_storage" flat round dense class="q-mr-sm" v-if="
|
|
240
|
-
:color="storageColor">
|
|
298
|
+
<q-btn icon="sym_o_storage" flat round dense class="q-mr-sm" v-if="system" :color="storageColor">
|
|
241
299
|
<q-menu>
|
|
242
300
|
<q-card style="width:250px">
|
|
243
301
|
<q-card-section>
|
|
@@ -315,7 +373,12 @@ const onFullscreen = () => {
|
|
|
315
373
|
@mouseover="isMouseOnDrawer = true">
|
|
316
374
|
<!-- drawer content -->
|
|
317
375
|
<q-scroll-area class="fit">
|
|
318
|
-
<
|
|
376
|
+
<div class="q-mx-sm">
|
|
377
|
+
<l-fav-menu :value="my.myFavorites" :dense="style.dense" v-if="my.myFavorites.length > 0" />
|
|
378
|
+
<l-menu v-for=" menu in menus " :value="menu" :dense="style.dense" />
|
|
379
|
+
|
|
380
|
+
</div>
|
|
381
|
+
|
|
319
382
|
</q-scroll-area>
|
|
320
383
|
</q-drawer>
|
|
321
384
|
|
|
@@ -331,7 +394,7 @@ const onFullscreen = () => {
|
|
|
331
394
|
<q-page-container :class="containerClass" :style="containerStyle">
|
|
332
395
|
|
|
333
396
|
<!-- Error message -->
|
|
334
|
-
<q-banner dense inline-actions class="bg-grey-4 q-ma-md" v-for="error
|
|
397
|
+
<q-banner dense inline-actions class="bg-grey-4 q-ma-md" v-for=" error in errors " rounded>
|
|
335
398
|
{{ error }}
|
|
336
399
|
<template v-slot:action>
|
|
337
400
|
<q-btn flat icon="sym_o_close" round dense @click="light.removeError(error)" />
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { type QBtnProps } from "quasar";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { computed } from "vue";
|
|
4
|
+
import { useLight } from '#imports';
|
|
5
5
|
|
|
6
6
|
export interface LBtnProps extends QBtnProps {
|
|
7
7
|
permission?: string;
|
|
@@ -11,17 +11,6 @@ const props = defineProps<LBtnProps>();
|
|
|
11
11
|
|
|
12
12
|
const light = useLight();
|
|
13
13
|
|
|
14
|
-
const granted = ref(false);
|
|
15
|
-
if (props.permission) {
|
|
16
|
-
const my = await q("my", [f("granted", { right: props.permission }, [])]);
|
|
17
|
-
|
|
18
|
-
if (my.granted) {
|
|
19
|
-
granted.value = true;
|
|
20
|
-
}
|
|
21
|
-
} else {
|
|
22
|
-
granted.value = true;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
14
|
const attrs = computed(() => {
|
|
26
15
|
return {
|
|
27
16
|
...{
|
|
@@ -37,7 +26,7 @@ const attrs = computed(() => {
|
|
|
37
26
|
|
|
38
27
|
</script>
|
|
39
28
|
<template>
|
|
40
|
-
<q-btn v-bind="attrs" color="primary" v-if="
|
|
29
|
+
<q-btn v-bind="attrs" color="primary" v-if="$light.isGranted(permission)">
|
|
41
30
|
<slot></slot>
|
|
42
31
|
</q-btn>
|
|
43
32
|
</template>
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { useLight } from '#imports'
|
|
2
|
+
import { useLight, q, m } from '#imports'
|
|
3
3
|
import { ref, computed } from 'vue'
|
|
4
4
|
import { type QCardProps } from 'quasar';
|
|
5
5
|
|
|
6
6
|
export interface LCardProps extends QCardProps {
|
|
7
7
|
loading?: boolean;
|
|
8
8
|
title?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Permission to access this card, if not granted, the card will not be shown, if the user is admin, a lock icon will be shown to allow the user to grant the permission
|
|
11
|
+
*/
|
|
12
|
+
permission?: string
|
|
9
13
|
}
|
|
10
14
|
|
|
11
15
|
const light = useLight();
|
|
@@ -40,18 +44,98 @@ const fullScreenIcon = computed(() => fullScreen.value ? "sym_o_fullscreen_exit"
|
|
|
40
44
|
|
|
41
45
|
const showBody = computed(() => !minimize.value || fullScreen.value);
|
|
42
46
|
|
|
47
|
+
const showBar = computed(() => {
|
|
48
|
+
if (props.title !== undefined) return true;
|
|
49
|
+
if (showSecurity.value) return true;
|
|
50
|
+
return false;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
const showSecurity = computed(() => {
|
|
55
|
+
if (props.permission === undefined) return false;
|
|
56
|
+
if (!light.isAdmin) return false;
|
|
57
|
+
return true;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
const roles = ref<{
|
|
62
|
+
name: string;
|
|
63
|
+
granted: boolean;
|
|
64
|
+
}[]>([]);
|
|
65
|
+
|
|
66
|
+
if (props.permission && light.isAdmin) {
|
|
67
|
+
//get role
|
|
68
|
+
const data = await q({
|
|
69
|
+
listRole: {
|
|
70
|
+
name: true
|
|
71
|
+
},
|
|
72
|
+
listPermission: {
|
|
73
|
+
__args: {
|
|
74
|
+
filters: {
|
|
75
|
+
value: props.permission
|
|
76
|
+
}
|
|
77
|
+
}, data: {
|
|
78
|
+
role: true
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
roles.value = (data.listRole.map((r: any) => {
|
|
85
|
+
return {
|
|
86
|
+
name: r.name,
|
|
87
|
+
granted: data.listPermission.data.find((p: any) => p.role == r.name) != undefined
|
|
88
|
+
}
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const onTogglePermission = async (role: any) => {
|
|
93
|
+
|
|
94
|
+
if (role.granted) {
|
|
95
|
+
|
|
96
|
+
//remove permission
|
|
97
|
+
if (await m("removePermission", {
|
|
98
|
+
role: role.name,
|
|
99
|
+
value: props.permission,
|
|
100
|
+
})) {
|
|
101
|
+
role.granted = false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (await m("addPermission", {
|
|
108
|
+
role: role.name,
|
|
109
|
+
value: props.permission,
|
|
110
|
+
})) {
|
|
111
|
+
role.granted = true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
43
117
|
</script>
|
|
44
118
|
<template>
|
|
45
|
-
<q-card v-bind="attrs" :class="{ 'fullscreen': fullScreen, 'no-margin': fullScreen }"
|
|
46
|
-
|
|
47
|
-
|
|
119
|
+
<q-card v-bind="attrs" :class="{ 'fullscreen': fullScreen, 'no-margin': fullScreen }"
|
|
120
|
+
v-if="$light.isGranted(permission)" :dark="$q.dark.isActive">
|
|
121
|
+
<q-bar :class="cl" v-if="showBar">
|
|
122
|
+
<div>{{ $t(title ?? "") }}</div>
|
|
48
123
|
<q-space />
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
124
|
+
|
|
125
|
+
<q-btn dense flat icon="sym_o_lock" persistent v-if="showSecurity">
|
|
126
|
+
<q-menu>
|
|
127
|
+
<q-list>
|
|
128
|
+
<q-item clickable v-for="role in roles" @click="onTogglePermission(role)">
|
|
129
|
+
<q-item-section>{{ role.name }}</q-item-section>
|
|
130
|
+
<q-item-section side>
|
|
131
|
+
<q-icon name="sym_o_done" v-if="role.granted" />
|
|
132
|
+
</q-item-section>
|
|
133
|
+
</q-item>
|
|
134
|
+
|
|
135
|
+
</q-list>
|
|
136
|
+
</q-menu>
|
|
137
|
+
</q-btn>
|
|
138
|
+
|
|
55
139
|
<q-btn dense flat @click="fullScreen = !fullScreen" :icon="fullScreenIcon" />
|
|
56
140
|
<q-btn dense flat :icon="icon" @click="minimize = !minimize" v-if="!fullScreen" />
|
|
57
141
|
</q-bar>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const props = defineProps(["value", "dense"])
|
|
3
|
+
|
|
4
|
+
const onClickRemove = () => {
|
|
5
|
+
console.log("onClickRemove")
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<style scoped>
|
|
11
|
+
.menu-list .q-item{border-radius:12px 12px 12px 12px}.menu-list .q-router-link--exact-active{background:linear-gradient(118deg,var(--q-primary),rgba(115,103,240,.7));color:#fff}
|
|
12
|
+
</style>
|
|
13
|
+
<template>
|
|
14
|
+
<q-expansion-item :label="$t('My Favorite')" :dense="dense" ref="expansion" icon="sym_o_favorite">
|
|
15
|
+
<q-list class="q-pl-md menu-list">
|
|
16
|
+
<q-item v-ripple :to="item.path" v-for="item in value">
|
|
17
|
+
<q-item-section avatar>
|
|
18
|
+
<q-icon name="sym_o_link" />
|
|
19
|
+
</q-item-section>
|
|
20
|
+
<q-item-section>
|
|
21
|
+
<q-item-label v-text="$t(item.label)"></q-item-label>
|
|
22
|
+
</q-item-section>
|
|
23
|
+
</q-item>
|
|
24
|
+
</q-list>
|
|
25
|
+
</q-expansion-item>
|
|
26
|
+
</template>
|
|
@@ -194,16 +194,16 @@ onMounted(() => {
|
|
|
194
194
|
<q-form ref="form1">
|
|
195
195
|
<div class="q-gutter-sm">
|
|
196
196
|
<l-input v-model.trim="data.username" label="Username" :rules="[v => !!v || $t('Username is required')]"
|
|
197
|
-
clearable :outlined="false" stackLabel/>
|
|
197
|
+
clearable :outlined="false" stackLabel autocomplete="username" />
|
|
198
198
|
<l-input v-model="data.password" label="Password" type="password" clearable show-password stackLabel
|
|
199
|
-
:rules="[v => !!v || $t('Password is required')]" @keydown.enter.prevent="submit" :outlined="false" />
|
|
199
|
+
:rules="[v => !!v || $t('Password is required')]" @keydown.enter.prevent="submit" :outlined="false" autocomplete="current-password" />
|
|
200
200
|
<l-input v-if="twoFactorAuthentication" v-model="data.code" label="2FA code" required type="text" clearable>
|
|
201
201
|
</l-input>
|
|
202
202
|
</div>
|
|
203
203
|
</q-form>
|
|
204
204
|
</q-card-section>
|
|
205
205
|
<q-card-actions>
|
|
206
|
-
<l-btn label="Login" outline rounded color="primary" icon="sym_o_login" @click="submit"
|
|
206
|
+
<l-btn label="Login" outline rounded color="primary" icon="sym_o_login" @click="submit"/>
|
|
207
207
|
<l-btn v-if="hasBioLogin" outline rounded color="primary" icon="sym_o_fingerprint" @click="bioLogin" />
|
|
208
208
|
<l-btn label="Forget password" outline rounded color="primary" icon="sym_o_lock_reset" @click="forgetPassword" />
|
|
209
209
|
</q-card-actions>
|
|
@@ -3,7 +3,6 @@ import { ref } from 'vue'
|
|
|
3
3
|
const props = defineProps(["value", "dense"])
|
|
4
4
|
const menus = ref(null);
|
|
5
5
|
|
|
6
|
-
|
|
7
6
|
const expansion = ref(null);
|
|
8
7
|
|
|
9
8
|
const onShowChild = (menu) => {
|
|
@@ -28,7 +27,7 @@ defineExpose({
|
|
|
28
27
|
</script>
|
|
29
28
|
|
|
30
29
|
<style scoped>
|
|
31
|
-
.menu-list .q-item{border-radius:
|
|
30
|
+
.menu-list .q-item{border-radius:12px 12px 12px 12px}.menu-list .q-router-link--exact-active{background:linear-gradient(118deg,var(--q-primary),rgba(115,103,240,.7));color:#fff}
|
|
32
31
|
</style>
|
|
33
32
|
<template>
|
|
34
33
|
<q-expansion-item v-if="value.children?.length > 0" :label="$t(value.label)" :icon="value.icon" :dense="dense"
|
|
@@ -39,7 +38,9 @@ defineExpose({
|
|
|
39
38
|
</q-list>
|
|
40
39
|
</q-expansion-item>
|
|
41
40
|
<q-list v-else class="menu-list" :dense="dense">
|
|
42
|
-
<q-
|
|
41
|
+
<q-separator v-if="value.type == 'separator'" :spaced="value.spaced" />
|
|
42
|
+
<q-item-label header v-if="value.type=='header'">{{ value.label }}</q-item-label>
|
|
43
|
+
<q-item v-ripple :to="value.to" v-if="!value.type" >
|
|
43
44
|
<q-item-section avatar>
|
|
44
45
|
<q-icon :name="value.icon" />
|
|
45
46
|
</q-item-section>
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import { computed } from "vue"
|
|
3
3
|
|
|
4
4
|
export interface LSmallBoxProps {
|
|
5
|
-
title
|
|
6
|
-
subtitle
|
|
7
|
-
icon
|
|
8
|
-
color
|
|
5
|
+
title?: string;
|
|
6
|
+
subtitle?: string;
|
|
7
|
+
icon?: string;
|
|
8
|
+
color?: 'red' | 'pink' | 'purple' | 'deep-purple' | 'indigo' | 'blue' | 'light-blue' | 'cyan' | 'teal' | 'green' | 'light-green' | 'lime' | 'yellow' | 'amber' | 'orange' | 'deep-orange' | 'brown' | 'grey' | 'blue-grey'
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
const props = withDefaults(defineProps<LSmallBoxProps>(), {
|
|
@@ -37,13 +37,15 @@ const localValue = computed({
|
|
|
37
37
|
}
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
|
|
42
42
|
</script>
|
|
43
43
|
|
|
44
44
|
<template>
|
|
45
45
|
<l-card>
|
|
46
|
-
|
|
46
|
+
|
|
47
|
+
<q-tabs class="text-grey" :active-color="$light.color" :indicator-color="$light.color" align="justify"
|
|
48
|
+
v-model="localValue">
|
|
47
49
|
<q-tab v-for="tab in tabContents" :label="$t(tab.label)" :name="tab.name"></q-tab>
|
|
48
50
|
</q-tabs>
|
|
49
51
|
<q-tab-panels v-model="localValue">
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { model, q } from "#imports";
|
|
3
|
+
const props = defineProps(['id']);
|
|
4
|
+
</script>
|
|
5
|
+
<template>
|
|
6
|
+
<div>
|
|
7
|
+
<l-table row-key="eventlog_id" sort-by="eventlog_id:desc"
|
|
8
|
+
:columns="model('EventLog').columns(['eventlog_id', 'class', 'id', 'action', 'created_time'])" @request="async (req) => {
|
|
9
|
+
const a = {
|
|
10
|
+
listUser: {
|
|
11
|
+
__args: {
|
|
12
|
+
filters: {
|
|
13
|
+
user_id: props.id
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
data: {
|
|
17
|
+
__args: {
|
|
18
|
+
limit: 1
|
|
19
|
+
},
|
|
20
|
+
eventLog: req.gql
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let resp = await q(a);
|
|
26
|
+
req.setData(resp.listUser.data[0].eventLog)
|
|
27
|
+
}" />
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
|
|
3
|
+
import { model } from "#imports";
|
|
4
|
+
const props = defineProps(['id']);
|
|
5
|
+
|
|
6
|
+
const obj = await model('User').get({ user_id: props.id }, ["user_id", "username", "first_name", "last_name", "email", "phone", "roles", 'status', 'join_date'])
|
|
7
|
+
|
|
8
|
+
</script>
|
|
9
|
+
<template>
|
|
10
|
+
<l-card class="q-mb-md">
|
|
11
|
+
<q-card-section>
|
|
12
|
+
<l-row>
|
|
13
|
+
<l-col md="5">
|
|
14
|
+
<q-list dense separator>
|
|
15
|
+
<q-item>
|
|
16
|
+
<q-item-section>
|
|
17
|
+
<q-item-label>{{ $t('Username') }}</q-item-label>
|
|
18
|
+
</q-item-section>
|
|
19
|
+
<q-item-section>{{ obj.username }}</q-item-section>
|
|
20
|
+
</q-item>
|
|
21
|
+
<q-item>
|
|
22
|
+
<q-item-section>
|
|
23
|
+
<q-item-label>{{ $t('First name') }}</q-item-label>
|
|
24
|
+
</q-item-section>
|
|
25
|
+
<q-item-section>{{ obj.first_name }}</q-item-section>
|
|
26
|
+
</q-item>
|
|
27
|
+
<q-item>
|
|
28
|
+
<q-item-section>
|
|
29
|
+
<q-item-label>{{ $t('Last name') }}</q-item-label>
|
|
30
|
+
</q-item-section>
|
|
31
|
+
<q-item-section>{{ obj.last_name }}</q-item-section>
|
|
32
|
+
</q-item>
|
|
33
|
+
<q-item>
|
|
34
|
+
<q-item-section>
|
|
35
|
+
<q-item-label>{{ $t('Email') }}</q-item-label>
|
|
36
|
+
</q-item-section>
|
|
37
|
+
<q-item-section>{{ obj.email }}</q-item-section>
|
|
38
|
+
</q-item>
|
|
39
|
+
<q-item>
|
|
40
|
+
<q-item-section>
|
|
41
|
+
<q-item-label>{{ $t('Phone') }}</q-item-label>
|
|
42
|
+
</q-item-section>
|
|
43
|
+
<q-item-section>{{ obj.phone }}</q-item-section>
|
|
44
|
+
</q-item>
|
|
45
|
+
|
|
46
|
+
<q-item>
|
|
47
|
+
<q-item-section>
|
|
48
|
+
<q-item-label>{{ $t('Roles') }}</q-item-label>
|
|
49
|
+
</q-item-section>
|
|
50
|
+
<q-item-section>
|
|
51
|
+
<div class="q-gutter-xs float-left">
|
|
52
|
+
<q-badge v-for="role in obj.roles" :key="role">{{ role }}</q-badge>
|
|
53
|
+
</div>
|
|
54
|
+
</q-item-section>
|
|
55
|
+
</q-item>
|
|
56
|
+
|
|
57
|
+
<q-item>
|
|
58
|
+
<q-item-section>
|
|
59
|
+
<q-item-label>{{ $t('Status') }}</q-item-label>
|
|
60
|
+
</q-item-section>
|
|
61
|
+
<q-item-section>{{ model('User').columns(["status"])[0].format(obj.status) }}</q-item-section>
|
|
62
|
+
</q-item>
|
|
63
|
+
|
|
64
|
+
<q-item>
|
|
65
|
+
<q-item-section>
|
|
66
|
+
<q-item-label>{{ $t('Join date') }}</q-item-label>
|
|
67
|
+
</q-item-section>
|
|
68
|
+
<q-item-section>{{ obj.join_date }}</q-item-section>
|
|
69
|
+
</q-item>
|
|
70
|
+
</q-list>
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
</l-col>
|
|
74
|
+
</l-row>
|
|
75
|
+
|
|
76
|
+
</q-card-section>
|
|
77
|
+
|
|
78
|
+
</l-card>
|
|
79
|
+
</template>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { model, q } from "#imports";
|
|
3
|
+
const props = defineProps(['id']);
|
|
4
|
+
|
|
5
|
+
const columns = model('UserLog').columns(["userlog_id", "login_dt", "last_access_time", "logout_dt", "result", "user_agent"]);
|
|
6
|
+
</script>
|
|
7
|
+
<template>
|
|
8
|
+
<div>
|
|
9
|
+
<l-table row-key="userlog_id" sort-by="userlog_id:desc" :columns="columns" @request="async (req) => {
|
|
10
|
+
req.loadObjects('UserLog', { filters: { user_id: props.id } })
|
|
11
|
+
}" />
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,8 +1,43 @@
|
|
|
1
|
+
declare const app: {
|
|
2
|
+
company: string;
|
|
3
|
+
companyLogo: string;
|
|
4
|
+
color: string;
|
|
5
|
+
theme: string;
|
|
6
|
+
isAdmin: boolean;
|
|
7
|
+
permissions: string[];
|
|
8
|
+
isDarkMode: () => boolean;
|
|
9
|
+
setCompany: (company: string) => void;
|
|
10
|
+
getCompany: () => string;
|
|
11
|
+
setCompanyLogo: (logo: string) => void;
|
|
12
|
+
getCompanyLogo: () => string;
|
|
13
|
+
getVersion: () => any;
|
|
14
|
+
addError: (error: String) => void;
|
|
15
|
+
getErrors: () => String[];
|
|
16
|
+
removeError: (error: String) => void;
|
|
17
|
+
getStyle: (name: String) => any;
|
|
18
|
+
setStyles: (s: Object) => void;
|
|
19
|
+
getStyles: () => {
|
|
20
|
+
[key: string]: any;
|
|
21
|
+
};
|
|
22
|
+
setStyle: (name: String, value: any) => Promise<void>;
|
|
23
|
+
setCurrentRoute: (to: any, from: any) => void;
|
|
24
|
+
getID: () => number | null;
|
|
25
|
+
init: (styles: any) => void;
|
|
26
|
+
isGranted: (right?: string | undefined) => boolean;
|
|
27
|
+
setPermissions: (permissions: string[]) => void;
|
|
28
|
+
};
|
|
29
|
+
declare module 'vue' {
|
|
30
|
+
interface ComponentCustomProperties {
|
|
31
|
+
$light: typeof app;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
1
34
|
export declare const useLight: () => {
|
|
2
35
|
company: string;
|
|
3
36
|
companyLogo: string;
|
|
4
37
|
color: string;
|
|
5
38
|
theme: string;
|
|
39
|
+
isAdmin: boolean;
|
|
40
|
+
permissions: string[];
|
|
6
41
|
isDarkMode: () => boolean;
|
|
7
42
|
setCompany: (company: string) => void;
|
|
8
43
|
getCompany: () => string;
|
|
@@ -15,12 +50,13 @@ export declare const useLight: () => {
|
|
|
15
50
|
getStyle: (name: String) => any;
|
|
16
51
|
setStyles: (s: Object) => void;
|
|
17
52
|
getStyles: () => {
|
|
18
|
-
|
|
19
|
-
color?: String | undefined;
|
|
53
|
+
[key: string]: any;
|
|
20
54
|
};
|
|
21
55
|
setStyle: (name: String, value: any) => Promise<void>;
|
|
22
56
|
setCurrentRoute: (to: any, from: any) => void;
|
|
23
57
|
getID: () => number | null;
|
|
24
58
|
init: (styles: any) => void;
|
|
59
|
+
isGranted: (right?: string) => boolean;
|
|
60
|
+
setPermissions: (permissions: Array<string>) => void;
|
|
25
61
|
};
|
|
26
62
|
export * from "./lib";
|
package/dist/runtime/index.mjs
CHANGED
|
@@ -15,6 +15,8 @@ const app = reactive({
|
|
|
15
15
|
companyLogo: "",
|
|
16
16
|
color: "primary",
|
|
17
17
|
theme: "light",
|
|
18
|
+
isAdmin: false,
|
|
19
|
+
permissions: Array(),
|
|
18
20
|
isDarkMode: () => {
|
|
19
21
|
return app.theme == "dark";
|
|
20
22
|
},
|
|
@@ -90,6 +92,18 @@ const app = reactive({
|
|
|
90
92
|
watch(() => app.theme, async () => {
|
|
91
93
|
await app.setStyle("theme", app.theme);
|
|
92
94
|
});
|
|
95
|
+
},
|
|
96
|
+
isGranted(right) {
|
|
97
|
+
if (right === void 0)
|
|
98
|
+
return true;
|
|
99
|
+
if (app.isAdmin)
|
|
100
|
+
return true;
|
|
101
|
+
if (app.permissions.includes(right))
|
|
102
|
+
return true;
|
|
103
|
+
return false;
|
|
104
|
+
},
|
|
105
|
+
setPermissions(permissions) {
|
|
106
|
+
this.permissions = permissions;
|
|
93
107
|
}
|
|
94
108
|
});
|
|
95
109
|
let currentRoute = null;
|
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, computed, inject } from 'vue';
|
|
3
|
-
import { m, q } from '
|
|
3
|
+
import { m, q } from '#imports'
|
|
4
4
|
import { useQuasar } from 'quasar';
|
|
5
5
|
|
|
6
6
|
const quasar = useQuasar();
|
|
7
|
-
const
|
|
7
|
+
const appMenus = await q("appMenus", [])
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const menus = ref([
|
|
11
|
+
{
|
|
12
|
+
label: "[Root]",
|
|
13
|
+
uuid: "ROOT",
|
|
14
|
+
children: appMenus.map(m => {
|
|
15
|
+
m.parent = 'ROOT'
|
|
16
|
+
return m
|
|
17
|
+
}),
|
|
18
|
+
type: 'root'
|
|
19
|
+
}
|
|
20
|
+
]);
|
|
8
21
|
|
|
9
|
-
const selected = ref(
|
|
22
|
+
const selected = ref('ROOT');
|
|
10
23
|
const tree1 = ref(null)
|
|
11
24
|
|
|
12
25
|
const getUUID = () => {
|
|
@@ -20,30 +33,9 @@ const getUUID = () => {
|
|
|
20
33
|
}
|
|
21
34
|
|
|
22
35
|
const onReload = async () => {
|
|
23
|
-
menus.value = await q("appMenus", []);
|
|
36
|
+
menus.value[0].children = await q("appMenus", []);
|
|
24
37
|
}
|
|
25
38
|
|
|
26
|
-
const onAdd = () => {
|
|
27
|
-
|
|
28
|
-
quasar.dialog({
|
|
29
|
-
title: 'Add Menu',
|
|
30
|
-
message: 'Enter menu label',
|
|
31
|
-
prompt: {
|
|
32
|
-
model: '',
|
|
33
|
-
},
|
|
34
|
-
cancel: true,
|
|
35
|
-
persistent: true,
|
|
36
|
-
}).onOk((data) => {
|
|
37
|
-
if (data === '') return;
|
|
38
|
-
|
|
39
|
-
menus.value.push({
|
|
40
|
-
label: data,
|
|
41
|
-
uuid: getUUID(),
|
|
42
|
-
children: [],
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
39
|
const onAddChild = (node) => {
|
|
48
40
|
|
|
49
41
|
quasar.dialog({
|
|
@@ -81,9 +73,10 @@ const onRemove = (node) => {
|
|
|
81
73
|
}
|
|
82
74
|
}
|
|
83
75
|
|
|
84
|
-
const splitterModel = ref(
|
|
76
|
+
const splitterModel = ref(20)
|
|
85
77
|
|
|
86
78
|
const selectedNode = computed(() => {
|
|
79
|
+
if (selected.value == 'ROOT') return menus.value[0];
|
|
87
80
|
return tree1.value.getNodeByKey(selected.value);
|
|
88
81
|
});
|
|
89
82
|
const reloadMenu = inject('reloadMenu')
|
|
@@ -98,7 +91,7 @@ const onSave = () => {
|
|
|
98
91
|
persistent: true,
|
|
99
92
|
}).onOk(async () => {
|
|
100
93
|
if (await m("updateAppMenus", {
|
|
101
|
-
data: menus.value
|
|
94
|
+
data: menus.value[0].children
|
|
102
95
|
})) {
|
|
103
96
|
quasar.notify({
|
|
104
97
|
message: 'Menu saved',
|
|
@@ -140,20 +133,6 @@ const onMoveConfirm = () => {
|
|
|
140
133
|
showMove.value = false;
|
|
141
134
|
|
|
142
135
|
}
|
|
143
|
-
const onMoveToRoot = () => {
|
|
144
|
-
const selectedNode = tree1.value.getNodeByKey(selected.value);
|
|
145
|
-
|
|
146
|
-
//clone selected node
|
|
147
|
-
const newNode = JSON.parse(JSON.stringify(selectedNode));
|
|
148
|
-
newNode.parent = null;
|
|
149
|
-
onRemove(selectedNode);
|
|
150
|
-
|
|
151
|
-
//add to target
|
|
152
|
-
|
|
153
|
-
menus.value.push(newNode);
|
|
154
|
-
|
|
155
|
-
showMove.value = false;
|
|
156
|
-
}
|
|
157
136
|
|
|
158
137
|
const getParentNode = (node) => {
|
|
159
138
|
if (!node.parent) return {
|
|
@@ -197,6 +176,34 @@ const onAddChildMenu = (node, type) => {
|
|
|
197
176
|
children: [],
|
|
198
177
|
});
|
|
199
178
|
}
|
|
179
|
+
|
|
180
|
+
const onAddSeparator = (node) => {
|
|
181
|
+
node.children.push({
|
|
182
|
+
label: '[Separator]',
|
|
183
|
+
uuid: getUUID(),
|
|
184
|
+
parent: node.uuid,
|
|
185
|
+
children: [],
|
|
186
|
+
type: 'separator',
|
|
187
|
+
spaced: true,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const onAddHeader = (node) => {
|
|
194
|
+
node.children.push({
|
|
195
|
+
label: '[Header]',
|
|
196
|
+
uuid: getUUID(),
|
|
197
|
+
parent: node.uuid,
|
|
198
|
+
children: [],
|
|
199
|
+
type: 'header',
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
const menusOnly = computed(() => {
|
|
205
|
+
return menus.value.filter((item) => !item.type);
|
|
206
|
+
});
|
|
200
207
|
</script>
|
|
201
208
|
|
|
202
209
|
<template>
|
|
@@ -211,79 +218,109 @@ const onAddChildMenu = (node, type) => {
|
|
|
211
218
|
<q-card-actions align="right">
|
|
212
219
|
<q-btn flat label="Cancel" color="primary" v-close-popup />
|
|
213
220
|
<q-btn flat label="Move" color="primary" @click="onMoveConfirm" />
|
|
214
|
-
|
|
221
|
+
<!-- q-btn flat label="Move to root" color="primary" @click="onMoveToRoot" /-->
|
|
215
222
|
</q-card-actions>
|
|
216
223
|
</l-card>
|
|
217
224
|
</q-dialog>
|
|
218
225
|
|
|
219
226
|
<l-card>
|
|
227
|
+
<q-card-actions v-if="selectedNode">
|
|
228
|
+
<l-btn @click="onSave" label="Save" icon="sym_o_save" />
|
|
229
|
+
<l-btn @click="onReload" label="Reload" icon="sym_o_refresh" />
|
|
230
|
+
<template v-if="selectedNode.type == 'root'">
|
|
231
|
+
<l-btn color="primary" @click="onAddChild(selectedNode)" label="Add" icon="sym_o_add" />
|
|
232
|
+
<l-btn color="primary" @click="onAddHeader(selectedNode)" label="Add Header" icon="sym_o_add" />
|
|
233
|
+
<l-btn color="primary" @click="onAddSeparator(selectedNode)" label="Add Separator" icon="sym_o_add" />
|
|
234
|
+
</template>
|
|
235
|
+
|
|
236
|
+
<template v-else>
|
|
237
|
+
<q-btn-dropdown color="primary" label="Add" icon="sym_o_add">
|
|
238
|
+
<q-list>
|
|
239
|
+
<q-item clickable v-close-popup @click="onAddChild(selectedNode)">
|
|
240
|
+
<q-item-section>
|
|
241
|
+
<q-item-label>Child</q-item-label>
|
|
242
|
+
</q-item-section>
|
|
243
|
+
</q-item>
|
|
244
|
+
<q-item clickable v-close-popup @click="onAddHeader(selectedNode)">
|
|
245
|
+
<q-item-section>
|
|
246
|
+
<q-item-label>Header</q-item-label>
|
|
247
|
+
</q-item-section>
|
|
248
|
+
</q-item>
|
|
249
|
+
<q-item clickable v-close-popup @click="onAddSeparator(selectedNode)">
|
|
250
|
+
<q-item-section>
|
|
251
|
+
<q-item-label>Separator</q-item-label>
|
|
252
|
+
</q-item-section>
|
|
253
|
+
</q-item>
|
|
254
|
+
|
|
255
|
+
</q-list>
|
|
256
|
+
</q-btn-dropdown>
|
|
257
|
+
|
|
258
|
+
<l-btn color="primary" @click="onRemove(selectedNode)" label="Remove" icon="sym_o_remove" />
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
<l-btn color="primary" @click="onMove(selectedNode)" label="Move" icon="sym_o_move" />
|
|
262
|
+
|
|
263
|
+
<l-btn color="primary" @click="onMoveUp(selectedNode)" label="Up" icon="sym_o_arrow_upward" />
|
|
264
|
+
|
|
265
|
+
<l-btn color="primary" @click="onMoveDown(selectedNode)" label="Down" icon="sym_o_arrow_downward" />
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
<q-btn-dropdown color="primary" label="Add menus" icon="sym_o_add">
|
|
269
|
+
<q-list>
|
|
270
|
+
<q-item clickable v-close-popup @click="onAddChildMenu(selectedNode, 'list')">
|
|
271
|
+
<q-item-section>
|
|
272
|
+
<q-item-label>List</q-item-label>
|
|
273
|
+
</q-item-section>
|
|
274
|
+
</q-item>
|
|
275
|
+
|
|
276
|
+
<q-item clickable v-close-popup @click="onAddChildMenu(selectedNode, 'add')">
|
|
277
|
+
<q-item-section>
|
|
278
|
+
<q-item-label>Add</q-item-label>
|
|
279
|
+
</q-item-section>
|
|
280
|
+
</q-item>
|
|
281
|
+
|
|
282
|
+
</q-list>
|
|
283
|
+
</q-btn-dropdown>
|
|
284
|
+
</template>
|
|
285
|
+
</q-card-actions>
|
|
220
286
|
<q-splitter v-model="splitterModel" style="height:680px">
|
|
221
287
|
<template #before>
|
|
222
|
-
<q-card-actions>
|
|
223
|
-
<l-btn @click="onAdd" label="Add" icon="sym_o_add" />
|
|
224
|
-
<l-btn @click="onReload" label="Reload" icon="sym_o_refresh" />
|
|
225
|
-
|
|
226
|
-
<l-btn @click="onSave" label="Save" icon="sym_o_save" />
|
|
227
|
-
</q-card-actions>
|
|
228
288
|
|
|
229
289
|
<q-tree :nodes="menus" selected-color="primary" default-expand-all v-model:selected="selected"
|
|
230
290
|
node-key="uuid" ref="tree1" />
|
|
231
291
|
</template>
|
|
232
292
|
|
|
233
293
|
<template #after v-if="selected">
|
|
234
|
-
|
|
235
|
-
<q-card-actions>
|
|
236
|
-
<l-btn outline rounded color="primary" @click="onRemove(selectedNode)" label="Remove"
|
|
237
|
-
icon="sym_o_remove" />
|
|
238
|
-
|
|
239
|
-
<l-btn outline rounded color="primary" @click="onAddChild(selectedNode)" label="Add Child"
|
|
240
|
-
icon="sym_o_add" />
|
|
241
|
-
|
|
242
|
-
<l-btn outline rounded color="primary" @click="onMove(selectedNode)" label="Move"
|
|
243
|
-
icon="sym_o_move" />
|
|
244
|
-
|
|
245
|
-
<l-btn outline rounded color="primary" @click="onMoveUp(selectedNode)" label="Up"
|
|
246
|
-
icon="sym_o_arrow_upward" />
|
|
247
|
-
|
|
248
|
-
<l-btn outline rounded color="primary" @click="onMoveDown(selectedNode)" label="Down"
|
|
249
|
-
icon="sym_o_arrow_downward" />
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
<q-btn-dropdown color="primary" label="Add menus" outline rounded icon="sym_o_add">
|
|
253
|
-
<q-list>
|
|
254
|
-
<q-item clickable v-close-popup @click="onAddChildMenu(selectedNode, 'list')">
|
|
255
|
-
<q-item-section>
|
|
256
|
-
<q-item-label>List</q-item-label>
|
|
257
|
-
</q-item-section>
|
|
258
|
-
</q-item>
|
|
259
|
-
|
|
260
|
-
<q-item clickable v-close-popup @click="onAddChildMenu(selectedNode, 'add')">
|
|
261
|
-
<q-item-section>
|
|
262
|
-
<q-item-label>Add</q-item-label>
|
|
263
|
-
</q-item-section>
|
|
264
|
-
</q-item>
|
|
265
|
-
|
|
266
|
-
</q-list>
|
|
267
|
-
</q-btn-dropdown>
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
</q-card-actions>
|
|
271
|
-
|
|
272
|
-
|
|
273
294
|
<q-card-section>
|
|
274
|
-
<
|
|
275
|
-
<
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
<
|
|
284
|
-
|
|
285
|
-
|
|
295
|
+
<template v-if="!selectedNode.type">
|
|
296
|
+
<div class="q-gutter-md">
|
|
297
|
+
<l-input label="Label" v-model="selectedNode.label" />
|
|
298
|
+
<l-input label="To" v-model="selectedNode.to" />
|
|
299
|
+
<l-input label="Icon" v-model="selectedNode.icon" hint="example: sym_o_add" />
|
|
300
|
+
<l-input label="Permission" v-model="selectedNode.permission" />
|
|
301
|
+
<l-input label="UUID" v-model="selectedNode.uuid" readonly />
|
|
302
|
+
</div>
|
|
303
|
+
|
|
304
|
+
<div>
|
|
305
|
+
<a href="https://fonts.google.com/icons" target="_blank">Material Icons</a>
|
|
306
|
+
</div>
|
|
307
|
+
|
|
308
|
+
</template>
|
|
309
|
+
|
|
310
|
+
<template v-if="selectedNode.type == 'separator'">
|
|
311
|
+
<div class="q-gutter-md">
|
|
312
|
+
<q-checkbox label="Spaced" v-model="selectedNode.spaced" />
|
|
313
|
+
|
|
314
|
+
</div>
|
|
315
|
+
</template>
|
|
316
|
+
|
|
317
|
+
<template v-if="selectedNode.type == 'header'">
|
|
318
|
+
<div class="q-gutter-md">
|
|
319
|
+
<l-input label="Label" v-model="selectedNode.label" />
|
|
320
|
+
</div>
|
|
321
|
+
</template>
|
|
286
322
|
</q-card-section>
|
|
323
|
+
|
|
287
324
|
</template>
|
|
288
325
|
|
|
289
326
|
</q-splitter>
|
|
@@ -1,55 +1,41 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import {
|
|
2
|
+
import { useLight } from "#imports";
|
|
3
|
+
import { useRoute } from "vue-router"
|
|
4
|
+
import { ref } from 'vue';
|
|
3
5
|
|
|
4
|
-
const
|
|
6
|
+
const route = useRoute();
|
|
7
|
+
const light = useLight();
|
|
5
8
|
|
|
9
|
+
const tab = ref('overview');
|
|
10
|
+
const splitter = ref(10);
|
|
11
|
+
const id = route.params.user_id;
|
|
6
12
|
</script>
|
|
7
13
|
|
|
8
14
|
<template>
|
|
9
|
-
<l-page>
|
|
15
|
+
<l-page edit-btn>
|
|
10
16
|
<template #header>
|
|
11
17
|
<l-btn to="change-password" icon="sym_o_key" permission="user.changePassword" label="Change password"></l-btn>
|
|
12
18
|
<l-btn to="update-role" icon="sym_o_people" permission="user.role.add" label="Update role"></l-btn>
|
|
13
19
|
</template>
|
|
14
20
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
listUser: {
|
|
35
|
-
__args: {
|
|
36
|
-
filters: {
|
|
37
|
-
user_id: obj.user_id
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
data: {
|
|
41
|
-
__args: {
|
|
42
|
-
limit: 1
|
|
43
|
-
},
|
|
44
|
-
eventLog: req.gql
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
let resp = await q(a);
|
|
50
|
-
req.setData(resp.listUser.data[0].eventLog)
|
|
51
|
-
}" />
|
|
52
|
-
</l-tab>
|
|
53
|
-
</l-tabs>
|
|
21
|
+
|
|
22
|
+
<q-card flat bordered>
|
|
23
|
+
<q-tabs v-model="tab" :class="`text-${light.color}`" inline-label align="justify">
|
|
24
|
+
<q-tab name="overview" icon="sym_o_person" label="Overview" />
|
|
25
|
+
<q-tab name="userlog" icon="sym_o_description" label="User log" />
|
|
26
|
+
<q-tab name="eventlog" icon="sym_o_description" label="Event log" />
|
|
27
|
+
</q-tabs>
|
|
28
|
+
|
|
29
|
+
<l-user-overview v-if="tab === 'overview'" :id="id" />
|
|
30
|
+
<l-user-userlog v-if="tab === 'userlog'" :id="id" />
|
|
31
|
+
<l-user-eventlog v-if="tab === 'eventlog'" :id="id" />
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
</q-card>
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
54
40
|
</l-page>
|
|
55
41
|
</template>
|
|
@@ -51,29 +51,52 @@ eventLogCols.forEach(col => {
|
|
|
51
51
|
<l-btn icon="sym_o_password" to="setting/password" label="Update password" />
|
|
52
52
|
<l-btn icon="sym_o_key" to="setting/two-factor-auth" label="Two factor auth" />
|
|
53
53
|
</template>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
54
|
+
|
|
55
|
+
<l-row>
|
|
56
|
+
<l-col md="4">
|
|
57
|
+
|
|
58
|
+
<l-card>
|
|
59
|
+
|
|
60
|
+
<q-card-section class="text-h5 text-center">
|
|
61
|
+
{{ my.first_name }} {{ my.last_name }}
|
|
62
|
+
</q-card-section>
|
|
63
|
+
|
|
64
|
+
<q-card-section class="text-center text-center q-pt-none">
|
|
65
|
+
<q-chip v-for="role in my.roles" :key="role" :label="role" :color="$light.color" text-color="white"
|
|
66
|
+
class="q-ma-xs" />
|
|
67
|
+
</q-card-section>
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
<q-separator />
|
|
71
|
+
<l-list :bordered="false">
|
|
72
|
+
|
|
73
|
+
<q-item-label header>Details</q-item-label>
|
|
74
|
+
<l-item label="Username">{{ my.username }}</l-item>
|
|
75
|
+
<l-item label="Email">{{ my.email }}</l-item>
|
|
76
|
+
<l-item label="Phone">{{ my.phone }}</l-item>
|
|
77
|
+
<l-item label="Address">{{ my.addr1 }} {{ my.addr2 }} {{ my.addr3 }}</l-item>
|
|
78
|
+
<l-item label="Join date">{{ my.join_date }}</l-item>
|
|
79
|
+
</l-list>
|
|
80
|
+
</l-card>
|
|
81
|
+
</l-col>
|
|
82
|
+
|
|
83
|
+
<l-col md="8">
|
|
84
|
+
|
|
85
|
+
<l-tabs>
|
|
86
|
+
<l-tab label="User Log">
|
|
87
|
+
<l-table :rows="my.userLog.data" :columns="userlogColumns" searchable :rows-per-page-options="[0]">
|
|
88
|
+
</l-table>
|
|
89
|
+
</l-tab>
|
|
90
|
+
<l-tab label="Event Log">
|
|
91
|
+
<l-table :rows="my.eventLog.data" :columns="eventLogCols" searchable :rows-per-page-options="[0]">
|
|
92
|
+
</l-table>
|
|
93
|
+
</l-tab>
|
|
94
|
+
|
|
95
|
+
</l-tabs>
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
</l-col>
|
|
99
|
+
</l-row>
|
|
100
|
+
|
|
78
101
|
</l-page>
|
|
79
102
|
</template>
|
package/dist/runtime/plugin.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Quasar, Dialog, Notify, Loading } from "quasar";
|
|
1
|
+
import { Quasar, Dialog, Notify, Loading, AppFullscreen } from "quasar";
|
|
2
2
|
import { createI18n } from "vue-i18n";
|
|
3
3
|
import { defineNuxtPlugin } from "#app";
|
|
4
4
|
import { useRouter } from "vue-router";
|
|
@@ -24,6 +24,7 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
24
24
|
defineModel("SystemValue", TypeSystemValue);
|
|
25
25
|
defineModel("MailLog", TypeMailLog);
|
|
26
26
|
defineModel("EventLog", TypeEventLog);
|
|
27
|
+
nuxtApp.vueApp.config.globalProperties.$light = useLight();
|
|
27
28
|
nuxtApp.vueApp.config.errorHandler = (error) => {
|
|
28
29
|
console.log(error);
|
|
29
30
|
const light = useLight();
|
|
@@ -58,7 +59,8 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
58
59
|
plugins: {
|
|
59
60
|
Dialog,
|
|
60
61
|
Notify,
|
|
61
|
-
Loading
|
|
62
|
+
Loading,
|
|
63
|
+
AppFullscreen
|
|
62
64
|
}
|
|
63
65
|
});
|
|
64
66
|
let locale = localStorage.getItem("locale") || "en";
|