Rebuild Leaflet map components from scratch.
This commit is contained in:
parent
e3513049df
commit
d51a4ca3d1
|
@ -16,10 +16,10 @@
|
||||||
"@fullcalendar/luxon2": "^5.10.2",
|
"@fullcalendar/luxon2": "^5.10.2",
|
||||||
"@fullcalendar/timegrid": "^5.9.0",
|
"@fullcalendar/timegrid": "^5.9.0",
|
||||||
"@fullcalendar/vue3": "^5.11",
|
"@fullcalendar/vue3": "^5.11",
|
||||||
"@vue-leaflet/vue-leaflet": "*",
|
|
||||||
"@vue/compat": "^3.2.45",
|
"@vue/compat": "^3.2.45",
|
||||||
"@vuelidate/core": "^2.0.0",
|
"@vuelidate/core": "^2.0.0",
|
||||||
"@vuelidate/validators": "^2.0.0",
|
"@vuelidate/validators": "^2.0.0",
|
||||||
|
"@vueuse/core": "^9.6.0",
|
||||||
"axios": "^1",
|
"axios": "^1",
|
||||||
"bootstrap": "^4.6.0 <5",
|
"bootstrap": "^4.6.0 <5",
|
||||||
"bootstrap-notify": "^3.1.3",
|
"bootstrap-notify": "^3.1.3",
|
||||||
|
@ -1970,14 +1970,10 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-5.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-5.0.3.tgz",
|
||||||
"integrity": "sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw=="
|
"integrity": "sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw=="
|
||||||
},
|
},
|
||||||
"node_modules/@vue-leaflet/vue-leaflet": {
|
"node_modules/@types/web-bluetooth": {
|
||||||
"version": "0.7.0",
|
"version": "0.0.16",
|
||||||
"resolved": "https://registry.npmjs.org/@vue-leaflet/vue-leaflet/-/vue-leaflet-0.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
|
||||||
"integrity": "sha512-YY1bcg+7ftGZk4ihCOckOSFMfuhimGxEpPabtpBpaaaquABliB0dkANNFx5d8Y5Vadko5Yjc8Vq74hb/dg4gQw==",
|
"integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ=="
|
||||||
"peerDependencies": {
|
|
||||||
"leaflet": "^1.6.0",
|
|
||||||
"vue": "^3.0.0"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/@vue/compat": {
|
"node_modules/@vue/compat": {
|
||||||
"version": "3.2.45",
|
"version": "3.2.45",
|
||||||
|
@ -2183,6 +2179,89 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vueuse/core": {
|
||||||
|
"version": "9.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.6.0.tgz",
|
||||||
|
"integrity": "sha512-qGUcjKQXHgN+jqXEgpeZGoxdCbIDCdVPz3QiF1uyecVGbMuM63o96I1GjYx5zskKgRI0FKSNsVWM7rwrRMTf6A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/web-bluetooth": "^0.0.16",
|
||||||
|
"@vueuse/metadata": "9.6.0",
|
||||||
|
"@vueuse/shared": "9.6.0",
|
||||||
|
"vue-demi": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vueuse/core/node_modules/vue-demi": {
|
||||||
|
"version": "0.13.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
|
||||||
|
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"bin": {
|
||||||
|
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||||
|
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vue/composition-api": "^1.0.0-rc.1",
|
||||||
|
"vue": "^3.0.0-0 || ^2.6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vue/composition-api": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vueuse/metadata": {
|
||||||
|
"version": "9.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.6.0.tgz",
|
||||||
|
"integrity": "sha512-sIC8R+kWkIdpi5X2z2Gk8TRYzmczDwHRhEFfCu2P+XW2JdPoXrziqsGpDDsN7ykBx4ilwieS7JUIweVGhvZ93w==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vueuse/shared": {
|
||||||
|
"version": "9.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.6.0.tgz",
|
||||||
|
"integrity": "sha512-/eDchxYYhkHnFyrb00t90UfjCx94kRHxc7J1GtBCqCG4HyPMX+krV9XJgVtWIsAMaxKVU4fC8NSUviG1JkwhUQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"vue-demi": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vueuse/shared/node_modules/vue-demi": {
|
||||||
|
"version": "0.13.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
|
||||||
|
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"bin": {
|
||||||
|
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||||
|
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vue/composition-api": "^1.0.0-rc.1",
|
||||||
|
"vue": "^3.0.0-0 || ^2.6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vue/composition-api": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@webassemblyjs/ast": {
|
"node_modules/@webassemblyjs/ast": {
|
||||||
"version": "1.11.1",
|
"version": "1.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
|
||||||
|
@ -11972,11 +12051,10 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-5.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-5.0.3.tgz",
|
||||||
"integrity": "sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw=="
|
"integrity": "sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw=="
|
||||||
},
|
},
|
||||||
"@vue-leaflet/vue-leaflet": {
|
"@types/web-bluetooth": {
|
||||||
"version": "0.7.0",
|
"version": "0.0.16",
|
||||||
"resolved": "https://registry.npmjs.org/@vue-leaflet/vue-leaflet/-/vue-leaflet-0.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
|
||||||
"integrity": "sha512-YY1bcg+7ftGZk4ihCOckOSFMfuhimGxEpPabtpBpaaaquABliB0dkANNFx5d8Y5Vadko5Yjc8Vq74hb/dg4gQw==",
|
"integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ=="
|
||||||
"requires": {}
|
|
||||||
},
|
},
|
||||||
"@vue/compat": {
|
"@vue/compat": {
|
||||||
"version": "3.2.45",
|
"version": "3.2.45",
|
||||||
|
@ -12124,6 +12202,46 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@vueuse/core": {
|
||||||
|
"version": "9.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.6.0.tgz",
|
||||||
|
"integrity": "sha512-qGUcjKQXHgN+jqXEgpeZGoxdCbIDCdVPz3QiF1uyecVGbMuM63o96I1GjYx5zskKgRI0FKSNsVWM7rwrRMTf6A==",
|
||||||
|
"requires": {
|
||||||
|
"@types/web-bluetooth": "^0.0.16",
|
||||||
|
"@vueuse/metadata": "9.6.0",
|
||||||
|
"@vueuse/shared": "9.6.0",
|
||||||
|
"vue-demi": "*"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vue-demi": {
|
||||||
|
"version": "0.13.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
|
||||||
|
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
|
||||||
|
"requires": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@vueuse/metadata": {
|
||||||
|
"version": "9.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.6.0.tgz",
|
||||||
|
"integrity": "sha512-sIC8R+kWkIdpi5X2z2Gk8TRYzmczDwHRhEFfCu2P+XW2JdPoXrziqsGpDDsN7ykBx4ilwieS7JUIweVGhvZ93w=="
|
||||||
|
},
|
||||||
|
"@vueuse/shared": {
|
||||||
|
"version": "9.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.6.0.tgz",
|
||||||
|
"integrity": "sha512-/eDchxYYhkHnFyrb00t90UfjCx94kRHxc7J1GtBCqCG4HyPMX+krV9XJgVtWIsAMaxKVU4fC8NSUviG1JkwhUQ==",
|
||||||
|
"requires": {
|
||||||
|
"vue-demi": "*"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vue-demi": {
|
||||||
|
"version": "0.13.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
|
||||||
|
"integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
|
||||||
|
"requires": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@webassemblyjs/ast": {
|
"@webassemblyjs/ast": {
|
||||||
"version": "1.11.1",
|
"version": "1.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
|
||||||
|
|
|
@ -17,10 +17,10 @@
|
||||||
"@fullcalendar/luxon2": "^5.10.2",
|
"@fullcalendar/luxon2": "^5.10.2",
|
||||||
"@fullcalendar/timegrid": "^5.9.0",
|
"@fullcalendar/timegrid": "^5.9.0",
|
||||||
"@fullcalendar/vue3": "^5.11",
|
"@fullcalendar/vue3": "^5.11",
|
||||||
"@vue-leaflet/vue-leaflet": "*",
|
|
||||||
"@vue/compat": "^3.2.45",
|
"@vue/compat": "^3.2.45",
|
||||||
"@vuelidate/core": "^2.0.0",
|
"@vuelidate/core": "^2.0.0",
|
||||||
"@vuelidate/validators": "^2.0.0",
|
"@vuelidate/validators": "^2.0.0",
|
||||||
|
"@vueuse/core": "^9.6.0",
|
||||||
"axios": "^1",
|
"axios": "^1",
|
||||||
"bootstrap": "^4.6.0 <5",
|
"bootstrap": "^4.6.0 <5",
|
||||||
"bootstrap-notify": "^3.1.3",
|
"bootstrap-notify": "^3.1.3",
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
<template>
|
||||||
|
<div id="leaflet-container" ref="map">
|
||||||
|
<slot v-if="$map" :map="$map"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import 'leaflet/dist/leaflet.css';
|
||||||
|
|
||||||
|
.leaflet-container {
|
||||||
|
height: 300px;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {onMounted, provide, ref} from "vue";
|
||||||
|
import L from "leaflet";
|
||||||
|
import {get, set, templateRef} from "@vueuse/core";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
attribution: String
|
||||||
|
});
|
||||||
|
|
||||||
|
const $container = templateRef('map');
|
||||||
|
const $map = ref();
|
||||||
|
|
||||||
|
provide('map', $map);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// Fix issue with Leaflet icons being built in Webpack
|
||||||
|
// https://github.com/Leaflet/Leaflet/issues/4968
|
||||||
|
delete L.Icon.Default.prototype._getIconUrl;
|
||||||
|
L.Icon.Default.mergeOptions({
|
||||||
|
iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
|
||||||
|
iconUrl: require('leaflet/dist/images/marker-icon.png'),
|
||||||
|
shadowUrl: require('leaflet/dist/images/marker-shadow.png')
|
||||||
|
});
|
||||||
|
|
||||||
|
// Init map
|
||||||
|
const map = L.map(get($container));
|
||||||
|
map.setView([40, 0], 1);
|
||||||
|
set($map, map);
|
||||||
|
|
||||||
|
// Add tile layer
|
||||||
|
const tileUrl = 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/{theme}_all/{z}/{x}/{y}.png';
|
||||||
|
const tileAttribution = 'Map tiles by Carto, under CC BY 3.0. Data by OpenStreetMap, under ODbL.';
|
||||||
|
|
||||||
|
L.tileLayer(tileUrl, {
|
||||||
|
theme: App.theme,
|
||||||
|
attribution: tileAttribution,
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Add fullscreen control
|
||||||
|
const fullscreenControl = new L.Control.Fullscreen();
|
||||||
|
map.addControl(fullscreenControl)
|
||||||
|
*/
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -1,63 +1,38 @@
|
||||||
<template>
|
<template>
|
||||||
<l-map v-if="mapPoints.length < 3000" style="height: 300px; z-index: 0;" :zoom="1" :center="[40, 0]">
|
<inner-map v-if="visibleListeners.length < 3000"
|
||||||
<l-tile-layer :url="tileUrl" :attribution="tileAttribution"></l-tile-layer>
|
:attribution="attribution">
|
||||||
<l-marker v-for="l in mapPoints" :key="l.hash"
|
<map-point v-for="l in visibleListeners" :key="l.hash"
|
||||||
:lat-lng="{lat: l.location.lat, lng: l.location.lon}">
|
:position="[l.location.lat, l.location.lon]">
|
||||||
<l-tooltip>
|
<translate key="l-ip">IP</translate>
|
||||||
IP: {{ l.ip }}<br>
|
: {{ l.ip }}<br>
|
||||||
Country: {{ l.location.country }}<br>
|
<translate key="l-country">Country</translate>
|
||||||
Region: {{ l.location.region }}<br>
|
: {{ l.location.country }}<br>
|
||||||
City: {{ l.location.city }}<br>
|
<translate key="l-region">Region</translate>
|
||||||
Time connected: {{ l.connected_time }}<br>
|
: {{ l.location.region }}<br>
|
||||||
User Agent: {{ l.user_agent }}
|
<translate key="l-city">City</translate>
|
||||||
</l-tooltip>
|
: {{ l.location.city }}<br>
|
||||||
</l-marker>
|
<translate key="l-time">Time</translate>
|
||||||
</l-map>
|
: {{ l.connected_time }}<br>
|
||||||
|
<translate key="l-ua">User Agent</translate>
|
||||||
|
: {{ l.user_agent }}
|
||||||
|
</map-point>
|
||||||
|
</inner-map>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="css">
|
<script setup>
|
||||||
@import '../../../../../node_modules/leaflet/dist/leaflet.css';
|
import InnerMap from "./InnerMap.vue";
|
||||||
</style>
|
import MapPoint from "./MapPoint.vue";
|
||||||
|
import {computed} from "vue";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
<script>
|
const props = defineProps({
|
||||||
import L from 'leaflet';
|
attribution: String,
|
||||||
import {LMap, LMarker, LTileLayer, LTooltip} from '@vue-leaflet/vue-leaflet';
|
listeners: Array,
|
||||||
import _ from 'lodash';
|
});
|
||||||
|
|
||||||
export default {
|
const visibleListeners = computed(() => {
|
||||||
name: 'StationReportsListenersMap',
|
return _.filter(props.listeners, function (l) {
|
||||||
props: {
|
return null !== l.location.lat && null !== l.location.lon;
|
||||||
attribution: String,
|
});
|
||||||
listeners: Array,
|
});
|
||||||
},
|
|
||||||
components: {
|
|
||||||
LMap,
|
|
||||||
LTileLayer,
|
|
||||||
LMarker,
|
|
||||||
LTooltip
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
tileUrl: 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/' + App.theme + '_all/{z}/{x}/{y}.png',
|
|
||||||
tileAttribution: 'Map tiles by Carto, under CC BY 3.0. Data by OpenStreetMap, under ODbL.',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
// Fix issue with Leaflet icons being built in Webpack
|
|
||||||
// https://github.com/Leaflet/Leaflet/issues/4968
|
|
||||||
delete L.Icon.Default.prototype._getIconUrl;
|
|
||||||
L.Icon.Default.mergeOptions({
|
|
||||||
iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
|
|
||||||
iconUrl: require('leaflet/dist/images/marker-icon.png'),
|
|
||||||
shadowUrl: require('leaflet/dist/images/marker-shadow.png')
|
|
||||||
});
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
mapPoints() {
|
|
||||||
return _.filter(this.listeners, function (l) {
|
|
||||||
return null !== l.location.lat && null !== l.location.lon;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
<template>
|
||||||
|
<div ref="popup-content">
|
||||||
|
<slot/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {get, set, templateRef} from '@vueuse/core';
|
||||||
|
import {inject, onUnmounted, ref, toRaw, watch} from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
position: Array
|
||||||
|
});
|
||||||
|
|
||||||
|
const $map = inject('map');
|
||||||
|
const $marker = ref();
|
||||||
|
|
||||||
|
const map = toRaw(get($map));
|
||||||
|
const marker = L.marker(props.position);
|
||||||
|
marker.addTo(map);
|
||||||
|
set($marker, marker);
|
||||||
|
|
||||||
|
const popup = new L.Popup();
|
||||||
|
const $popupContent = templateRef('popup-content');
|
||||||
|
watch(
|
||||||
|
$popupContent,
|
||||||
|
(content) => {
|
||||||
|
popup.setContent(content);
|
||||||
|
marker.bindPopup(popup);
|
||||||
|
},
|
||||||
|
{immediate: true}
|
||||||
|
);
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
get($marker).remove();
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<template>
|
||||||
|
<slot/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup></script>
|
Loading…
Reference in New Issue