Я создаю веб-приложение для обмена изображениями с использованием VueJS, GraphQL, Apollo и MongoDB.
Github-репо: https://github.com/DimitarBelchev/Vue-Project-Softuni
Развернуто здесь: https://gamingarts.now.sh/
У меня проблемы с обновлением содержимого различных компонентов после добавления или удаления сообщения. Операции успешны, и серверная часть обновлена должным образом, но конец frond повсюду. Например, когда я удаляю сообщение, MongoDB удалит его, и он будет удален визуально из «Профиль. vue», но когда я go на другую страницу, которая отображает сообщение (например, Home. vue и сообщения. vue) это все еще будет там визуально. И вдобавок ко всему, когда я вернусь на страницу профиля. vue (откуда я удаляю сообщения), он снова появится снова, хотя на этот раз я не могу нажать на нее. Я получаю ошибку в консоли. Когда сайт обновляется, все работает нормально.
Второй пример: когда я загружаю сообщение, оно должно отображаться в 3 местах:
1.Home. vue - это должен быть первым элементом в карусели там. Это так, но я не могу щелкнуть по сообщению go для компонента Post. Vue, в котором содержатся сведения для каждого сообщения. Я должен щелкнуть по сообщению и быть перенаправленным на «/ post /», но до тех пор, пока я не обновлю sh страницу, когда я нажимаю на только что созданное сообщение, я перенаправлен на «/ post / -1». Все остальные посты ведут себя правильно. Я полагаю, что -1 исходит из оптимистического ответа c, который я использую, но если он не является частью кода, я не могу добавить его в начало карусели.
optimisticResponse: {
__typename: "Mutation",
addPost: {
__typename: "Post",
_id: -1,
...payload,
},
2.Posts. vue - компонент представляет собой список всех сообщений. Предполагается, что только что добавленный пост будет добавлен и здесь, но только после того, как я обновлю sh страницу.
3.Profile. vue - профиль для добавленного в данный момент пользователя (того, кто создал пост). Как вы, наверное, догадались, сообщение здесь не отображается, пока я не обновлю sh страницу ..
ЧТО Я ПОПРОБОВАЛ: 1. Vue .forceUpdate (); В консоли появилось сообщение о том, что forceupdate не является функцией. 2. Переключение с $ router.pu sh на $ router. go, но я знаю, что одностраничное приложение не должно обновлять sh. 3. Поиск в Google о том, как повторно выполнить рендеринг компонента после операции, но ничего, что я нашел, не сработало, или это пошло мне на ум.
Любая помощь будет принята с благодарностью.
КОД: Дом. Vue
<template>
<v-container text-xs-center>
<v-layout row>
<v-dialog v-model="loading" persistent fullscreen>
<v-container fill-height>
<v-layout row justify-center align-center>
<v-progress-circular
indeterminate
:size="70"
:width="7"
color="secondary"
></v-progress-circular>
</v-layout>
</v-container>
</v-dialog>
</v-layout>
<v-layout class="mt-2 mb-3" row wrap v-if="!loading">
<v-flex xs-12>
<v-btn class="secondary" to="/posts" large dark>
View All Posts
</v-btn>
</v-flex>
</v-layout>
<v-flex xs12>
<v-carousel
v-if="!loading && posts.length > 0"
v-bind="{ cycle: true }"
interval="3000"
>
<v-carousel-item
v-for="post in posts"
:key="post._id"
:src="post.imageUrl"
@click.native="goToPost(post._id)"
>
<h1 id="carousel__title">{{ post.title }}</h1>
</v-carousel-item>
</v-carousel>
</v-flex>
</v-container>
</template>
<script>
import { mapGetters } from "vuex";
import Vue from "vue";
export default {
name: "home",
created() {
this.handleGetCarouselPosts();
},
computed: {
...mapGetters(["loading", "posts"]),
},
methods: {
handleGetCarouselPosts() {
this.$store.dispatch("getPosts");
},
goToPost(postId) {
this.$router.push(`/posts/${postId}`);
},
},
};
</script>
<style>
#carousel__title {
position: absolute;
cursor: pointer;
background-color: rgba(0, 0, 0, 0.5);
color: white;
border-radius: 5px 5px 0 0;
padding: 0.5em;
margin: 0 auto;
bottom: 50px;
left: 0;
right: 0;
}
</style>
Профиль. vue
<template>
<v-container class="text-xs-center">
<v-flex sm6 offset-sm3>
<v-card class="white--text" color="secondary">
<v-layout>
<v-flex xs5>
<v-img height="125px" contain :src="user.avatar"></v-img>
</v-flex>
<v-flex xs7>
<v-card-title primary-title>
<div>
<div class="headline">{{ user.username }}</div>
<div>Joined {{ formatJoinDate(user.joinDate) }}</div>
<div class="hidden-xs-only font-weight-thin">
{{ user.favorites.length }} Favorites
</div>
<div class="hidden-xs-only font-weight-thin">
{{ userPosts.length }} Posts Added
</div>
</div>
</v-card-title>
</v-flex>
</v-layout>
</v-card>
</v-flex>
<v-container v-if="!userFavorites.length">
<v-layout row wrap>
<v-flex xs12>
<h2>
You can add posts you like to your "Favorites" section via the
hearth icon above them!
</h2>
</v-flex>
</v-layout>
</v-container>
<v-container class="mt-3" v-else>
<v-flex xs12>
<h2 class="font-weight-light">
Favorited
<span class="font-weight-regular">({{ userFavorites.length }})</span>
</h2>
</v-flex>
<v-layout row wrap>
<v-flex xs12 sm6 v-for="favorite in userFavorites" :key="favorite._id">
<v-card class="mt-3 ml-1 mr-2" hover>
<v-img
height="30vh"
:src="favorite.imageUrl"
@click="goToPost(favorite._id)"
></v-img>
<v-card-text>{{ favorite.title }}</v-card-text>
</v-card>
</v-flex>
</v-layout>
</v-container>
<v-container v-if="!userPosts.length">
<v-layout row wrap>
<v-flex xs12>
<h2>
Here you can view,edit or delete posts you have added! You can add
posts via the horizontal or side bar!
</h2>
</v-flex>
</v-layout>
</v-container>
<v-container class="mt-3" v-else>
<v-flex xs12>
<h2 class="font-weight-light">
Created posts
<span class="font-weight-regular">({{ userPosts.length }})</span>
</h2>
</v-flex>
<v-layout row wrap>
<v-flex xs12 sm6 v-for="post in userPosts" :key="post._id">
<v-card class="mt-3 ml-1 mr-2" hover>
<v-btn color="info" floating fab small dark @click="loadPost(post)">
<v-icon>edit</v-icon>
</v-btn>
<v-btn
color="error"
floating
fab
small
dark
@click="handleDeleteUserPost(post)"
>
<v-icon>delete</v-icon>
</v-btn>
<v-img
height="30vh"
:src="post.imageUrl"
@click="goToPost(post._id)"
></v-img>
<v-card-text>{{ post.title }}</v-card-text>
</v-card>
</v-flex>
</v-layout>
</v-container>
<v-dialog xs12 sm6 offset-sm3 persistent v-model="editPostDialog">
<v-card>
<v-card-title class="headline grey lighten-2"
>Update Your Post</v-card-title
>
<v-container>
<v-form
v-model="isFormValid"
lazy-validation
ref="form"
@submit.prevent="handleUpdateUserPost"
>
<v-layout row>
<v-flex xs12>
<v-text-field
:rules="titleRules"
v-model="title"
label="Post Title"
type="text"
required
></v-text-field>
</v-flex>
</v-layout>
<v-layout row>
<v-flex xs12>
<v-text-field
:rules="imageRules"
v-model="imageUrl"
label="Image URL"
type="text"
required
></v-text-field>
</v-flex>
</v-layout>
<v-layout row>
<v-flex xs12>
<img :src="imageUrl" height="300px" />
</v-flex>
</v-layout>
<v-layout row>
<v-flex xs12>
<v-select
v-model="categories"
:rules="categoriesRules"
:items="[
'Gaming',
'Art',
'Picture',
'Painting',
'Hardware',
'PC',
'Laptop',
'NVIDIA',
'AMD',
'Intel',
'Razer',
'Asus',
'HP',
'Toshiba',
'MSI',
'Origin',
'RGB',
]"
multiple
label="Categories"
></v-select>
</v-flex>
</v-layout>
<v-layout row>
<v-flex xs12>
<v-textarea
:rules="descRules"
v-model="description"
label="Description"
type="text"
required
></v-textarea>
</v-flex>
</v-layout>
<v-divider></v-divider>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
:disabled="!isFormValid"
type="submit"
class="success--text"
flat
>Update</v-btn
>
<v-btn class="error--text" flat @click="editPostDialog = false"
>Cancel</v-btn
>
</v-card-actions>
</v-form>
</v-container>
</v-card>
</v-dialog>
</v-container>
</template>
<script>
import moment from "moment";
import { mapGetters } from "vuex";
import Vue from "vue";
export default {
name: "Profile",
data() {
return {
editPostDialog: false,
isFormValid: true,
title: "",
imageUrl: "",
categories: [],
description: "",
titleRules: [
(title) => !!title || "The post must have a title!",
(title) =>
title.length < 30 ||
"The post title must not be longer than 30 characters!",
],
imageRules: [(image) => !!image || "The post must have an image!"],
categoriesRules: [
(categories) =>
categories.length >= 1 ||
"The post must have at least one category associated with it!",
],
descRules: [
(desc) => !!desc || "The post must have a description!",
(desc) =>
desc.length < 200 ||
"The post description must not be longer than 200 characters!",
],
};
},
computed: {
...mapGetters(["user", "userFavorites", "userPosts"]),
},
created() {
this.handleGetUserPosts();
},
methods: {
goToPost(id) {
this.$router.push(`/posts/${id}`);
},
formatJoinDate(date) {
return moment(new Date(date)).format("ll");
},
handleGetUserPosts() {
this.$store.dispatch("getUserPosts", {
userId: this.user._id,
});
},
handleUpdateUserPost() {
if (this.$refs.form.validate()) {
this.$store.dispatch("updateUserPost", {
postId: this.postId,
userId: this.user._id,
title: this.title,
imageUrl: this.imageUrl,
categories: this.categories,
description: this.description,
});
this.editPostDialog = false;
}
},
handleDeleteUserPost(post) {
this.loadPost(post, false);
const deletePost = window.confirm("Do you want to delete your post?");
if (deletePost) {
this.$store.dispatch("deleteUserPost", {
postId: this.postId,
});
// ,this.$router.go("/");
}
},
loadPost(
{ _id, title, imageUrl, categories, description },
editPostDialog = true
) {
this.editPostDialog = editPostDialog;
this.postId = _id;
this.title = title;
this.imageUrl = imageUrl;
this.categories = categories;
this.description = description;
},
},
};
</script>
Сообщений. vue
<template>
<v-container fluid grid-list-xl>
<v-layout row wrap v-if="infiniteScrollPosts">
<v-flex xl sm3 v-for="post in infiniteScrollPosts.posts" :key="post._id">
<v-card hover>
<v-img
@click.native="goToPost(post._id)"
:src="post.imageUrl"
height="30vh"
lazy
></v-img>
<v-card-actions>
<v-card-title primary>
<div>
<div class="headline">{{ post.title }}</div>
<span class="grey--text"
>{{ post.likes }} likes -
{{ post.messages.length }} comments</span
>
</div>
</v-card-title>
<v-spacer></v-spacer>
<v-btn @click="showPostCreator = !showPostCreator" icon>
<v-icon>{{
`keyboard_arrow_${showPostCreator ? "up" : "down"}`
}}</v-icon>
</v-btn>
</v-card-actions>
<v-slide-y-transition>
<v-card-text v-show="showPostCreator" class="grey lighten-4">
<v-list-tile avatar>
<v-list-tile-avatar>
<img :src="post.createdBy.avatar" />
</v-list-tile-avatar>
<v-list-tile-content>
<v-list-tile-title class="text--primary">{{
post.createdBy.username
}}</v-list-tile-title>
<v-list-tile-sub-title class="font-weight-thin"
>Added
{{
formatCreatedDate(post.createdDate)
}}</v-list-tile-sub-title
>
</v-list-tile-content>
<v-list-tile-action>
<v-btn icon ripple>
<v-icon color="grey lighten-1">info</v-icon>
</v-btn>
</v-list-tile-action>
</v-list-tile>
</v-card-text>
</v-slide-y-transition>
</v-card>
</v-flex>
</v-layout>
<v-layout v-if="showMoreEnabled" column>
<v-flex xs12>
<v-layout justify-center row>
<v-btn color="info" @click="showMorePosts">Display More</v-btn>
</v-layout>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
import moment from "moment";
import { INFINITE_SCROLL_POSTS } from "../../queries";
import Vue from "vue";
const pageSize = 8;
export default {
name: "Posts",
data() {
return {
pageNum: 1,
showPostCreator: false,
};
},
apollo: {
infiniteScrollPosts: {
query: INFINITE_SCROLL_POSTS,
variables: {
pageNum: 1,
pageSize,
},
},
},
computed: {
showMoreEnabled() {
return this.infiniteScrollPosts && this.infiniteScrollPosts.hasMore;
},
},
methods: {
formatCreatedDate(date) {
return moment(new Date(date)).format("ll");
},
showMorePosts() {
this.pageNum += 1;
this.$apollo.queries.infiniteScrollPosts.fetchMore({
variables: {
pageNum: this.pageNum,
pageSize,
},
updateQuery: (prevResult, { fetchMoreResult }) => {
const newPosts = fetchMoreResult.infiniteScrollPosts.posts;
const hasMore = fetchMoreResult.infiniteScrollPosts.hasMore;
return {
infiniteScrollPosts: {
__typename: prevResult.infiniteScrollPosts.__typename,
posts: [...prevResult.infiniteScrollPosts.posts, ...newPosts],
hasMore,
},
};
},
});
},
goToPost(postId) {
this.$router.push(`/posts/${postId}`);
},
},
};
</script>
Сообщение. vue
<template>
<v-container v-if="getPost" class="mt-3" flexbox center>
<v-layout row wrap>
<v-flex xs12>
<v-card hover>
<v-card-title>
<h1>{{ getPost.title }}</h1>
<v-btn @click="handleToggleLike" large icon v-if="user">
<v-icon
large
:color="checkIfPostLiked(getPost._id) ? 'red' : 'grey'"
>favorite</v-icon
>
</v-btn>
<h3 class="ml-3 font-weight-thin">
{{ getPost.likes }} TIMES FAVORITED
</h3>
<v-spacer></v-spacer>
<v-icon @click="goToPreviousPage" color="info" large
>arrow_back</v-icon
>
</v-card-title>
<v-tooltip right>
<span>Enlarge Image</span>
<v-img
@click="toggleImageDialog"
slot="activator"
:src="getPost.imageUrl"
id="post__image"
></v-img>
</v-tooltip>
<v-dialog v-model="dialog">
<v-card>
<v-img :src="getPost.imageUrl" height="80vh"></v-img>
</v-card>
</v-dialog>
<v-card-text>
<span v-for="(category, index) in getPost.categories" :key="index">
<v-chip class="mb-3" color="accent" text-color="white">{{
category
}}</v-chip>
</span>
<h3>{{ getPost.description }}</h3>
</v-card-text>
</v-card>
</v-flex>
</v-layout>
<div class="mt-3">
<v-layout class="mb-3" v-if="user">
<v-flex xs12>
<v-form
v-model="isFormValid"
lazy-validation
ref="form"
@submit.prevent="handleAddPostMessage"
>
<v-layout row>
<v-flex xs12>
<v-text-field
:rules="messageRules"
v-model="messageBody"
clearable
:append-outer-icon="messageBody && 'send'"
label="Add Comment"
type="text"
@click:append-outer="handleAddPostMessage"
prepend-icon="email"
required
></v-text-field>
</v-flex>
</v-layout>
</v-form>
</v-flex>
</v-layout>
<v-layout row wrap>
<v-flex xs12>
<v-list subheader two-line>
<v-subheader>Comments ({{ getPost.messages.length }})</v-subheader>
<template v-for="message in getPost.messages">
<v-divider :key="message._id"></v-divider>
<v-list-tile avatar inset :key="message.title">
<v-list-tile-avatar>
<img :src="message.messageUser.avatar" />
</v-list-tile-avatar>
<v-list-tile-content>
<v-list-tile-title>
{{ message.messageBody }}
</v-list-tile-title>
<v-list-tile-sub-title>
{{ message.messageUser.username }}
<span class="grey--text text--lighten-1 hidden-xs-only">{{
getTimeFromNow(message.messageDate)
}}</span>
</v-list-tile-sub-title>
</v-list-tile-content>
<v-list-tile-action class="hidden-xs-only">
<v-icon
:color="checkIfOwnMessage(message) ? 'accent' : 'grey'"
>chat_bubble</v-icon
>
</v-list-tile-action>
</v-list-tile>
</template>
</v-list>
</v-flex>
</v-layout>
</div>
</v-container>
</template>
<script>
import moment from "moment";
import { mapGetters } from "vuex";
import {
GET_POST,
ADD_POST_MESSAGE,
LIKE_POST,
UNLIKE_POST
} from "../../queries";
export default {
name: "Post",
props: ["postId"],
data() {
return {
postLiked: false,
dialog: false,
messageBody: "",
isFormValid: true,
messageRules: [
message => !!message || "A comment is required!",
message =>
(message && message.length < 100) ||
"The comment must not be longer than 100 characters!"
]
};
},
apollo: {
getPost: {
query: GET_POST,
variables() {
return {
postId: this.postId
};
}
}
},
computed: {
...mapGetters(["user", "userFavorites"])
},
methods: {
getTimeFromNow(time) {
return moment(new Date(time)).fromNow();
},
checkIfPostLiked(postId) {
this.postLiked =
this.userFavorites &&
this.userFavorites.some(fave => fave._id === postId);
return this.postLiked;
},
handleToggleLike() {
if (this.postLiked) {
this.handleUnlikePost();
} else {
this.handleLikePost();
}
},
handleLikePost() {
const variables = {
postId: this.postId,
username: this.user.username
};
this.$apollo
.mutate({
mutation: LIKE_POST,
variables,
update: (cache, { data: { likePost } }) => {
const data = cache.readQuery({
query: GET_POST,
variables: { postId: this.postId }
});
data.getPost.likes += 1;
cache.writeQuery({
query: GET_POST,
variables: { postId: this.postId },
data
});
}
})
.then(({ data }) => {
const updatedUser = {
...this.user,
favorites: data.likePost.favorites
};
this.$store.commit("setUser", updatedUser);
})
.catch(err => console.error(err));
},
handleUnlikePost() {
const variables = {
postId: this.postId,
username: this.user.username
};
this.$apollo
.mutate({
mutation: UNLIKE_POST,
variables,
update: (cache, { data: { unlikePost } }) => {
const data = cache.readQuery({
query: GET_POST,
variables: { postId: this.postId }
});
data.getPost.likes -= 1;
cache.writeQuery({
query: GET_POST,
variables: { postId: this.postId },
data
});
}
})
.then(({ data }) => {
const updatedUser = {
...this.user,
favorites: data.unlikePost.favorites
};
this.$store.commit("setUser", updatedUser);
})
.catch(err => console.error(err));
},
handleAddPostMessage() {
if (this.$refs.form.validate()) {
const variables = {
messageBody: this.messageBody,
userId: this.user._id,
postId: this.postId
};
this.$apollo
.mutate({
mutation: ADD_POST_MESSAGE,
variables,
update: (cache, { data: { addPostMessage } }) => {
const data = cache.readQuery({
query: GET_POST,
variables: { postId: this.postId }
});
data.getPost.messages.unshift(addPostMessage);
cache.writeQuery({
query: GET_POST,
variables: { postId: this.postId },
data
});
}
})
.then(({ data }) => {
this.$refs.form.reset();
})
.catch(err => console.error(err));
}
},
goToPreviousPage() {
this.$router.go(-1);
},
toggleImageDialog() {
if (window.innerWidth > 500) {
this.dialog = !this.dialog;
}
},
checkIfOwnMessage(message) {
return this.user && this.user._id === message.messageUser._id;
}
}
};
</script>
<style scoped>
#post__image {
height: 400px !important;
}
</style>
Мне не хватило места. Если вы хотите проверить приложение. vue, main, js, store. js et c. пожалуйста, проверьте мой GitHub!