@fy-/fws-vue 0.0.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/components/fws/CmsArticleBoxed.vue +113 -0
- package/components/fws/CmsArticleSingle.vue +115 -0
- package/components/fws/DataTable.vue +260 -0
- package/components/fws/FilterData.vue +179 -0
- package/components/fws/UserFlow.vue +305 -0
- package/components/ssr/ClientOnly.ts +12 -0
- package/components/ui/DefaultBreadcrumb.vue +75 -0
- package/components/ui/DefaultConfirm.vue +69 -0
- package/components/ui/DefaultDateSelection.vue +56 -0
- package/components/ui/DefaultInput.vue +243 -0
- package/components/ui/DefaultLoader.vue +49 -0
- package/components/ui/DefaultModal.vue +90 -0
- package/components/ui/DefaultPaging.vue +212 -0
- package/components/ui/transitions/CollapseTransition.vue +30 -0
- package/components/ui/transitions/ExpandTransition.vue +32 -0
- package/components/ui/transitions/FadeTransition.vue +17 -0
- package/components/ui/transitions/ScaleTransition.vue +35 -0
- package/components/ui/transitions/SlideTransition.vue +127 -0
- package/components.d.ts +22 -0
- package/env.d.ts +6 -0
- package/event-bus.ts +14 -0
- package/index.ts +121 -0
- package/package.json +43 -0
- package/rest.ts +72 -0
- package/seo.ts +114 -0
- package/ssr.ts +97 -0
- package/stores/rest.ts +24 -0
- package/stores/serverRouter.ts +49 -0
- package/stores/user.ts +81 -0
- package/style.css +42 -0
- package/templating.ts +79 -0
- package/translations.ts +29 -0
- package/types.d.ts +4 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { LinkIcon } from "@heroicons/vue/24/solid";
|
|
3
|
+
import { computed, ref, toRef } from "vue";
|
|
4
|
+
import type { ErrorObject } from "@vuelidate/core";
|
|
5
|
+
import { useTranslation } from "../../translations";
|
|
6
|
+
type modelValueType = string | number | string[] | undefined;
|
|
7
|
+
type checkboxValueType = any[] | Set<any> | undefined | boolean;
|
|
8
|
+
const props = withDefaults(
|
|
9
|
+
defineProps<{
|
|
10
|
+
id: string;
|
|
11
|
+
showLabel?: boolean;
|
|
12
|
+
label?: string;
|
|
13
|
+
type?: string;
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
autocomplete?: string;
|
|
16
|
+
checkboxTrueValue?: string | boolean;
|
|
17
|
+
checkboxFalseValue?: string | boolean;
|
|
18
|
+
req?: boolean;
|
|
19
|
+
linkIcon?: string;
|
|
20
|
+
modelValue?: modelValueType;
|
|
21
|
+
checkboxValue?: checkboxValueType;
|
|
22
|
+
options?: string[][];
|
|
23
|
+
help?: string;
|
|
24
|
+
error?: string;
|
|
25
|
+
errorVuelidate?: ErrorObject[];
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
}>(),
|
|
28
|
+
{
|
|
29
|
+
showLabel: true,
|
|
30
|
+
type: "text",
|
|
31
|
+
req: false,
|
|
32
|
+
options: () => [],
|
|
33
|
+
checkboxTrueValue: true,
|
|
34
|
+
checkboxFalseValue: false,
|
|
35
|
+
disabled: false,
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
const translate = useTranslation();
|
|
39
|
+
const inputRef = ref<HTMLInputElement>();
|
|
40
|
+
const errorProps = toRef(props, "error");
|
|
41
|
+
const errorVuelidateProps = toRef(props, "errorVuelidate");
|
|
42
|
+
|
|
43
|
+
const checkErrors = computed(() => {
|
|
44
|
+
if (errorProps.value) return errorProps.value;
|
|
45
|
+
if (errorVuelidateProps.value && errorVuelidateProps.value.length > 0) {
|
|
46
|
+
const err = `vuelidate_validator_${errorVuelidateProps.value[0].$validator.toString()}`;
|
|
47
|
+
return translate(err);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return null;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const focus = () => {
|
|
54
|
+
if (inputRef.value) inputRef.value.focus();
|
|
55
|
+
};
|
|
56
|
+
const getInputRef = () => {
|
|
57
|
+
if (inputRef.value) return inputRef.value;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const emit = defineEmits(["update:modelValue", "update:checkboxValue"]);
|
|
61
|
+
const model = computed({
|
|
62
|
+
get: () => props.modelValue,
|
|
63
|
+
set: (items) => {
|
|
64
|
+
emit("update:modelValue", items);
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
const modelCheckbox = computed({
|
|
68
|
+
get: () => props.checkboxValue,
|
|
69
|
+
set: (items) => {
|
|
70
|
+
emit("update:checkboxValue", items);
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
defineExpose({ focus, getInputRef });
|
|
74
|
+
</script>
|
|
75
|
+
<template>
|
|
76
|
+
<div>
|
|
77
|
+
<template
|
|
78
|
+
v-if="
|
|
79
|
+
[
|
|
80
|
+
'text',
|
|
81
|
+
'password',
|
|
82
|
+
'email',
|
|
83
|
+
'search',
|
|
84
|
+
'date',
|
|
85
|
+
'datetime',
|
|
86
|
+
'url',
|
|
87
|
+
'textarea',
|
|
88
|
+
'select',
|
|
89
|
+
'phone',
|
|
90
|
+
].includes(type)
|
|
91
|
+
"
|
|
92
|
+
>
|
|
93
|
+
<label
|
|
94
|
+
:for="id"
|
|
95
|
+
v-if="label"
|
|
96
|
+
class="fws-label"
|
|
97
|
+
:class="{
|
|
98
|
+
error: checkErrors,
|
|
99
|
+
}"
|
|
100
|
+
>
|
|
101
|
+
{{ label }} <span v-if="req" class="text-red-700">*</span>
|
|
102
|
+
</label>
|
|
103
|
+
<input
|
|
104
|
+
v-if="
|
|
105
|
+
[
|
|
106
|
+
'text',
|
|
107
|
+
'password',
|
|
108
|
+
'email',
|
|
109
|
+
'search',
|
|
110
|
+
'date',
|
|
111
|
+
'datetime',
|
|
112
|
+
'url',
|
|
113
|
+
].includes(type)
|
|
114
|
+
"
|
|
115
|
+
class="fws-input"
|
|
116
|
+
:class="{
|
|
117
|
+
error: checkErrors,
|
|
118
|
+
}"
|
|
119
|
+
ref="inputRef"
|
|
120
|
+
:aria-describedby="label"
|
|
121
|
+
:autocomplete="autocomplete"
|
|
122
|
+
:id="id"
|
|
123
|
+
v-model="model"
|
|
124
|
+
:type="type"
|
|
125
|
+
:disabled="disabled"
|
|
126
|
+
:required="req"
|
|
127
|
+
:placeholder="placeholder"
|
|
128
|
+
/>
|
|
129
|
+
<textarea
|
|
130
|
+
:aria-describedby="label"
|
|
131
|
+
ref="inputRef"
|
|
132
|
+
v-if="type == 'textarea'"
|
|
133
|
+
class="fws-textarea"
|
|
134
|
+
:class="{
|
|
135
|
+
error: checkErrors,
|
|
136
|
+
}"
|
|
137
|
+
:autocomplete="autocomplete"
|
|
138
|
+
:id="id"
|
|
139
|
+
v-model="model"
|
|
140
|
+
:disabled="disabled"
|
|
141
|
+
:required="req"
|
|
142
|
+
:placeholder="placeholder"
|
|
143
|
+
></textarea>
|
|
144
|
+
<select
|
|
145
|
+
:aria-describedby="label"
|
|
146
|
+
:disabled="disabled"
|
|
147
|
+
v-if="type == 'select'"
|
|
148
|
+
:required="req"
|
|
149
|
+
v-model="model"
|
|
150
|
+
:id="id"
|
|
151
|
+
ref="inputRef"
|
|
152
|
+
:class="{
|
|
153
|
+
error: checkErrors,
|
|
154
|
+
}"
|
|
155
|
+
class="fws-select"
|
|
156
|
+
>
|
|
157
|
+
<option v-for="opt in options" :value="opt[0]" :key="opt[0].toString()">
|
|
158
|
+
{{ opt[1] }}
|
|
159
|
+
</option>
|
|
160
|
+
</select>
|
|
161
|
+
</template>
|
|
162
|
+
|
|
163
|
+
<template v-if="type == 'checkbox'">
|
|
164
|
+
<div class="fws-checkbox">
|
|
165
|
+
<input
|
|
166
|
+
:aria-describedby="label"
|
|
167
|
+
:id="id"
|
|
168
|
+
ref="inputRef"
|
|
169
|
+
:true-value="checkboxTrueValue"
|
|
170
|
+
:false-value="checkboxFalseValue"
|
|
171
|
+
v-model="modelCheckbox"
|
|
172
|
+
type="checkbox"
|
|
173
|
+
value=""
|
|
174
|
+
/>
|
|
175
|
+
<label
|
|
176
|
+
:for="id"
|
|
177
|
+
v-if="label"
|
|
178
|
+
:class="{
|
|
179
|
+
error: checkErrors,
|
|
180
|
+
}"
|
|
181
|
+
>{{ label }}
|
|
182
|
+
<a class="fws-link" :href="linkIcon" target="_blank" v-if="linkIcon">
|
|
183
|
+
<LinkIcon class="w-4 h-4" />
|
|
184
|
+
</a>
|
|
185
|
+
</label>
|
|
186
|
+
</div>
|
|
187
|
+
</template>
|
|
188
|
+
<p v-if="checkErrors" class="mt-2 text-xs fws-error-text">
|
|
189
|
+
{{ checkErrors }}
|
|
190
|
+
</p>
|
|
191
|
+
|
|
192
|
+
<p
|
|
193
|
+
class="fws-input-helper"
|
|
194
|
+
v-if="help && !['checkbox', 'radio'].includes(type)"
|
|
195
|
+
>
|
|
196
|
+
{{ help }}
|
|
197
|
+
</p>
|
|
198
|
+
</div>
|
|
199
|
+
</template>
|
|
200
|
+
<style scoped>
|
|
201
|
+
.fws-label {
|
|
202
|
+
@apply block mb-2 text-sm font-medium text-fv-neutral-900 dark:text-white;
|
|
203
|
+
|
|
204
|
+
&.error {
|
|
205
|
+
@apply text-red-700 dark:text-red-500;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
.fws-input-helper {
|
|
209
|
+
@apply mt-2 text-sm text-fv-neutral-500 dark:text-fv-neutral-400;
|
|
210
|
+
}
|
|
211
|
+
.fws-input {
|
|
212
|
+
@apply shadow-sm bg-fv-neutral-50 border border-fv-neutral-300 text-fv-neutral-900 text-sm rounded-lg focus:ring-fv-primary-500 focus:border-fv-primary-500 block w-full p-2.5 dark:bg-fv-neutral-700 dark:border-fv-neutral-600 dark:placeholder-fv-neutral-400 dark:text-white dark:focus:ring-fv-primary-500 dark:focus:border-fv-primary-500;
|
|
213
|
+
&.error {
|
|
214
|
+
@apply bg-red-50 border border-red-500 text-red-900 placeholder-red-700 text-sm rounded-lg focus:ring-red-500 focus:border-red-500 block w-full p-2.5 dark:bg-red-100 dark:border-red-400;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
.fws-textarea {
|
|
218
|
+
@apply block p-2.5 w-full text-sm text-fv-neutral-900 bg-fv-neutral-50 rounded-lg border border-fv-neutral-300 focus:ring-fv-primary-500 focus:border-fv-primary-500 dark:bg-fv-neutral-700 dark:border-fv-neutral-600 dark:placeholder-fv-neutral-400 dark:text-white dark:focus:ring-fv-primary-500 dark:focus:border-fv-primary-500;
|
|
219
|
+
&.error {
|
|
220
|
+
@apply bg-red-50 border border-red-500 text-red-900 placeholder-red-700 text-sm rounded-lg focus:ring-red-500 focus:border-red-500 block w-full p-2.5 dark:bg-red-100 dark:border-red-400;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.fws-select {
|
|
225
|
+
@apply bg-fv-neutral-50 border border-fv-neutral-300 text-fv-neutral-900 text-sm rounded-lg focus:ring-fv-primary-500 focus:border-fv-primary-500 block w-full p-2.5 dark:bg-fv-neutral-700 dark:border-fv-neutral-600 dark:placeholder-fv-neutral-400 dark:text-white dark:focus:ring-fv-primary-500 dark:focus:border-fv-primary-500;
|
|
226
|
+
&.error {
|
|
227
|
+
@apply bg-red-50 border border-red-500 text-red-900 placeholder-red-700 text-sm rounded-lg focus:ring-red-500 focus:border-red-500 block w-full p-2.5 dark:bg-red-100 dark:border-red-400;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
.fws-checkbox {
|
|
231
|
+
@apply flex items-center mb-4;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.fws-checkbox input {
|
|
235
|
+
@apply w-4 h-4 text-fv-primary-600 bg-fv-neutral-100 border-fv-neutral-300 rounded focus:ring-fv-primary-500 dark:focus:ring-fv-primary-600 dark:ring-offset-fv-neutral-800 dark:focus:ring-offset-fv-neutral-800 focus:ring-2 dark:bg-fv-neutral-700 dark:border-fv-neutral-600;
|
|
236
|
+
&.error {
|
|
237
|
+
@apply bg-red-50 border border-red-500 text-red-900 placeholder-red-700 text-sm rounded-lg focus:ring-red-500 focus:border-red-500 block w-full p-2.5 dark:bg-red-100 dark:border-red-400;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
.fws-checkbox label {
|
|
241
|
+
@apply ml-2 text-sm font-medium text-fv-neutral-900 dark:text-fv-neutral-300;
|
|
242
|
+
}
|
|
243
|
+
</style>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onMounted, onUnmounted, ref } from "vue";
|
|
3
|
+
import type { Component } from "vue";
|
|
4
|
+
import { useEventBus } from "../../event-bus";
|
|
5
|
+
|
|
6
|
+
const props = withDefaults(
|
|
7
|
+
defineProps<{
|
|
8
|
+
image: string;
|
|
9
|
+
force?: boolean;
|
|
10
|
+
id?: string;
|
|
11
|
+
}>(),
|
|
12
|
+
{
|
|
13
|
+
force: false,
|
|
14
|
+
id: "",
|
|
15
|
+
},
|
|
16
|
+
);
|
|
17
|
+
const eventBus = useEventBus();
|
|
18
|
+
const loading = ref<boolean>(false);
|
|
19
|
+
function setLoading(value: boolean) {
|
|
20
|
+
loading.value = value;
|
|
21
|
+
}
|
|
22
|
+
onMounted(() => {
|
|
23
|
+
if (props.id) eventBus.on(`${props.id}-loading`, setLoading);
|
|
24
|
+
else eventBus.on("loading", setLoading);
|
|
25
|
+
});
|
|
26
|
+
onUnmounted(() => {
|
|
27
|
+
if (props.id) eventBus.off(`${props.id}-loading`, setLoading);
|
|
28
|
+
else eventBus.off("loading", setLoading);
|
|
29
|
+
});
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<div
|
|
34
|
+
v-if="loading || force"
|
|
35
|
+
class="flex-grow flex flex-col bg-fv-neutral-800/[.8] items-center justify-center absolute inset-0 z-50"
|
|
36
|
+
>
|
|
37
|
+
<div class="text-center animate-pulse w-40 h-40 p-4 rounded-full shadow">
|
|
38
|
+
<div class="relative flex flex-col items-center">
|
|
39
|
+
<div class="flex items-center justify-center flex-col">
|
|
40
|
+
<img
|
|
41
|
+
:src="image"
|
|
42
|
+
:alt="$t('global_loading')"
|
|
43
|
+
class="w-full h-full relative"
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</template>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
Dialog,
|
|
4
|
+
DialogPanel,
|
|
5
|
+
DialogTitle,
|
|
6
|
+
TransitionRoot,
|
|
7
|
+
} from "@headlessui/vue";
|
|
8
|
+
import { ref, onMounted, onUnmounted, h } from "vue";
|
|
9
|
+
import { XCircleIcon } from "@heroicons/vue/24/solid";
|
|
10
|
+
import { useEventBus } from "../../event-bus";
|
|
11
|
+
const props = withDefaults(
|
|
12
|
+
defineProps<{
|
|
13
|
+
id: string;
|
|
14
|
+
title?: string;
|
|
15
|
+
onOpen?: Function;
|
|
16
|
+
onClose?: Function;
|
|
17
|
+
closeIcon?: Object;
|
|
18
|
+
}>(),
|
|
19
|
+
{
|
|
20
|
+
closeIcon: () => h(XCircleIcon),
|
|
21
|
+
},
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const eventBus = useEventBus();
|
|
25
|
+
|
|
26
|
+
const isOpen = ref<boolean>(false);
|
|
27
|
+
const setModal = (value: boolean) => {
|
|
28
|
+
if (value === true) {
|
|
29
|
+
if (props.onOpen) props.onOpen();
|
|
30
|
+
} else {
|
|
31
|
+
if (props.onClose) props.onClose();
|
|
32
|
+
}
|
|
33
|
+
isOpen.value = value;
|
|
34
|
+
};
|
|
35
|
+
onMounted(() => {
|
|
36
|
+
eventBus.on(`${props.id}Modal`, setModal);
|
|
37
|
+
});
|
|
38
|
+
onUnmounted(() => {
|
|
39
|
+
eventBus.off(`${props.id}Modal`, setModal);
|
|
40
|
+
});
|
|
41
|
+
</script>
|
|
42
|
+
<template>
|
|
43
|
+
<TransitionRoot
|
|
44
|
+
:show="isOpen"
|
|
45
|
+
as="template"
|
|
46
|
+
enter="duration-300 ease-out"
|
|
47
|
+
enter-from="opacity-0"
|
|
48
|
+
enter-to="opacity-100"
|
|
49
|
+
leave="duration-200 ease-in"
|
|
50
|
+
leave-from="opacity-100"
|
|
51
|
+
leave-to="opacity-0"
|
|
52
|
+
>
|
|
53
|
+
<Dialog
|
|
54
|
+
:open="isOpen"
|
|
55
|
+
@close="setModal"
|
|
56
|
+
class="fixed inset-0 overflow-y-auto"
|
|
57
|
+
style="z-index: 40"
|
|
58
|
+
>
|
|
59
|
+
<DialogPanel
|
|
60
|
+
class="flex flex-col items-center justify-center min-h-screen text-fv-neutral-800 dark:text-fv-neutral-300 bg-fv-neutral-900/[.20] dark:bg-fv-neutral-50/[.20]"
|
|
61
|
+
style="z-index: 41"
|
|
62
|
+
>
|
|
63
|
+
<div
|
|
64
|
+
class="relative w-full max-w-2xl max-h-full bg-white rounded-lg shadow dark:bg-gray-700"
|
|
65
|
+
>
|
|
66
|
+
<div
|
|
67
|
+
class="flex items-center justify-between p-2 w-full border-b rounded-t dark:border-gray-600"
|
|
68
|
+
>
|
|
69
|
+
<slot name="before"></slot>
|
|
70
|
+
<DialogTitle
|
|
71
|
+
class="text-xl font-semibold text-gray-900 dark:text-white"
|
|
72
|
+
v-if="title"
|
|
73
|
+
>
|
|
74
|
+
{{ title }}
|
|
75
|
+
</DialogTitle>
|
|
76
|
+
<button
|
|
77
|
+
@click="setModal(false)"
|
|
78
|
+
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ml-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white"
|
|
79
|
+
>
|
|
80
|
+
<component :is="closeIcon" class="w-7 h-7" />
|
|
81
|
+
</button>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="p-3 space-y-3">
|
|
84
|
+
<slot></slot>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</DialogPanel>
|
|
88
|
+
</Dialog>
|
|
89
|
+
</TransitionRoot>
|
|
90
|
+
</template>
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/vue/24/solid";
|
|
3
|
+
import {
|
|
4
|
+
watch,
|
|
5
|
+
onUnmounted,
|
|
6
|
+
ref,
|
|
7
|
+
WatchStopHandle,
|
|
8
|
+
onMounted,
|
|
9
|
+
computed,
|
|
10
|
+
} from "vue";
|
|
11
|
+
import type { APIPaging } from "../../rest";
|
|
12
|
+
import { useEventBus } from "../../event-bus";
|
|
13
|
+
import { useServerRouter } from "../../stores/serverRouter";
|
|
14
|
+
import { useRoute } from "vue-router";
|
|
15
|
+
import { useFyHead } from "@fy-/head";
|
|
16
|
+
import { hasFW, getURL } from "@fy-/fws-js";
|
|
17
|
+
const props = defineProps<{
|
|
18
|
+
items: APIPaging;
|
|
19
|
+
id: string;
|
|
20
|
+
}>();
|
|
21
|
+
const route = useRoute();
|
|
22
|
+
const eventBus = useEventBus();
|
|
23
|
+
const history = useServerRouter();
|
|
24
|
+
const prevNextSeo = ref<any>({});
|
|
25
|
+
const isNewPage = (page: number) => {
|
|
26
|
+
return (
|
|
27
|
+
page >= 1 && page <= props.items.page_max && page != props.items.page_no
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
const pageWatcher = ref<WatchStopHandle>();
|
|
31
|
+
|
|
32
|
+
const next = () => {
|
|
33
|
+
const page = props.items.page_no + 1;
|
|
34
|
+
|
|
35
|
+
if (!isNewPage(page)) return;
|
|
36
|
+
|
|
37
|
+
history.push({
|
|
38
|
+
path: history.currentRoute.path,
|
|
39
|
+
query: { page: page.toString() },
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
const prev = () => {
|
|
43
|
+
const page = props.items.page_no - 1;
|
|
44
|
+
if (!isNewPage(page)) return;
|
|
45
|
+
|
|
46
|
+
history.push({
|
|
47
|
+
path: history.currentRoute.path,
|
|
48
|
+
query: { page: page.toString() },
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
const page = (page: number) => {
|
|
52
|
+
if (!isNewPage(page)) return;
|
|
53
|
+
|
|
54
|
+
history.push({
|
|
55
|
+
path: history.currentRoute.path,
|
|
56
|
+
query: { page: page.toString() },
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const checkPageNumber = (page: number = 1) => {
|
|
61
|
+
prevNextSeo.value.next = undefined;
|
|
62
|
+
prevNextSeo.value.prev = undefined;
|
|
63
|
+
|
|
64
|
+
if (hasFW()) {
|
|
65
|
+
const url = getURL();
|
|
66
|
+
if (page + 1 <= props.items.page_max && url) {
|
|
67
|
+
prevNextSeo.value.next = `${url.Scheme}://${url.Host}${url.Path}?page=${
|
|
68
|
+
page + 1
|
|
69
|
+
}`;
|
|
70
|
+
}
|
|
71
|
+
if (page - 1 >= 1 && url) {
|
|
72
|
+
prevNextSeo.value.prev = `${url.Scheme}://${url.Host}${url.Path}?page=${
|
|
73
|
+
page - 1
|
|
74
|
+
}`;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
pageWatcher.value = watch(
|
|
80
|
+
() => route.query.page,
|
|
81
|
+
(v) => {
|
|
82
|
+
eventBus.emit(`${props.id}GoToPage`, v ? v : 1);
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
onMounted(() => {
|
|
86
|
+
eventBus.on(`${props.id}GoToPage`, checkPageNumber);
|
|
87
|
+
});
|
|
88
|
+
onUnmounted(() => {
|
|
89
|
+
eventBus.off(`${props.id}GoToPage`, checkPageNumber);
|
|
90
|
+
//if (pageWatcher.value) pageWatcher.value();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
checkPageNumber(props.items.page_no);
|
|
94
|
+
useFyHead({
|
|
95
|
+
links: computed(() => {
|
|
96
|
+
const result: any = [];
|
|
97
|
+
if (prevNextSeo.value.next)
|
|
98
|
+
result.push({
|
|
99
|
+
href: prevNextSeo.value.next,
|
|
100
|
+
rel: "next",
|
|
101
|
+
key: "next",
|
|
102
|
+
});
|
|
103
|
+
if (prevNextSeo.value.prev)
|
|
104
|
+
result.push({
|
|
105
|
+
href: prevNextSeo.value.prev,
|
|
106
|
+
rel: "prev",
|
|
107
|
+
key: "prev",
|
|
108
|
+
});
|
|
109
|
+
return result;
|
|
110
|
+
}),
|
|
111
|
+
});
|
|
112
|
+
</script>
|
|
113
|
+
<template>
|
|
114
|
+
<div
|
|
115
|
+
class="flex items-center justify-center"
|
|
116
|
+
v-if="items && items.page_max > 1 && items.page_no"
|
|
117
|
+
>
|
|
118
|
+
<div class="paging-container">
|
|
119
|
+
<nav
|
|
120
|
+
aria-label="Pagination"
|
|
121
|
+
class="relative z-0 inline-flex rounded-md shadow-sm -space-x-px"
|
|
122
|
+
>
|
|
123
|
+
<a
|
|
124
|
+
href="javascript:void(0);"
|
|
125
|
+
@click="prev()"
|
|
126
|
+
v-if="items.page_no >= 2"
|
|
127
|
+
class="relative inline-flex items-center px-2 pt-2 pb-1.5 border text-sm font-medium border-fv-neutral-300 bg-fv-neutral-100 text-fv-neutral-500 hover:bg-fv-neutral-200 hover:text-fv-neutral-600 dark:border-fv-neutral-600 dark:bg-fv-neutral-800 dark:text-fv-neutral-200 dark:hover:bg-fv-neutral-800 dark:hover:text-fv-neutral-100"
|
|
128
|
+
>
|
|
129
|
+
<span class="sr-only">{{ $t("previous_paging") }}</span>
|
|
130
|
+
<ChevronLeftIcon class="w-4 h-4" />
|
|
131
|
+
</a>
|
|
132
|
+
<a
|
|
133
|
+
v-if="items.page_no - 2 > 1"
|
|
134
|
+
class="relative inline-flex items-center px-4 pt-2 pb-1.5 border text-sm font-medium bg-fv-neutral-100 border-fv-neutral-300 text-fv-neutral-500 hover:bg-fv-neutral-200 hover:text-fv-neutral-600 dark:bg-fv-neutral-800 dark:border-fv-neutral-600 dark:text-fv-neutral-200 dark:hover:bg-fv-neutral-800 dark:hover:text-fv-neutral-100"
|
|
135
|
+
href="javascript:void(0);"
|
|
136
|
+
@click="page(1)"
|
|
137
|
+
>
|
|
138
|
+
1
|
|
139
|
+
</a>
|
|
140
|
+
<span
|
|
141
|
+
v-if="items.page_no - 2 > 2"
|
|
142
|
+
class="relative inline-flex items-center px-2 pt-2 pb-1.5 border text-sm font-medium border-fv-neutral-300 bg-fv-neutral-100 text-fv-neutral-700 hover:text-fv-neutral-600 dark:border-fv-neutral-600 dark:bg-fv-neutral-800 dark:text-fv-neutral-700 dark:hover:text-fv-neutral-100"
|
|
143
|
+
>
|
|
144
|
+
...
|
|
145
|
+
</span>
|
|
146
|
+
<template v-for="i in 2">
|
|
147
|
+
<a
|
|
148
|
+
v-if="items.page_no - (3 - i) >= 1"
|
|
149
|
+
class="relative inline-flex items-center px-4 pt-2 pb-1.5 border text-sm font-medium bg-fv-neutral-100 border-fv-neutral-300 text-fv-neutral-500 hover:bg-fv-neutral-200 hover:text-fv-neutral-600 dark:bg-fv-neutral-800 dark:border-fv-neutral-600 dark:text-fv-neutral-200 dark:hover:bg-fv-neutral-800 dark:hover:text-fv-neutral-100"
|
|
150
|
+
href="javascript:void(0);"
|
|
151
|
+
:key="`${i}-sm`"
|
|
152
|
+
@click="page(items.page_no - (3 - i))"
|
|
153
|
+
>
|
|
154
|
+
{{ items.page_no - (3 - i) }}
|
|
155
|
+
</a>
|
|
156
|
+
</template>
|
|
157
|
+
<a
|
|
158
|
+
href="#"
|
|
159
|
+
aria-current="page"
|
|
160
|
+
class="z-10 relative inline-flex items-center px-4 pt-2 pb-1.5 border text-sm font-medium font-bold bg-fv-primary-50 border-fv-primary-500 text-fv-primary-600 hover:text-fv-neutral-600 dark:bg-fv-primary-900 dark:border-fv-primary-500 dark:text-fv-primary-200 dark:hover:text-fv-neutral-100"
|
|
161
|
+
>
|
|
162
|
+
{{ items.page_no }}
|
|
163
|
+
</a>
|
|
164
|
+
<template v-for="i in 2">
|
|
165
|
+
<a
|
|
166
|
+
v-if="items.page_no + i <= items.page_max"
|
|
167
|
+
class="relative inline-flex items-center px-4 pt-2 pb-1.5 border text-sm font-medium bg-fv-neutral-100 border-fv-neutral-300 text-fv-neutral-500 hover:bg-fv-neutral-200 hover:text-fv-neutral-600 dark:bg-fv-neutral-800 dark:border-fv-neutral-600 dark:text-fv-neutral-200 dark:hover:bg-fv-neutral-800 dark:hover:text-fv-neutral-100"
|
|
168
|
+
href="javascript:void(0);"
|
|
169
|
+
:key="`${i}-md`"
|
|
170
|
+
@click="page(items.page_no + i)"
|
|
171
|
+
>
|
|
172
|
+
{{ items.page_no + i }}
|
|
173
|
+
</a>
|
|
174
|
+
</template>
|
|
175
|
+
<span
|
|
176
|
+
v-if="items.page_no + 2 < items.page_max - 1"
|
|
177
|
+
class="relative inline-flex items-center px-2 pt-2 pb-1.5 border text-sm font-medium border-fv-neutral-300 bg-fv-neutral-100 text-fv-neutral-700 hover:text-fv-neutral-600 dark:border-fv-neutral-600 dark:bg-fv-neutral-800 dark:text-fv-neutral-700 dark:hover:text-fv-neutral-100"
|
|
178
|
+
>
|
|
179
|
+
...
|
|
180
|
+
</span>
|
|
181
|
+
<a
|
|
182
|
+
v-if="items.page_no + 2 < items.page_max"
|
|
183
|
+
class="relative inline-flex items-center px-4 pt-2 pb-1.5 border text-sm font-medium bg-fv-neutral-100 border-fv-neutral-300 text-fv-neutral-500 hover:bg-fv-neutral-200 hover:text-fv-neutral-600 dark:bg-fv-neutral-800 dark:border-fv-neutral-600 dark:text-fv-neutral-200 dark:hover:bg-fv-neutral-800 dark:hover:text-fv-neutral-100"
|
|
184
|
+
href="javascript:void(0);"
|
|
185
|
+
@click="page(items.page_max)"
|
|
186
|
+
>
|
|
187
|
+
{{ items.page_max }}
|
|
188
|
+
</a>
|
|
189
|
+
<a
|
|
190
|
+
href="javascript:void(0);"
|
|
191
|
+
@click="next()"
|
|
192
|
+
v-if="items.page_no < items.page_max - 1"
|
|
193
|
+
class="relative inline-flex items-center px-2 pt-2 pb-1.5 border text-sm font-medium border-fv-neutral-300 bg-fv-neutral-100 text-fv-neutral-500 hover:bg-fv-neutral-200 hover:text-fv-neutral-600 dark:border-fv-neutral-600 dark:bg-fv-neutral-800 dark:text-fv-neutral-200 dark:hover:bg-fv-neutral-800 dark:hover:text-fv-neutral-100"
|
|
194
|
+
>
|
|
195
|
+
<span class="sr-only">{{ $t("next_paging") }}</span>
|
|
196
|
+
<ChevronRightIcon class="w-4 h-4" />
|
|
197
|
+
</a>
|
|
198
|
+
</nav>
|
|
199
|
+
<p
|
|
200
|
+
class="text-xs text-center italic mt-0.5 text-fv-neutral-700 dark:text-fv-neutral-300"
|
|
201
|
+
>
|
|
202
|
+
{{
|
|
203
|
+
$t("global_paging", {
|
|
204
|
+
start: items.results_per_page * (items.page_no - 1),
|
|
205
|
+
end: items.results_per_page * items.page_no,
|
|
206
|
+
total: items.count >= 10000 ? $t("paging_a_lot_of") : items.count,
|
|
207
|
+
})
|
|
208
|
+
}}
|
|
209
|
+
</p>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
</template>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Transition name="collapse">
|
|
3
|
+
<slot></slot>
|
|
4
|
+
</Transition>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<style scoped>
|
|
8
|
+
.collapse-enter-active {
|
|
9
|
+
animation: collapse reverse 300ms ease;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.collapse-leave-active {
|
|
13
|
+
animation: collapse 300ms ease;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@keyframes collapse {
|
|
17
|
+
100% {
|
|
18
|
+
max-height: 0px;
|
|
19
|
+
opacity: 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
50% {
|
|
23
|
+
max-height: 400px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
0% {
|
|
27
|
+
opacity: 1;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
</style>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Transition name="expand">
|
|
3
|
+
<slot></slot>
|
|
4
|
+
</Transition>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<style scoped>
|
|
8
|
+
.expand-enter-active {
|
|
9
|
+
animation: expand reverse 300ms ease;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.expand-leave-active {
|
|
13
|
+
animation: expand 300ms ease;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@keyframes expand {
|
|
17
|
+
100% {
|
|
18
|
+
max-height: 0px;
|
|
19
|
+
opacity: 0;
|
|
20
|
+
transform: scale(0.9);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
50% {
|
|
24
|
+
max-height: 400px;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
0% {
|
|
28
|
+
transform: scale(1);
|
|
29
|
+
opacity: 1;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Transition name="fade" mode="out-in">
|
|
3
|
+
<slot></slot>
|
|
4
|
+
</Transition>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<style scoped>
|
|
8
|
+
.fade-enter-active,
|
|
9
|
+
.fade-leave-active {
|
|
10
|
+
transition: opacity 0.2s ease-in;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.fade-enter-from,
|
|
14
|
+
.fade-leave-to {
|
|
15
|
+
opacity: 0;
|
|
16
|
+
}
|
|
17
|
+
</style>
|