adminforth 1.0.69 → 1.0.71

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/auth.ts CHANGED
@@ -43,8 +43,10 @@ class AdminForthAuth {
43
43
  const decoded = jwt.verify(jwtToken, secret);
44
44
  return decoded;
45
45
  } catch (err) {
46
- if (err.name === 'JsonWebTokenError') {
47
- console.error('Token expired', err.message);
46
+ if (err.name === 'TokenExpiredError') {
47
+ console.error('Token expired:', err.message);
48
+ } else if (err.name === 'JsonWebTokenError') {
49
+ console.error('Token error:', err.message);
48
50
  } else {
49
51
  console.error('Failed to verify JWT token', err);
50
52
  }
package/dist/auth.js CHANGED
@@ -46,8 +46,11 @@ class AdminForthAuth {
46
46
  return decoded;
47
47
  }
48
48
  catch (err) {
49
- if (err.name === 'JsonWebTokenError') {
50
- console.error('Token expired', err.message);
49
+ if (err.name === 'TokenExpiredError') {
50
+ console.error('Token expired:', err.message);
51
+ }
52
+ else if (err.name === 'JsonWebTokenError') {
53
+ console.error('Token error:', err.message);
51
54
  }
52
55
  else {
53
56
  console.error('Failed to verify JWT token', err);
package/dist/index.js CHANGED
@@ -789,7 +789,9 @@ class AdminForth {
789
789
  if (record[recordField] !== oldRecord[recordField]) {
790
790
  const column = resource.columns.find((col) => col.name === recordField);
791
791
  if (column) {
792
- newValues[recordField] = connector.setFieldValue(column, record[recordField]);
792
+ if (!column.virtual) {
793
+ newValues[recordField] = connector.setFieldValue(column, record[recordField]);
794
+ }
793
795
  }
794
796
  else {
795
797
  newValues[recordField] = record[recordField];
@@ -90,7 +90,7 @@
90
90
  </svg>
91
91
  </button>
92
92
 
93
- <ul :id="`dropdown-example${i}`" role="none" class="pt-1 space-y-1" :class="{ 'hidden': !item.open }">
93
+ <ul :id="`dropdown-example${i}`" role="none" class="pt-1 space-y-1" :class="{ 'hidden': !opened.includes(i) }">
94
94
  <template v-for="(child, j) in item.children" :key="`menu-${i}-${j}`">
95
95
  <li>
96
96
  <MenuLink :item="child" isChild="true" />
@@ -147,6 +147,8 @@ import { useRoute, useRouter } from 'vue-router';
147
147
  import { getIcon } from '@/utils';
148
148
  import { useHead } from 'unhead'
149
149
  import { createHead } from 'unhead'
150
+ import { loadFile } from './utils';
151
+
150
152
  // import { link } from 'fs';
151
153
  const coreStore = useCoreStore();
152
154
 
@@ -156,20 +158,6 @@ const splitAtLast = (str: string, separator: string) => {
156
158
  return [str.slice(0, index), str.slice(index + 1)];
157
159
  }
158
160
 
159
- const loadFile = (file: string) => {
160
- let path;
161
- let baseUrl = '';
162
- if (file.startsWith('@/')) {
163
- path = file.replace('@/', '');
164
- baseUrl = new URL(`./${path}`, import.meta.url).href;
165
- } else if (file.startsWith('@@/')) {
166
- path = file.replace('@@/', '');
167
- baseUrl = new URL(`./custom/${path}`, import.meta.url).href;
168
- } else {
169
- baseUrl = new URL(`./${file}`, import.meta.url).href;
170
- }
171
- return baseUrl;
172
- }
173
161
 
174
162
 
175
163
  createHead()
@@ -183,8 +171,8 @@ const route = useRoute();
183
171
  watch(route, () => {
184
172
  title.value = `${coreStore.config?.title || coreStore.config?.brandName || 'Adminforth'} | ${ Object.values(route.params)[0] || route.meta.title || ' '}`;
185
173
  useHead({
186
- title: title.value,
187
- })
174
+ title: title.value,
175
+ })
188
176
  });
189
177
 
190
178
  const router = useRouter();
@@ -227,20 +215,27 @@ async function initRouter() {
227
215
  routerIsReady.value = true;
228
216
  }
229
217
 
218
+ async function loadMenu() {
219
+ await coreStore.fetchMenuAndResource()
220
+ loginRedirectCheckIsReady.value = true;
221
+
222
+ coreStore.menu.forEach((item, i) => {
223
+ if (item.open) {
224
+ opened.value.push(i);
225
+ }
226
+ });
227
+ }
228
+
230
229
  // initialize components based on data attribute selectors
231
230
  onMounted(async () => {
232
- initRouter();
233
- setTimeout(() => {
234
- initFlowbite();
235
- }, 100);
236
- await coreStore.fetchMenuAndResource();
237
- loginRedirectCheckIsReady.value = true;
238
-
239
- coreStore.menu.forEach((item, i) => {
240
- if (item.open) {
241
- opened.value.push(i);
242
- }
243
- });
231
+ loadMenu(); // run this in async mode
232
+ // before init flowbite we have to wait router initialized because it affects dom(our v-ifs) and fetch menu
233
+ await initRouter()
234
+ setTimeout(() => {
235
+ initFlowbite();
236
+ });
237
+
238
+
244
239
  })
245
240
 
246
241
  </script>
@@ -67,6 +67,9 @@ const props = defineProps({
67
67
  },
68
68
  label: {
69
69
  type: String,
70
+ },
71
+ autoHide: {
72
+ type: Boolean,
70
73
  }
71
74
  });
72
75
 
@@ -100,8 +103,7 @@ function updateFromProps() {
100
103
  if (!props.valueStart) {
101
104
  datepickerStartEl.value.value = '';
102
105
  startTime.value = '';
103
- }
104
- else {
106
+ } else {
105
107
  datepickerObject.value.setDate(dayjs(props.valueStart).format('DD MMM YYYY'));
106
108
  startTime.value = dayjs(props.valueStart).format('HH:mm:ss')
107
109
  }
@@ -121,7 +123,13 @@ watch(start, () => {
121
123
  })
122
124
 
123
125
  function initDatepickers() {
124
- datepickerObject.value = new Datepicker(datepickerStartEl.value, { format: 'dd M yyyy' });
126
+ const options = {format: 'dd M yyyy'};
127
+
128
+ if (props.autoHide) {
129
+ options.autohide = true;
130
+ }
131
+
132
+ datepickerObject.value = new Datepicker(datepickerStartEl.value, options);
125
133
 
126
134
  addChangeDateListener();
127
135
  }
@@ -134,6 +142,10 @@ function removeChangeDateListener() {
134
142
  datepickerStartEl.value.removeEventListener('changeDate', setStartDate);
135
143
  }
136
144
 
145
+ function destroyDatepickerElement() {
146
+ datepickerObject.value.destroy();
147
+ }
148
+
137
149
  function setStartDate(event) {
138
150
  startDate.value = event.detail.date
139
151
  }
@@ -157,5 +169,6 @@ onMounted(() => {
157
169
 
158
170
  onBeforeUnmount(() => {
159
171
  removeChangeDateListener();
172
+ destroyDatepickerElement();
160
173
  });
161
174
  </script>
@@ -101,6 +101,9 @@ const endDate = ref('');
101
101
  const startTime = ref('');
102
102
  const endTime = ref('');
103
103
 
104
+ const datepickerStartObject = ref('')
105
+ const datepickerEndObject = ref('')
106
+
104
107
  const start = computed(() => {
105
108
  if (!startDate.value) {
106
109
  return;
@@ -162,9 +165,9 @@ watch(end, () => {
162
165
 
163
166
  function initDatepickers() {
164
167
 
165
- new Datepicker(datepickerStartEl.value, {format: 'dd M yyyy'});
168
+ datepickerStartObject.value = new Datepicker(datepickerStartEl.value, {format: 'dd M yyyy'});
166
169
 
167
- new Datepicker(datepickerEndEl.value, {format: 'dd M yyyy'});
170
+ datepickerEndObject.value = new Datepicker(datepickerEndEl.value, {format: 'dd M yyyy'});
168
171
  addChangeDateListener();
169
172
  }
170
173
 
@@ -178,6 +181,11 @@ function removeChangeDateListener() {
178
181
  datepickerEndEl.value.removeEventListener('changeDate', setEndDate);
179
182
  }
180
183
 
184
+ function destroyDatepickerElement() {
185
+ datepickerStartObject.value.destroy();
186
+ datepickerEndObject.value.destroy();
187
+ }
188
+
181
189
  function setStartDate(event) {
182
190
  startDate.value = event.detail.date
183
191
  }
@@ -205,5 +213,6 @@ onMounted(() => {
205
213
 
206
214
  onBeforeUnmount(() => {
207
215
  removeChangeDateListener();
216
+ destroyDatepickerElement();
208
217
  })
209
218
  </script>
@@ -69,7 +69,7 @@
69
69
  >
70
70
 
71
71
  <CustomRangePicker
72
- v-else-if="['integer', 'decimal', 'float'].includes(c.type)"
72
+ v-else-if="['integer', 'decimal', 'float'].includes(c.type) && c.allowMinMaxQuery"
73
73
  :min="c.min"
74
74
  :max="c.max"
75
75
  :valueStart="getFilterItem({ column: c, operator: 'gte' })"
@@ -77,6 +77,23 @@
77
77
  :valueEnd="getFilterItem({ column: c, operator: 'lte' })"
78
78
  @update:valueEnd="setFilterItem({ column: c, operator: 'lte', value: $event || undefined })"
79
79
  />
80
+
81
+ <div v-else-if="['integer', 'decimal', 'float'].includes(c.type)" class="flex gap-2">
82
+ <input
83
+ type="number" aria-describedby="helper-text-explanation"
84
+ class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
85
+ placeholder="From"
86
+ @input="setFilterItem({ column: c, operator: 'gte', value: $event.target.value || undefined })"
87
+ :value="getFilterItem({ column: c, operator: 'gte' })"
88
+ >
89
+ <input
90
+ type="number" aria-describedby="helper-text-explanation"
91
+ class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
92
+ placeholder="To"
93
+ @input="setFilterItem({ column: c, operator: 'lte', value: $event.target.value || undefined})"
94
+ :value="getFilterItem({ column: c, operator: 'lte' })"
95
+ >
96
+ </div>
80
97
 
81
98
  </li>
82
99
  </ul>
@@ -79,6 +79,7 @@
79
79
  v-else-if="['datetime'].includes(column.type)"
80
80
  :column="column"
81
81
  :valueStart="currentValues[column.name]"
82
+ auto-hide
82
83
  @update:valueStart="setCurrentValue(column.name, $event)"
83
84
  />
84
85
  <input
@@ -12,7 +12,6 @@ export const useCoreStore = defineStore('core', () => {
12
12
  const resourceColumnsError = ref('');
13
13
  const resourceColumnsId = ref(null);
14
14
  const user = ref(null);
15
- const flowBitIsInitialised = ref(false);
16
15
 
17
16
  async function fetchMenuAndResource() {
18
17
  const resp = await callAdminForthApi({
@@ -129,7 +128,6 @@ export const useCoreStore = defineStore('core', () => {
129
128
  resourceColumns,
130
129
  fetchResourceFull,
131
130
  resourceColumnsError,
132
- flowBitIsInitialised,
133
131
  resourceOptions,
134
132
  logout
135
133
  }
@@ -43,4 +43,22 @@ export function getIcon(icon: string) {
43
43
  const [iconSet, iconName] = icon.split(':');
44
44
  const compName = 'Icon' + iconName.split('-').map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join('');
45
45
  return resolveComponent(compName);
46
- }
46
+ }
47
+
48
+ export const loadFile = (file: string) => {
49
+ if (file.startsWith('http')) {
50
+ return file;
51
+ }
52
+ let path;
53
+ let baseUrl = '';
54
+ if (file.startsWith('@/')) {
55
+ path = file.replace('@/', '');
56
+ baseUrl = new URL(`./${path}`, import.meta.url).href;
57
+ } else if (file.startsWith('@@/')) {
58
+ path = file.replace('@@/', '');
59
+ baseUrl = new URL(`./custom/${path}`, import.meta.url).href;
60
+ } else {
61
+ baseUrl = new URL(`./${file}`, import.meta.url).href;
62
+ }
63
+ return baseUrl;
64
+ }
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="relative flex items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-800"
3
3
  :style="coreStore.config?.loginBackgroundImage ? {
4
- 'background-image': 'url(' + coreStore.config?.loginBackgroundImage + ')',
4
+ 'background-image': 'url(' + loadFile(coreStore.config?.loginBackgroundImage) + ')',
5
5
  'background-size': 'cover',
6
6
  'background-position': 'center',
7
7
  'background-blend-mode': 'darken'
@@ -81,8 +81,9 @@
81
81
  import { onMounted, ref } from 'vue';
82
82
  import { useCoreStore } from '@/stores/core';
83
83
  import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
84
- import { callAdminForthApi } from '@/utils';
84
+ import { callAdminForthApi, loadFile } from '@/utils';
85
85
  import { useRouter } from 'vue-router';
86
+ import { initFlowbite } from 'flowbite'
86
87
 
87
88
  const router = useRouter();
88
89
  const inProgress = ref(false);
@@ -114,6 +115,10 @@ async function login() {
114
115
  error.value = null;
115
116
  router.push('/');
116
117
  await coreStore.fetchMenuAndResource();
118
+ setTimeout(() => {
119
+ initFlowbite();
120
+ });
121
+
117
122
  }
118
123
 
119
124
  }
package/index.ts CHANGED
@@ -893,7 +893,9 @@ class AdminForth {
893
893
  if (record[recordField] !== oldRecord[recordField]) {
894
894
  const column = resource.columns.find((col) => col.name === recordField);
895
895
  if (column) {
896
- newValues[recordField] = connector.setFieldValue(column, record[recordField]);
896
+ if (!column.virtual) {
897
+ newValues[recordField] = connector.setFieldValue(column, record[recordField]);
898
+ }
897
899
  } else {
898
900
  newValues[recordField] = record[recordField];
899
901
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adminforth",
3
- "version": "1.0.69",
3
+ "version": "1.0.71",
4
4
  "description": "OpenSource Vue3 powered forth-generation admin panel",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/spa/src/App.vue CHANGED
@@ -90,7 +90,7 @@
90
90
  </svg>
91
91
  </button>
92
92
 
93
- <ul :id="`dropdown-example${i}`" role="none" class="pt-1 space-y-1" :class="{ 'hidden': !item.open }">
93
+ <ul :id="`dropdown-example${i}`" role="none" class="pt-1 space-y-1" :class="{ 'hidden': !opened.includes(i) }">
94
94
  <template v-for="(child, j) in item.children" :key="`menu-${i}-${j}`">
95
95
  <li>
96
96
  <MenuLink :item="child" isChild="true" />
@@ -147,6 +147,8 @@ import { useRoute, useRouter } from 'vue-router';
147
147
  import { getIcon } from '@/utils';
148
148
  import { useHead } from 'unhead'
149
149
  import { createHead } from 'unhead'
150
+ import { loadFile } from './utils';
151
+
150
152
  // import { link } from 'fs';
151
153
  const coreStore = useCoreStore();
152
154
 
@@ -156,20 +158,6 @@ const splitAtLast = (str: string, separator: string) => {
156
158
  return [str.slice(0, index), str.slice(index + 1)];
157
159
  }
158
160
 
159
- const loadFile = (file: string) => {
160
- let path;
161
- let baseUrl = '';
162
- if (file.startsWith('@/')) {
163
- path = file.replace('@/', '');
164
- baseUrl = new URL(`./${path}`, import.meta.url).href;
165
- } else if (file.startsWith('@@/')) {
166
- path = file.replace('@@/', '');
167
- baseUrl = new URL(`./custom/${path}`, import.meta.url).href;
168
- } else {
169
- baseUrl = new URL(`./${file}`, import.meta.url).href;
170
- }
171
- return baseUrl;
172
- }
173
161
 
174
162
 
175
163
  createHead()
@@ -183,8 +171,8 @@ const route = useRoute();
183
171
  watch(route, () => {
184
172
  title.value = `${coreStore.config?.title || coreStore.config?.brandName || 'Adminforth'} | ${ Object.values(route.params)[0] || route.meta.title || ' '}`;
185
173
  useHead({
186
- title: title.value,
187
- })
174
+ title: title.value,
175
+ })
188
176
  });
189
177
 
190
178
  const router = useRouter();
@@ -227,20 +215,27 @@ async function initRouter() {
227
215
  routerIsReady.value = true;
228
216
  }
229
217
 
218
+ async function loadMenu() {
219
+ await coreStore.fetchMenuAndResource()
220
+ loginRedirectCheckIsReady.value = true;
221
+
222
+ coreStore.menu.forEach((item, i) => {
223
+ if (item.open) {
224
+ opened.value.push(i);
225
+ }
226
+ });
227
+ }
228
+
230
229
  // initialize components based on data attribute selectors
231
230
  onMounted(async () => {
232
- initRouter();
233
- setTimeout(() => {
234
- initFlowbite();
235
- }, 100);
236
- await coreStore.fetchMenuAndResource();
237
- loginRedirectCheckIsReady.value = true;
238
-
239
- coreStore.menu.forEach((item, i) => {
240
- if (item.open) {
241
- opened.value.push(i);
242
- }
243
- });
231
+ loadMenu(); // run this in async mode
232
+ // before init flowbite we have to wait router initialized because it affects dom(our v-ifs) and fetch menu
233
+ await initRouter()
234
+ setTimeout(() => {
235
+ initFlowbite();
236
+ });
237
+
238
+
244
239
  })
245
240
 
246
241
  </script>
@@ -67,6 +67,9 @@ const props = defineProps({
67
67
  },
68
68
  label: {
69
69
  type: String,
70
+ },
71
+ autoHide: {
72
+ type: Boolean,
70
73
  }
71
74
  });
72
75
 
@@ -100,8 +103,7 @@ function updateFromProps() {
100
103
  if (!props.valueStart) {
101
104
  datepickerStartEl.value.value = '';
102
105
  startTime.value = '';
103
- }
104
- else {
106
+ } else {
105
107
  datepickerObject.value.setDate(dayjs(props.valueStart).format('DD MMM YYYY'));
106
108
  startTime.value = dayjs(props.valueStart).format('HH:mm:ss')
107
109
  }
@@ -121,7 +123,13 @@ watch(start, () => {
121
123
  })
122
124
 
123
125
  function initDatepickers() {
124
- datepickerObject.value = new Datepicker(datepickerStartEl.value, { format: 'dd M yyyy' });
126
+ const options = {format: 'dd M yyyy'};
127
+
128
+ if (props.autoHide) {
129
+ options.autohide = true;
130
+ }
131
+
132
+ datepickerObject.value = new Datepicker(datepickerStartEl.value, options);
125
133
 
126
134
  addChangeDateListener();
127
135
  }
@@ -134,6 +142,10 @@ function removeChangeDateListener() {
134
142
  datepickerStartEl.value.removeEventListener('changeDate', setStartDate);
135
143
  }
136
144
 
145
+ function destroyDatepickerElement() {
146
+ datepickerObject.value.destroy();
147
+ }
148
+
137
149
  function setStartDate(event) {
138
150
  startDate.value = event.detail.date
139
151
  }
@@ -157,5 +169,6 @@ onMounted(() => {
157
169
 
158
170
  onBeforeUnmount(() => {
159
171
  removeChangeDateListener();
172
+ destroyDatepickerElement();
160
173
  });
161
174
  </script>
@@ -101,6 +101,9 @@ const endDate = ref('');
101
101
  const startTime = ref('');
102
102
  const endTime = ref('');
103
103
 
104
+ const datepickerStartObject = ref('')
105
+ const datepickerEndObject = ref('')
106
+
104
107
  const start = computed(() => {
105
108
  if (!startDate.value) {
106
109
  return;
@@ -162,9 +165,9 @@ watch(end, () => {
162
165
 
163
166
  function initDatepickers() {
164
167
 
165
- new Datepicker(datepickerStartEl.value, {format: 'dd M yyyy'});
168
+ datepickerStartObject.value = new Datepicker(datepickerStartEl.value, {format: 'dd M yyyy'});
166
169
 
167
- new Datepicker(datepickerEndEl.value, {format: 'dd M yyyy'});
170
+ datepickerEndObject.value = new Datepicker(datepickerEndEl.value, {format: 'dd M yyyy'});
168
171
  addChangeDateListener();
169
172
  }
170
173
 
@@ -178,6 +181,11 @@ function removeChangeDateListener() {
178
181
  datepickerEndEl.value.removeEventListener('changeDate', setEndDate);
179
182
  }
180
183
 
184
+ function destroyDatepickerElement() {
185
+ datepickerStartObject.value.destroy();
186
+ datepickerEndObject.value.destroy();
187
+ }
188
+
181
189
  function setStartDate(event) {
182
190
  startDate.value = event.detail.date
183
191
  }
@@ -205,5 +213,6 @@ onMounted(() => {
205
213
 
206
214
  onBeforeUnmount(() => {
207
215
  removeChangeDateListener();
216
+ destroyDatepickerElement();
208
217
  })
209
218
  </script>
@@ -69,7 +69,7 @@
69
69
  >
70
70
 
71
71
  <CustomRangePicker
72
- v-else-if="['integer', 'decimal', 'float'].includes(c.type)"
72
+ v-else-if="['integer', 'decimal', 'float'].includes(c.type) && c.allowMinMaxQuery"
73
73
  :min="c.min"
74
74
  :max="c.max"
75
75
  :valueStart="getFilterItem({ column: c, operator: 'gte' })"
@@ -77,6 +77,23 @@
77
77
  :valueEnd="getFilterItem({ column: c, operator: 'lte' })"
78
78
  @update:valueEnd="setFilterItem({ column: c, operator: 'lte', value: $event || undefined })"
79
79
  />
80
+
81
+ <div v-else-if="['integer', 'decimal', 'float'].includes(c.type)" class="flex gap-2">
82
+ <input
83
+ type="number" aria-describedby="helper-text-explanation"
84
+ class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
85
+ placeholder="From"
86
+ @input="setFilterItem({ column: c, operator: 'gte', value: $event.target.value || undefined })"
87
+ :value="getFilterItem({ column: c, operator: 'gte' })"
88
+ >
89
+ <input
90
+ type="number" aria-describedby="helper-text-explanation"
91
+ class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
92
+ placeholder="To"
93
+ @input="setFilterItem({ column: c, operator: 'lte', value: $event.target.value || undefined})"
94
+ :value="getFilterItem({ column: c, operator: 'lte' })"
95
+ >
96
+ </div>
80
97
 
81
98
  </li>
82
99
  </ul>
@@ -79,6 +79,7 @@
79
79
  v-else-if="['datetime'].includes(column.type)"
80
80
  :column="column"
81
81
  :valueStart="currentValues[column.name]"
82
+ auto-hide
82
83
  @update:valueStart="setCurrentValue(column.name, $event)"
83
84
  />
84
85
  <input
@@ -12,7 +12,6 @@ export const useCoreStore = defineStore('core', () => {
12
12
  const resourceColumnsError = ref('');
13
13
  const resourceColumnsId = ref(null);
14
14
  const user = ref(null);
15
- const flowBitIsInitialised = ref(false);
16
15
 
17
16
  async function fetchMenuAndResource() {
18
17
  const resp = await callAdminForthApi({
@@ -129,7 +128,6 @@ export const useCoreStore = defineStore('core', () => {
129
128
  resourceColumns,
130
129
  fetchResourceFull,
131
130
  resourceColumnsError,
132
- flowBitIsInitialised,
133
131
  resourceOptions,
134
132
  logout
135
133
  }
package/spa/src/utils.ts CHANGED
@@ -43,4 +43,22 @@ export function getIcon(icon: string) {
43
43
  const [iconSet, iconName] = icon.split(':');
44
44
  const compName = 'Icon' + iconName.split('-').map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join('');
45
45
  return resolveComponent(compName);
46
- }
46
+ }
47
+
48
+ export const loadFile = (file: string) => {
49
+ if (file.startsWith('http')) {
50
+ return file;
51
+ }
52
+ let path;
53
+ let baseUrl = '';
54
+ if (file.startsWith('@/')) {
55
+ path = file.replace('@/', '');
56
+ baseUrl = new URL(`./${path}`, import.meta.url).href;
57
+ } else if (file.startsWith('@@/')) {
58
+ path = file.replace('@@/', '');
59
+ baseUrl = new URL(`./custom/${path}`, import.meta.url).href;
60
+ } else {
61
+ baseUrl = new URL(`./${file}`, import.meta.url).href;
62
+ }
63
+ return baseUrl;
64
+ }
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div class="relative flex items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-800"
3
3
  :style="coreStore.config?.loginBackgroundImage ? {
4
- 'background-image': 'url(' + coreStore.config?.loginBackgroundImage + ')',
4
+ 'background-image': 'url(' + loadFile(coreStore.config?.loginBackgroundImage) + ')',
5
5
  'background-size': 'cover',
6
6
  'background-position': 'center',
7
7
  'background-blend-mode': 'darken'
@@ -81,8 +81,9 @@
81
81
  import { onMounted, ref } from 'vue';
82
82
  import { useCoreStore } from '@/stores/core';
83
83
  import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
84
- import { callAdminForthApi } from '@/utils';
84
+ import { callAdminForthApi, loadFile } from '@/utils';
85
85
  import { useRouter } from 'vue-router';
86
+ import { initFlowbite } from 'flowbite'
86
87
 
87
88
  const router = useRouter();
88
89
  const inProgress = ref(false);
@@ -114,6 +115,10 @@ async function login() {
114
115
  error.value = null;
115
116
  router.push('/');
116
117
  await coreStore.fetchMenuAndResource();
118
+ setTimeout(() => {
119
+ initFlowbite();
120
+ });
121
+
117
122
  }
118
123
 
119
124
  }