@hostlink/nuxt-light 1.36.0 → 1.37.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.
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "light",
3
3
  "configKey": "light",
4
- "version": "1.36.0",
4
+ "version": "1.37.1",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.1",
7
7
  "unbuild": "3.5.0"
@@ -2,40 +2,23 @@
2
2
  import { q, m } from "#imports";
3
3
  import { useQuasar } from "quasar";
4
4
  const $q = useQuasar();
5
- const modelValue = defineModel({ type: Object, ...{
6
- required: true
7
- } });
8
- if (modelValue.value.mode != "prod") {
9
- modelValue.value.mode = "dev";
10
- } else {
11
- modelValue.value.mode = "prod";
12
- }
13
- if (modelValue.value.custom_field_models) {
14
- modelValue.value.custom_field_models = modelValue.value.custom_field_models.split(",");
15
- } else {
16
- modelValue.value.custom_field_models = [];
17
- }
5
+ const props = defineProps({
6
+ mode: { type: String, required: true, default: "dev" },
7
+ custom_field_models: { type: String, required: true, default: "" }
8
+ });
9
+ const emit = defineEmits(["submit"]);
10
+ const processedMode = props.mode !== "prod" ? "dev" : "prod";
11
+ const processedCustomFieldModels = props.custom_field_models ? props.custom_field_models.split(",") : [];
18
12
  const onSubmit = async (d) => {
19
- let data = [];
20
- Object.keys(d).forEach((key) => {
21
- data.push({
22
- name: key,
23
- value: d[key]
24
- });
25
- });
26
- await m("updateAppConfigs", { data });
27
- Object.keys(d).forEach((key) => {
28
- modelValue.value[key] = d[key];
29
- });
30
- $q.notify({ message: "Settings saved", color: "positive" });
13
+ emit("submit", d);
31
14
  };
32
15
  </script>
33
16
 
34
17
  <template>
35
18
  <FormKit type="l-form" :bordered="false" :value="{
36
- mode: modelValue.mode,
37
- custom_field_models: modelValue.custom_field_models
38
- }" @submit="onSubmit" #default="{ value }">
19
+ mode: processedMode,
20
+ custom_field_models: processedCustomFieldModels
21
+ }" @submit="onSubmit">
39
22
 
40
23
  <form-kit type="l-select" name="custom_field_models" label="Custom Field Model" use-input use-chips multiple
41
24
  hide-dropdown-icon input-debounce="0" new-value-mode="add-unique" />
@@ -2,12 +2,12 @@ export interface LSystemSettingDeveloperProps {
2
2
  mode: string;
3
3
  custom_field_models: string;
4
4
  }
