@hostlink/nuxt-light 1.50.0 → 1.51.1

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 (35) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +13 -3
  3. package/dist/runtime/components/l-add-btn.d.vue.ts +2 -2
  4. package/dist/runtime/components/l-add-btn.vue.d.ts +2 -2
  5. package/dist/runtime/components/l-app-main.d.vue.ts +3 -3
  6. package/dist/runtime/components/l-app-main.vue +14 -4
  7. package/dist/runtime/components/l-app-main.vue.d.ts +3 -3
  8. package/dist/runtime/components/l-customizer.d.vue.ts +4 -4
  9. package/dist/runtime/components/l-customizer.vue.d.ts +4 -4
  10. package/dist/runtime/components/l-date-picker.d.vue.ts +1 -1
  11. package/dist/runtime/components/l-date-picker.vue.d.ts +1 -1
  12. package/dist/runtime/components/l-editor.d.vue.ts +1 -1
  13. package/dist/runtime/components/l-editor.vue.d.ts +1 -1
  14. package/dist/runtime/components/l-form-dialog.d.vue.ts +2 -2
  15. package/dist/runtime/components/l-form-dialog.vue.d.ts +2 -2
  16. package/dist/runtime/components/l-table.vue +1 -12
  17. package/dist/runtime/components/l-time-picker.d.vue.ts +1 -1
  18. package/dist/runtime/components/l-time-picker.vue.d.ts +1 -1
  19. package/dist/runtime/composables/useLight.d.ts +8 -8
  20. package/dist/runtime/pages/System/database/check.vue +149 -0
  21. package/dist/runtime/pages/System/database/event.vue +5 -4
  22. package/dist/runtime/pages/System/database/table.vue +2 -3
  23. package/dist/runtime/pages/Translate/index.vue +5 -5
  24. package/dist/runtime/pages/User/setting/favorite.d.vue.ts +2 -0
  25. package/dist/runtime/pages/User/setting/favorite.vue +164 -0
  26. package/dist/runtime/pages/User/setting/favorite.vue.d.ts +2 -0
  27. package/dist/runtime/pages/User/setting/index.vue +0 -1
  28. package/dist/runtime/pages/User/setting/menu.d.vue.ts +2 -0
  29. package/dist/runtime/pages/User/setting/menu.vue +422 -0
  30. package/dist/runtime/pages/User/setting/menu.vue.d.ts +2 -0
  31. package/dist/runtime/pages/User/setting.vue +4 -2
  32. package/package.json +1 -1
  33. package/dist/runtime/pages/User/setting/my_favorite.vue +0 -156
  34. /package/dist/runtime/pages/{User/setting/my_favorite.d.vue.ts → System/database/check.d.vue.ts} +0 -0
  35. /package/dist/runtime/pages/{User/setting/my_favorite.vue.d.ts → System/database/check.vue.d.ts} +0 -0
@@ -3,7 +3,7 @@ import { ref, reactive } from "vue";
3
3
  import { m, q } from "#imports";
4
4
  import { useQuasar } from "quasar";
5
5
  import { useI18n } from "vue-i18n";
6
- const quasar = useQuasar();
6
+ const $q = useQuasar();
7
7
  const { t } = useI18n();
8
8
  const app = await q("app", ["languages"]);
9
9
  const splitterModel = ref(62);
@@ -23,7 +23,7 @@ const onSave = async () => {
23
23
  })
24
24
  }
25
25
  });
