@hostlink/nuxt-light 1.64.3 → 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-form.vue +16 -10
- 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/Translate/index.vue +178 -62
- 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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { ref, reactive } from "vue";
|
|
2
|
+
import { ref, reactive, computed } from "vue";
|
|
3
3
|
import { m, q, useAsyncData } from "#imports";
|
|
4
4
|
import { useQuasar } from "quasar";
|
|
5
5
|
import { useI18n } from "vue-i18n";
|
|
@@ -7,29 +7,74 @@ const $q = useQuasar();
|
|
|
7
7
|
const { t } = useI18n();
|
|
8
8
|
const { app } = await q({ app: { languages: true } });
|
|
9
9
|
const splitterModel = ref(62);
|
|
10
|
+
const filter = ref("");
|
|
10
11
|
const { data: all, refresh } = await useAsyncData("translate", () => {
|
|
11
12
|
return q({ allTranslate: true }).then((res) => res.allTranslate);
|
|
12
13
|
});
|
|
14
|
+
const filteredRows = computed(() => {
|
|
15
|
+
if (!filter.value || !all.value) return all.value;
|
|
16
|
+
const searchTerm = filter.value.toLowerCase();
|
|
17
|
+
return all.value.filter((row) => {
|
|
18
|
+
if (row.name?.toLowerCase().includes(searchTerm)) return true;
|
|
19
|
+
for (const language of app.languages) {
|
|
20
|
+
if (row[language.value]?.toLowerCase().includes(searchTerm)) return true;
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
});
|
|
24
|
+
});
|
|
13
25
|
const obj = reactive({
|
|
14
|
-
name: ""
|
|
26
|
+
name: "",
|
|
27
|
+
_originalName: ""
|
|
28
|
+
// 用於追蹤原始名稱(編輯模式)
|
|
15
29
|
});
|
|
30
|
+
const isEditMode = computed(() => !!obj._originalName);
|
|
31
|
+
const onSelectRow = (row) => {
|
|
32
|
+
obj.name = row.name;
|
|
33
|
+
obj._originalName = row.name;
|
|
34
|
+
for (const language of app.languages) {
|
|
35
|
+
obj[language.value] = row[language.value] || "";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const onClearForm = () => {
|
|
39
|
+
obj.name = "";
|
|
40
|
+
obj._originalName = "";
|
|
41
|
+
for (const language of app.languages) {
|
|
42
|
+
obj[language.value] = "";
|
|
43
|
+
}
|
|
44
|
+
};
|
|
16
45
|
const onSave = async () => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
25
|
-
})
|
|
46
|
+
if (isEditMode.value) {
|
|
47
|
+
for (const language of app.languages) {
|
|
48
|
+
await m("updateTranslate", {
|
|
49
|
+
name: obj._originalName,
|
|
50
|
+
language: language.value,
|
|
51
|
+
value: obj[language.value] || ""
|
|
52
|
+
});
|
|
26
53
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
54
|
+
$q.notify({
|
|
55
|
+
message: t("Update success"),
|
|
56
|
+
color: "positive",
|
|
57
|
+
icon: "check"
|
|
58
|
+
});
|
|
59
|
+
} else {
|
|
60
|
+
await m("addTranslate", {
|
|
61
|
+
data: {
|
|
62
|
+
name: obj.name,
|
|
63
|
+
values: app.languages.map((language) => {
|
|
64
|
+
return {
|
|
65
|
+
language: language.value,
|
|
66
|
+
value: obj[language.value] || ""
|
|
67
|
+
};
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
$q.notify({
|
|
72
|
+
message: t("Save success"),
|
|
73
|
+
color: "positive",
|
|
74
|
+
icon: "check"
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
onClearForm();
|
|
33
78
|
await refresh();
|
|
34
79
|
};
|
|
35
80
|
const columns = [
|
|
@@ -52,31 +97,25 @@ for (const language of app.languages) {
|
|
|
52
97
|
align: "left"
|
|
53
98
|
});
|
|
54
99
|
}
|
|
55
|
-
const onUpdateTranslate = async (value, language, name) => {
|
|
56
|
-
if (await m("updateTranslate", {
|
|
57
|
-
name,
|
|
58
|
-
language,
|
|
59
|
-
value
|
|
60
|
-
})) {
|
|
61
|
-
$q.notify({
|
|
62
|
-
message: "Update success",
|
|
63
|
-
color: "positive",
|
|
64
|
-
icon: "check"
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
await refresh();
|
|
68
|
-
};
|
|
69
100
|
const onDelete = async (name) => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
101
|
+
$q.dialog({
|
|
102
|
+
title: t("Confirm"),
|
|
103
|
+
message: t("Are you sure you want to delete this item?"),
|
|
104
|
+
cancel: true,
|
|
105
|
+
persistent: true
|
|
106
|
+
}).onOk(async () => {
|
|
107
|
+
if (await m("deleteTranslate", { name })) {
|
|
108
|
+
$q.notify({
|
|
109
|
+
message: t("Delete success"),
|
|
110
|
+
color: "positive",
|
|
111
|
+
icon: "check"
|
|
112
|
+
});
|
|
113
|
+
if (obj._originalName === name) {
|
|
114
|
+
onClearForm();
|
|
115
|
+
}
|
|
116
|
+
await refresh();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
80
119
|
};
|
|
81
120
|
</script>
|
|
82
121
|
|
|
@@ -85,38 +124,115 @@ const onDelete = async (name) => {
|
|
|
85
124
|
<l-card>
|
|
86
125
|
<q-splitter v-model="splitterModel" style="height:680px">
|
|
87
126
|
<template #before>
|
|
88
|
-
<q-table
|
|
89
|
-
:
|
|
127
|
+
<q-table
|
|
128
|
+
:rows="filteredRows"
|
|
129
|
+
flat
|
|
130
|
+
:rows-per-page-options="[0]"
|
|
131
|
+
:columns="columns"
|
|
132
|
+
dense
|
|
133
|
+
separator="cell"
|
|
134
|
+
:bordered="false"
|
|
135
|
+
>
|
|
136
|
+
<template #top>
|
|
137
|
+
<q-input
|
|
138
|
+
v-model="filter"
|
|
139
|
+
dense
|
|
140
|
+
outlined
|
|
141
|
+
:placeholder="t('Search...')"
|
|
142
|
+
clearable
|
|
143
|
+
class="full-width"
|
|
144
|
+
>
|
|
145
|
+
<template #prepend>
|
|
146
|
+
<q-icon name="sym_o_search" />
|
|
147
|
+
</template>
|
|
148
|
+
</q-input>
|
|
149
|
+
</template>
|
|
90
150
|
<template #body="props">
|
|
91
|
-
<q-tr
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
151
|
+
<q-tr
|
|
152
|
+
:props="props"
|
|
153
|
+
:class="{ 'bg-blue-1': obj._originalName === props.row.name }"
|
|
154
|
+
class="cursor-pointer"
|
|
155
|
+
@click="onSelectRow(props.row)"
|
|
156
|
+
>
|
|
157
|
+
<q-td key="_delete" auto-width @click.stop>
|
|
158
|
+
<q-btn dense flat round icon="sym_o_delete" color="negative"
|
|
159
|
+
@click="onDelete(props.row.name)">
|
|
160
|
+
<q-tooltip>{{ t("Delete") }}</q-tooltip>
|
|
161
|
+
</q-btn>
|
|
95
162
|
</q-td>
|
|
96
|
-
<q-td key="name">
|
|
97
|
-
|
|
163
|
+
<q-td key="name" style="max-width: 200px;">
|
|
164
|
+
<div class="text-weight-medium ellipsis">
|
|
165
|
+
{{ props.row.name }}
|
|
166
|
+
<q-tooltip v-if="props.row.name?.length > 25" anchor="top middle" self="bottom middle">
|
|
167
|
+
{{ props.row.name }}
|
|
168
|
+
</q-tooltip>
|
|
169
|
+
</div>
|
|
98
170
|
</q-td>
|
|
99
|
-
<q-td :key="language.value" v-for="language in app.languages" :props="props">
|
|
100
|
-
<div class="text-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
</
|
|
171
|
+
<q-td :key="language.value" v-for="language in app.languages" :props="props" style="max-width: 250px;">
|
|
172
|
+
<div class="text-grey-8 ellipsis-2-lines">
|
|
173
|
+
{{ props.row[language.value] || '-' }}
|
|
174
|
+
<q-tooltip v-if="props.row[language.value]?.length > 50" anchor="top middle" self="bottom middle" max-width="400px">
|
|
175
|
+
<div class="text-pre-wrap">{{ props.row[language.value] }}</div>
|
|
176
|
+
</q-tooltip>
|
|
177
|
+
</div>
|
|
106
178
|
</q-td>
|
|
107
179
|
</q-tr>
|
|
108
180
|
</template>
|
|
109
181
|
</q-table>
|
|
110
|
-
|
|
111
182
|
</template>
|
|
112
183
|
<template #after>
|
|
113
|
-
<
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
184
|
+
<div class="q-pa-md">
|
|
185
|
+
<div class="row items-center q-mb-md">
|
|
186
|
+
<div class="text-h6">
|
|
187
|
+
{{ isEditMode ? t("Edit Translation") : t("Add Translation") }}
|
|
188
|
+
</div>
|
|
189
|
+
<q-space />
|
|
190
|
+
<q-btn
|
|
191
|
+
v-if="isEditMode"
|
|
192
|
+
flat
|
|
193
|
+
dense
|
|
194
|
+
icon="sym_o_add"
|
|
195
|
+
:label="t('New')"
|
|
196
|
+
color="primary"
|
|
197
|
+
@click="onClearForm"
|
|
198
|
+
>
|
|
199
|
+
<q-tooltip>{{ t("Clear form and add new") }}</q-tooltip>
|
|
200
|
+
</q-btn>
|
|
201
|
+
</div>
|
|
202
|
+
<l-form :bordered="false" @submit="onSave">
|
|
203
|
+
<l-input
|
|
204
|
+
label="Name"
|
|
205
|
+
required
|
|
206
|
+
v-model.trim="obj.name"
|
|
207
|
+
clearable
|
|
208
|
+
:disable="isEditMode"
|
|
209
|
+
:hint="isEditMode ? t('Name cannot be changed in edit mode') : ''"
|
|
210
|
+
/>
|
|
211
|
+
<l-input
|
|
212
|
+
v-for="language in app.languages"
|
|
213
|
+
:key="language.value"
|
|
214
|
+
:label="language.name"
|
|
215
|
+
v-model="obj[language.value]"
|
|
216
|
+
type="textarea"
|
|
217
|
+
/>
|
|
218
|
+
<template #buttons>
|
|
219
|
+
<q-btn
|
|
220
|
+
v-if="isEditMode"
|
|
221
|
+
flat
|
|
222
|
+
:label="t('Cancel')"
|
|
223
|
+
@click="onClearForm"
|
|
224
|
+
class="q-mr-sm"
|
|
225
|
+
/>
|
|
226
|
+
<l-save-btn :label="isEditMode ? t('Update') : t('Save')" />
|
|
227
|
+
</template>
|
|
228
|
+
</l-form>
|
|
229
|
+
</div>
|
|
118
230
|
</template>
|
|
119
231
|
</q-splitter>
|
|
120
232
|
</l-card>
|
|
121
233
|
</l-page>
|
|
122
234
|
</template>
|
|
235
|
+
|
|
236
|
+
<style scoped>
|
|
237
|
+
.ellipsis{white-space:nowrap}.ellipsis,.ellipsis-2-lines{overflow:hidden;text-overflow:ellipsis}.ellipsis-2-lines{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;word-break:break-word}
|
|
238
|
+
</style>
|
|
@@ -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
|
}
|