@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.
Files changed (29) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +13 -3
  3. package/dist/runtime/components/l-app-main.d.vue.ts +3 -3
  4. package/dist/runtime/components/l-app-main.vue +16 -6
  5. package/dist/runtime/components/l-app-main.vue.d.ts +3 -3
  6. package/dist/runtime/components/l-select.d.vue.ts +1 -1
  7. package/dist/runtime/components/l-select.vue.d.ts +1 -1
  8. package/dist/runtime/components/l-table.d.vue.ts +7 -5
  9. package/dist/runtime/components/l-table.vue +106 -54
  10. package/dist/runtime/components/l-table.vue.d.ts +7 -5
  11. package/dist/runtime/composables/useLight.d.ts +0 -18
  12. package/dist/runtime/models/User.d.ts +0 -1
  13. package/dist/runtime/models/User.js +0 -1
  14. package/dist/runtime/pages/System/database/check.vue +149 -0
  15. package/dist/runtime/pages/System/database/table.vue +2 -3
  16. package/dist/runtime/pages/Translate/index.vue +5 -5
  17. package/dist/runtime/pages/User/index.vue +5 -5
  18. package/dist/runtime/pages/User/setting/favorite.d.vue.ts +2 -0
  19. package/dist/runtime/pages/User/setting/favorite.vue +164 -0
  20. package/dist/runtime/pages/User/setting/favorite.vue.d.ts +2 -0
  21. package/dist/runtime/pages/User/setting/index.vue +0 -1
  22. package/dist/runtime/pages/User/setting/menu.d.vue.ts +2 -0
  23. package/dist/runtime/pages/User/setting/menu.vue +422 -0
  24. package/dist/runtime/pages/User/setting/menu.vue.d.ts +2 -0
  25. package/dist/runtime/pages/User/setting.vue +4 -2
  26. package/package.json +5 -5
  27. package/dist/runtime/pages/User/setting/my_favorite.vue +0 -156
  28. /package/dist/runtime/pages/{User/setting/my_favorite.d.vue.ts → System/database/check.d.vue.ts} +0 -0
  29. /package/dist/runtime/pages/{User/setting/my_favorite.vue.d.ts → System/database/check.vue.d.ts} +0 -0
@@ -57,7 +57,6 @@ declare const _default: {
57
57
  };
58
58
  has2FA: {
59
59
  label: string;
60
- searchType: string;
61
60
  format: (value: any) => "" | "✔️";
62
61
  };