5
- type __VLS_PublicProps = {
6
- modelValue: LSystemSettingDeveloperProps;
7
- };
8
- declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
9
- "update:modelValue": (value: LSystemSettingDeveloperProps) => any;
10
- }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
11
- "onUpdate:modelValue"?: ((value: LSystemSettingDeveloperProps) => any) | undefined;
12
- }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
5
+ declare const _default: import("vue").DefineComponent<LSystemSettingDeveloperProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
+ submit: (data: Record<string, any>) => any;
7
+ }, string, import("vue").PublicProps, Readonly<LSystemSettingDeveloperProps> & Readonly<{
8
+ onSubmit?: ((data: Record<string, any>) => any) | undefined;
9
+ }>, {
10
+ mode: string;
11
+ custom_field_models: string;
12
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
13
  export default _default;
@@ -1,43 +1,23 @@
1
1
  <script setup>
2
- import { q, m } from "#imports";
3
- import { useQuasar } from "quasar";
4
- const $q = useQuasar();
5
- const modelValue = defineModel({ type: Object, ...{
6
- required: true
7
- } });
8
- if (modelValue.value.forget_password_enabled != "0") {
9
- modelValue.value.forget_password_enabled = "1";
10
- }
11
- if (!modelValue.value.forget_password_subject) {
12
- modelValue.value.forget_password_subject = "Password Reset Code";
13
- }
14
- if (!modelValue.value.forget_password_template) {
15
- modelValue.value.forget_password_template = "Your password reset code is: {code}";
16
- }
2
+ const props = defineProps({
3
+ forget_password_enabled: { type: String, required: false, default: "0" },
4
+ forget_password_subject: { type: String, required: false, default: "" },
5
+ forget_password_template: { type: String, required: false, default: "" }
6
+ });
7
+ const emit = defineEmits(["submit"]);
8
+ const processedEnabled = props.forget_password_enabled !== "0" ? "1" : props.forget_password_enabled;
9
+ const processedSubject = props.forget_password_subject || "Password Reset Code";
10
+ const processedTemplate = props.forget_password_template || "Your password reset code is: {code}";
17
11
  const onSubmit = async (d) => {
18
- let data = [];
19
- Object.keys(d).forEach((key) => {
20
- if (d[key] === void 0) {
21
- d[key] = "";
22
- }
23
- data.push({
24
- name: key,
25
- value: d[key]
26
- });
27
- });
28
- await m("updateAppConfigs", { data });
29
- modelValue.value.forget_password_enabled = d.forget_password_enabled;
30
- modelValue.value.forget_password_subject = d.forget_password_subject;
31
- modelValue.value.forget_password_template = d.forget_password_template;
32
- $q.notify({ message: "Settings saved", color: "positive" });
12
+ emit("submit", d);
33
13
  };
34
14
  </script>
35
15
 
36
16
  <template>
37
17
  <form-kit type="l-form" :value="{
38
- forget_password_enabled: modelValue.forget_password_enabled,
39
- forget_password_subject: modelValue.forget_password_subject,
40
- forget_password_template: modelValue.forget_password_template
18
+ forget_password_enabled: processedEnabled,
19
+ forget_password_subject: processedSubject,
20
+ forget_password_template: processedTemplate
41
21
  }" @submit="onSubmit">
42
22
  <form-kit type="l-checkbox" label="Forget Password Enabled" name="forget_password_enabled" true-value="1"
43
23
  false-value="0" />
@@ -1,14 +1,15 @@
1
1
  export type LSystemSettingForgetPasswordProps = {
2
+ forget_password_enabled?: string;
3
+ forget_password_subject?: string;
4
+ forget_password_template?: string;
5
+ };
6
+ declare const _default: import("vue").DefineComponent<LSystemSettingForgetPasswordProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
7
+ submit: (data: Record<string, any>) => any;
8
+ }, string, import("vue").PublicProps, Readonly<LSystemSettingForgetPasswordProps> & Readonly<{
9
+ onSubmit?: ((data: Record<string, any>) => any) | undefined;
10
+ }>, {
2
11
  forget_password_enabled: string;
3
12
  forget_password_subject: string;
4
13
  forget_password_template: string;
5
- };
6
- type __VLS_PublicProps = {
7
- modelValue: LSystemSettingForgetPasswordProps;
8
- };
9
- declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
10
- "update:modelValue": (value: LSystemSettingForgetPasswordProps) => any;
11
- }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
12
- "onUpdate:modelValue"?: ((value: LSystemSettingForgetPasswordProps) => any) | undefined;
13
- }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
14
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
14
15
  export default _default;
@@ -1,30 +1,23 @@
1
1
  <script setup>
2
- import { q, m } from "#imports";
3
2
  import { useQuasar } from "quasar";
4
- const $q = useQuasar();
5
- const modelValue = defineModel({ type: Object });
3
+ const props = defineProps({
4
+ company: { type: String, required: false, default: "" },
5
+ company_logo: { type: String, required: false, default: "" },
6
+ copyright_name: { type: String, required: false, default: "" },
7
+ copyright_year: { type: String, required: false, default: "" }
8
+ });
9
+ const emit = defineEmits(["submit"]);
6
10
  const onSubmit = async (d, form) => {
7
- let data = [];
8
- Object.keys(d).forEach((key) => {
9
- data.push({
10
- name: key,
11
- value: d[key]
12
- });
13
- });
14
- await m("updateAppConfigs", { data });
15
- Object.keys(d).forEach((key) => {
16
- modelValue.value[key] = d[key];
17
- });
18
- $q.notify({ message: "Settings saved", color: "positive" });
11
+ emit("submit", d);
19
12
  };
