add page routing
This commit is contained in:
parent
f624109dea
commit
baf0b4e476
169
src/App.vue
169
src/App.vue
@ -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
44
src/components/Navbar.vue
Normal 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>
|
||||
@ -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>
|
||||
|
||||
@ -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
15
src/pages/AboutPage.vue
Normal 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
15
src/pages/Main.vue
Normal 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
177
src/pages/PostPage.vue
Normal 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
26
src/router/router.js
Normal 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;
|
||||
Loading…
x
Reference in New Issue
Block a user