add page routing

This commit is contained in:
Sergey Krylov 2025-01-15 06:47:34 +03:00
parent f624109dea
commit baf0b4e476
8 changed files with 293 additions and 165 deletions

View File

@ -1,153 +1,17 @@
<template>
<navbar/>
<div class="app">
<h1>Страница постов</h1>
<my-input 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 ref="observer" class="observer"></div>
<!-- <my-pagination-->
<!-- :currentPage="page"-->
<!-- :totalPages="totalPages"-->
<!-- @changePage="changePage"-->
<!-- />-->
<router-view/>
</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 Navbar from '@/components/Navbar.vue';
export default {
components: {
MyPagination,
PostForm,
PostList
},
data() {
return {
posts: [],
dialogVisible: false,
isPostLoading: false,
selectedSort: '',
sortOptions: [
{value: 'title', name: 'По названию'},
{value: 'body', name: 'По описанию'}
],
searchQuery: '',
page: 1,
limit: 10,
totalPages: 0
}
},
methods: {
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;
},
async fetchPosts() {
try {
const { data, headers } = await axios.get(`https://jsonplaceholder.typicode.com/posts`, {
params: {
_limit: this.limit,
_page: this.page
}
});
this.posts = data;
this.totalPages = Math.ceil(headers['x-total-count'] / this.limit);
} catch (e) {
alert(e.message);
}
},
async loadMorePost() {
this.page += 1
try {
const { data, headers } = await axios.get(`https://jsonplaceholder.typicode.com/posts`, {
params: {
_limit: this.limit,
_page: this.page,
}
});
this.posts.push(...data);
this.totalPages = Math.ceil(headers['x-total-count'] / this.limit);
} catch (e) {
alert(e.message);
}
},
// changePage(newPage) {
// this.page = newPage;
// }
},
async mounted() {
this.isPostLoading = true;
await this.fetchPosts()
this.isPostLoading = false
components: {Navbar},
console.log();
const options = {
rootMargin: "0px",
threshold: 1.0,
};
const callback = (entries) => {
if (entries[0].isIntersecting && this.page < this.totalPages) {
this.loadMorePost();
}
};
const observer = new IntersectionObserver(callback, options);
observer.observe(this.$refs.observer)
},
computed: {
sortedPost() {
return [...this.posts].sort((post1, post2) => {
return post1?.[this.selectedSort]?.localeCompare(post2?.[this.selectedSort])
})
},
sortedAndSearchedPosts() {
return this.sortedPost.filter((post) => post.title.toLowerCase().includes(this.searchQuery.toLowerCase()))
}
},
watch: {
// selectedSort(newValue) {
// this.posts.sort((post1, post2) => {
// return post1[newValue].localeCompare(post2[newValue])
// })
// },
dialogVisible(newValue) {
if (newValue) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'auto';
}
},
// page() {
// this.fetchPosts()
// }
}
}
</script>
@ -161,27 +25,4 @@ export default {
.app {
padding: 20px;
}
.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>

44
src/components/Navbar.vue Normal file
View File

@ -0,0 +1,44 @@
<template>
<div class="navbar">
<div>Vue 3</div>
<div class="navbar__btns">
<router-link to="/">
<my-button>Главная</my-button>
</router-link>
<router-link to="/posts">
<my-button>Посты</my-button>
</router-link>
<my-button @click="$router.push('/about')">
О Нас
</my-button>
</div>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
.navbar {
height: 50px;
background: lightgray;
box-shadow: 2px 2px 4px gray;
display: flex;
align-items: center;
padding: 10px 15px;
justify-content: space-between;
}
.navbar__btns {
display: flex;
align-items: center;
column-gap: 10px;
}
</style>

View File

@ -17,5 +17,12 @@ export default {
color: teal;
border: 1px solid teal;
border-radius: 5px;
cursor: pointer;
transition: background 0.2s ease-in-out, color 0.2s ease-in-out;
&:hover {
background: teal;
color: white;
}
}
</style>

View File

@ -2,6 +2,7 @@ import { createApp } from 'vue'
import App from './App';
import components from '@/components/ui';
import router from '@/router/router';
const app = createApp(App);
@ -9,4 +10,6 @@ components.forEach(component => {
app.component(component.name, component)
})
app.mount('#app')
app
.use(router)
.mount('#app')

15
src/pages/AboutPage.vue Normal file
View File

@ -0,0 +1,15 @@
<template>
<div>
<h1>О Нас</h1>
</div>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
</style>

15
src/pages/Main.vue Normal file
View File

@ -0,0 +1,15 @@
<template>
<div>
<h1>Главная</h1>
</div>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
</style>

177
src/pages/PostPage.vue Normal file
View File

@ -0,0 +1,177 @@
<template>
<div>
<h1>Страница постов</h1>
<my-input 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 ref="observer" class="observer"></div>
<!-- <my-pagination-->
<!-- :currentPage="page"-->
<!-- :totalPages="totalPages"-->
<!-- @changePage="changePage"-->
<!-- />-->
</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';
export default {
components: {
MyPagination,
PostForm,
PostList
},
data() {
return {
posts: [],
dialogVisible: false,
isPostLoading: false,
selectedSort: '',
sortOptions: [
{value: 'title', name: 'По названию'},
{value: 'body', name: 'По описанию'}
],
searchQuery: '',
page: 1,
limit: 10,
totalPages: 0
}
},
methods: {
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;
},
async fetchPosts() {
try {
const { data, headers } = await axios.get(`https://jsonplaceholder.typicode.com/posts`, {
params: {
_limit: this.limit,
_page: this.page
}
});
this.posts = data;
this.totalPages = Math.ceil(headers['x-total-count'] / this.limit);
} catch (e) {
alert(e.message);
}
},
async loadMorePost() {
this.page += 1
try {
const { data, headers } = await axios.get(`https://jsonplaceholder.typicode.com/posts`, {
params: {
_limit: this.limit,
_page: this.page,
}
});
this.posts.push(...data);
this.totalPages = Math.ceil(headers['x-total-count'] / this.limit);
} catch (e) {
alert(e.message);
}
},
// changePage(newPage) {
// this.page = newPage;
// }
},
async mounted() {
this.isPostLoading = true;
await this.fetchPosts()
this.isPostLoading = false
console.log();
const options = {
rootMargin: "0px",
threshold: 1.0,
};
const callback = (entries) => {
if (entries[0].isIntersecting && this.page < this.totalPages) {
this.loadMorePost();
}
};
const observer = new IntersectionObserver(callback, options);
observer.observe(this.$refs.observer)
},
computed: {
sortedPost() {
return [...this.posts].sort((post1, post2) => {
return post1?.[this.selectedSort]?.localeCompare(post2?.[this.selectedSort])
})
},
sortedAndSearchedPosts() {
return this.sortedPost.filter((post) => post.title.toLowerCase().includes(this.searchQuery.toLowerCase()))
}
},
watch: {
// selectedSort(newValue) {
// this.posts.sort((post1, post2) => {
// return post1[newValue].localeCompare(post2[newValue])
// })
// },
dialogVisible(newValue) {
if (newValue) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'auto';
}
},
// page() {
// 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>

26
src/router/router.js Normal file
View File

@ -0,0 +1,26 @@
import Main from '@/pages/Main.vue';
import { createRouter, createWebHistory } from 'vue-router';
import PostPage from '@/pages/PostPage.vue';
import AboutPage from '@/pages/AboutPage.vue';
const routes = [
{
path: '/',
component: Main,
},
{
path: '/posts',
component: PostPage,
},
{
path: '/about',
component: AboutPage,
}
]
const router = createRouter({
routes,
history: createWebHistory(process.env.BASE_URL)
})
export default router;