@hostlink/nuxt-light 1.65.0 → 1.66.0

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 (28) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +5 -0
  3. package/dist/runtime/components/L/CustomField/Add.vue +6 -1
  4. package/dist/runtime/components/L/Database/create-table-dialog.vue +34 -26
  5. package/dist/runtime/components/L/DocumentViewer.d.vue.ts +50 -0
  6. package/dist/runtime/components/L/DocumentViewer.vue +154 -0
  7. package/dist/runtime/components/L/DocumentViewer.vue.d.ts +50 -0
  8. package/dist/runtime/components/L/DocumentViewerDialog.d.vue.ts +65 -0
  9. package/dist/runtime/components/L/DocumentViewerDialog.vue +67 -0
  10. package/dist/runtime/components/L/DocumentViewerDialog.vue.d.ts +65 -0
  11. package/dist/runtime/components/l-customizer.d.vue.ts +19 -3
  12. package/dist/runtime/components/l-customizer.vue.d.ts +19 -3
  13. package/dist/runtime/components/l-file-manager-labels.d.vue.ts +5 -1
  14. package/dist/runtime/components/l-file-manager-labels.vue.d.ts +5 -1
  15. package/dist/runtime/components/l-file-manager-preview.vue +23 -22
  16. package/dist/runtime/components/l-file-manager.vue +14 -26
  17. package/dist/runtime/components/l-repeater.d.vue.ts +1 -1
  18. package/dist/runtime/components/l-repeater.vue.d.ts +1 -1
  19. package/dist/runtime/composables/showDocumentDialog.d.ts +9 -0
  20. package/dist/runtime/composables/showDocumentDialog.js +15 -0
  21. package/dist/runtime/pages/CustomField/index.vue +5 -5
  22. package/dist/runtime/pages/System/database/table.vue +49 -62
  23. package/dist/runtime/pages/System/setting.vue +2 -2
  24. package/dist/runtime/pages/User/createAccessToken.d.vue.ts +3 -0
  25. package/dist/runtime/pages/User/createAccessToken.vue +63 -0
  26. package/dist/runtime/pages/User/createAccessToken.vue.d.ts +3 -0
  27. package/dist/runtime/pages/User/profile.vue +1 -0
  28. package/package.json +4 -4
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "light",
3
3
  "configKey": "light",
4
- "version": "1.65.0",
4
+ "version": "1.66.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -216,6 +216,11 @@ const routes = [
216
216
  path: "/User/profile",
217
217
  file: "runtime/pages/User/profile.vue"
218
218
  },
219
+ {
220
+ name: "User-createAccessToken",
221
+ path: "/User/createAccessToken",
222
+ file: "runtime/pages/User/createAccessToken.vue"
223
+ },
219
224
  {
220
225
  path: "/User/setting",
221
226
  file: "runtime/pages/User/setting.vue",
@@ -33,12 +33,17 @@ const onOKClick = async (value) => {
33
33
  });
34
34
  onDialogOK();
35
35
  } catch (e) {
36
+ $q.notify({
37
+ icon: "sym_o_warning",
38
+ message: e.message,
39
+ color: "negative"
40
+ });
36
41
  }
37
42
  };
38
43
  </script>
39
44
 
40
45
  <template>
41
- <q-dialog ref="dialogRef" full-width>
46
+ <q-dialog ref="dialogRef">
42
47
  <q-card class="q-dialog-plugin">
43
48
  <q-toolbar>
44
49
  <q-toolbar-title>Add Custom Field</q-toolbar-title>
@@ -20,36 +20,44 @@ const types = ["varchar", "int", "date", "time", "datetime", "timestamp", "text"
20
20
 
21
21
  <template>
22
22
  <q-dialog ref="dialogRef" full-width>
23
- <q-card class="q-dialog-plugin">
24
- <q-toolbar>
25
- <q-toolbar-title>Create Table</q-toolbar-title>
26
- <q-space />
27
- <q-btn dense flat icon="sym_o_close" v-close-popup />
28
- </q-toolbar>
29
- <form-kit type="l-form" :bordered="false" @submit="onOKClick" :value="{
30
- name: '',
31
- fields: [],
32
- }" :action="false" #default="{ value }">
23
+ <Suspense>
24
+ <template #default>
25
+ <q-card class="q-dialog-plugin">
26
+ <q-toolbar>
27
+ <q-toolbar-title>Create Table</q-toolbar-title>
28
+ <q-space />
29
+ <q-btn dense flat icon="sym_o_close" v-close-popup />
30
+ </q-toolbar>
31
+ <form-kit type="l-form" :bordered="false" @submit="onOKClick" :value="{
32
+ name: '',
33
+ fields: [],
34
+ }" :action="false" #default="{ value }">
33
35
 
34
- <form-kit type="l-input" name="name" label="Name" validation="required" />
36
+ <form-kit type="l-input" name="name" label="Name" validation="required" />
35
37
 
36
- <form-kit type="l-repeater" name="fields">
37
- <div class="row q-gutter-sm">
38
- <form-kit type="l-input" name="name" label="Name" validation="required" class="col" />
38
+ <form-kit type="l-repeater" name="fields">
39
+ <div class="row q-gutter-sm">
40
+ <form-kit type="l-input" name="name" label="Name" validation="required" class="col"
41
+ dense />
39
42
 
40
- <form-kit type="l-select" name="type" label="Type" :options="types" validation="required"
41
- class="col" />
42
- <form-kit type="l-input" name="length" label="Length" class="col" number />
43
- <form-kit type="l-input" name="default" label="Default" class="col" />
44
- </div>
43
+ <form-kit type="l-select" name="type" label="Type" :options="types"
44
+ validation="required" class="col" dense />
45
+ <form-kit type="l-input" name="length" label="Length" class="col" number dense />
46
+ <form-kit type="l-input" name="default" label="Default" class="col" dense />
47
+ <form-kit type="l-checkbox" name="nullable" label="Nullable" />
48
+ </div>
49
+ </form-kit>
45
50
 
46
- <div class="row">
47
- <form-kit type="l-checkbox" name="nullable" label="Nullable" />
48
- </div>
49
- </form-kit>
51
+ </form-kit>
50
52
 