26
- quasar.notify({
26
+ $q.notify({
27
27
  message: "Save success",
28
28
  color: "positive",
29
29
  icon: "check"
@@ -56,7 +56,7 @@ const onUpdateTranslate = async (value, language, name) => {
56
56
  language,
57
57
  value
58
58
  })) {
59
- quasar.notify({
59
+ $q.notify({
60
60
  message: "Update success",
61
61
  color: "positive",
62
62
  icon: "check"
@@ -67,7 +67,7 @@ const onDelete = async (name) => {
67
67
  if (await m("deleteTranslate", {
68
68
  name
69
69
  })) {
70
- quasar.notify({
70
+ $q.notify({
71
71
  message: "Delete success",
72
72
  color: "positive",
73
73
  icon: "check"
@@ -82,7 +82,7 @@ const onDelete = async (name) => {
82
82
  <l-card>
83
83
  <q-splitter v-model="splitterModel" style="height:680px">
84
84
  <template #before>
85
- <q-table :rows="all" flat :rows-per-page-options="[0]" :columns="columns" dense separator="cell">
85
+ <q-table :rows="all" flat :rows-per-page-options="[0]" :columns="columns" dense separator="cell" :bordered="false">
86
86
  <template #body="props">
87
87
  <q-tr :props="props">
88
88
  <q-td key="_delete" auto-width>
@@ -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;
@@ -0,0 +1,164 @@
1
+ <script setup>
2
+ import { useLight, model, q } from "#imports";
3
+ import { useQuasar } from "quasar";
4
+ import { ref, watch, onMounted } 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 [parent, rows] = useDragAndDrop(my.myFavorites, {
21
+ plugins: [animations()],
22
+ dragHandle: ".drag-handle"
23
+ });
24
+ const isUpdating = ref(false);
25
+ const hasChanges = ref(false);
26
+ watch(rows, (newRows) => {
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
+ hasChanges.value = hasChanged;
35
+ }, { deep: true });
36
+ const updateSequence = async () => {
37
+ if (isUpdating.value || !hasChanges.value) return;
38
+ isUpdating.value = true;
39
+ try {
40
+ for (let i = 0; i < rows.value.length; i++) {
41
+ const item = rows.value[i];
42
+ await model("MyFavorite").update(item.my_favorite_id, {
43
+ sequence: i + 1
44
+ });
45
+ item.sequence = i + 1;
46
+ }
47
+ await light.reloadMyFavorites();
48
+ hasChanges.value = false;
49
+ $q.notify({
50
+ message: "Order updated",
51
+ color: "positive",
52
+ icon: "check"
53
+ });
54
+ } catch (error) {
55
+ $q.notify({
56
+ message: "Update failed: " + error.message,
57
+ color: "negative",
58
+ icon: "error"
59
+ });
60
+ } finally {
61
+ isUpdating.value = false;
62
+ }
63
+ };
64
+ const onSave = async (id, data) => {
65
+ await model("MyFavorite").update(id, data);
66
+ $q.notify({
67
+ message: "Updated successfully",
68
+ color: "positive",
69
+ icon: "check"
70
+ });
71
+ const index = rows.value.findIndex((item) => item.my_favorite_id === id);
72
+ if (index !== -1) {
73
+ Object.assign(rows.value[index], data);
74
+ }
75
+ await light.reloadMyFavorites();
76
+ };
77
+ const onRemove = async (id) => {
78
+ await model("MyFavorite").delete(id);
79
+ const index = rows.value.findIndex((item) => item.my_favorite_id === id);
80
+ if (index !== -1) {
81
+ rows.value.splice(index, 1);
82
+ }
83
+ await light.reloadMyFavorites();
84
+ };
85
+ const columns = [
86
+ { name: "handler", label: "", field: "handler", align: "center" },
87
+ { name: "label", label: "Label", field: "label", align: "left" },
88
+ { name: "path", label: "Path", field: "path", align: "left" },
89
+ { name: "icon", label: "Icon", field: "icon", align: "center" },
90
+ { name: "actions", label: "Actions", field: "actions", align: "center" }
91
+ ];
92
+ onMounted(() => {
93
+ parent.value = document.querySelector("#myfav-table .q-table__middle.scroll > table > tbody");
94
+ });
95
+ </script>
96
+
97
+ <template>
98
+ <div>
99
+ <q-table id="myfav-table" :rows="rows" :columns="columns" row-key="my_favorite_id" flat :bordered="false"
100
+ :rows-per-page-options="[0]">
101
+
102
+ <template #body-cell-label="props">
103
+ <q-td :props="props">
104
+ {{ props.row.label }}
105
+ <q-popup-edit v-model="props.row.label" v-slot="scope" buttons
106
+ @save="onSave(props.row.my_favorite_id, { 'label': $event })">
107
+ <q-input v-model="scope.value" dense autofocus counter @keyup.enter="scope.set" />
108
+ </q-popup-edit>
109
+ </q-td>
110
+ </template>
111
+
112
+
113
+ <template #body-cell-icon="props">
114
+ <q-td :props="props" class="text-center" style="width: 80px;">
115
+ <!-- Icon -->
116
+ <l-icon-picker v-model="props.row.icon" flat round size="sm"
117
+ @update:model-value="onSave(props.row.my_favorite_id, { 'icon': $event })" />
118
+ </q-td>
119
+
120
+ </template>
121
+
122
+ <template #body-cell-handler="props">
123
+ <q-td :props="props" class="drag-handle" style="cursor: move; width: 40px; text-align: center;">
124
+ <q-icon name="drag_handle" />
125
+ </q-td>
126
+ </template>
127
+ <!-- 操作列 -->
128
+ <template #body-cell-actions="props">
129
+ <q-td :props="props">
130
+ <q-btn icon="sym_o_delete" color="negative" flat dense @click="onRemove(props.row.my_favorite_id)"
131
+ :disable="saving">
132
+ <q-tooltip>Delete item</q-tooltip>
133
+ </q-btn>
134
+ </q-td>
135
+ </template>
136
+
137
+ <template #no-data>
138
+ <div class="text-center q-pa-md">
139
+ <l-icon name="sym_o_favorite_border" size="48px" class="text-grey-5" />
140
+ <div class="text-h6 q-mt-sm">No favorites added yet</div>
141
+ <div class="text-subtitle2 text-grey-6 q-mt-xs">
142
+ You can add frequently used features here for quick access.
143
+ </div>
144
+ </div>
145
+ </template>
146
+ </q-table>
147
+
148
+ <!-- 更新按鈕 -->
149
+ <div v-if="hasChanges" class="q-mt-md text-center">
150
+ <q-btn color="primary" icon="save" label="Update Order" @click="updateSequence" :loading="isUpdating"
151
+ :disable="isUpdating" />
152
+ </div>
153
+
154
+ <!-- 狀態提示 -->
155
+ <div v-if="isUpdating" class="q-mt-md">
156
+ <q-linear-progress indeterminate color="primary" />
157
+ <div class="text-center q-mt-sm text-grey-6">Updating order...</div>
158
+ </div>
159
+ </div>
160
+ </template>
161
+
162
+ <style scoped>
163
+ .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:"Drop here";font-size:14px;font-weight:500}
164
+ </style>
@@ -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;
@@ -1,5 +1,4 @@
1
1
  <script setup>
2
- import { reset } from "@formkit/core";
3
2
  import { q, m, notify } from "#imports";
4
3
  const { my } = await q({
5
4
  my: {
@@ -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;
@@ -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="myfav" icon="sym_o_favorite" :label="$t('My favorite')"
28
- to="/User/setting/my_favorite" exact />
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.50.0",
3
+ "version": "1.51.1",
4
4
  "description": "HostLink Nuxt Light Framework",
5
5
  "repository": {
6
6
  "type": "git",