63
62
  _test2: {
@@ -72,7 +72,6 @@ export default {
72
72
  },
73
73
  has2FA: {
74
74
  label: "2FA",
75
- searchType: "select",
76
75
  format: (value) => {
77
76
  return value ? "\u2714\uFE0F" : "";
78
77
  }
@@ -0,0 +1,149 @@
1
+ <script setup>
2
+ import { ref } from "vue";
3
+ import { q, m, useAsyncData, useQuasar } from "#imports";
4
+ const $q = useQuasar();
5
+ const { data: system, refresh } = await useAsyncData("database-check", async () => {
6
+ const result = await q({
7
+ system: {
8
+ database: {
9
+ checkResult: true
10
+ }
11
+ }
12
+ });
13
+ return result.system;
14
+ });
15
+ const expanded = ref([]);
16
+ const tableColumns = [
17
+ { name: "table", label: "Table", field: "table", align: "left" },
18
+ { name: "status", label: "Status", field: "status", align: "center" },
19
+ { name: "count", label: "Differences", field: (row) => row.differences.length, align: "center" },
20
+ { name: "action", label: "Action", field: "action", align: "center" }
21
+ ];
22
+ const diffColumns = [
23
+ { name: "type", label: "Type", field: "type", align: "left" },
24
+ { name: "column", label: "Column", field: "column", align: "left" },
25
+ { name: "details", label: "Details", field: "details", align: "left" }
26
+ ];
27
+ const getStatusColor = (status) => status === "OK" ? "positive" : "warning";
28
+ const getDiffDetails = (diff) => {
29
+ if (diff.type === "missing_column") {
30
+ return `Missing: ${diff.expected.name} (${diff.expected.type})`;
31
+ }
32
+ if (diff.type === "extra_column") {
33
+ return `Extra: ${diff.current.type}${diff.current.length ? `(${diff.current.length})` : ""}`;
34
+ }
35
+ if (diff.type === "column_mismatch") {
36
+ return Object.entries(diff.differences).map(([key, val]) => `${key}: ${JSON.stringify(val)}`).join(" | ");
37
+ }
38
+ if (diff.current) {
39
+ return JSON.stringify(diff.current).substring(0, 100) + "...";
40
+ }
41
+ return "";
42
+ };
43
+ const getDiffTypeColor = (type) => {
44
+ switch (type) {
45
+ case "missing_column":
46
+ return "negative";
47
+ case "extra_column":
48
+ return "info";
49
+ case "column_mismatch":
50
+ return "warning";
51
+ default:
52
+ return "grey";
53
+ }
54
+ };
55
+ const toggleExpanded = (tableName) => {
56
+ const index = expanded.value.indexOf(tableName);
57
+ if (index === -1) {
58
+ expanded.value.push(tableName);
59
+ } else {
60
+ expanded.value.splice(index, 1);
61
+ }
62
+ };
63
+ const handleFix = async (tableName) => {
64
+ $q.dialog({
65
+ title: "Confirm Fix",
66
+ message: `Are you sure you want to fix the table "${tableName}"? This action cannot be undone.`,
67
+ cancel: true,
68
+ persistent: true
69
+ }).onOk(async () => {
70
+ await m("fixDatabaseTable", { name: tableName });
71
+ await refresh();
72
+ });
73
+ };
74
+ </script>
75
+
76
+ <template>
77
+ <l-page>
78
+
79
+ <div class="q-mb-md">
80
+ <h5 class="q-ma-none">Database Check Results</h5>
81
+ </div>
82
+
83
+ <q-table :rows="system.database.checkResult" :columns="tableColumns" row-key="table" flat bordered
84
+ class="q-mb-lg" v-model:expanded="expanded" :pagination.sync="{ rowsPerPage: 0 }" hide-bottom>
85
+ <template #body="props">
86
+ <q-tr :props="props"
87
+ :class="{ 'bg-red-1': props.row.status === 'DIFFERENT', 'bg-green-1': props.row.status === 'OK' }">
88
+ <q-td auto-width>
89
+ <q-btn size="sm" flat dense round
90
+ :icon="expanded.includes(props.row.table) ? 'expand_less' : 'expand_more'"
91
+ @click="toggleExpanded(props.row.table)" />
92
+ </q-td>
93
+ <q-td key="table" :props="props" class="text-weight-bold">
94
+ {{ props.row.table }}
95
+ </q-td>
96
+ <q-td key="status" :props="props">
97
+ <q-chip :label="props.row.status" :color="getStatusColor(props.row.status)" text-color="white"
98
+ size="sm" />
99
+ </q-td>
100
+ <q-td key="count" :props="props">
101
+ <q-chip v-if="props.row.differences.length > 0" :label="props.row.differences.length"
102
+ color="warning" text-color="white" size="sm" />
103
+ <span v-else class="text-positive">✓</span>
104
+ </q-td>
105
+ <q-td key="action" :props="props">
106
+ <q-btn v-if="props.row.status !== 'OK'" label="Fix" size="sm" :color="$light.color"
107
+ @click="handleFix(props.row.table)" />
108
+ </q-td>
109
+ </q-tr>
110
+
111
+ <!-- Differences Details Expansion -->
112
+ <q-tr v-show="expanded.includes(props.row.table)" :props="props">
113
+ <q-td colspan="100%" class="q-pa-none">
114
+ <div class="q-pa-md bg-grey-1">
115
+ <q-table v-if="props.row.differences.length > 0" :rows="props.row.differences"
116
+ :columns="diffColumns" row-key="column" flat dense class="q-mb-md"
117
+ :pagination.sync="{ rowsPerPage: 0 }" hide-bottom>
118
+ <template #body-cell-type="cellProps">
119
+ <q-td :props="cellProps">
120
+ <q-chip :label="cellProps.row.type"
121
+ :color="getDiffTypeColor(cellProps.row.type)" text-color="white"
122
+ size="sm" />
123
+ </q-td>
124
+ </template>
125
+ <template #body-cell-details="cellProps">
126
+ <q-td :props="cellProps">
127
+ <q-expansion-item header-class="text-caption" expand-icon-class="text-caption"
128
+ dense>
129
+ <template #header>
130
+ <span class="text-caption">{{ getDiffDetails(cellProps.row) }}</span>
131
+ </template>
132
+ <pre class="q-ma-none text-caption">{{
133
+ JSON.stringify(cellProps.row.differences || cellProps.row.current ||
134
+ cellProps.row.expected, null,
135
+ 2) }}
136
+ </pre>
137
+ </q-expansion-item>
138
+ </q-td>
139
+ </template>
140
+ </q-table>
141
+ <div v-else class="text-positive text-weight-bold">✓ No Differences</div>
142
+ </div>
143
+ </q-td>
144
+ </q-tr>
145
+ </template>
146
+ </q-table>
147
+
148
+ </l-page>
149
+ </template>
@@ -64,11 +64,10 @@ const removeField = async (table) => {
64
64
  ok: "Yes",
65
65
  cancel: "No"
66
66
  }).onOk(async () => {
67
- const fields = selected[table].map((field) => field.Field);
68
67
  try {
69
68
  await m("lightDatabaseRemoveFields", {
70
69
  table,
71
- fields
70
+ fields: selected[table].map((field) => field.name)
72
71
  });
73
72
  light.notify({
74
73
  type: "positive",
@@ -218,7 +217,7 @@ const truncatTable = async () => {
218
217
  <q-list separator bordered>
219
218
  <q-expansion-item :label="table.name" v-for="table in data.table" dense>
220
219
  <div class="q-ma-sm">
221
- <q-table row-key="Field" :rows="table.columns" :rows-per-page-options="[0]" hide-pagination flat
220
+ <q-table row-key="name" :rows="table.columns" :rows-per-page-options="[0]" hide-pagination flat
222
221
  bordered selection="multiple" v-model:selected="selected[table.name]" :color="$light.color">
223
222
  <template #top-left>
224
223
  <q-btn icon="sym_o_add" @click="add(table.name)" round flat size="sm">
@@ -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>
@@ -18,21 +18,21 @@ const columns = model("User").columns({
18
18
  email: true,
19
19
  phone: true,
20
20
  join_date: true,
21
- //status: true,
22
21
  has2FA: true
23
- //_test: true,
24
22
  });
23
+ const selectedRows = ref([]);
25
24
  </script>
26
25
 
27
26
  <template>
28
27
  <l-page>
29
28
  <l-tabs route v-model="status">
30
29
  <l-tab label="Active" name="active">
31
- <l-table ref="table" row-key="user_id" @request-data="onRequest" :columns="columns"
32
- :actions="['view', 'update', 'delete']"></l-table>
30
+ <l-table ref="table" row-key="user_id" @request-data="onRequest" :columns="columns" name="user-active"
31
+ :actions="['view', 'update', 'delete']">
32
+ </l-table>
33
33
  </l-tab>
34
34
  <l-tab label="Inactive" name="inactive">
35
- <l-table row-key="user_id" @request-data="onRequest" :columns="columns"
35
+ <l-table row-key="user_id" @request-data="onRequest" :columns="columns" name="user-inactive"
36
36
  :actions="['view', 'update', 'delete']">
37
37
  </l-table>
38
38
  </l-tab>
@@ -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;