Feat: Add infinite scroll in attraction list
This commit is contained in:
parent
f05243e7e3
commit
9f340ee6ef
@ -31,7 +31,10 @@ onMounted(() => {
|
|||||||
setTarget(newMarkers[0].getPosition());
|
setTarget(newMarkers[0].getPosition());
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(target, (newTarget) => {
|
watch(target, (newTarget, oldTarget) => {
|
||||||
|
if (oldTarget && newTarget.La === oldTarget.La && newTarget.Ma === oldTarget.Ma) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
map.panTo(newTarget);
|
map.panTo(newTarget);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { computed } from 'vue'
|
|
||||||
|
|
||||||
const props = defineProps({ currentPage: Number, totalPage: Number })
|
|
||||||
const emit = defineEmits(['pageChange'])
|
|
||||||
|
|
||||||
// const navigationSize = parseInt(import.meta.env.VITE_ARTICLE_NAVIGATION_SIZE)
|
|
||||||
const navigationSize = 5
|
|
||||||
|
|
||||||
const startPage = computed(() => {
|
|
||||||
return parseInt((props.currentPage - 1) / navigationSize) * navigationSize + 1
|
|
||||||
})
|
|
||||||
|
|
||||||
const endPage = computed(() => {
|
|
||||||
let lastPage =
|
|
||||||
parseInt((props.currentPage - 1) / navigationSize) * navigationSize + navigationSize
|
|
||||||
return props.totalPage < lastPage ? props.totalPage : lastPage
|
|
||||||
})
|
|
||||||
|
|
||||||
const endRange = computed(() => {
|
|
||||||
return parseInt((props.totalPage - 1) / navigationSize) * navigationSize < props.currentPage
|
|
||||||
})
|
|
||||||
|
|
||||||
function range(start, end) {
|
|
||||||
const list = []
|
|
||||||
for (let i = start; i <= end; i++) list.push(i)
|
|
||||||
return list
|
|
||||||
// return Array(end - start + 1)
|
|
||||||
// .fill()
|
|
||||||
// .map((val, i) => start + i);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPageChange(pg) {
|
|
||||||
console.log(pg + '로 이동!!!')
|
|
||||||
emit('pageChange', pg)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="row">
|
|
||||||
<ul class="pagination justify-content-center">
|
|
||||||
<li class="page-item">
|
|
||||||
<a class="page-link" @click="onPageChange(1)">최신</a>
|
|
||||||
</li>
|
|
||||||
<li class="page-item">
|
|
||||||
<a class="page-link" @click="onPageChange(startPage == 1 ? 1 : startPage - 1)">이전</a>
|
|
||||||
</li>
|
|
||||||
<template v-for="pg in range(startPage, endPage)" :key="pg">
|
|
||||||
<li :class="currentPage === pg ? 'page-item active' : 'page-item'">
|
|
||||||
<a class="page-link" @click="onPageChange(pg)">{{ pg }}</a>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
<li class="page-item">
|
|
||||||
<a class="page-link" @click="onPageChange(endRange ? totalPage : endPage + 1)">다음</a>
|
|
||||||
</li>
|
|
||||||
<li class="page-item"><a class="page-link" @click="onPageChange(totalPage)">마지막</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
a {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
import TextButton from '../common/TextButton.vue';
|
import TextButton from '../common/TextButton.vue';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import Select from '../common/Select.vue';
|
import Select from '../common/Select.vue';
|
||||||
@ -15,21 +15,13 @@ const contentTypeList = [
|
|||||||
{ value: 38, name: '쇼핑' },
|
{ value: 38, name: '쇼핑' },
|
||||||
{ value: 39, name: '음식점' },
|
{ value: 39, name: '음식점' },
|
||||||
];
|
];
|
||||||
const { search } = useAttractionStore();
|
const { search, setQueryString } = useAttractionStore();
|
||||||
const sidoList = ref([]);
|
const sidoList = ref([]);
|
||||||
const gugunList = ref([]);
|
const gugunList = ref([]);
|
||||||
const sido = ref({ sidoCode: 0 });
|
const sido = ref({ sidoCode: 0 });
|
||||||
const gugun = ref({ gugunCode: 0 });
|
const gugun = ref({ gugunCode: 0 });
|
||||||
const contentType = ref(contentTypeList[0]);
|
const contentType = ref(contentTypeList[0]);
|
||||||
const keyword = ref('');
|
const keyword = ref('');
|
||||||
const searchQuery = computed(() => {
|
|
||||||
const { sidoCode } = sido.value;
|
|
||||||
const { gugunCode } = gugun.value;
|
|
||||||
const areaQuery = `sidoCode=${sidoCode}` + (sidoCode ? `&gugunCode=${gugunCode}` : '');
|
|
||||||
const keywordQuery = `title=${keyword.value}`;
|
|
||||||
|
|
||||||
return [areaQuery, keywordQuery].join('&');
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(sido, ({ sidoCode }) => {
|
watch(sido, ({ sidoCode }) => {
|
||||||
axios.get(`//localhost:8000/area/gugun?sidoCode=${sidoCode}`).then(({ data }) => {
|
axios.get(`//localhost:8000/area/gugun?sidoCode=${sidoCode}`).then(({ data }) => {
|
||||||
@ -38,13 +30,18 @@ watch(sido, ({ sidoCode }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (sidoList.value.length === 0) {
|
if (sidoList.value.length === 0) {
|
||||||
axios.get('//localhost:8000/area/sido').then(({ data }) => {
|
axios.get('//localhost:8000/area/sido').then(({ data }) => (sidoList.value = data));
|
||||||
sidoList.value = data;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSubmit() {
|
function handleSubmit() {
|
||||||
search(searchQuery.value);
|
const { sidoCode } = sido.value;
|
||||||
|
const { gugunCode } = gugun.value;
|
||||||
|
const areaQuery = `sidoCode=${sidoCode}` + (sidoCode ? `&gugunCode=${gugunCode}` : '');
|
||||||
|
const keywordQuery = `title=${keyword.value}`;
|
||||||
|
const queryString = [areaQuery, keywordQuery].join('&');
|
||||||
|
|
||||||
|
setQueryString(queryString);
|
||||||
|
search();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -127,19 +124,3 @@ button {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style>
|
|
||||||
/* .p-dropdown {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.p-dropdown-items {
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
.p-dropdown-item {
|
|
||||||
padding: 4px 16px;
|
|
||||||
}
|
|
||||||
.p-dropdown-filter-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
} */
|
|
||||||
</style>
|
|
||||||
|
@ -3,9 +3,27 @@ import { useAttractionStore } from '@/stores/attractionStore';
|
|||||||
import AttractionItem from './AttractionItem.vue';
|
import AttractionItem from './AttractionItem.vue';
|
||||||
import SearchBar from './SearchBar.vue';
|
import SearchBar from './SearchBar.vue';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { onMounted, onUnmounted, ref } from 'vue';
|
||||||
|
|
||||||
const attractionStore = useAttractionStore();
|
const attractionStore = useAttractionStore();
|
||||||
const { attractionList } = storeToRefs(attractionStore);
|
const { attractionList, page, hasNextPage } = storeToRefs(attractionStore);
|
||||||
|
const observer = ref(null);
|
||||||
|
const sentinal = ref(null);
|
||||||
|
|
||||||
|
async function handleIntersect(entries) {
|
||||||
|
if (entries[0].isIntersecting && page.value && hasNextPage.value) {
|
||||||
|
await attractionStore.fetchNextPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
observer.value = new IntersectionObserver(handleIntersect, { threshold: 1 });
|
||||||
|
observer.value.observe(sentinal.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
observer.value.disconnect();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -15,6 +33,7 @@ const { attractionList } = storeToRefs(attractionStore);
|
|||||||
<li v-for="attraction in attractionList" :key="attraction.title">
|
<li v-for="attraction in attractionList" :key="attraction.title">
|
||||||
<AttractionItem :place="attraction" />
|
<AttractionItem :place="attraction" />
|
||||||
</li>
|
</li>
|
||||||
|
<li ref="sentinal"></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -4,17 +4,45 @@ import { ref } from 'vue';
|
|||||||
|
|
||||||
export const useAttractionStore = defineStore('attraction', () => {
|
export const useAttractionStore = defineStore('attraction', () => {
|
||||||
const attractionList = ref([]);
|
const attractionList = ref([]);
|
||||||
|
const queryString = ref('');
|
||||||
|
const page = ref(0);
|
||||||
|
const hasNextPage = ref(true);
|
||||||
const target = ref(null);
|
const target = ref(null);
|
||||||
|
|
||||||
function search(queryString) {
|
function setQueryString(value) {
|
||||||
searchAttarctions(queryString, ({ data }) => {
|
queryString.value = value;
|
||||||
attractionList.value = data;
|
}
|
||||||
|
|
||||||
|
function search(preventClear = false) {
|
||||||
|
if (page.value === 0) {
|
||||||
|
page.value = 1;
|
||||||
|
}
|
||||||
|
const query = `${queryString.value}&page=${page.value}`;
|
||||||
|
searchAttarctions(query, ({ data }) => {
|
||||||
|
preventClear
|
||||||
|
? (attractionList.value = [...attractionList.value, ...data])
|
||||||
|
: (attractionList.value = data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fetchNextPage() {
|
||||||
|
page.value += 1;
|
||||||
|
search(true);
|
||||||
|
}
|
||||||
|
|
||||||
function setTarget(coords) {
|
function setTarget(coords) {
|
||||||
target.value = coords;
|
target.value = coords;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { attractionList, target, setTarget, search };
|
return {
|
||||||
|
attractionList,
|
||||||
|
queryString,
|
||||||
|
target,
|
||||||
|
setTarget,
|
||||||
|
page,
|
||||||
|
hasNextPage,
|
||||||
|
setQueryString,
|
||||||
|
search,
|
||||||
|
fetchNextPage,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user