@hostlink/nuxt-light 1.49.4 → 1.51.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 +13 -3
- package/dist/runtime/components/l-app-main.d.vue.ts +3 -3
- package/dist/runtime/components/l-app-main.vue +16 -6
- package/dist/runtime/components/l-app-main.vue.d.ts +3 -3
- package/dist/runtime/components/l-select.d.vue.ts +1 -1
- package/dist/runtime/components/l-select.vue.d.ts +1 -1
- package/dist/runtime/components/l-table.d.vue.ts +7 -5
- package/dist/runtime/components/l-table.vue +106 -54
- package/dist/runtime/components/l-table.vue.d.ts +7 -5
- package/dist/runtime/composables/useLight.d.ts +0 -18
- package/dist/runtime/models/User.d.ts +0 -1
- package/dist/runtime/models/User.js +0 -1
- package/dist/runtime/pages/System/database/check.vue +149 -0
- package/dist/runtime/pages/System/database/table.vue +2 -3
- package/dist/runtime/pages/Translate/index.vue +5 -5
- package/dist/runtime/pages/User/index.vue +5 -5
- package/dist/runtime/pages/User/setting/favorite.d.vue.ts +2 -0
- package/dist/runtime/pages/User/setting/favorite.vue +164 -0
- package/dist/runtime/pages/User/setting/favorite.vue.d.ts +2 -0
- package/dist/runtime/pages/User/setting/index.vue +0 -1
- package/dist/runtime/pages/User/setting/menu.d.vue.ts +2 -0
- package/dist/runtime/pages/User/setting/menu.vue +422 -0
- package/dist/runtime/pages/User/setting/menu.vue.d.ts +2 -0
- package/dist/runtime/pages/User/setting.vue +4 -2
- package/package.json +5 -5
- package/dist/runtime/pages/User/setting/my_favorite.vue +0 -156
- /package/dist/runtime/pages/{User/setting/my_favorite.d.vue.ts → System/database/check.d.vue.ts} +0 -0
- /package/dist/runtime/pages/{User/setting/my_favorite.vue.d.ts → System/database/check.vue.d.ts} +0 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, computed, inject } from "vue";
|
|
3
|
+
import { q, m } from "#imports";
|
|
4
|
+
import { useQuasar } from "quasar";
|
|
5
|
+
import { useI18n } from "vue-i18n";
|
|
6
|
+
const reloadMenu = inject("reloadMenu");
|
|
7
|
+
const { t } = useI18n();
|
|
8
|
+
const $q = useQuasar();
|
|
9
|
+
const { app, my } = await q({
|
|
10
|
+
app: {
|
|
11
|
+
menus: true
|
|
12
|
+
},
|
|
13
|
+
my: {
|
|
14
|
+
menu: true
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
const getUUID = () => {
|
|
18
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
|
|
19
|
+
let r = Math.random() * 16 | 0, v = c == "x" ? r : r & 3 | 8;
|
|
20
|
+
return v.toString(16);
|
|
21
|
+
}).toUpperCase();
|
|
22
|
+
};
|
|
23
|
+
const convertMenusToTree = (menus) => {
|
|
24
|
+
if (!menus) return [];
|
|
25
|
+
return menus.map((menu) => ({
|
|
26
|
+
...menu,
|
|
27
|
+
uuid: menu.uuid || getUUID(),
|
|
28
|
+
icon: menu.icon || "sym_o_menu",
|
|
29
|
+
children: menu.children ? convertMenusToTree(menu.children) : []
|
|
30
|
+
}));
|
|
31
|
+
};
|
|
32
|
+
const availableMenus = ref(convertMenusToTree(app.menus));
|
|
33
|
+
const userMenus = ref([
|
|
34
|
+
{
|
|
35
|
+
label: "[My Menu]",
|
|
36
|
+
uuid: "ROOT",
|
|
37
|
+
children: my.menu,
|
|
38
|
+
type: "root"
|
|
39
|
+
}
|
|
40
|
+
]);
|
|
41
|
+
const selectedNode = ref("ROOT");
|
|
42
|
+
const splitterModel = ref(30);
|
|
43
|
+
const userMenuTree = ref(null);
|
|
44
|
+
const currentNodeDetail = computed(() => {
|
|
45
|
+
if (selectedNode.value === "ROOT") return userMenus.value[0];
|
|
46
|
+
if (!userMenuTree.value) return null;
|
|
47
|
+
return userMenuTree.value.getNodeByKey(selectedNode.value);
|
|
48
|
+
});
|
|
49
|
+
const cloneMenuWithChildren = (menu, parentUuid) => {
|
|
50
|
+
const clonedMenu = {
|
|
51
|
+
...menu,
|
|
52
|
+
uuid: getUUID(),
|
|
53
|
+
parent: parentUuid,
|
|
54
|
+
children: []
|
|
55
|
+
};
|
|
56
|
+
if (menu.children && menu.children.length > 0) {
|
|
57
|
+
clonedMenu.children = menu.children.map(
|
|
58
|
+
(child) => cloneMenuWithChildren(child, clonedMenu.uuid)
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return clonedMenu;
|
|
62
|
+
};
|
|
63
|
+
const addMenuToCustom = (menu) => {
|
|
64
|
+
const targetNode = currentNodeDetail.value;
|
|
65
|
+
if (!targetNode) return;
|
|
66
|
+
const newMenuItem = cloneMenuWithChildren(menu, targetNode.uuid);
|
|
67
|
+
if (!targetNode.children) targetNode.children = [];
|
|
68
|
+
targetNode.children.push(newMenuItem);
|
|
69
|
+
if (userMenuTree.value && targetNode.uuid !== "ROOT") {
|
|
70
|
+
userMenuTree.value.setExpanded(selectedNode.value, true);
|
|
71
|
+
}
|
|
72
|
+
if (newMenuItem.children && newMenuItem.children.length > 0) {
|
|
73
|
+
setTimeout(() => {
|
|
74
|
+
if (userMenuTree.value) {
|
|
75
|
+
userMenuTree.value.setExpanded(newMenuItem.uuid, true);
|
|
76
|
+
}
|
|
77
|
+
}, 100);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const createNewFolder = () => {
|
|
81
|
+
$q.dialog({
|
|
82
|
+
title: "Create New Folder",
|
|
83
|
+
message: "Please enter folder name",
|
|
84
|
+
prompt: {
|
|
85
|
+
model: "",
|
|
86
|
+
type: "text"
|
|
87
|
+
},
|
|
88
|
+
cancel: true,
|
|
89
|
+
persistent: true
|
|
90
|
+
}).onOk((folderName) => {
|
|
91
|
+
if (folderName.trim() === "") return;
|
|
92
|
+
const targetNode = currentNodeDetail.value;
|
|
93
|
+
if (!targetNode) return;
|
|
94
|
+
const newFolder = {
|
|
95
|
+
label: folderName,
|
|
96
|
+
uuid: getUUID(),
|
|
97
|
+
parent: targetNode.uuid,
|
|
98
|
+
children: [],
|
|
99
|
+
icon: "sym_o_folder"
|
|
100
|
+
};
|
|
101
|
+
if (!targetNode.children) targetNode.children = [];
|
|
102
|
+
targetNode.children.push(newFolder);
|
|
103
|
+
if (userMenuTree.value && targetNode.uuid !== "ROOT") {
|
|
104
|
+
userMenuTree.value.setExpanded(selectedNode.value, true);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
const createNewSubItem = () => {
|
|
109
|
+
$q.dialog({
|
|
110
|
+
title: "Create New Sub Item",
|
|
111
|
+
message: "Please enter item name",
|
|
112
|
+
prompt: {
|
|
113
|
+
model: "",
|
|
114
|
+
type: "text"
|
|
115
|
+
},
|
|
116
|
+
cancel: true,
|
|
117
|
+
persistent: true
|
|
118
|
+
}).onOk((itemName) => {
|
|
119
|
+
if (itemName.trim() === "") return;
|
|
120
|
+
const targetNode = currentNodeDetail.value;
|
|
121
|
+
if (!targetNode) return;
|
|
122
|
+
const newItem = {
|
|
123
|
+
label: itemName,
|
|
124
|
+
uuid: getUUID(),
|
|
125
|
+
parent: targetNode.uuid,
|
|
126
|
+
children: [],
|
|
127
|
+
icon: "sym_o_menu",
|
|
128
|
+
to: ""
|
|
129
|
+
};
|
|
130
|
+
if (!targetNode.children) targetNode.children = [];
|
|
131
|
+
targetNode.children.push(newItem);
|
|
132
|
+
if (userMenuTree.value && targetNode.uuid !== "ROOT") {
|
|
133
|
+
userMenuTree.value.setExpanded(selectedNode.value, true);
|
|
134
|
+
}
|
|
135
|
+
selectedNode.value = newItem.uuid;
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
const addSeparator = () => {
|
|
139
|
+
const targetNode = currentNodeDetail.value;
|
|
140
|
+
if (!targetNode) return;
|
|
141
|
+
const separator = {
|
|
142
|
+
label: "[Separator]",
|
|
143
|
+
uuid: getUUID(),
|
|
144
|
+
parent: targetNode.uuid,
|
|
145
|
+
children: [],
|
|
146
|
+
type: "separator",
|
|
147
|
+
spaced: true
|
|
148
|
+
};
|
|
149
|
+
if (!targetNode.children) targetNode.children = [];
|
|
150
|
+
targetNode.children.push(separator);
|
|
151
|
+
};
|
|
152
|
+
const removeMenuItem = (node) => {
|
|
153
|
+
if (node.type === "root") return;
|
|
154
|
+
$q.dialog({
|
|
155
|
+
title: "Confirm Delete",
|
|
156
|
+
message: `Are you sure you want to delete "${node.label}"?`,
|
|
157
|
+
cancel: true,
|
|
158
|
+
persistent: true
|
|
159
|
+
}).onOk(() => {
|
|
160
|
+
const parent = getParentNode(node);
|
|
161
|
+
if (parent && parent.children) {
|
|
162
|
+
parent.children = parent.children.filter((item) => item.uuid !== node.uuid);
|
|
163
|
+
selectedNode.value = parent.uuid;
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
const getParentNode = (node) => {
|
|
168
|
+
if (!node.parent || node.parent === "ROOT") {
|
|
169
|
+
return userMenus.value[0];
|
|
170
|
+
}
|
|
171
|
+
return userMenuTree.value.getNodeByKey(node.parent);
|
|
172
|
+
};
|
|
173
|
+
const moveUp = (node) => {
|
|
174
|
+
const parent = getParentNode(node);
|
|
175
|
+
if (!parent || !parent.children) return;
|
|
176
|
+
const index = parent.children.findIndex((item) => item.uuid === node.uuid);
|
|
177
|
+
if (index > 0) {
|
|
178
|
+
const temp = parent.children[index - 1];
|
|
179
|
+
parent.children[index - 1] = node;
|
|
180
|
+
parent.children[index] = temp;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
const moveDown = (node) => {
|
|
184
|
+
const parent = getParentNode(node);
|
|
185
|
+
if (!parent || !parent.children) return;
|
|
186
|
+
const index = parent.children.findIndex((item) => item.uuid === node.uuid);
|
|
187
|
+
if (index < parent.children.length - 1) {
|
|
188
|
+
const temp = parent.children[index + 1];
|
|
189
|
+
parent.children[index + 1] = node;
|
|
190
|
+
parent.children[index] = temp;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
const showMoveDialog = ref(false);
|
|
194
|
+
const moveTarget = ref(null);
|
|
195
|
+
const moveToGroup = () => {
|
|
196
|
+
showMoveDialog.value = true;
|
|
197
|
+
moveTarget.value = null;
|
|
198
|
+
};
|
|
199
|
+
const confirmMove = () => {
|
|
200
|
+
if (!moveTarget.value || !currentNodeDetail.value) return;
|
|
201
|
+
const nodeToMove = JSON.parse(JSON.stringify(currentNodeDetail.value));
|
|
202
|
+
removeMenuItem(currentNodeDetail.value);
|
|
203
|
+
nodeToMove.parent = moveTarget.value;
|
|
204
|
+
const targetNode = moveTarget.value === "ROOT" ? userMenus.value[0] : userMenuTree.value.getNodeByKey(moveTarget.value);
|
|
205
|
+
if (!targetNode.children) targetNode.children = [];
|
|
206
|
+
targetNode.children.push(nodeToMove);
|
|
207
|
+
if (userMenuTree.value && moveTarget.value !== "ROOT") {
|
|
208
|
+
userMenuTree.value.setExpanded(moveTarget.value, true);
|
|
209
|
+
}
|
|
210
|
+
showMoveDialog.value = false;
|
|
211
|
+
selectedNode.value = nodeToMove.uuid;
|
|
212
|
+
};
|
|
213
|
+
const saveCustomMenus = async () => {
|
|
214
|
+
console.log("User custom menu structure:", userMenus.value[0].children);
|
|
215
|
+
try {
|
|
216
|
+
await m("updateMyMenu", {
|
|
217
|
+
menu: userMenus.value[0].children
|
|
218
|
+
});
|
|
219
|
+
if (reloadMenu) {
|
|
220
|
+
reloadMenu();
|
|
221
|
+
}
|
|
222
|
+
} catch (e) {
|
|
223
|
+
console.error("Failed to save custom menu:", e);
|
|
224
|
+
$q.notify({
|
|
225
|
+
message: "Failed to save custom menu",
|
|
226
|
+
color: "negative",
|
|
227
|
+
icon: "error",
|
|
228
|
+
position: "top",
|
|
229
|
+
timeout: 2e3
|
|
230
|
+
});
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
$q.notify({
|
|
234
|
+
message: "Menu structure saved successfully",
|
|
235
|
+
color: "positive",
|
|
236
|
+
icon: "check",
|
|
237
|
+
position: "top",
|
|
238
|
+
timeout: 2e3
|
|
239
|
+
});
|
|
240
|
+
};
|
|
241
|
+
const resetMenus = () => {
|
|
242
|
+
$q.dialog({
|
|
243
|
+
title: "Confirm Reset",
|
|
244
|
+
message: "This will clear all custom menu settings. Are you sure you want to reset?",
|
|
245
|
+
cancel: true,
|
|
246
|
+
persistent: true
|
|
247
|
+
}).onOk(() => {
|
|
248
|
+
userMenus.value[0].children = [];
|
|
249
|
+
selectedNode.value = "ROOT";
|
|
250
|
+
});
|
|
251
|
+
};
|
|
252
|
+
</script>
|
|
253
|
+
|
|
254
|
+
<template>
|
|
255
|
+
<div>
|
|
256
|
+
<!-- Move to group dialog -->
|
|
257
|
+
<q-dialog v-model="showMoveDialog">
|
|
258
|
+
<l-card>
|
|
259
|
+
<q-card-section>
|
|
260
|
+
<div class="text-h6">Select Move Target</div>
|
|
261
|
+
<q-tree :nodes="userMenus" :selected-color="$light.color" default-expand-all
|
|
262
|
+
v-model:selected="moveTarget" node-key="uuid" />
|
|
263
|
+
</q-card-section>
|
|
264
|
+
<q-card-actions align="right">
|
|
265
|
+
<q-btn flat label="Cancel" :color="$light.color" v-close-popup />
|
|
266
|
+
<q-btn flat label="Move" :color="$light.color" @click="confirmMove" />
|
|
267
|
+
</q-card-actions>
|
|
268
|
+
</l-card>
|
|
269
|
+
</q-dialog>
|
|
270
|
+
|
|
271
|
+
<l-card>
|
|
272
|
+
<q-card-actions class="q-gutter-sm">
|
|
273
|
+
<l-btn @click="saveCustomMenus" label="Save Custom Menu" icon="sym_o_save" />
|
|
274
|
+
<l-btn @click="resetMenus" label="Reset Menu" icon="sym_o_refresh" />
|
|
275
|
+
</q-card-actions>
|
|
276
|
+
|
|
277
|
+
<q-splitter v-model="splitterModel" style="height:680px">
|
|
278
|
+
<template #before>
|
|
279
|
+
<div class="q-pa-md">
|
|
280
|
+
<div class="text-h6 q-mb-md">Available Menu Items</div>
|
|
281
|
+
<q-tree :nodes="availableMenus" :selected-color="$light.color" default-expand-all
|
|
282
|
+
node-key="uuid">
|
|
283
|
+
<template v-slot:default-header="prop">
|
|
284
|
+
<div class="row items-center full-width">
|
|
285
|
+
<q-icon :name="prop.node.icon || 'sym_o_menu'" class="q-mr-sm" size="sm" />
|
|
286
|
+
<span>{{ prop.node.label }}</span>
|
|
287
|
+
<q-space />
|
|
288
|
+
<small v-if="prop.node.to" class="text-grey-6 q-mr-sm">{{ prop.node.to }}</small>
|
|
289
|
+
<q-btn flat round dense size="sm" icon="sym_o_add" color="positive"
|
|
290
|
+
@click.stop="addMenuToCustom(prop.node)">
|
|
291
|
+
<q-tooltip>Add to My Menu</q-tooltip>
|
|
292
|
+
</q-btn>
|
|
293
|
+
</div>
|
|
294
|
+
</template>
|
|
295
|
+
</q-tree>
|
|
296
|
+
</div>
|
|
297
|
+
</template>
|
|
298
|
+
|
|
299
|
+
<template #after>
|
|
300
|
+
<div class="q-pa-md">
|
|
301
|
+
<div class="text-h6 q-mb-md">My Custom Menu</div>
|
|
302
|
+
|
|
303
|
+
<!-- Action buttons -->
|
|
304
|
+
<q-card-actions class="q-mb-md q-gutter-xs" v-if="currentNodeDetail">
|
|
305
|
+
<template v-if="currentNodeDetail.type === 'root'">
|
|
306
|
+
<q-btn-dropdown v-bind="$light.getButtonProps({ label: 'Add' })" icon="sym_o_add"
|
|
307
|
+
size="sm">
|
|
308
|
+
<q-list>
|
|
309
|
+
<q-item clickable v-close-popup @click="createNewFolder">
|
|
310
|
+
<q-item-section avatar>
|
|
311
|
+
<q-icon name="sym_o_folder" color="orange" />
|
|
312
|
+
</q-item-section>
|
|
313
|
+
<q-item-section>
|
|
314
|
+
<q-item-label>Add Folder</q-item-label>
|
|
315
|
+
</q-item-section>
|
|
316
|
+
</q-item>
|
|
317
|
+
<q-item clickable v-close-popup @click="createNewSubItem">
|
|
318
|
+
<q-item-section avatar>
|
|
319
|
+
<q-icon name="sym_o_menu" />
|
|
320
|
+
</q-item-section>
|
|
321
|
+
<q-item-section>
|
|
322
|
+
<q-item-label>Add Sub Item</q-item-label>
|
|
323
|
+
</q-item-section>
|
|
324
|
+
</q-item>
|
|
325
|
+
<q-item clickable v-close-popup @click="addSeparator">
|
|
326
|
+
<q-item-section avatar>
|
|
327
|
+
<q-icon name="sym_o_more_horiz" color="grey" />
|
|
328
|
+
</q-item-section>
|
|
329
|
+
<q-item-section>
|
|
330
|
+
<q-item-label>Add Separator</q-item-label>
|
|
331
|
+
</q-item-section>
|
|
332
|
+
</q-item>
|
|
333
|
+
</q-list>
|
|
334
|
+
</q-btn-dropdown>
|
|
335
|
+
</template>
|
|
336
|
+
<template v-else>
|
|
337
|
+
<q-btn-dropdown v-bind="$light.getButtonProps({ label: 'Add' })" icon="sym_o_add"
|
|
338
|
+
size="sm">
|
|
339
|
+
<q-list>
|
|
340
|
+
<q-item clickable v-close-popup @click="createNewFolder">
|
|
341
|
+
<q-item-section avatar>
|
|
342
|
+
<q-icon name="sym_o_folder" color="orange" />
|
|
343
|
+
</q-item-section>
|
|
344
|
+
<q-item-section>
|
|
345
|
+
<q-item-label>Add Folder</q-item-label>
|
|
346
|
+
</q-item-section>
|
|
347
|
+
</q-item>
|
|
348
|
+
<q-item clickable v-close-popup @click="createNewSubItem">
|
|
349
|
+
<q-item-section avatar>
|
|
350
|
+
<q-icon name="sym_o_menu" />
|
|
351
|
+
</q-item-section>
|
|
352
|
+
<q-item-section>
|
|
353
|
+
<q-item-label>Add Sub Item</q-item-label>
|
|
354
|
+
</q-item-section>
|
|
355
|
+
</q-item>
|
|
356
|
+
<q-item clickable v-close-popup @click="addSeparator">
|
|
357
|
+
<q-item-section avatar>
|
|
358
|
+
<q-icon name="sym_o_more_horiz" color="grey" />
|
|
359
|
+
</q-item-section>
|
|
360
|
+
<q-item-section>
|
|
361
|
+
<q-item-label>Add Separator</q-item-label>
|
|
362
|
+
</q-item-section>
|
|
363
|
+
</q-item>
|
|
364
|
+
</q-list>
|
|
365
|
+
</q-btn-dropdown>
|
|
366
|
+
|
|
367
|
+
<l-btn @click="removeMenuItem(currentNodeDetail)" label="Delete" icon="sym_o_delete"
|
|
368
|
+
size="sm" color="negative" />
|
|
369
|
+
<l-btn @click="moveUp(currentNodeDetail)" label="Move Up" icon="sym_o_arrow_upward"
|
|
370
|
+
size="sm" />
|
|
371
|
+
<l-btn @click="moveDown(currentNodeDetail)" label="Move Down" icon="sym_o_arrow_downward"
|
|
372
|
+
size="sm" />
|
|
373
|
+
<l-btn @click="moveToGroup" label="Move to..." icon="sym_o_drive_file_move" size="sm" />
|
|
374
|
+
</template>
|
|
375
|
+
</q-card-actions>
|
|
376
|
+
|
|
377
|
+
<!-- Menu tree -->
|
|
378
|
+
<q-tree :nodes="userMenus" :selected-color="$light.color" default-expand-all
|
|
379
|
+
v-model:selected="selectedNode" node-key="uuid" ref="userMenuTree">
|
|
380
|
+
<template v-slot:default-header="prop">
|
|
381
|
+
<div class="row items-center full-width">
|
|
382
|
+
<q-icon :name="prop.node.type === 'separator' ? 'sym_o_more_horiz' :
|
|
383
|
+
prop.node.type === 'root' ? 'sym_o_account_tree' :
|
|
384
|
+
prop.node.icon || 'sym_o_menu'" class="q-mr-sm" size="sm" :color="prop.node.type === 'separator' ? 'grey' :
|
|
385
|
+
prop.node.type === 'root' ? 'primary' : undefined" />
|
|
386
|
+
<span :class="{
|
|
387
|
+
'text-grey-6': prop.node.type === 'separator',
|
|
388
|
+
'text-primary': prop.node.type === 'root'
|
|
389
|
+
}">
|
|
390
|
+
{{ prop.node.label }}
|
|
391
|
+
</span>
|
|
392
|
+
<q-space />
|
|
393
|
+
<small v-if="prop.node.to" class="text-grey-6">{{ prop.node.to }}</small>
|
|
394
|
+
</div>
|
|
395
|
+
</template>
|
|
396
|
+
</q-tree>
|
|
397
|
+
|
|
398
|
+
<!-- Current selected node detail settings -->
|
|
399
|
+
<div v-if="currentNodeDetail && currentNodeDetail.type !== 'root'" class="q-mt-lg">
|
|
400
|
+
<q-separator class="q-mb-md" />
|
|
401
|
+
<div class="text-subtitle1 q-mb-md">Menu Settings</div>
|
|
402
|
+
|
|
403
|
+
<template v-if="currentNodeDetail.type === 'separator'">
|
|
404
|
+
<div class="q-gutter-md">
|
|
405
|
+
<l-checkbox label="Spaced Separator" v-model="currentNodeDetail.spaced" />
|
|
406
|
+
</div>
|
|
407
|
+
</template>
|
|
408
|
+
|
|
409
|
+
<template v-else>
|
|
410
|
+
<div class="q-gutter-md">
|
|
411
|
+
<l-input label="Label" v-model="currentNodeDetail.label" />
|
|
412
|
+
<l-input label="Route" v-model="currentNodeDetail.to" />
|
|
413
|
+
<l-icon-picker label="Icon" v-model="currentNodeDetail.icon" />
|
|
414
|
+
</div>
|
|
415
|
+
</template>
|
|
416
|
+
</div>
|
|
417
|
+
</div>
|
|
418
|
+
</template>
|
|
419
|
+
</q-splitter>
|
|
420
|
+
</l-card>
|
|
421
|
+
</div>
|
|
422
|
+
</template>
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
export default _default;
|
|
@@ -24,9 +24,11 @@ const route = useRoute();
|
|
|
24
24
|
exact />
|
|
25
25
|
<q-route-tab name="openid" icon="sym_o_key" :label="$t('Open ID')" to="/User/setting/open_id"
|
|
26
26
|
exact />
|
|
27
|
-
<q-route-tab name="
|
|
28
|
-
to="/User/setting/
|
|
27
|
+
<q-route-tab name="fav" icon="sym_o_favorite" :label="$t('Favorite')"
|
|
28
|
+
to="/User/setting/favorite" exact />
|
|
29
29
|
|
|
30
|
+
<q-route-tab name="menu" icon="sym_o_menu" :label="$t('Menu')"
|
|
31
|
+
to="/User/setting/menu" exact />
|
|
30
32
|
</q-tabs>
|
|
31
33
|
</template>
|
|
32
34
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hostlink/nuxt-light",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.51.0",
|
|
4
4
|
"description": "HostLink Nuxt Light Framework",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -44,19 +44,19 @@
|
|
|
44
44
|
"json-to-graphql-query": "^2.3.0",
|
|
45
45
|
"nuxt-quasar-ui": "^2.1.12",
|
|
46
46
|
"quasar": "^2.18.5",
|
|
47
|
-
"vue-i18n": "^
|
|
47
|
+
"vue-i18n": "^11.1.12",
|
|
48
48
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@nuxt/devtools": "latest",
|
|
52
52
|
"@nuxt/eslint-config": "^0.2.0",
|
|
53
|
-
"@nuxt/kit": "^4.2.
|
|
54
|
-
"@nuxt/schema": "^4.2.
|
|
53
|
+
"@nuxt/kit": "^4.2.1",
|
|
54
|
+
"@nuxt/schema": "^4.2.1",
|
|
55
55
|
"@nuxt/test-utils": "^3.17.2",
|
|
56
56
|
"@types/node": "^22.5.0",
|
|
57
57
|
"changelogen": "^0.5.4",
|
|
58
58
|
"eslint": "^8.46.0",
|
|
59
|
-
"nuxt": "^4.2.
|
|
59
|
+
"nuxt": "^4.2.1",
|
|
60
60
|
"typescript": "^5.9.2",
|
|
61
61
|
"vue-tsc": "^2.2.8"
|
|
62
62
|
}
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
import { useLight, model, q } from "#imports";
|
|
3
|
-
import { useQuasar } from "quasar";
|
|
4
|
-
import { ref, watch } from "vue";
|
|
5
|
-
import { useDragAndDrop } from "@formkit/drag-and-drop/vue";
|
|
6
|
-
import { animations } from "@formkit/drag-and-drop";
|
|
7
|
-
const light = useLight();
|
|
8
|
-
const $q = useQuasar();
|
|
9
|
-
const { my } = await q({
|
|
10
|
-
my: {
|
|
11
|
-
myFavorites: {
|
|
12
|
-
my_favorite_id: true,
|
|
13
|
-
label: true,
|
|
14
|
-
path: true,
|
|
15
|
-
icon: true,
|
|
16
|
-
sequence: true
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
const [dragParent, rows] = useDragAndDrop(my.myFavorites, {
|
|
21
|
-
plugins: [animations()],
|
|
22
|
-
dragHandle: ".drag-handle"
|
|
23
|
-
});
|
|
24
|
-
const isUpdating = ref(false);
|
|
25
|
-
watch(rows, async (newRows) => {
|
|
26
|
-
if (isUpdating.value) return;
|
|
27
|
-
let hasChanged = false;
|
|
28
|
-
for (let i = 0; i < newRows.length; i++) {
|
|
29
|
-
if (newRows[i].sequence !== i + 1) {
|
|
30
|
-
hasChanged = true;
|
|
31
|
-
break;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
if (!hasChanged) return;
|
|
35
|
-
isUpdating.value = true;
|
|
36
|
-
try {
|
|
37
|
-
for (let i = 0; i < newRows.length; i++) {
|
|
38
|
-
const item = newRows[i];
|
|
39
|
-
await model("MyFavorite").update(item.my_favorite_id, {
|
|
40
|
-
sequence: i + 1
|
|
41
|
-
});
|
|
42
|
-
item.sequence = i + 1;
|
|
43
|
-
}
|
|
44
|
-
await light.reloadMyFavorites();
|
|
45
|
-
$q.notify({
|
|
46
|
-
message: "\u9806\u5E8F\u5DF2\u66F4\u65B0",
|
|
47
|
-
color: "positive",
|
|
48
|
-
icon: "check"
|
|
49
|
-
});
|
|
50
|
-
} catch (error) {
|
|
51
|
-
$q.notify({
|
|
52
|
-
message: "\u66F4\u65B0\u5931\u6557: " + error.message,
|
|
53
|
-
color: "negative",
|
|
54
|
-
icon: "error"
|
|
55
|
-
});
|
|
56
|
-
} finally {
|
|
57
|
-
isUpdating.value = false;
|
|
58
|
-
}
|
|
59
|
-
}, { deep: true });
|
|
60
|
-
const onSave = async (id, data) => {
|
|
61
|
-
await model("MyFavorite").update(id, data);
|
|
62
|
-
$q.notify({
|
|
63
|
-
message: "Updated successfully",
|
|
64
|
-
color: "positive",
|
|
65
|
-
icon: "check"
|
|
66
|
-
});
|
|
67
|
-
const index = rows.value.findIndex((item) => item.my_favorite_id === id);
|
|
68
|
-
if (index !== -1) {
|
|
69
|
-
Object.assign(rows.value[index], data);
|
|
70
|
-
}
|
|
71
|
-
await light.reloadMyFavorites();
|
|
72
|
-
};
|
|
73
|
-
const onRemove = async (id) => {
|
|
74
|
-
await model("MyFavorite").delete(id);
|
|
75
|
-
const index = rows.value.findIndex((item) => item.my_favorite_id === id);
|
|
76
|
-
if (index !== -1) {
|
|
77
|
-
rows.value.splice(index, 1);
|
|
78
|
-
}
|
|
79
|
-
await light.reloadMyFavorites();
|
|
80
|
-
};
|
|
81
|
-
</script>
|
|
82
|
-
|
|
83
|
-
<template>
|
|
84
|
-
<div>
|
|
85
|
-
<q-list ref="dragParent" bordered separator class="drag-list">
|
|
86
|
-
<q-item
|
|
87
|
-
v-for="(row, index) in rows"
|
|
88
|
-
:key="row.my_favorite_id"
|
|
89
|
-
class="drag-item"
|
|
90
|
-
clickable
|
|
91
|
-
>
|
|
92
|
-
<!-- 拖拽手柄 -->
|
|
93
|
-
<q-item-section avatar class="drag-handle">
|
|
94
|
-
<q-icon name="drag_handle" class="cursor-move" color="grey-6" />
|
|
95
|
-
</q-item-section>
|
|
96
|
-
|
|
97
|
-
<!-- 主要內容 -->
|
|
98
|
-
<q-item-section>
|
|
99
|
-
<q-item-label>
|
|
100
|
-
<div class="row items-center q-gutter-sm">
|
|
101
|
-
<!-- Icon -->
|
|
102
|
-
<l-icon-picker
|
|
103
|
-
v-model="row.icon"
|
|
104
|
-
flat
|
|
105
|
-
round
|
|
106
|
-
size="sm"
|
|
107
|
-
@update:model-value="onSave(row.my_favorite_id, { 'icon': $event })"
|
|
108
|
-
/>
|
|
109
|
-
|
|
110
|
-
<!-- Label -->
|
|
111
|
-
<span class="text-weight-medium">
|
|
112
|
-
{{ row.label }}
|
|
113
|
-
<q-popup-edit v-model="row.label" #default="scope" buttons
|
|
114
|
-
@save="onSave(row.my_favorite_id, { 'label': $event })">
|
|
115
|
-
<q-input v-model="scope.value" dense autofocus counter @keyup.enter="scope.set" />
|
|
116
|
-
</q-popup-edit>
|
|
117
|
-
</span>
|
|
118
|
-
</div>
|
|
119
|
-
</q-item-label>
|
|
120
|
-
|
|
121
|
-
<q-item-label caption class="text-grey-7">
|
|
122
|
-
{{ row.path }}
|
|
123
|
-
<q-popup-edit v-model="row.path" #default="scope" buttons
|
|
124
|
-
@save="onSave(row.my_favorite_id, { 'path': $event })">
|
|
125
|
-
<q-input v-model="scope.value" dense autofocus counter @keyup.enter="scope.set" />
|
|
126
|
-
</q-popup-edit>
|
|
127
|
-
</q-item-label>
|
|
128
|
-
</q-item-section>
|
|
129
|
-
|
|
130
|
-
<!-- 操作按鈕 -->
|
|
131
|
-
<q-item-section side>
|
|
132
|
-
<q-btn
|
|
133
|
-
flat
|
|
134
|
-
round
|
|
135
|
-
icon="sym_o_delete"
|
|
136
|
-
size="sm"
|
|
137
|
-
color="negative"
|
|
138
|
-
@click="onRemove(row.my_favorite_id)"
|
|
139
|
-
>
|
|
140
|
-
<q-tooltip>刪除</q-tooltip>
|
|
141
|
-
</q-btn>
|
|
142
|
-
</q-item-section>
|
|
143
|
-
</q-item>
|
|
144
|
-
</q-list>
|
|
145
|
-
|
|
146
|
-
<!-- 狀態提示 -->
|
|
147
|
-
<div v-if="isUpdating" class="q-mt-md">
|
|
148
|
-
<q-linear-progress indeterminate color="primary" />
|
|
149
|
-
<div class="text-center q-mt-sm text-grey-6">正在更新順序...</div>
|
|
150
|
-
</div>
|
|
151
|
-
</div>
|
|
152
|
-
</template>
|
|
153
|
-
|
|
154
|
-
<style scoped>
|
|
155
|
-
.drag-list{border-radius:8px;overflow:hidden}.drag-item{transition:all .2s}.drag-item:hover{background-color:rgba(0,0,0,.02)}.drag-handle{cursor:move;min-width:40px;padding:0 8px}.drag-handle:hover .q-icon{color:var(--q-primary)!important}:deep(.formkit-dnd-is-dragging){background-color:rgba(25,118,210,.05);box-shadow:0 4px 12px rgba(0,0,0,.15);opacity:.7;transform:scale(1.02)}:deep(.formkit-dnd-placeholder){align-items:center;background-color:rgba(25,118,210,.1);border:2px dashed var(--q-primary);border-radius:4px;display:flex;height:72px;justify-content:center;margin:2px 0}:deep(.formkit-dnd-placeholder:before){color:var(--q-primary);content:"放置在此處";font-size:14px;font-weight:500}
|
|
156
|
-
</style>
|
/package/dist/runtime/pages/{User/setting/my_favorite.d.vue.ts → System/database/check.d.vue.ts}
RENAMED
|
File without changes
|
/package/dist/runtime/pages/{User/setting/my_favorite.vue.d.ts → System/database/check.vue.d.ts}
RENAMED
|
File without changes
|