add composition api
This commit is contained in:
parent
0eafaeb27f
commit
67466483ac
@ -18,6 +18,10 @@
|
|||||||
<router-link to="/store">
|
<router-link to="/store">
|
||||||
<my-button>Посты Vuex</my-button>
|
<my-button>Посты Vuex</my-button>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
|
<router-link to="/composition">
|
||||||
|
<my-button>Посты Composition</my-button>
|
||||||
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
22
src/hooks/useCreateDialog.js
Normal file
22
src/hooks/useCreateDialog.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
|
export default function useCreateDialog() {
|
||||||
|
const dialogVisible = ref(false);
|
||||||
|
|
||||||
|
const showCreateDialog = () => {
|
||||||
|
dialogVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(dialogVisible, (newValue) => {
|
||||||
|
if (newValue) {
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
} else {
|
||||||
|
document.body.style.overflow = 'auto';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
dialogVisible,
|
||||||
|
showCreateDialog
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/hooks/usePosts.js
Normal file
55
src/hooks/usePosts.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
export default function usePosts({page, limit}) {
|
||||||
|
const posts = ref([]);
|
||||||
|
const totalPages = ref(0);
|
||||||
|
const isPostLoading = ref(true);
|
||||||
|
|
||||||
|
const fetchPosts = async () => {
|
||||||
|
try {
|
||||||
|
isPostLoading.value = true;
|
||||||
|
const { data, headers } = await axios.get(`https://jsonplaceholder.typicode.com/posts`, {
|
||||||
|
params: {
|
||||||
|
_limit: limit.value,
|
||||||
|
_page: page.value
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
posts.value = data;
|
||||||
|
totalPages.value = Math.ceil(headers['x-total-count'] / limit.value);
|
||||||
|
} catch (e) {
|
||||||
|
alert(e.message);
|
||||||
|
} finally {
|
||||||
|
isPostLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadMorePost = async () => {
|
||||||
|
page.value += 1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data, headers } = await axios.get(`https://jsonplaceholder.typicode.com/posts`, {
|
||||||
|
params: {
|
||||||
|
_limit: limit.value,
|
||||||
|
_page: page.value,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
posts.value.push(...data);
|
||||||
|
totalPages.value = Math.ceil(headers['x-total-count'] / limit.value);
|
||||||
|
} catch (e) {
|
||||||
|
alert(e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(fetchPosts);
|
||||||
|
|
||||||
|
return {
|
||||||
|
posts,
|
||||||
|
totalPages,
|
||||||
|
isPostLoading,
|
||||||
|
fetchPosts,
|
||||||
|
loadMorePost,
|
||||||
|
page,
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/hooks/useSortedAndSearchedPosts.js
Normal file
14
src/hooks/useSortedAndSearchedPosts.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
export default function useSortedAndSearchedPosts (sortedPosts) {
|
||||||
|
const searchQuery = ref('');
|
||||||
|
|
||||||
|
const sortedAndSearchedPosts = computed(() => {
|
||||||
|
return sortedPosts.value.filter((post) => post.title.toLowerCase().includes(searchQuery.value.toLowerCase()))
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
searchQuery,
|
||||||
|
sortedAndSearchedPosts
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/hooks/useSortedPosts.js
Normal file
16
src/hooks/useSortedPosts.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
export default function useSortedPosts (posts) {
|
||||||
|
const selectedSort = ref('');
|
||||||
|
|
||||||
|
const sortedPosts = computed(() => {
|
||||||
|
return [...posts.value].sort((post1, post2) => {
|
||||||
|
return post1?.[selectedSort.value]?.localeCompare(post2?.[selectedSort.value])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
selectedSort,
|
||||||
|
sortedPosts
|
||||||
|
}
|
||||||
|
}
|
||||||
124
src/pages/PostsPageWithCompositionApi.vue
Normal file
124
src/pages/PostsPageWithCompositionApi.vue
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>Страница постов (Composition API)</h1>
|
||||||
|
|
||||||
|
<my-input v-focus v-model="searchQuery" placeholder="Поиск" class="search"/>
|
||||||
|
|
||||||
|
<div class="app__btns">
|
||||||
|
<my-button @click="showCreateDialog" class="createBtn">
|
||||||
|
Создать пост
|
||||||
|
</my-button>
|
||||||
|
|
||||||
|
<my-select v-model="selectedSort" :options="sortOptions"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<my-dialog v-model:show="dialogVisible">
|
||||||
|
<post-form @createPost="createPost" />
|
||||||
|
</my-dialog>
|
||||||
|
|
||||||
|
<div v-if="isPostLoading" style="margin-top: 15px">Loading...</div>
|
||||||
|
<post-list v-else :posts="sortedAndSearchedPosts" @removePost="removePost" class="postList"/>
|
||||||
|
<div v-intersection="getIntersectionData()" ref="observer" class="observer"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import PostForm from '@/components/PostForm.vue';
|
||||||
|
import PostList from '@/components/PostList.vue';
|
||||||
|
import axios from 'axios';
|
||||||
|
import MyPagination from '@/components/ui/Pagination.vue';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import MyButton from '@/components/ui/Button.vue';
|
||||||
|
import usePosts from '@/hooks/usePosts';
|
||||||
|
import useSortedPosts from '@/hooks/useSortedPosts';
|
||||||
|
import useSortedAndSearchedPosts from '@/hooks/useSortedAndSearchedPosts';
|
||||||
|
import useCreateDialog from '@/hooks/useCreateDialog';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
MyButton,
|
||||||
|
MyPagination,
|
||||||
|
PostForm,
|
||||||
|
PostList
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
dialogVisible: false,
|
||||||
|
sortOptions: [
|
||||||
|
{value: 'title', name: 'По названию'},
|
||||||
|
{value: 'body', name: 'По описанию'}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getIntersectionData() {
|
||||||
|
return {
|
||||||
|
loadMorePost: this.loadMorePost,
|
||||||
|
page: this.page.value,
|
||||||
|
totalPages: this.totalPages.value,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setSelectedSort(newValue) {
|
||||||
|
console.log('selectedSort', this.selectedSort);
|
||||||
|
this.selectedSort.value = newValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const page = ref(1);
|
||||||
|
const limit = 10;
|
||||||
|
const { isPostLoading, posts, totalPages, loadMorePost } = usePosts({ page, limit });
|
||||||
|
const { selectedSort, sortedPosts } = useSortedPosts(posts);
|
||||||
|
const { searchQuery, sortedAndSearchedPosts } = useSortedAndSearchedPosts(sortedPosts);
|
||||||
|
const {dialogVisible, showCreateDialog} = useCreateDialog();
|
||||||
|
|
||||||
|
const createPost = (newPost) => {
|
||||||
|
posts.value.push(newPost);
|
||||||
|
dialogVisible.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const removePost = (post) => {
|
||||||
|
posts.value = posts.value.filter(p => p.id !== post.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isPostLoading,
|
||||||
|
posts,
|
||||||
|
totalPages,
|
||||||
|
selectedSort,
|
||||||
|
searchQuery,
|
||||||
|
sortedAndSearchedPosts,
|
||||||
|
dialogVisible,
|
||||||
|
showCreateDialog,
|
||||||
|
createPost,
|
||||||
|
removePost,
|
||||||
|
loadMorePost,
|
||||||
|
page,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.createBtn {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.postList {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app__btns {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.observer {
|
||||||
|
height: 0px;
|
||||||
|
background: teal;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -4,6 +4,7 @@ import PostsPage from '@/pages/PostsPage.vue';
|
|||||||
import AboutPage from '@/pages/AboutPage.vue';
|
import AboutPage from '@/pages/AboutPage.vue';
|
||||||
import PostPage from '@/pages/PostPage.vue';
|
import PostPage from '@/pages/PostPage.vue';
|
||||||
import PostsPageWithStore from '@/pages/PostsPageWithStore.vue';
|
import PostsPageWithStore from '@/pages/PostsPageWithStore.vue';
|
||||||
|
import PostsPageWithCompositionApi from '@/pages/PostsPageWithCompositionApi.vue';
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
@ -25,6 +26,10 @@ const routes = [
|
|||||||
{
|
{
|
||||||
path: '/store',
|
path: '/store',
|
||||||
component: PostsPageWithStore,
|
component: PostsPageWithStore,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/composition',
|
||||||
|
component: PostsPageWithCompositionApi,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user