This commit is contained in:
Sergey Krylov 2025-01-16 06:40:25 +03:00
parent 76e26d876a
commit 0eafaeb27f
6 changed files with 236 additions and 0 deletions

View File

@ -14,6 +14,10 @@
<my-button @click="$router.push('/about')">
О Нас
</my-button>
<router-link to="/store">
<my-button>Посты Vuex</my-button>
</router-link>
</div>
</div>
</template>

View File

@ -4,6 +4,7 @@ import App from './App';
import components from '@/components/ui';
import dirictives from '@/dirictives';
import router from '@/router/router';
import store from '@/store';
const app = createApp(App);
@ -18,4 +19,5 @@ dirictives.forEach(dirictive => {
app
.use(router)
.use(store)
.mount('#app')

View File

@ -0,0 +1,134 @@
<template>
<div>
<h1>Страница постов (Vuex)</h1>
<my-input
v-focus
:model-value="searchQuery"
@update:model-value="setSearchQuery"
placeholder="Поиск"
class="search"
/>
<div class="app__btns">
<my-button @click="showCreateDialog" class="createBtn">
Создать пост
</my-button>
<my-select
:model-value="selectedSort"
@update:model-value="setSelectedSort"
: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 {mapActions, mapState, mapGetters, mapMutations} from 'vuex';
export default {
components: {
PostForm,
PostList
},
data() {
return {
dialogVisible: false,
}
},
methods: {
...mapMutations({
setPage: 'post/setPage',
setSearchQuery: 'post/setSearchQuery',
setSelectedSort: 'post/setSelectedSort',
}),
...mapActions({
fetchPosts: 'post/fetchPosts',
loadMorePost: 'post/loadMorePost'
}),
createPost(newPost) {
this.posts.push(newPost);
this.dialogVisible = false;
},
removePost(post) {
this.posts = this.posts.filter(p => p.id !== post.id)
},
showCreateDialog() {
this.dialogVisible = true;
},
getIntersectionData() {
return {
loadMorePost: this.loadMorePost,
page: this.page,
totalPages: this.totalPages,
}
}
},
computed: {
...mapState({
posts: (state) => state.post.posts,
isPostLoading: (state) => state.post.isPostLoading,
selectedSort: (state) => state.post.selectedSort,
sortOptions: (state) => state.post.sortOptions,
searchQuery: (state) => state.post.searchQuery,
page: (state) => state.post.page,
limit: (state) => state.post.limit,
totalPages: (state) => state.post.totalPagess
}),
...mapGetters({
sortedPost: 'post/sortedPost',
sortedAndSearchedPosts: 'post/sortedAndSearchedPosts'
})
},
watch: {
dialogVisible(newValue) {
if (newValue) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'auto';
}
},
},
mounted() {
this.fetchPosts();
}
}
</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>

View File

@ -3,6 +3,7 @@ import { createRouter, createWebHistory } from 'vue-router';
import PostsPage from '@/pages/PostsPage.vue';
import AboutPage from '@/pages/AboutPage.vue';
import PostPage from '@/pages/PostPage.vue';
import PostsPageWithStore from '@/pages/PostsPageWithStore.vue';
const routes = [
{
@ -20,6 +21,10 @@ const routes = [
{
path: '/about',
component: AboutPage,
},
{
path: '/store',
component: PostsPageWithStore,
}
]

8
src/store/index.js Normal file
View File

@ -0,0 +1,8 @@
import { createStore } from 'vuex';
import { postModule } from '@/store/postModule';
export default createStore({
modules: {
post: postModule,
},
})

83
src/store/postModule.js Normal file
View File

@ -0,0 +1,83 @@
import axios from 'axios';
export const postModule = {
state: () => ({
posts: [],
isPostLoading: false,
selectedSort: '',
sortOptions: [
{value: 'title', name: 'По названию'},
{value: 'body', name: 'По описанию'}
],
searchQuery: '',
page: 1,
limit: 10,
totalPages: 0
}),
getters: {
sortedPost(state) {
return [...state.posts].sort((post1, post2) => {
return post1?.[state.selectedSort]?.localeCompare(post2?.[state.selectedSort])
})
},
sortedAndSearchedPosts(state, getters) {
return getters.sortedPost.filter((post) => post.title.toLowerCase().includes(state.searchQuery.toLowerCase()))
}
},
mutations: {
setPosts: (state, posts) => {
state.posts = posts
},
setIsPostLoading: (state, isPostLoading) => {
state.isPostLoading = isPostLoading
},
setSelectedSort: (state, selectedSort) => {
state.selectedSort = selectedSort
},
setSearchQuery: (state, searchQuery) => {
state.searchQuery = searchQuery
},
setPage: (state, page) => {
state.page = page
},
setTotalPages: (state, totalPages) => {
state.totalPages = totalPages
},
},
actions: {
async fetchPosts({state, commit}) {
try {
commit('setIsPostLoading', true);
const { data, headers } = await axios.get(`https://jsonplaceholder.typicode.com/posts`, {
params: {
_limit: state.limit,
_page: state.page
}
});
commit('setPosts', data);
commit('setTotalPages', Math.ceil(headers['x-total-count'] / state.limit))
} catch (e) {
alert(e.message);
} finally {
commit('setIsPostLoading', false);
}
},
async loadMorePost({state, commit}) {
commit('setPage', state.page + 1)
try {
const { data, headers } = await axios.get(`https://jsonplaceholder.typicode.com/posts`, {
params: {
_limit: state.limit,
_page: state.page,
}
});
commit('setPosts', [...state.posts, ...data]);
commit('setTotalPages', Math.ceil(headers['x-total-count'] / state.limit))
} catch (e) {
alert(e.message);
}
},
},
namespaced: true,
}