20
13
  </script>
21
14
 
22
15
  <template>
23
16
  <FormKit type="l-form" :bordered="false" :value="{
24
- company: modelValue.company,
25
- company_logo: modelValue.company_logo,
26
- copyright_name: modelValue.copyright_name,
27
- copyright_year: modelValue.copyright_year
17
+ company: props.company,
18
+ company_logo: props.company_logo,
19
+ copyright_name: props.copyright_name,
20
+ copyright_year: props.copyright_year
28
21
  }" @submit="onSubmit">
29
22
 
30
23
  <FormKit type="l-input" label="Company" name="company" validation="required"></FormKit>
@@ -1,24 +1,17 @@
1
- type __VLS_PublicProps = {
2
- modelValue?: {
3
- company: string;
4
- company_logo: string;
5
- copyright_name: string;
6
- copyright_year: string;
7
- };
8
- };
9
- declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
10
- "update:modelValue": (value: {
11
- company: string;
12
- company_logo: string;
13
- copyright_name: string;
14
- copyright_year: string;
15
- } | undefined) => any;
16
- }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
17
- "onUpdate:modelValue"?: ((value: {
18
- company: string;
19
- company_logo: string;
20
- copyright_name: string;
21
- copyright_year: string;
22
- } | undefined) => any) | undefined;
23
- }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
1
+ interface Props {
2
+ company?: string;
3
+ company_logo?: string;
4
+ copyright_name?: string;
5
+ copyright_year?: string;
6
+ }
7
+ declare const _default: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
8
+ submit: (data: Record<string, any>) => any;
9
+ }, string, import("vue").PublicProps, Readonly<Props> & Readonly<{
10
+ onSubmit?: ((data: Record<string, any>) => any) | undefined;
11
+ }>, {
12
+ company: string;
13
+ company_logo: string;
14
+ copyright_name: string;
15
+ copyright_year: string;
16
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
24
17
  export default _default;
@@ -21,30 +21,23 @@ const obj = await collect("User", {
21
21
  </script>
22
22
 
23
23
  <template>
24
- <l-card>
25
- <q-card-section>
26
- <l-row>
27
- <l-col md="6">
28
- <q-list dense separator>
29
- <l-item label="Username">{{ obj.username }}</l-item>
30
- <l-item label="First name">{{ obj.first_name }}</l-item>
31
- <l-item label="Last name">{{ obj.last_name }}</l-item>
32
- <l-item label="Email">{{ obj.email }}</l-item>
33
- <l-item label="Phone">{{ obj.phone }}</l-item>
34
- <l-item label="Roles">
35
- <div class="q-gutter-xs float-left">
36
- <q-badge v-for="role in obj.roles" :key="role" :color="$light.color">{{ role
37
- }}</q-badge>
38
- </div>
39
- </l-item>
40
- <l-item label="Status">{{ model("User").columns(["status"])[0].format(obj.status) }}</l-item>
41
- <l-item label="Join date">{{ obj.join_date }}</l-item>
42
- <l-item label="Two factor authentication">{{ obj.twoFactorEnabled ? "Yes" : "No"
43
- }}</l-item>
44
- </q-list>
45
- </l-col>
46
-
47
- </l-row>
48
- </q-card-section>
49
- </l-card>
24
+ <div class="row q-gutter-md">
25
+ <q-list dense separator class="col-md-6 col-sm-12">
26
+ <l-item label="Username">{{ obj.username }}</l-item>
27
+ <l-item label="First name">{{ obj.first_name }}</l-item>
28
+ <l-item label="Last name">{{ obj.last_name }}</l-item>
29
+ <l-item label="Email">{{ obj.email }}</l-item>
30
+ <l-item label="Phone">{{ obj.phone }}</l-item>
31
+ <l-item label="Roles">
32
+ <div class="q-gutter-xs float-left">
33
+ <q-badge v-for="role in obj.roles" :key="role" :color="$light.color">{{ role
34
+ }}</q-badge>
35
+ </div>
36
+ </l-item>
37
+ <l-item label="Status">{{ model("User").columns(["status"])[0].format(obj.status) }}</l-item>
38
+ <l-item label="Join date">{{ obj.join_date }}</l-item>
39
+ <l-item label="Two factor authentication">{{ obj.twoFactorEnabled ? "Yes" : "No"
40
+ }}</l-item>
41
+ </q-list>
42
+ </div>
50
43
  </template>
@@ -183,7 +183,7 @@ const onToggleFav = async () => {
183
183
  message: "Enter favorite label",
184
184
  prompt: {
185
185
  //get window title, remove - and replace with space
186
- model: route.name.replace(/-/g, " "),
186
+ model: String(route.name || "Untitled").replace(/-/g, " "),
187
187
  isValid: (val) => val !== ""
188
188
  },
189
189
  cancel: true,
@@ -237,12 +237,10 @@ const onLogout = async () => {
237
237
  </template>
238
238
  </q-toolbar-title>
239
239
 
240
-
241
- <slot name="header" ></slot>
242
-
243
-
244
240
  <q-space />
245
241
 
242
+ <slot name="header"></slot>
243
+
246
244
  <q-btn :icon="isFav ? 'favorite' : 'sym_o_favorite'" round flat dense class="q-mr-xs"
247
245
  @click="onToggleFav" v-if="app.hasFavorite">
248
246
  </q-btn>
@@ -293,11 +291,11 @@ const onLogout = async () => {
293
291
  <q-btn flat round dense icon="sym_o_person" class="q-mr-sm">
294
292
  <q-menu max-width="250px">
295
293
  <q-list>
296
- <q-item v-close-popup to="/User/profile" >
294
+ <q-item v-close-popup to="/User/profile">
297
295
  <q-item-section avatar>
298
296
  <q-icon name="sym_o_person" />
299
297
  </q-item-section>
300
- <q-item-section >{{ $t("Profile") }}</q-item-section>
298
+ <q-item-section>{{ $t("Profile") }}</q-item-section>
301
299
  </q-item>
302
300
 
303
301
  <q-item v-close-popup to="/User/setting">
@@ -365,12 +363,12 @@ const onLogout = async () => {
365
363
  <q-expansion-item expand-separator :label="error.message">
366
364
  <q-card>
367
365
  <q-card-section style="white-space: pre-wrap; overflow: auto;">
368
- {{ error.stack }}
366
+ {{ error.stack }}
369
367
  </q-card-section>
370
368
  </q-card>
371
369
  </q-expansion-item>
372
370
 
373
- <template v-slot:action>
371
+ <template v-slot:action>
374
372
  <q-btn flat icon="sym_o_close" round dense @click="$light.removeError(error)" />
375
373
  </template>
376
374
  </q-banner>
@@ -1,6 +1,6 @@
1
- declare var __VLS_26: {};
1
+ declare var __VLS_30: {};
2
2
  type __VLS_Slots = {} & {
3
- header?: (props: typeof __VLS_26) => any;
3
+ header?: (props: typeof __VLS_30) => any;
4
4
  };
5
5
  declare const __VLS_component: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
6
  logout: (...args: any[]) => void;
@@ -19,7 +19,7 @@ const color2 = computed(() => {
19
19
 
20
20
  <template>
21
21
  <q-expansion-item :label="$t('My favorite')" :dense="dense" icon="sym_o_favorite">
22
- <q-list class="q-pl-md menu-list">
22
+ <q-list class="q-pl-md menu-list" :dense="dense">
23
23
  <q-item v-ripple :to="item.path" v-for="item in value">
24
24
  <q-item-section avatar>
25
25
  <q-icon :name="item.icon ?? 'sym_o_link'" />
@@ -115,15 +115,21 @@ const normalizeOption = (option) => {
115
115
  return option;
116
116
  };
117
117
  const normalizeOptions = (options) => {
118
+ if (!options) {
119
+ return [];
120
+ }
118
121
  if (Array.isArray(options)) {
119
122
  return options.map((option) => normalizeOption(option));
120
123
  }
121
- return Object.keys(options).map((value) => ({
122
- label: options[value],
123
- value
124
- }));
124
+ if (typeof options === "object") {
125
+ return Object.keys(options).map((value) => ({
126
+ label: options[value],
127
+ value
128
+ }));
129
+ }
130
+ return [];
125
131
  };
126
- const select_options = ref(props.options);
132
+ const select_options = ref(normalizeOptions(props.options || []));
127
133
  const filterFn = (val, update, abort) => {
128
134
  if (props.onFilter) {
129
135
  props.onFilter(val, update, abort);
@@ -271,12 +271,13 @@ const onLocalRequest = async (p) => {
271
271
  let c = collect(model2, builder.get());
272
272
  if (localFilters) {
273
273
  for (let [key, value] of Object.entries(localFilters)) {
274
- if (typeof value == "object") {
275
- if (value.contains) {
276
- c = c.whereContains(key, value.contains);
274
+ if (typeof value == "object" && value !== null) {
275
+ const filterValue = value;
276
+ if (filterValue.contains) {
277
+ c = c.whereContains(key, filterValue.contains);
277
278
  }
278
- if (value.between) {
279
- c = c.whereBetween(key, value.between);
279
+ if (filterValue.between) {
280
+ c = c.whereBetween(key, filterValue.between);
280
281
  }
281
282
  } else {
282
283
  c = c.where(key, value);
@@ -454,12 +455,19 @@ const localSearchOptions = ref({});
454
455
  const searchSelectFilter = (val, update, name) => {
455
456
  if (val == "") {
456
457
  update(() => {
457
- localSearchOptions.value[name] = props.columns.find((col) => col.name == name).searchOptions;
458
+ const column = props.columns?.find((col) => col.name == name);
459
+ localSearchOptions.value[name] = column?.searchOptions;
458
460
  });
459
461
  } else {
460
462
  update(() => {
461
463
  const needle = val.toLocaleLowerCase();
462
- localSearchOptions.value[name] = props.columns.find((col) => col.name == name).searchOptions.filter((v) => v.label.toLocaleLowerCase().indexOf(needle) > -1);
464
+ const column = props.columns?.find((col) => col.name == name);
465
+ const options = column?.searchOptions;
466
+ if (Array.isArray(options)) {
467
+ localSearchOptions.value[name] = options.filter((v) => v.label.toLocaleLowerCase().indexOf(needle) > -1);
468
+ } else {
469
+ localSearchOptions.value[name] = options;
470
+ }
463
471
  console.log(localSearchOptions.value[name]);
464
472
  });
465
473
  }
@@ -51,7 +51,7 @@ export interface LTableRequest {
51
51
  data: any;
52
52
  }>;
53
53
  meta: {
54
- total: Number;
54
+ total: number;
55
55
  key: string;
56
56
  name: string;
57
57
  };
@@ -268,10 +268,10 @@ declare const light: {
268
268
  align: string;
269
269
  left: string;
270
270
  right: string;
271
- bold: string;
272
271
  code: string;
273
272
  hr: string;
274
273
  center: string;
274
+ bold: string;
275
275
  fontSize: string;
276
276
  url: string;
277
277
  italic: string;
@@ -364,11 +364,11 @@ declare const light: {
364
364
  align: string;
365
365
  left: string;
366
366
  right: string;
367
- bold: string;
368
367
  code: string;
369
368
  hr: string;
370
369
  center: string;
371
370
  font: string;
371
+ bold: string;
372
372
  fontSize: string;
373
373
  italic: string;
374
374
  strikethrough: string;
@@ -872,10 +872,10 @@ declare const _default: () => {
872
872
  align: string;
873
873
  left: string;
874
874
  right: string;
875
- bold: string;
876
875
  code: string;
877
876
  hr: string;
878
877
  center: string;
878
+ bold: string;
879
879
  fontSize: string;
880
880
  url: string;
881
881
  italic: string;
@@ -968,11 +968,11 @@ declare const _default: () => {
968
968
  align: string;
969
969
  left: string;
970
970
  right: string;
971
- bold: string;
972
971
  code: string;
973
972
  hr: string;
974
973
  center: string;
975
974
  font: string;
975
+ bold: string;
976
976
  fontSize: string;
977
977
  italic: string;
978
978
  strikethrough: string;
@@ -165,7 +165,8 @@ const light = reactive({
165
165
  my_favorite_id: true,
166
166
  label: true,
167
167
  path: true,
168
- icon: true
168
+ icon: true,
169
+ sequence: true
169
170
  }
170
171
  }
171
172
  });
@@ -55,12 +55,12 @@ const onSubmit = async (d) => {
55
55
  </q-tabs>
56
56
  </template>
57
57
  <template #after>
58
- <l-system-setting-general v-if="tab == 'general'" v-model="obj" />
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
61
  <l-system-setting-modules v-if="tab == 'Modules'" v-bind="obj" @submit="onSubmit" />
62
- <l-system-setting-developer v-if="tab == 'developer'" v-model="obj" />
63
- <l-system-setting-forget-password v-if="tab == 'forget-password'" v-model="obj" />
62
+ <l-system-setting-developer v-if="tab == 'developer'" v-bind="obj" @submit="onSubmit" />
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" />
65
65
 
66
66
 
@@ -5,7 +5,6 @@ import { ref } from "vue";
5
5
  const route = useRoute();
6
6
  const { data: obj } = await useAsyncObject({ canUpdate: true, canDelete: true });
7
7
  const light = useLight();
8
- const tab = ref("overview");
9
8
  const id = route.params.user_id;
10
9
  const reset2fa = async () => {
11
10
  await light.dialog({
@@ -43,19 +42,19 @@ const reset2fa = async () => {
43
42
  </template>
44
43
 
45
44
  <q-card flat bordered>
46
- <q-tabs v-model="tab" :active-color="$light.color" inline-label align="justify">
47
- <q-tab name="overview" icon="sym_o_person" :label="$t('Overview')" />
48
- <q-tab name="userlog" icon="sym_o_description" :label="$t('User log')"
49
- v-if="$light.isGranted('userlog.list')" />
50
- <q-tab name="eventlog" icon="sym_o_description" :label="$t('Event log')"
51
- v-if="$light.isGranted('eventlog.list')" />
52
- </q-tabs>
53
-
54
- <l-user-overview v-if="tab === 'overview'" :id="id" />
55
- <l-user-userlog v-if="tab === 'userlog'" :id="id" />
56
- <l-user-eventlog v-if="tab === 'eventlog'" :id="id" />
57
-
58
-
45
+ <l-tabs :active-color="$light.color" inline-label align="justify">
46
+ <l-tab name="overview" icon="sym_o_person" :label="$t('Overview')">
47
+ <l-user-overview :id="id" />
48
+ </l-tab>
49
+ <l-tab name="userlog" icon="sym_o_description" :label="$t('User log')"
50
+ v-if="$light.isGranted('userlog.list')">
51
+ <l-user-userlog :id="id" />
52
+ </l-tab>
53
+ <l-tab name="eventlog" icon="sym_o_description" :label="$t('Event log')"
54
+ v-if="$light.isGranted('eventlog.list')">
55
+ <l-user-eventlog :id="id" />
56
+ </l-tab>
57
+ </l-tabs>
59
58
 
60
59
  </q-card>
61
60
 
@@ -1,15 +1,62 @@
1
1
  <script setup>
2
2
  import { useLight, model, q } from "#imports";
3
- import { ref } from "vue";
4
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";
5
7
  const light = useLight();
6
8
  const $q = useQuasar();
7
- const columns = [
8
- { name: "_delete", align: "center" },
9
- { name: "label", label: "Label", align: "left" },
10
- { name: "path", label: "Path", align: "left" },
11
- { name: "icon", label: "Icon", align: "left" }
12
- ];
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 });
13
60
  const onSave = async (id, data) => {
14
61
  await model("MyFavorite").update(id, data);
15
62
  $q.notify({
@@ -17,50 +64,93 @@ const onSave = async (id, data) => {
17
64
  color: "positive",
18
65
  icon: "check"
19
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
+ }
20
71
  await light.reloadMyFavorites();
21
72
  };
22
73
  const onRemove = async (id) => {
23
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
+ }
24
79
  await light.reloadMyFavorites();
25
80
  };
26
81
  </script>
27
82
 
28
83
  <template>
29
- <q-table :columns="columns" :rows="$light.myFavorites" :rows-per-page-options="[0]">
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>
30
96
 
31
- <template #body-cell-_delete="props">
32
- <q-td auto-width>
33
- <q-btn flat icon="sym_o_delete" round @click="onRemove(props.row.my_favorite_id)"></q-btn>
34
- </q-td>
35
- </template>
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>
36
129
 
37
- <template #body-cell-label="props">
38
- <q-td key="label">
39
- {{ props.row.label }}
40
- <q-popup-edit v-model="props.row.label" #default="scope" buttons
41
- @save="onSave(props.row.my_favorite_id, { 'label': $event })">
42
- <q-input v-model="scope.value" dense autofocus counter @keyup.enter="scope.set" />
43
- </q-popup-edit>
44
- </q-td>
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>
45
145
 
46
- </template>
47
-
48
- <template #body-cell-path="props">
49
- <q-td key="path">
50
- {{ props.row.path }}
51
- <q-popup-edit v-model="props.row.path" #default="scope" buttons
52
- @save="onSave(props.row.my_favorite_id, { 'path': $event })">
53
- <q-input v-model="scope.value" dense autofocus counter @keyup.enter="scope.set" />
54
- </q-popup-edit>
55
- </q-td>
56
- </template>
57
-
58
- <template #body-cell-icon="props">
59
- <q-td key="icon">
60
- <l-icon-picker v-model="props.row.icon" flat round @update:model-value="onSave(
61
- props.row.my_favorite_id, { 'icon': $event })"></l-icon-picker>
62
- </q-td>
63
- </template>
64
-
65
- </q-table>
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>
66
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>
@@ -24,8 +24,10 @@ my = reactive(my);
24
24
  const handleGoogleCredentialResponse = async (response) => {
25
25
  try {
26
26
  await api.auth.google.register(response.credential);
27
- const resp = await q("my", ["gmail"]);
28
- my.google = resp.google;
27
+ const resp = await q({
28
+ my: { google: true }
29
+ });
30
+ my.google = resp.my.google;
29
31
  light.notify({
30
32
  message: "Google account linked",
31
33
  color: "positive"
@@ -47,7 +47,7 @@ declare const _default: {
47
47
  has2FA: {
48
48
  label: string;
49
49
  searchType: string;
50
- format: (value: any) => "Yes" | "No";
50
+ format: (value: any) => "" | "✔️";
51
51
  };
52
52
  _test: {
53
53
  label: string;
@@ -61,7 +61,7 @@ export default {
61
61
  label: "2FA",
62
62
  searchType: "select",
63
63
  format: (value) => {
64
- return value ? "Yes" : "No";
64
+ return value ? "\u2714\uFE0F" : "";
65
65
  }
66
66
  },
67
67
  _test: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hostlink/nuxt-light",
3
- "version": "1.36.0",
3
+ "version": "1.37.1",
4
4
  "description": "HostLink Nuxt Light Framework",
5
5
  "repository": {
6
6
  "type": "git",