@hostlink/nuxt-light 1.0.1 → 1.0.3

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.
Files changed (39) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +2 -13
  3. package/dist/runtime/components/l-app-main.vue +13 -4
  4. package/dist/runtime/components/l-card.vue +1 -1
  5. package/dist/runtime/components/l-col.vue +1 -1
  6. package/dist/runtime/components/l-customizer.vue +3 -4
  7. package/dist/runtime/components/l-file-manager.vue +4 -4
  8. package/dist/runtime/components/l-input-xlsx.vue +78 -0
  9. package/dist/runtime/components/l-login.vue +17 -9
  10. package/dist/runtime/components/l-page.vue +7 -0
  11. package/dist/runtime/components/l-row.vue +4 -2
  12. package/dist/runtime/components/l-table.vue +5 -4
  13. package/dist/runtime/formkit/File.vue +51 -0
  14. package/dist/runtime/formkit/Form.vue +2 -2
  15. package/dist/runtime/formkit/InputXlsx.vue +22 -0
  16. package/dist/runtime/formkit/Toggle.vue +18 -0
  17. package/dist/runtime/formkit/index.mjs +12 -0
  18. package/dist/runtime/lib/index.mjs +1 -1
  19. package/dist/runtime/locales/en.json +5 -12
  20. package/dist/runtime/locales/zh-hk.json +15 -2
  21. package/dist/runtime/pages/Permission/all.vue +1 -1
  22. package/dist/runtime/pages/Role/add.vue +1 -5
  23. package/dist/runtime/pages/Role/index.vue +21 -5
  24. package/dist/runtime/pages/System/database/backup.vue +25 -2
  25. package/dist/runtime/pages/System/database/table.vue +5 -3
  26. package/dist/runtime/pages/System/index.vue +3 -2
  27. package/dist/runtime/pages/System/menu/index.vue +6 -1
  28. package/dist/runtime/pages/System/package.vue +21 -2
  29. package/dist/runtime/pages/System/setting.vue +65 -9
  30. package/dist/runtime/pages/System/test.vue +1 -0
  31. package/dist/runtime/pages/System/view_as.vue +38 -11
  32. package/dist/runtime/pages/Translate/index.vue +5 -4
  33. package/dist/runtime/pages/User/_user_id/edit.vue +13 -12
  34. package/dist/runtime/pages/User/add.vue +18 -10
  35. package/dist/runtime/pages/User/setting/bio-auth.vue +4 -7
  36. package/dist/runtime/pages/User/setting/password.vue +29 -11
  37. package/dist/runtime/pages/User/setting/two-factor-auth.vue +13 -7
  38. package/dist/types.d.mts +2 -2
  39. package/package.json +2 -4
package/dist/module.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "light",
3
3
  "configKey": "light",
4
- "version": "1.0.1"
4
+ "version": "1.0.3"
5
5
  }
package/dist/module.mjs CHANGED
@@ -1,5 +1,4 @@
1
1
  import { defineNuxtModule, createResolver, addComponentsDir, addImports, addPlugin } from '@nuxt/kit';
2
- import AutoImport from 'unplugin-auto-import/vite';
3
2
 
