@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.
- package/dist/module.json +1 -1
- package/dist/module.mjs +5 -0
- package/dist/runtime/components/L/CustomField/Add.vue +6 -1
- package/dist/runtime/components/L/Database/create-table-dialog.vue +34 -26
- package/dist/runtime/components/L/DocumentViewer.d.vue.ts +50 -0
- package/dist/runtime/components/L/DocumentViewer.vue +154 -0
- package/dist/runtime/components/L/DocumentViewer.vue.d.ts +50 -0
- package/dist/runtime/components/L/DocumentViewerDialog.d.vue.ts +65 -0
- package/dist/runtime/components/L/DocumentViewerDialog.vue +67 -0
- package/dist/runtime/components/L/DocumentViewerDialog.vue.d.ts +65 -0
- package/dist/runtime/components/l-customizer.d.vue.ts +19 -3
- package/dist/runtime/components/l-customizer.vue.d.ts +19 -3
- package/dist/runtime/components/l-file-manager-labels.d.vue.ts +5 -1
- package/dist/runtime/components/l-file-manager-labels.vue.d.ts +5 -1
- package/dist/runtime/components/l-file-manager-preview.vue +23 -22
- package/dist/runtime/components/l-file-manager.vue +14 -26
- package/dist/runtime/components/l-repeater.d.vue.ts +1 -1
- package/dist/runtime/components/l-repeater.vue.d.ts +1 -1
- package/dist/runtime/composables/showDocumentDialog.d.ts +9 -0
- package/dist/runtime/composables/showDocumentDialog.js +15 -0
- package/dist/runtime/pages/CustomField/index.vue +5 -5
- package/dist/runtime/pages/System/database/table.vue +49 -62
- package/dist/runtime/pages/System/setting.vue +2 -2
- package/dist/runtime/pages/User/createAccessToken.d.vue.ts +3 -0
- package/dist/runtime/pages/User/createAccessToken.vue +63 -0
- package/dist/runtime/pages/User/createAccessToken.vue.d.ts +3 -0
- package/dist/runtime/pages/User/profile.vue +1 -0
- package/package.json +4 -4
package/dist/module.json
CHANGED
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"
|
|
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
|
-
<
|
|
24
|
-
<
|
|
25
|
-
<q-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
36
|
+
<form-kit type="l-input" name="name" label="Name" validation="required" />
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
47
|
-
<form-kit type="l-checkbox" name="nullable" label="Nullable" />
|
|
48
|
-
</div>
|
|
49
|
-
</form-kit>
|
|
51
|
+
</form-kit>
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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, {
|
|
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, {
|
|
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, {
|
|
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, {
|
|
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 {
|
|
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
|
-
<
|
|
64
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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)"
|
|
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
|
-
<
|
|
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 {
|
|
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="
|
|
29
|
-
|
|
30
|
-
|
|
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 {
|
|
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
|
-
|
|
28
|
-
component:
|
|
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
|
-
|
|
87
|
-
component:
|
|
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
|
-
<
|
|
179
|
-
<q-
|
|
180
|
-
<q-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
<
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
208
|
-
</div>
|
|
209
|
-
</q-expansion-item>
|
|
210
|
-
</q-list>
|
|
211
|
-
</q-card>
|
|
196
|
+
<q-separator />
|
|
212
197
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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="
|
|
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 == '
|
|
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.
|
|
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.
|
|
55
|
-
"@nuxt/schema": "^4.
|
|
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.
|
|
61
|
+
"nuxt": "^4.3.0",
|
|
62
62
|
"typescript": "^5.9.2",
|
|
63
63
|
"vue-tsc": "^3.1.5"
|
|
64
64
|
}
|