51
- </form-kit>
52
-
53
- </q-card>
53
+ </q-card>
54
+ </template>
55
+ <template #fallback>
56
+ <div class="q-pa-lg flex flex-center">
57
+ <q-spinner-dots color="primary" size="40px" />
58
+ <div class="q-ml-sm">Loading...</div>
59
+ </div>
60
+ </template>
61
+ </Suspense>
54
62
  </q-dialog>
55
63
  </template>
@@ -0,0 +1,50 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
2
+ url: {
3
+ type: StringConstructor;
4
+ required: true;
5
+ };
6
+ mimeType: {
7
+ type: StringConstructor;
8
+ default: string;
9
+ };
10
+ width: {
11
+ type: StringConstructor;
12
+ default: string;
13
+ };
14
+ height: {
15
+ type: StringConstructor;
16
+ default: string;
17
+ };
18
+ fit: {
19
+ type: () => "cover" | "contain" | "fill" | "none" | "scale-down";
20
+ default: string;
21
+ };
22
+ }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
23
+ url: {
24
+ type: StringConstructor;
25
+ required: true;
26
+ };
27
+ mimeType: {
28
+ type: StringConstructor;
29
+ default: string;
30
+ };
31
+ width: {
32
+ type: StringConstructor;
33
+ default: string;
34
+ };
35
+ height: {
36
+ type: StringConstructor;
37
+ default: string;
38
+ };
39
+ fit: {
40
+ type: () => "cover" | "contain" | "fill" | "none" | "scale-down";
41
+ default: string;
42
+ };
43
+ }>> & Readonly<{}>, {
44
+ width: string;
45
+ height: string;
46
+ mimeType: string;
47
+ fit: "fill" | "none" | "contain" | "cover" | "scale-down";
48
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
49
+ declare const _default: typeof __VLS_export;
50
+ export default _default;
@@ -0,0 +1,154 @@
1
+ <script setup>
2
+ import { ref, computed, watch } from "vue";
3
+ const props = defineProps({
4
+ url: {
5
+ type: String,
6
+ required: true
7
+ },
8
+ mimeType: {
9
+ type: String,
10
+ default: ""
11
+ },
12
+ // 圖片/視頻的寬高
13
+ width: {
14
+ type: String,
15
+ default: "100%"
16
+ },
17
+ height: {
18
+ type: String,
19
+ default: "500px"
20
+ },
21
+ // 圖片的 fit 模式
22
+ fit: {
23
+ type: String,
24
+ default: "contain"
25
+ }
26
+ });
27
+ const textContent = ref("");
28
+ const isLoading = ref(false);
29
+ const error = ref(null);
30
+ const fileType = computed(() => {
31
+ const mime = props.mimeType.toLowerCase();
32
+ if (mime.startsWith("image/")) return "image";
33
+ if (mime.startsWith("video/")) return "video";
34
+ if (mime.startsWith("audio/")) return "audio";
35
+ if (mime === "application/pdf") return "pdf";
36
+ if (mime.startsWith("text/") || mime === "application/json") return "text";
37
+ if (mime.includes("word") || mime.includes("spreadsheet") || mime.includes("presentation") || mime.includes("excel") || mime.includes("powerpoint") || mime.includes("msword") || mime.includes("officedocument")) {
38
+ return "office";
39
+ }
40
+ return "unknown";
41
+ });
42
+ const officeViewerUrl = computed(() => {
43
+ if (fileType.value === "office") {
44
+ return `https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(props.url)}`;
45
+ }
46
+ return "";
47
+ });
48
+ const loadTextContent = async () => {
49
+ if (fileType.value !== "text") return;
50
+ isLoading.value = true;
51
+ error.value = null;
52
+ try {
53
+ const response = await fetch(props.url);
54
+ if (!response.ok) throw new Error("\u7121\u6CD5\u8F09\u5165\u6587\u4EF6");
55
+ textContent.value = await response.text();
56
+ } catch (e) {
57
+ error.value = e instanceof Error ? e.message : "\u8F09\u5165\u5931\u6557";
58
+ } finally {
59
+ isLoading.value = false;
60
+ }
61
+ };
62
+ watch(() => [props.url, fileType.value], () => {
63
+ if (fileType.value === "text") {
64
+ loadTextContent();
65
+ }
66
+ }, { immediate: true });
67
+ </script>
68
+
69
+ <template>
70
+ <div class="l-document-viewer">
71
+ <!-- 圖片預覽 -->
72
+ <q-img
73
+ v-if="fileType === 'image'"
74
+ :src="url"
75
+ :fit="fit"
76
+ :style="{ width, height }"
77
+ spinner-color="primary"
78
+ >
79
+ <template #error>
80
+ <div class="absolute-full flex flex-center bg-negative text-white">
81
+ 無法載入圖片
82
+ </div>
83
+ </template>
84
+ </q-img>
85
+
86
+ <!-- 視頻預覽 -->
87
+ <q-video
88
+ v-else-if="fileType === 'video'"
89
+ :src="url"
90
+ :style="{ width, height }"
91
+ />
92
+
93
+ <!-- 音頻預覽 -->
94
+ <audio
95
+ v-else-if="fileType === 'audio'"
96
+ :src="url"
97
+ controls
98
+ style="width: 100%"
99
+ >
100
+ 您的瀏覽器不支援音頻播放
101
+ </audio>
102
+
103
+ <!-- PDF 預覽 -->
104
+ <iframe
105
+ v-else-if="fileType === 'pdf'"
106
+ :src="url"
107
+ :style="{ width, height, border: 'none' }"
108
+ title="PDF Viewer"
109
+ />
110
+
111
+ <!-- 文字預覽 (txt, json 等) -->
112
+ <div v-else-if="fileType === 'text'" class="text-viewer" :style="{ height }">
113
+ <q-spinner v-if="isLoading" color="primary" size="3em" />
114
+ <q-banner v-else-if="error" class="bg-negative text-white">
115
+ {{ error }}
116
+ </q-banner>
117
+ <pre v-else class="text-content">{{ textContent }}</pre>
118
+ </div>
119
+
120
+ <!-- Office 文件預覽 -->
121
+ <iframe
122
+ v-else-if="fileType === 'office'"
123
+ :src="officeViewerUrl"
124
+ :style="{ width, height, border: 'none' }"
125
+ title="Office Viewer"
126
+ />
127
+
128
+ <!-- 不支援的類型 -->
129
+ <div v-else class="unsupported flex flex-center" :style="{ height }">
130
+ <div class="text-center">
131
+ <q-icon name="sym_r_description" size="64px" color="grey-5" />
132
+ <div class="text-grey-7 q-mt-md">
133
+ 不支援預覽此文件類型
134
+ </div>
135
+ <div class="text-caption text-grey-5">
136
+ {{ mimeType || "\u672A\u77E5\u985E\u578B" }}
137
+ </div>
138
+ <q-btn
139
+ class="q-mt-md"
140
+ color="primary"
141
+ icon="sym_r_download"
142
+ label="下載文件"
143
+ :href="url"
144
+ target="_blank"
145
+ outline
146
+ />
147
+ </div>
148
+ </div>
149
+ </div>
150
+ </template>
151
+
152
+ <style scoped>
153
+ .l-document-viewer{width:100%}.text-viewer{background:#f5f5f5;border-radius:4px;overflow:auto;padding:16px}.text-content{margin:0;white-space:pre-wrap;word-wrap:break-word;font-family:Consolas,Monaco,monospace;font-size:14px;line-height:1.5}.unsupported{background:#fafafa;border:1px dashed #e0e0e0;border-radius:8px}
154
+ </style>
@@ -0,0 +1,50 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
2
+ url: {
3
+ type: StringConstructor;
4
+ required: true;
5
+ };
6
+ mimeType: {
7
+ type: StringConstructor;
8
+ default: string;
9
+ };
10
+ width: {
11
+ type: StringConstructor;
12
+ default: string;
13
+ };
14
+ height: {
15
+ type: StringConstructor;
16
+ default: string;
17
+ };
18
+ fit: {
19
+ type: () => "cover" | "contain" | "fill" | "none" | "scale-down";
20
+ default: string;
21
+ };
22
+ }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
23
+ url: {
24
+ type: StringConstructor;
25
+ required: true;
26
+ };
27
+ mimeType: {
28
+ type: StringConstructor;
29
+ default: string;
30
+ };
31
+ width: {
32
+ type: StringConstructor;
33
+ default: string;
34
+ };
35
+ height: {
36
+ type: StringConstructor;
37
+ default: string;
38
+ };
39
+ fit: {
40
+ type: () => "cover" | "contain" | "fill" | "none" | "scale-down";
41
+ default: string;
42
+ };
43
+ }>> & Readonly<{}>, {
44
+ width: string;
45
+ height: string;
46
+ mimeType: string;
47
+ fit: "fill" | "none" | "contain" | "cover" | "scale-down";
48
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
49
+ declare const _default: typeof __VLS_export;
50
+ export default _default;
@@ -0,0 +1,65 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
2
+ url: {
3
+ type: StringConstructor;
4
+ required: true;
5
+ };
6
+ mimeType: {
7
+ type: StringConstructor;
8
+ default: string;
9
+ };
10
+ title: {
11
+ type: StringConstructor;
12
+ default: string;
13
+ };
14
+ width: {
15
+ type: StringConstructor;
16
+ default: string;
17
+ };
18
+ height: {
19
+ type: StringConstructor;
20
+ default: string;
21
+ };
22
+ fit: {
23
+ type: () => "cover" | "contain" | "fill" | "none" | "scale-down";
24
+ default: string;
25
+ };
26
+ }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
27
+ hide: (...args: any[]) => void;
28
+ ok: (...args: any[]) => void;
29
+ }, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
30
+ url: {
31
+ type: StringConstructor;
32
+ required: true;
33
+ };
34
+ mimeType: {
35
+ type: StringConstructor;
36
+ default: string;
37
+ };
38
+ title: {
39
+ type: StringConstructor;
40
+ default: string;
41
+ };
42
+ width: {
43
+ type: StringConstructor;
44
+ default: string;
45
+ };
46
+ height: {
47
+ type: StringConstructor;
48
+ default: string;
49
+ };
50
+ fit: {
51
+ type: () => "cover" | "contain" | "fill" | "none" | "scale-down";
52
+ default: string;
53
+ };
54
+ }>> & Readonly<{
55
+ onHide?: ((...args: any[]) => any) | undefined;
56
+ onOk?: ((...args: any[]) => any) | undefined;
57
+ }>, {
58
+ title: string;
59
+ width: string;
60
+ height: string;
61
+ mimeType: string;
62
+ fit: "fill" | "none" | "contain" | "cover" | "scale-down";
63
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
64
+ declare const _default: typeof __VLS_export;
65
+ export default _default;
@@ -0,0 +1,67 @@
1
+ <script setup>
2
+ import { useDialogPluginComponent } from "quasar";
3
+ const props = defineProps({
4
+ url: {
5
+ type: String,
6
+ required: true
7
+ },
8
+ mimeType: {
9
+ type: String,
10
+ default: ""
11
+ },
12
+ title: {
13
+ type: String,
14
+ default: ""
15
+ },
16
+ // 圖片/視頻的寬高
17
+ width: {
18
+ type: String,
19
+ default: "100%"
20
+ },
21
+ height: {
22
+ type: String,
23
+ default: "70vh"
24
+ },
25
+ // 圖片的 fit 模式
26
+ fit: {
27
+ type: String,
28
+ default: "contain"
29
+ }
30
+ });
31
+ defineEmits([
32
+ // REQUIRED; need to specify some events that your
33
+ // component will emit through useDialogPluginComponent()
34
+ ...useDialogPluginComponent.emits
35
+ ]);
36
+ const { dialogRef, onDialogHide, onDialogCancel } = useDialogPluginComponent();
37
+ </script>
38
+
39
+ <template>
40
+ <q-dialog ref="dialogRef" @hide="onDialogHide" full-width >
41
+ <q-card class="q-dialog-plugin" :bordered="false" square>
42
+ <q-toolbar class="bg-primary text-white">
43
+ <q-toolbar-title>{{ title || "\u6587\u4EF6\u9810\u89BD" }}</q-toolbar-title>
44
+ <q-space />
45
+ <q-btn
46
+ :href="url"
47
+ target="_blank"
48
+ dense
49
+ flat
50
+ icon="sym_o_download"
51
+ title="下載"
52
+ />
53
+ <q-btn dense flat icon="sym_o_close" v-close-popup />
54
+ </q-toolbar>
55
+
56
+ <q-card-section class="q-pa-md">
57
+ <LDocumentViewer
58
+ :url="url"
59
+ :mime-type="mimeType"
60
+ :width="width"
61
+ :height="height"
62
+ :fit="fit"
63
+ />
64
+ </q-card-section>
65
+ </q-card>
66
+ </q-dialog>
67
+ </template>
@@ -0,0 +1,65 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
2
+ url: {
3
+ type: StringConstructor;
4
+ required: true;
5
+ };
6
+ mimeType: {
7
+ type: StringConstructor;
8
+ default: string;
9
+ };
10
+ title: {
11
+ type: StringConstructor;
12
+ default: string;
13
+ };
14
+ width: {
15
+ type: StringConstructor;
16
+ default: string;
17
+ };
18
+ height: {
19
+ type: StringConstructor;
20
+ default: string;
21
+ };
22
+ fit: {
23
+ type: () => "cover" | "contain" | "fill" | "none" | "scale-down";
24
+ default: string;
25
+ };
26
+ }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
27
+ hide: (...args: any[]) => void;
28
+ ok: (...args: any[]) => void;
29
+ }, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
30
+ url: {
31
+ type: StringConstructor;
32
+ required: true;
33
+ };
34
+ mimeType: {
35
+ type: StringConstructor;
36
+ default: string;
37
+ };
38
+ title: {
39
+ type: StringConstructor;
40
+ default: string;
41
+ };
42
+ width: {
43
+ type: StringConstructor;
44
+ default: string;
45
+ };
46
+ height: {
47
+ type: StringConstructor;
48
+ default: string;
49
+ };
50
+ fit: {
51
+ type: () => "cover" | "contain" | "fill" | "none" | "scale-down";
52
+ default: string;
53
+ };
54
+ }>> & Readonly<{
55
+ onHide?: ((...args: any[]) => any) | undefined;
56
+ onOk?: ((...args: any[]) => any) | undefined;
57
+ }>, {
58
+ title: string;
59
+ width: string;
60
+ height: string;
61
+ mimeType: string;
62
+ fit: "fill" | "none" | "contain" | "cover" | "scale-down";
63
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
64
+ declare const _default: typeof __VLS_export;
65
+ export default _default;
@@ -32,7 +32,15 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
32
32
  menuWidth: {
33
33
  type: import("vue").PropType<number>;
34
34
  };
35
- }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
35
+ }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
36
+ "update:theme": (...args: any[]) => void;
37
+ "update:menuOverlayHeader": (...args: any[]) => void;
38
+ "update:dense": (...args: any[]) => void;
39
+ "update:color": (...args: any[]) => void;
40
+ "update:miniState": (...args: any[]) => void;
41
+ "update:footer": (...args: any[]) => void;
42
+ "update:menuWidth": (value: number) => void;
43
+ }, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
36
44
  time: {
37
45
  type: StringConstructor;
38
46
  default: string;
@@ -64,12 +72,20 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
64
72
  menuWidth: {
65
73
  type: import("vue").PropType<number>;
66
74
  };
67
- }>> & Readonly<{}>, {
75
+ }>> & Readonly<{
76
+ "onUpdate:theme"?: ((...args: any[]) => any) | undefined;
77
+ "onUpdate:menuOverlayHeader"?: ((...args: any[]) => any) | undefined;
78
+ "onUpdate:dense"?: ((...args: any[]) => any) | undefined;
79
+ "onUpdate:color"?: ((...args: any[]) => any) | undefined;
80
+ "onUpdate:miniState"?: ((...args: any[]) => any) | undefined;
81
+ "onUpdate:footer"?: ((...args: any[]) => any) | undefined;
82
+ "onUpdate:menuWidth"?: ((value: number) => any) | undefined;
83
+ }>, {
68
84
  color: string;
69
85
  theme: string;
70
86
  footer: boolean;
71
87
  time: string;
72
88
  dense: boolean;
73
- miniState: boolean;
74
89
  menuOverlayHeader: boolean;
90
+ miniState: boolean;
75
91
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -32,7 +32,15 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
32
32
  menuWidth: {
33
33
  type: import("vue").PropType<number>;
34
34
  };
35
- }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
35
+ }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
36
+ "update:theme": (...args: any[]) => void;
37
+ "update:menuOverlayHeader": (...args: any[]) => void;
38
+ "update:dense": (...args: any[]) => void;
39
+ "update:color": (...args: any[]) => void;
40
+ "update:miniState": (...args: any[]) => void;
41
+ "update:footer": (...args: any[]) => void;
42
+ "update:menuWidth": (value: number) => void;
43
+ }, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
36
44
  time: {
37
45
  type: StringConstructor;
38
46
  default: string;
@@ -64,12 +72,20 @@ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractP
64
72
  menuWidth: {
65
73
  type: import("vue").PropType<number>;
66
74
  };
67
- }>> & Readonly<{}>, {
75
+ }>> & Readonly<{
76
+ "onUpdate:theme"?: ((...args: any[]) => any) | undefined;
77
+ "onUpdate:menuOverlayHeader"?: ((...args: any[]) => any) | undefined;
78
+ "onUpdate:dense"?: ((...args: any[]) => any) | undefined;
79
+ "onUpdate:color"?: ((...args: any[]) => any) | undefined;
80
+ "onUpdate:miniState"?: ((...args: any[]) => any) | undefined;
81
+ "onUpdate:footer"?: ((...args: any[]) => any) | undefined;
82
+ "onUpdate:menuWidth"?: ((value: number) => any) | undefined;
83
+ }>, {
68
84
  color: string;
69
85
  theme: string;
70
86
  footer: boolean;
71
87
  time: string;
72
88
  dense: boolean;
73
- miniState: boolean;
74
89
  menuOverlayHeader: boolean;
90
+ miniState: boolean;
75
91
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -1,6 +1,10 @@
1
1
  declare const _default: typeof __VLS_export;
2
2
  export default _default;
3
- declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
3
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
4
+ "update:modelValue": (...args: any[]) => void;
5
+ }, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
6
+ "onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
7
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
4
8
  type __VLS_ModelProps = {
5
9
  modelValue?: string | undefined;
6
10
  };