4
3
  const module = defineNuxtModule({
5
4
  meta: {
@@ -20,17 +19,6 @@ const module = defineNuxtModule({
20
19
  rel: "stylesheet",
21
20
  href: "https://fonts.googleapis.com/css2?family=Noto+Sans&family=Noto+Sans+HK&family=Noto+Sans+TC&family=Material+Symbols+Outlined"
22
21
  });
23
- nuxt.options.vite.plugins?.push(AutoImport({
24
- imports: [{
25
- "quasar": [
26
- "useQuasar"
27
- ]
28
- }, {
29
- "vue-i18n": [
30
- "useI18n"
31
- ]
32
- }]
33
- }));
34
22
  addComponentsDir({
35
23
  path: resolver.resolve("./runtime/components")
36
24
  });
@@ -60,7 +48,8 @@ const module = defineNuxtModule({
60
48
  { name: "getModelFields", from },
61
49
  { name: "getModelField", from },
62
50
  { name: "getGQLFields", from },
63
- { name: "model", from }
51
+ { name: "model", from },
52
+ { name: "notify", from }
64
53
  ]);
65
54
  addPlugin({
66
55
  src: resolver.resolve("./runtime/plugin"),
@@ -16,7 +16,11 @@ const appVersion = config.public.appVersion ?? '0.0.1';
16
16
 
17
17
  const quasar = useQuasar();
18
18
  const tt = await q({
19
- app: ["menus", "viewAsMode", "languages", { i18nMessages: ["name", "value"] }],
19
+ system: ["devMode"],
20
+ app: ["menus", "viewAsMode", "languages",
21
+ "copyrightYear",
22
+ "copyrightName",
23
+ { i18nMessages: ["name", "value"] }],
20
24
  my: ['username', 'first_name', 'last_name', 'roles', "styles", "language", f('granted_storage:granted', { right: "system.storage" }, [])],
21
25
  })
22
26
 
@@ -194,6 +198,9 @@ const containerStyle = computed(() => {
194
198
 
195
199
  <q-toolbar-title>
196
200
  {{ light.getCompany() }}
201
+ <template v-if="tt.system.devMode">
202
+ - Development mode
203
+ </template>
197
204
  </q-toolbar-title>
198
205
 
199
206
  <q-space />
@@ -212,7 +219,7 @@ const containerStyle = computed(() => {
212
219
  </q-btn>
213
220
 
214
221
  <q-btn icon="sym_o_storage" flat round dense class="q-mr-sm" v-if="my.granted_storage"
215
- :color="storageColor" >
222
+ :color="storageColor">
216
223
  <q-menu>
217
224
  <q-card style="width:250px">
218
225
  <q-card-section>
@@ -304,7 +311,7 @@ const containerStyle = computed(() => {
304
311
  </q-drawer>
305
312
 
306
313
  <q-page-container :class="containerClass" :style="containerStyle">
307
-
314
+
308
315
  <!-- Error message -->
309
316
  <q-banner dense inline-actions class="bg-grey-4 q-ma-md" v-for="error in errors" rounded>
310
317
  {{ error }}
@@ -328,7 +335,9 @@ const containerStyle = computed(() => {
328
335
  <q-footer bordered v-if="style.footer">
329
336
  <q-item>
330
337
  <q-item-section>
331
- {{ light.getCompany() }} {{ appVersion }} - Copyright 2023 HostLink(HK). Build {{ light.getVersion() }}
338
+ {{ light.getCompany() }} {{ appVersion }} - Copyright {{ app.copyrightYear }} {{ app.copyrightName }}.
339
+ Build {{
340
+ light.getVersion() }}
332
341
  </q-item-section>
333
342
  </q-item>
334
343
  </q-footer>
@@ -38,7 +38,7 @@ const showBody = computed(() => !minimize.value || fullScreen.value);
38
38
  <template>
39
39
  <q-card v-bind="attrs" :class="{ 'fullscreen': fullScreen, 'no-margin': fullScreen }">
40
40
  <q-bar :class="cl" v-if="title">
41
- <div>{{ title }}</div>
41
+ <div>{{ $t(title) }}</div>
42
42
  <q-space />
43
43
  <!-- q-btn-dropdown dense flat icon="sym_o_search" persistent>
44
44
  <div class="q-ma-md">
@@ -7,7 +7,7 @@ const props = defineProps({
7
7
  },
8
8
  gutter: {
9
9
  type: String,
10
- default: "none"
10
+ default: "md"
11
11
  }
12
12
  })
13
13
 
@@ -1,5 +1,4 @@
1
1
  <script setup>
2
- import { defineModel } from 'vue'
3
2
  const COLORS = [
4
3
  'primary',
5
4
  'secondary',
@@ -84,7 +83,7 @@ const props = defineProps({
84
83
  <q-separator />
85
84
  <q-item>
86
85
  <q-item-section>
87
- <q-item-label>Color</q-item-label>
86
+ <q-item-label>{{ $t('Color') }}</q-item-label>
88
87
  <div class="row">
89
88
  <div v-for="c in COLORS" :key="c" :class="`bg-${c}`" style="width: 1.5rem; height: 1.5rem;"
90
89
  class="q-ma-xs cursor-pointer rounded-borders" @click="$emit('update:color', c)" />
@@ -115,7 +114,7 @@ const props = defineProps({
115
114
 
116
115
  <q-item>
117
116
  <q-item-section>
118
- <q-item-label>Menu overlay header </q-item-label>
117
+ <q-item-label>Menu overlay header</q-item-label>
119
118
  </q-item-section>
120
119
  <q-item-section side>
121
120
  <q-toggle :model-value="menuOverlayHeader"
@@ -127,7 +126,7 @@ const props = defineProps({
127
126
 
128
127
  <q-item>
129
128
  <q-item-section>
130
- <q-item-label>Show footer</q-item-label>
129
+ <q-item-label>{{ $t('Show footer') }}</q-item-label>
131
130
  </q-item-section>
132
131
  <q-item-section side>
133
132
  <q-toggle :model-value="footer" @update:model-value="$emit('update:footer', $event)" />
@@ -464,7 +464,7 @@ const isDark = computed(() => light.isDarkMode());
464
464
  </script>
465
465
  <template>
466
466
  <q-layout view="hHh lpR fFf" :class="isDark ? '' : 'bg-white'" container :style="{ 'min-height': height }">
467
- <q-header bordered :class="isDark?'':'bg-white text-grey-8'" height-hint="64">
467
+ <q-header bordered :class="isDark ? '' : 'bg-white text-grey-8'" height-hint="64">
468
468
  <q-toolbar>
469
469
  <q-btn flat round @click="toggleLeftDrawer" aria-label="Menu" icon="menu" class="q-mr-sm" />
470
470
 
@@ -629,7 +629,7 @@ const isDark = computed(() => light.isDarkMode());
629
629
 
630
630
  <q-table flat bordered :columns="columns" :rows="items" @row-dblclick="onDblclickRow" @row-click="onClickRow"
631
631
  :pagination="pagination" row-key="path" selection="multiple" v-model:selected="selected" dense
632
- :loading="loading">
632
+ :loading="loading" :loading-label="$t('Loading...')" :no-data-label="$t('No data available')">
633
633
  <template #body-cell-icon="props">
634
634
  <q-td auto-width>
635
635
  <q-icon name="sym_o_folder" v-if="props.value == 'folder'" size="sm" />
@@ -646,14 +646,14 @@ const isDark = computed(() => light.isDarkMode());
646
646
  <q-item-section avatar>
647
647
  <q-icon name="sym_o_delete"></q-icon>
648
648
  </q-item-section>
649
- <q-item-section>{{ $t('Delete') }} </q-item-section>
649
+ <q-item-section>{{ $t('Delete') }}</q-item-section>
650
650
  </q-item>
651
651
 
652
652
  <q-item v-if="props.row.type == 'file'" clickable v-close-popup @click="onDownloadRow(props.row)">
653
653
  <q-item-section avatar>
654
654
  <q-icon name="sym_o_download"></q-icon>
655
655
  </q-item-section>
656
- <q-item-section>Download</q-item-section>
656
+ <q-item-section>{{ $t('Download') }}</q-item-section>
657
657
  </q-item>
658
658
 
659
659
  <q-item clickable v-close-popup @click="onRenameRow(props.row)" v-if="canRenameRow(props.row)">
@@ -0,0 +1,78 @@
1
+
2
+ <script setup>
3
+ import * as XLSX from "xlsx";
4
+ import { computed, ref, useAttrs, useSlots } from "vue";
5
+ import { useLight } from "#imports"
6
+
7
+ const emit = defineEmits(["update:modelValue"]);
8
+
9
+ const light = useLight();
10
+ const attrs = {
11
+ ...{
12
+ filled: light.getStyle("inputFilled"),
13
+ outlined: light.getStyle("inputOutlined"),
14
+ standout: light.getStyle("inputStandout"),
15
+ rounded: light.getStyle("inputRounded"),
16
+ dense: light.getStyle("inputDense"),
17
+ square: light.getStyle("inputSquare"),
18
+ stackLabel: light.getStyle("inputStackLabel")
19
+ },
20
+ ...useAttrs(),
21
+
22
+ }
23
+ const loading = ref(false);
24
+ const localData = ref([]);
25
+ const hasFile = ref(false);
26
+ const file = ref(null);
27
+ const onChange = () => {
28
+ hasFile.value = true;
29
+ loading.value = true;
30
+ file.value.files[0].arrayBuffer().then((data) => {
31
+ const workbook = XLSX.read(data);
32
+ const sheet = workbook.Sheets[workbook.SheetNames[0]];
33
+ const json = XLSX.utils.sheet_to_json(sheet);
34
+ localData.value = json;
35
+ loading.value = false;
36
+
37
+ emit("update:modelValue", json);
38
+
39
+ });
40
+
41
+ }
42
+
43
+ const onClear = () => {
44
+ hasFile.value = false;
45
+ localData.value = [];
46
+ emit("update:modelValue", []);
47
+ }
48
+
49
+ const showView = ref(false);
50
+ </script>
51
+
52
+ <template>
53
+ <q-field v-bind="attrs" :loading="loading" :model-value="localData" @update:model-value="onClear" :clearable="hasFile">
54
+ <template v-slot:control>
55
+ <template v-if="!hasFile">
56
+ <input type="file" accept=".xlsx" @change="onChange" ref="file" />
57
+ </template>
58
+
59
+ <template v-else>
60
+ {{ localData.length }} records
61
+ </template>
62
+
63
+ </template>
64
+
65
+ <template v-slot:prepend v-if="hasFile">
66
+ <q-icon name="sym_o_table_view" @click="showView = true" class="cursor-pointer" />
67
+ </template>
68
+
69
+ <q-dialog v-model="showView" full-width>
70
+ <q-card>
71
+ <q-card-section>
72
+ <q-table :rows="localData" />
73
+ </q-card-section>
74
+ </q-card>
75
+ </q-dialog>
76
+
77
+ </q-field>
78
+ </template>
@@ -1,9 +1,9 @@
1
1
  <script setup>
2
2
  import { useLight } from "#imports";
3
3
  import { ref, reactive, onMounted } from 'vue'
4
- import { useQuasar, Notify } from 'quasar';
4
+ import { useQuasar, Notify, Dialog } from 'quasar';
5
5
  import { useI18n } from 'vue-i18n';
6
- import { m, notify } from '../';
6
+ import { m, notify } from '#imports';
7
7
 
8
8
  import { login, webauthnLogin } from '@hostlink/light';
9
9
 
@@ -35,9 +35,7 @@ const submit = async () => {
35
35
  }
36
36
 
37
37
  const forgetPassword = async () => {
38
-
39
- //show dialog
40
- qua.dialog({
38
+ Dialog.create({
41
39
  title: i18n.t("Forget password"),
42
40
  message: "Please enter your email address, we will send you a code to reset your password",
43
41
  prompt: {
@@ -50,9 +48,9 @@ const forgetPassword = async () => {
50
48
  return;
51
49
  }
52
50
 
53
-
54
- if (await m("forgetPassword", { email: email })) {
55
- qua.dialog({
51
+ try {
52
+ await m("forgetPassword", { email: email });
53
+ Dialog.create({
56
54
  title: "Enter your code",
57
55
  message: "Please enter the code sent to your email, your code will expire in 10 minutes",
58
56
  prompt: {
@@ -67,7 +65,7 @@ const forgetPassword = async () => {
67
65
  }
68
66
 
69
67
  if (await m("verifyCode", { code: code, email: email })) {
70
- qua.dialog({
68
+ Dialog.create({
71
69
  title: "Reset password",
72
70
  message: "Please enter your new password",
73
71
  prompt: {
@@ -101,9 +99,19 @@ const forgetPassword = async () => {
101
99
  }
102
100
 
103
101
 
102
+ });
103
+ } catch (e) {
104
+ qua.notify({
105
+ message: e.message,
106
+ color: "negative",
107
+ icon: "sym_o_error",
108
+ position: "top",
109
+ timeout: 2000
104
110
  });
105
111
  }
106
112
  });
113
+
114
+
107
115
  };
108
116
 
109
117
  const hasBioLogin = ref(false);
@@ -92,6 +92,12 @@ useHead({
92
92
 
93
93
  <template>
94
94
  <q-page padding>
95
+ <!-- q-breadcrumbs>
96
+ <q-breadcrumbs-el icon="home" to="/" />
97
+ <q-breadcrumbs-el label="Permission" to="/Permission" />
98
+ <q-breadcrumbs-el label="Build" />
99
+ </q-breadcrumbs -->
100
+
95
101
  <q-toolbar v-if="showToolbar">
96
102
  <l-back-btn v-if="showBackBtn" />
97
103
  <l-add-btn v-if="showAddBtn" />
@@ -100,6 +106,7 @@ useHead({
100
106
  <q-toolbar-title>{{ $t(title) }}</q-toolbar-title>
101
107
  </q-toolbar>
102
108
 
109
+
103
110
  <div class="q-gutter-sm q-mb-sm">
104
111
  <slot name="header"></slot>
105
112
  </div>
@@ -1,5 +1,7 @@
1
1
  <template>
2
- <div class="row q-col-gutter-md">
3
- <slot></slot>
2
+ <div>
3
+ <div class="row q-col-gutter-md">
4
+ <slot></slot>
5
+ </div>
4
6
  </div>
5
7
  </template>
@@ -589,7 +589,7 @@ const getCellClass = (col: any, row: any) => {
589
589
 
590
590
 
591
591
  <template #top-right="props" v-if="fullscreen || searchable">
592
- <q-input v-if="searchable" outlined dense debounce="300" v-model="filter" placeholder="Search">
592
+ <q-input v-if="searchable" outlined dense debounce="300" v-model="filter" :placeholder="$t('Search')">
593
593
  <template v-slot:append>
594
594
  <q-icon name="search" />
595
595
  </template>
@@ -618,8 +618,9 @@ const getCellClass = (col: any, row: any) => {
618
618
  <template v-if="col.searchable">
619
619
 
620
620
  <template v-if="col.searchType == 'number'">
621
- <q-input dense clearable filled square v-model.number="filters[col.name]"
622
- @keydown.enter.prevent="onFilters" @clear="onFilters" mask="##########"></q-input>
621
+ <q-input style="min-width: 80px;" dense clearable filled square
622
+ v-model.number="filters[col.name]" @keydown.enter.prevent="onFilters" @clear="onFilters"
623
+ mask="##########"></q-input>
623
624
  </template>
624
625
 
625
626
  <template v-if="col.searchType == 'select'">
@@ -635,7 +636,7 @@ const getCellClass = (col: any, row: any) => {
635
636
  </template>
636
637
 
637
638
  <template v-if="!col.searchType">
638
- <q-input dense clearable filled square v-model="filters[col.name]"
639
+ <q-input style="min-width: 80px;" dense clearable filled square v-model="filters[col.name]"
639
640
  @keydown.enter.prevent="onFilters" @clear="onFilters" enterkeyhint="search"></q-input>
640
641
 
641
642
  </template>
@@ -0,0 +1,51 @@
1
+ <script setup>
2
+ import { computed, useSlots } from 'vue'
3
+ import { getErrorMessage } from 'formkit-quasar';
4
+ import { useLight } from '#imports';
5
+
6
+
7
+ const props = defineProps({
8
+ context: Object
9
+ });
10
+
11
+ const { error, errorMessage } = getErrorMessage(props.context.node);
12
+
13
+ const value = computed({
14
+ get: () => props.context.value,
15
+ set: (val) => props.context.node.input(val)
16
+ })
17
+
18
+ const ss = Object.entries(useSlots()).map(([key]) => key);
19
+
20
+ const onBlur = () => {
21
+ if (errorMessage.value) {
22
+ error.value = true
23
+ } else {
24
+ error.value = false
25
+ }
26
+ }
27
+
28
+ const light = useLight();
29
+
30
+ const attrs = {
31
+ ...{
32
+ filled: light.getStyle("inputFilled"),
33
+ outlined: light.getStyle("inputOutlined"),
34
+ standout: light.getStyle("inputStandout"),
35
+ rounded: light.getStyle("inputRounded"),
36
+ dense: light.getStyle("inputDense"),
37
+ square: light.getStyle("inputSquare"),
38
+ stackLabel: light.getStyle("inputStackLabel")
39
+ },
40
+ ...props.context.attrs
41
+ }
42
+
43
+ </script>
44
+ <template>
45
+ <q-file v-model="value" :label="context.label" v-bind="attrs" :error="error" :error-message="errorMessage"
46
+ @blur="onBlur">
47
+ <template v-for="s in ss" v-slot:[s]="props" :key="s">
48
+ <slot :name="s" v-bind="props ?? {}"></slot>
49
+ </template>
50
+ </q-file>
51
+ </template>
@@ -77,13 +77,13 @@ if (!props.context.onSubmit) {
77
77
  <form v-bind="context.attrs">
78
78
  <l-card :bordered="bordered">
79
79
  <q-card-section>
80
- <div :class="`q-col-gutter-${gutter}`">
80
+ <div :class="`q-gutter-${gutter}`">
81
81
  <slot v-bind="context"></slot>
82
82
  </div>
83
83
  </q-card-section>
84
84
 
85
85
  <q-card-actions align="right">
86
- <l-btn color="primary" icon="sym_o_check" label="Submit" @click="onSubmit" :disabled="!context.state.dirty"
86
+ <l-btn icon="sym_o_check" label="Submit" @click="onSubmit" :disabled="!context.state.dirty"
87
87
  :loading="loading"></l-btn>
88
88
  </q-card-actions>
89
89
  </l-card>
@@ -0,0 +1,22 @@
1
+ <script setup>
2
+ import { computed } from 'vue'
3
+ import { getErrorMessage } from 'formkit-quasar';
4
+
5
+ const props = defineProps({
6
+ context: Object
7
+ });
8
+
9
+ const { error, errorMessage } = getErrorMessage(props.context.node);
10
+
11
+ const value = computed({
12
+ get: () => props.context.value,
13
+ set: (val) => props.context.node.input(val)
14
+ })
15
+
16
+ </script>
17
+ <template>
18
+ <l-input-xlsx v-model="value" :label="context.label" v-bind="context.attrs" :error="error" :type="context.inputType"
19
+ :error-message="errorMessage">
20
+ <slot></slot>
21
+ </l-input-xlsx>
22
+ </template>
@@ -0,0 +1,18 @@
1
+ <script setup>
2
+ import { computed } from 'vue'
3
+
4
+ const props = defineProps({
5
+ context: Object
6
+ });
7
+
8
+ const value = computed({
9
+ get: () => props.context.value,
10
+ set: (val) => props.context.node.input(val)
11
+ })
12
+
13
+
14
+ </script>
15
+ <template>
16
+ <q-toggle v-model="value" :label="context.label" v-bind="context.attrs">
17
+ </q-toggle>
18
+ </template>
@@ -9,10 +9,22 @@ import DatePickerVue from "./DatePicker.vue";
9
9
  import TimePickerVue from "./TimePicker.vue";
10
10
  import OptionGroupVue from "./OptionGroup.vue";
11
11
  import FilePickerVue from "./FilePicker.vue";
12
+ import FileVue from "./File.vue";
13
+ import InputXlsxVue from "./InputXlsx.vue";
12
14
  export const createLightPlugin = () => {
13
15
  return (node) => {
14
16
  let type = node.props.type + "";
15
17
  switch (type) {
18
+ case "l-input-xlsx":
19
+ return node.define({
20
+ type: "input",
21
+ component: InputXlsxVue
22
+ });
23
+ case "l-file":
24
+ return node.define({
25
+ type: "input",
26
+ component: FileVue
27
+ });
16
28
  case "l-file-picker":
17
29
  return node.define({
18
30
  type: "input",
@@ -18,7 +18,7 @@ import getModelFields from "./getModelFields.mjs";
18
18
  import getModelColumns from "./getModelColumns.mjs";
19
19
  import sv from "./sv.mjs";
20
20
  import model from "./model.mjs";
21
- const notify = function(message, color = "green") {
21
+ const notify = function(message, color = "positive") {
22
22
  Notify.create({
23
23
  message,
24
24
  color,
@@ -1,16 +1,9 @@
1
1
  {
2
- "SystemBackup": "System backup",
3
- "SystemValue": "System value",
4
- "UserGroup": "User group",
5
- "UserLog": "User log",
6
- "FileManager": "File manager",
7
- "mail-test": "Mail test",
8
- "vx-table-message": "Showing {0} to {1} of {2} entries",
9
- "vx-per-page": "Per page",
10
- "Dashboard": "Dashboard",
11
- "Theme Customizer": "Theme Customizer",
12
- "Customize & Preview in Real Time": "Customize & Preview in Real Time",
13
- "Permission": "Permission",
2
+ "Must contain at least {0} characters": "Must contain at least {0} characters",
3
+ "contains_uppercase": "Must contain at least one uppercase letter",
4
+ "contains_lowercase": "Must contain at least one lowercase letter",
5
+ "contains_numeric": "Must contain at least one number",
6
+ "contains_symbol": "Must contain at least one special character",
14
7
  "storage_usage": "{0} free of {1}",
15
8
  "input_required": "Please input {0}",
16
9
  "input_min": "Please input at least {0} characters"
@@ -1,4 +1,19 @@
1
1
  {
2
+ "Password policy": "密碼規則",
3
+ "Must contain at least {0} characters": "必須包含至少{0}個字元",
4
+ "contains_symbol": "必須包含符號",
5
+ "contains_numeric": "必須包含數字",
6
+ "contains_lowercase": "必須包含小寫字母",
7
+ "contains_uppercase": "必須包含大寫字母",
8
+ "Last access time": "最後存取時間",
9
+ "Default": "預設",
10
+ "Register": "註冊",
11
+ "Reset your 2FA": "重設雙重認證",
12
+ "Result": "結果",
13
+ "Update password": "更新密碼",
14
+ "Two factor auth": "雙重認證",
15
+ "Color": "顏色",
16
+ "Show footer": "顯示頁尾",
2
17
  "Date": "日期",
3
18
  "Online": "在線",
4
19
  "Update": "更新",
@@ -108,8 +123,6 @@
108
123
  "Rename": "重新命名",
109
124
  "Database": "資料庫",
110
125
  "Charset": "字元編碼",
111
- "vx-table-message": "顯示 {0} 至 {1} 共 {2} 項資料",
112
- "vx-per-page": "每頁顯示",
113
126
  "Role": "角色",
114
127
  "Permission": "權限",
115
128
  "Old password": "舊密碼",
@@ -69,7 +69,7 @@ const onUpdate = (value, role, permission) => {
69
69
 
70
70
  <template>
71
71
  <l-page>
72
- <q-table :columns="columns" flat bordered :rows="rows" :pagination="{ rowsPerPage: 0 }">
72
+ <q-table :columns="columns" flat bordered :rows="rows" :pagination="{ rowsPerPage: 0 }" dense>
73
73
  <template #body="props">
74
74
  <q-tr :props="props">
75
75
  <q-td>
@@ -1,5 +1,5 @@
1
1
  <script setup>
2
- import { q } from '../../'
2
+ import { q } from '#imports'
3
3
 
4
4
  let roles = await q("listRole", ["name"]);
5
5
  roles = roles.map((role) => {
@@ -9,10 +9,6 @@ roles = roles.map((role) => {
9
9
  };
10
10
  });
11
11
 
12
- const onSubmit = (e) => {
13
- e.preventDefault();
14
- console.log("submit");
15
- }
16
12
  </script>
17
13
 
18
14
  <template>