@@ -1,6 +1,10 @@
1
1
  declare const _default: typeof __VLS_export;
2
2
  export default _default;
3
- declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
3
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
4
+ "update:modelValue": (...args: any[]) => void;
5
+ }, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
6
+ "onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
7
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
4
8
  type __VLS_ModelProps = {
5
9
  modelValue?: string | undefined;
6
10
  };
@@ -1,8 +1,7 @@
1
1
  <script setup>
2
2
  import { format, useQuasar, date } from "quasar";
3
3
  import { computed } from "vue";
4
- import { getApiClient, query } from "@hostlink/light";
5
- const api = getApiClient();
4
+ import { query } from "@hostlink/light";
6
5
  const { humanStorageSize } = format;
7
6
  const quasar = useQuasar();
8
7
  const props = defineProps({
@@ -60,27 +59,29 @@ const isVideo = computed(() => {
60
59
  </script>
61
60
 
62
61
  <template>
63
- <q-img :src="file.publicUrl" v-if="isImage"></q-img>
64
- <q-video :src="file.publicUrl" v-else-if="isVideo"></q-video>
62
+ <div>
63
+ <q-img :src="file.publicUrl" v-if="isImage"></q-img>
64
+ <q-video :src="file.publicUrl" v-else-if="isVideo"></q-video>
65
65
 
66
- <q-list dense>
67
- <l-item label="Name">{{ file.name }}</l-item>
68
- <l-item label="Size">{{ size }} ({{ file.size }})</l-item>
69
- <l-item label="Location">{{ file.path }}</l-item>
70
- <l-item label="Last modified">{{ lastModifiedHuman }}</l-item>
71
- <l-item label="MIME type">{{ file.mimeType }}</l-item>
66
+ <q-list dense>
67
+ <l-item label="Name">{{ file.name }}</l-item>
68
+ <l-item label="Size">{{ size }} ({{ file.size }})</l-item>
69
+ <l-item label="Location">{{ file.path }}</l-item>
70
+ <l-item label="Last modified">{{ lastModifiedHuman }}</l-item>
71
+ <l-item label="MIME type">{{ file.mimeType }}</l-item>
72
72
 
73
- <q-item>
74
- <q-item-section side>
75
- <q-item-label>URL</q-item-label>
76
- </q-item-section>
77
- <q-item-section style="align-items: flex-end;">
78
- <q-item-label lines="1">{{ file.publicUrl }}</q-item-label>
79
- </q-item-section>
73
+ <q-item>
74
+ <q-item-section side>
75
+ <q-item-label>URL</q-item-label>
76
+ </q-item-section>
77
+ <q-item-section style="align-items: flex-end;">
78
+ <q-item-label lines="1">{{ file.publicUrl }}</q-item-label>
79
+ </q-item-section>
80
80
 
81
- <q-item-section side>
82
- <q-btn size="md" flat dense round icon="sym_o_content_copy" @click="copyToClipboard(file.publicUrl)"></q-btn>
83
- </q-item-section>
84
- </q-item>
85
- </q-list>
81
+ <q-item-section side>
82
+ <q-btn size="md" flat dense round icon="sym_o_content_copy" @click="copyToClipboard(file.publicUrl)"></q-btn>
83
+ </q-item-section>
84
+ </q-item>
85
+ </q-list>
86
+ </div>
86
87
  </template>
@@ -2,7 +2,7 @@
2
2
  import { useI18n } from "vue-i18n";
3
3
  import { ref, watch, computed } from "vue";
4
4
  import { useQuasar, format, date } from "quasar";
5
- import { q, m, useLight, showUploadFilesDialog } from "#imports";
5
+ import { q, m, useLight, showUploadFilesDialog, showDocumentDialog } from "#imports";
6
6
  import { getGrantedRights } from "@hostlink/light";
7
7
  import { fs } from "@hostlink/light";
8
8
  const { humanStorageSize } = format;
@@ -347,18 +347,10 @@ const getFilePublicUrl = async (location) => {
347
347
  return file.publicUrl;
348
348
  };
349
349
  const onPreview = async (node) => {
350
- showPreviewImgDialog.value = true;
351
- previewImg.value = await getFilePublicUrl(node.location);
352
- };
353
- const onPreviewPDF = async (node) => {
354
- const height = window.innerHeight - 200;
355
- $q.dialog({
356
- autoClose: true,
357
- fullWidth: true,
358
- fullHeight: true,
359
- title: "Preview PDF",
360
- message: "<iframe src='" + await getFilePublicUrl(node.location) + "' width='100%' height='" + height + "px'></iframe>",
361
- html: true
350
+ showDocumentDialog({
351
+ url: await getFilePublicUrl(node.location),
352
+ mimeType: node.mimeType,
353
+ title: node.name
362
354
  });
363
355
  };
364
356
  const onClickInfo = async (row) => {
@@ -376,7 +368,7 @@ const canPreview = (file) => {
376
368
  if (!file.mimeType) {
377
369
  return false;
378
370
  }
379
- return file.mimeType.startsWith("image/");
371
+ return file.mimeType.startsWith("image/") || file.mimeType === "text/plain" || file.mimeType === "application/pdf" || file.mimeType.startsWith("video/") || file.mimeType.startsWith("audio/");
380
372
  };
381
373
  const onCheckTotalSize = async (folder) => {
382
374
  const d = $q.dialog({
@@ -562,8 +554,9 @@ const getFileIcon = (file) => {
562
554
  <q-toolbar>
563
555
  <l-file-manager-breadcrumbs v-model="selectedLocation" />
564
556
  <q-space></q-space>
565
- <q-btn flat round icon="sym_o_drive_file_move" v-if="selected.length > 0">
566
- <l-file-manager-move @selected="moveToFolder($event)" :current_location="selectedLocation"
557
+ <q-btn flat round icon="sym_o_drive_file_move" v-if="selected.length > 0 && selectedLocation">
558
+ <l-file-manager-move @selected="moveToFolder($event)"
559
+ :current_location="selectedLocation"
567
560
  :allow_cross_fs="selected.every(item => item.__typename == 'File')" />
568
561
  <q-tooltip>
569
562
  {{ $t('Move to') }}
@@ -624,7 +617,10 @@ const getFileIcon = (file) => {
624
617
  </q-item-section>
625
618
  </q-item>
626
619
 
627
- <q-img v-if="canPreview(props.row)" :src="props.row.url"></q-img>
620
+ <l-document-viewer :url="props.row.publicUrl"
621
+ height="100px"
622
+
623
+ :mime-type="props.row.mimeType" :title="props.row.name" v-if="props.row.mimeType.startsWith('image/')" />
628
624
 
629
625
  </q-card>
630
626
  </div>
@@ -636,7 +632,7 @@ const getFileIcon = (file) => {
636
632
  <q-table flat :columns="columns" :rows="items" @row-dblclick="onDblclickRow" @row-click="onClickRow"
637
633
  :pagination="pagination" row-key="location" selection="multiple" v-model:selected="selected" dense
638
634
  :loading="loading" :loading-label="$t('Loading...')" :no-data-label="$t('No data available')"
639
- separator="horizontal" :rows-per-page-options="[0]" color="primary">
635
+ separator="horizontal" :rows-per-page-options="[0]" color="primary" square>
640
636
  <template #body-cell-icon="props">
641
637
  <q-td auto-width>
642
638
  <q-icon name="sym_o_folder" v-if="props.row.__typename == 'Folder'" size="sm" />
@@ -695,14 +691,6 @@ const getFileIcon = (file) => {
695
691
  <q-item-section>{{ $t('Preview') }}</q-item-section>
696
692
  </q-item>
697
693
 
698
- <q-item clickable v-close-popup v-if="props.row.mimeType == 'application/pdf'"
699
- @click="onPreviewPDF(props.row)">
700
- <q-item-section avatar>
701
- <q-icon name="sym_o_preview"></q-icon>
702
- </q-item-section>
703
- <q-item-section>{{ $t('Preview') }}</q-item-section>
704
- </q-item>
705
-
706
694
  <q-item clickable v-close-popup @click="onClickInfo(props.row)" class="lt-lg">
707
695
  <q-item-section avatar>
708
696
  <q-icon name="sym_o_info"></q-icon>
@@ -80,8 +80,8 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
80
80
  bordered: boolean;
81
81
  separator: boolean;
82
82
  modelValue: unknown[];
83
- max: number;
84
83
  min: number;
84
+ max: number;
85
85
  addLabel: string;
86
86
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
87
87
  type __VLS_Slots = {
@@ -80,8 +80,8 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
80
80
  bordered: boolean;
81
81
  separator: boolean;
82
82
  modelValue: unknown[];
83
- max: number;
84
83
  min: number;
84
+ max: number;
85
85
  addLabel: string;
86
86
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
87
87
  type __VLS_Slots = {
@@ -0,0 +1,9 @@
1
+ export interface DocumentDialogOptions {
2
+ url: string;
3
+ mimeType?: string;
4
+ title?: string;
5
+ width?: string;
6
+ height?: string;
7
+ fit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down';
8
+ }
9
+ export declare function showDocumentDialog(options: DocumentDialogOptions): import("quasar").DialogChainObject;
@@ -0,0 +1,15 @@
1
+ import { Dialog } from "quasar";
2
+ import { LDocumentViewerDialog } from "#components";
3
+ export function showDocumentDialog(options) {
4
+ return Dialog.create({
5
+ component: LDocumentViewerDialog,
6
+ componentProps: {
7
+ url: options.url,
8
+ mimeType: options.mimeType ?? "",
9
+ title: options.title ?? "",
10
+ width: options.width ?? "100%",
11
+ height: options.height ?? "70vh",
12
+ fit: options.fit ?? "contain"
13
+ }
14
+ });
15
+ }
@@ -1,6 +1,6 @@
1
1
  <script setup>
2
2
  import { model, q } from "#imports";
3
- import { resolveComponent } from "vue";
3
+ import { LCustomFieldAdd } from "#components";
4
4
  const onRequest = async (request) => {
5
5
  request.loadObjects("CustomField");
6
6
  };
@@ -15,7 +15,6 @@ const columns = model("CustomField").columns({
15
15
  type: true,
16
16
  validation: true
17
17
  });
18
- const addComponent = await resolveComponent("l-custom-field-add");
19
18
  </script>
20
19
 
21
20
  <template>
@@ -25,9 +24,10 @@ const addComponent = await resolveComponent("l-custom-field-add");
25
24
  on Customization settings page.
26
25
  </p>
27
26
 
28
- <l-table row-key="custom_field_id" @request-data="onRequest" :columns="columns" :add-component="addComponent" :add-component-props="{
29
- models: app.customFieldModels
30
- }" :actions="['edit', 'delete']">
27
+ <l-table row-key="custom_field_id" @request-data="onRequest" :columns="columns" :add-component="LCustomFieldAdd"
28
+ :add-component-props="{
29
+ models: app.customFieldModels
30
+ }" :actions="['edit', 'delete']">
31
31
  </l-table>
32
32
 
33
33
  </l-page>
@@ -1,6 +1,9 @@
1
1
  <script setup>
2
- import { q, m, useLight, useAsyncData } from "#imports";
3
- import { resolveComponent, ref, reactive, computed } from "vue";
2
+ import { q, m, useLight, useAsyncData, useQuasar } from "#imports";
3
+ import { ref, reactive, computed } from "vue";
4
+ import { LDatabaseCreateTableDialog, LDialogDatabaseFieldAdd } from "#components";
5
+ import { Dialog } from "quasar";
6
+ const $q = useQuasar();
4
7
  const light = useLight();
5
8
  const { data, refresh } = await useAsyncData("database", async () => {
6
9
  return await q({
@@ -14,18 +17,16 @@ const { data, refresh } = await useAsyncData("database", async () => {
14
17
  }
15
18
  }).then((res) => res.system.database);
16
19
  });
17
- const SYSTEM_TABLES = ["Config", "EventLog", "MailLog", "Permission", "Role", "SystemValue", "Translate", "User", "UserLog", "UserRole", "MyFavorite", "CustomField"];
20
+ const SYSTEM_TABLES = ["Config", "EventLog", "MailLog", "Permission", "Role", "SystemValue", "Translate", "User", "UserLog", "UserRole", "MyFavorite", "CustomField", "Revision"];
18
21
  const custom_tables = computed(() => {
19
22
  return (data.value?.tableStatus ?? []).filter((table) => !SYSTEM_TABLES.includes(table.Name));
20
23
  });
21
24
  const systables = computed(() => {
22
25
  return (data.value?.tableStatus ?? []).filter((table) => SYSTEM_TABLES.includes(table.Name));
23
26
  });
24
- const field_add = resolveComponent("l-dialog-database-field-add");
25
- const table_add = resolveComponent("l-database-create-table-dialog");
26
27
  const add = async (table) => {
27
- light.dialog({
28
- component: field_add
28
+ $q.dialog({
29
+ component: LDialogDatabaseFieldAdd
29
30
  }).onOk(async (data2) => {
30
31
  try {
31
32
  await m("lightDatabaseAddField", {
@@ -83,8 +84,8 @@ const removeField = async (table) => {
83
84
  });
84
85
  };
85
86
  const createTable = () => {
86
- light.dialog({
87
- component: table_add
87
+ Dialog.create({
88
+ component: LDatabaseCreateTableDialog
88
89
  }).onOk(async (data2) => {
89
90
  try {
90
91
  await m("lightDatabaseCreateTable", {
@@ -175,62 +176,48 @@ const truncatTable = async () => {
175
176
  </l-list>
176
177
  </l-card>
177
178
 
178
- <q-card>
179
- <q-list separator bordered>
180
- <q-expansion-item label="Table Status" dense>
181
- <div s>
182
-
183
- <q-table :rows="custom_tables" hide-pagination flat bordered separator="cell" dense
184
- :rows-per-page-options="[0]" v-model:selected="selectedTable" selection="multiple"
185
- row-key="Name">
186
-
187
- <template #top-left>
188
- <q-btn icon="sym_o_add" @click="createTable()" round flat size="sm">
189
- <q-tooltip>Create table</q-tooltip>
190
- </q-btn>
191
- <q-btn icon="sym_o_delete" @click="removeTable()" round flat size="sm"
192
- :disable="selectedTable.length == 0">
193
- <q-tooltip>Remove table</q-tooltip>
194
- </q-btn>
195
-
196
- <q-btn icon="sym_o_scan_delete" @click="truncatTable" round flat size="sm"
197
- :disable="selectedTable.length == 0">
198
- <q-tooltip>Truncate table</q-tooltip>
199
- </q-btn>
200
-
201
- </template>
202
- </q-table>
203
-
204
- <q-table :rows="systables" hide-pagination flat bordered separator="cell" dense
205
- :rows-per-page-options="[0]" row-key="Name" title="System tables">
179
+ <l-card>
180
+ <q-expansion-item label="Custom Tables" dense header-class="text-weight-medium">
181
+ <q-table :rows="custom_tables" hide-pagination flat separator="cell" dense :rows-per-page-options="[0]"
182
+ v-model:selected="selectedTable" selection="multiple" row-key="Name" class="q-ma-sm">
183
+ <template #top>
184
+ <div class="row items-center q-gutter-sm">
185
+ <q-btn icon="sym_o_add" @click="createTable" flat size="sm" label="Add Table"
186
+ color="primary" />
187
+ <q-btn icon="sym_o_delete" @click="removeTable" :disable="selectedTable.length == 0"
188
+ label="Remove" flat size="sm" color="negative" />
189
+ <q-btn icon="sym_o_scan_delete" @click="truncatTable" :disable="selectedTable.length == 0"
190
+ label="Truncate" flat size="sm" color="warning" />
191
+ </div>
192
+ </template>
193
+ </q-table>
194
+ </q-expansion-item>
206
195
 
207
- </q-table>
208
- </div>
209
- </q-expansion-item>
210
- </q-list>
211
- </q-card>
196
+ <q-separator />
212
197
 
213
- <q-card>
214
- <q-list separator bordered>
215
- <q-expansion-item :label="table.name" v-for="table in data.table" dense>
216
- <div class="q-ma-sm">
217
- <q-table row-key="name" :rows="table.columns" :rows-per-page-options="[0]" hide-pagination flat
218
- bordered selection="multiple" v-model:selected="selected[table.name]">
219
- <template #top-left>
220
- <q-btn icon="sym_o_add" @click="add(table.name)" round flat size="sm">
221
- <q-tooltip>Add field</q-tooltip>
222
- </q-btn>
223
- <q-btn icon="sym_o_delete" @click="removeField(table.name)" round flat size="sm"
224
- :disable="selected[table.name].length == 0">
225
- <q-tooltip>Remove field</q-tooltip>
226
- </q-btn>
227
- </template>
228
- </q-table>
229
- </div>
198
+ <q-expansion-item label="System Tables" dense header-class="text-weight-medium">
199
+ <q-table :rows="systables" hide-pagination flat separator="cell" dense :rows-per-page-options="[0]"
200
+ row-key="Name" class="q-ma-sm" />
201
+ </q-expansion-item>
202
+ </l-card>
230
203
 
231
- </q-expansion-item>
232
- </q-list>
233
- </q-card>
204
+ <l-card title="Tables">
205
+ <q-expansion-item v-for="table in data.table" :key="table.name" :label="table.name" dense
206
+ header-class="text-weight-medium">
207
+ <q-table row-key="name" :rows="table.columns" :rows-per-page-options="[0]" hide-pagination flat
208
+ separator="cell" dense selection="multiple" v-model:selected="selected[table.name]" class="q-ma-sm">
209
+ <template #top>
210
+ <div class="row items-center q-gutter-sm">
211
+ <q-btn icon="sym_o_add" @click="add(table.name)" flat size="sm" label="Add Field"
212
+ color="primary" />
213
+ <q-btn icon="sym_o_delete" @click="removeField(table.name)"
214
+ :disable="selected[table.name]?.length == 0" label="Remove Field" flat size="sm"
215
+ color="negative" />
216
+ </div>
217
+ </template>
218
+ </q-table>
219
+ </q-expansion-item>
220
+ </l-card>
234
221
 
235
222
  </l-page>
236
223
  </template>
@@ -47,7 +47,7 @@ const onSubmit = async (d) => {
47
47
  <q-tabs v-model="tab" vertical>
48
48
  <q-tab name="general" icon="sym_o_info" :label="$t('General')" />
49
49
  <q-tab name="security" icon="sym_o_security" :label="$t('Security')" />
50
- <q-tab name="Modules" icon="sym_o_apps" :label="$t('Modules')" />
50
+ <q-tab name="modules" icon="sym_o_apps" :label="$t('Modules')" />
51
51
  <q-tab name="mail" icon="sym_o_email" :label="$t('Mail')" />
52
52
  <q-tab name="forget-password" icon="sym_o_lock" :label="$t('Forget password')" />
53
53
  <q-tab name="authentication" icon="sym_o_passkey" :label="$t('Authentication')" />
@@ -58,7 +58,7 @@ const onSubmit = async (d) => {
58
58
  <l-system-setting-general v-if="tab == 'general'" v-bind="obj" @submit="onSubmit" />
59
59
  <l-system-setting-mail v-if="tab == 'mail'" v-bind="obj" @submit="onSubmit" />
60
60
  <l-system-setting-security v-if="tab == 'security'" v-bind="obj" @submit="onSubmit" />
61
- <l-system-setting-modules v-if="tab == 'Modules'" v-bind="obj" @submit="onSubmit" />
61
+ <l-system-setting-modules v-if="tab == 'modules'" v-bind="obj" @submit="onSubmit" />
62
62
  <l-system-setting-developer v-if="tab == 'developer'" v-bind="obj" @submit="onSubmit" />
63
63
  <l-system-setting-forget-password v-if="tab == 'forget-password'" v-bind="obj" @submit="onSubmit" />
64
64
  <l-system-setting-authentication v-if="tab == 'authentication'" v-bind="obj" @submit="onSubmit" />
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,63 @@
1
+ <script setup>
2
+ import { ref } from "vue";
3
+ import { useLight } from "#imports";
4
+ import m from "../../composables/m";
5
+ const light = useLight();
6
+ const token = ref(null);
7
+ const expiresOptions = [
8
+ { label: "7 \u65E5", value: 7 * 24 * 60 * 60 },
9
+ { label: "30 \u65E5", value: 30 * 24 * 60 * 60 },
10
+ { label: "3 \u500B\u6708", value: 90 * 24 * 60 * 60 }
11
+ ];
12
+ const onSubmit = async (data) => {
13
+ token.value = null;
14
+ try {
15
+ const result = await m("createAccessToken", {
16
+ name: data.name,
17
+ expired_time: data.expired_time
18
+ });
19
+ token.value = result;
20
+ } catch (e) {
21
+ light.notify({ type: "negative", message: "\u5EFA\u7ACB\u5931\u6557" });
22
+ }
23
+ };
24
+ const copyToken = () => {
25
+ if (!token.value) return;
26
+ navigator.clipboard.writeText(token.value);
27
+ light.notify({ type: "positive", message: "\u5DF2\u8907\u88FD\u5230\u526A\u8CBC\u7C3F" });
28
+ };
29
+ const reset = () => {
30
+ token.value = null;
31
+ };
32
+ </script>
33
+
34
+ <template>
35
+ <l-page title="Create Access Token" :back-btn="true">
36
+ <div style="max-width: 560px;">
37
+ <form-kit type="l-form" :disabled="!!token" @submit="onSubmit">
38
+ <form-kit type="l-input" name="name" label="Token Name" validation="required" />
39
+ <form-kit type="l-select" name="expired_time" label="Expiration" :options="expiresOptions"
40
+ validation="required" />
41
+ </form-kit>
42
+ </div>
43
+
44
+ <!-- Token result -->
45
+ <q-card v-if="token" flat bordered class="q-pa-md q-mt-md" style="max-width: 560px;">
46
+ <q-card-section>
47
+ <div class="text-subtitle1 q-mb-sm text-positive">Token Created</div>
48
+ <div class="text-caption text-grey q-mb-sm">Please copy and save this token immediately. It will only be
49
+ shown once.</div>
50
+ <q-input :model-value="token" v-bind="light.styles.input" readonly type="textarea" autogrow>
51
+ <template #append>
52
+ <q-btn flat round icon="content_copy" @click="copyToken">
53
+ <q-tooltip>複製</q-tooltip>
54
+ </q-btn>
55
+ </template>
56
+ </q-input>
57
+ </q-card-section>
58
+ <q-card-actions>
59
+ <q-btn flat label="建立另一個" @click="reset" />
60
+ </q-card-actions>
61
+ </q-card>
62
+ </l-page>
63
+ </template>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -53,6 +53,7 @@ eventLogCols.forEach((col) => {
53
53
  <template #header>
54
54
  <l-btn icon="sym_o_password" to="setting/password" label="Update password" />
55
55
  <l-btn icon="sym_o_key" to="setting/two-factor-auth" label="Two factor auth" />
56
+ <l-btn icon="sym_o_token" to="createAccessToken" label="Create access token" />
56
57
  </template>
57
58
 
58
59
  <l-row>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hostlink/nuxt-light",
3
- "version": "1.65.0",
3
+ "version": "1.66.0",
4
4
  "description": "HostLink Nuxt Light Framework",
5
5
  "repository": {
6
6
  "type": "git",
@@ -51,14 +51,14 @@
51
51
  "devDependencies": {
52
52
  "@nuxt/devtools": "latest",
53
53
  "@nuxt/eslint-config": "^1.10.0",
54
- "@nuxt/kit": "^4.2.2",
55
- "@nuxt/schema": "^4.2.2",
54
+ "@nuxt/kit": "^4.3.0",
55
+ "@nuxt/schema": "^4.3.0",
56
56
  "@nuxt/test-utils": "^3.17.2",
57
57
  "@types/google.accounts": "^0.0.18",
58
58
  "@types/node": "^24.10.1",
59
59
  "changelogen": "^0.6.2",
60
60
  "eslint": "^9.39.1",
61
- "nuxt": "^4.2.2",
61
+ "nuxt": "^4.3.0",
62
62
  "typescript": "^5.9.2",
63
63
  "vue-tsc": "^3.1.5"
64
64
  }