Last active
August 23, 2021 08:48
-
-
Save imposibrus/e6227d0c5632508442c53c2d86869933 to your computer and use it in GitHub Desktop.
Vue.js example components
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div> | |
<section class="page-content__content page-content__wrap-items"> | |
<div class="community page-content__community"> | |
<div class="community__head"> | |
<figure class="community__cover"> | |
<img :src="community.image_big_url" class="community__cover-img" alt=""> | |
</figure> | |
<div class="community__head-panel"> | |
<div class="community__head-avatar"> | |
<user-avatar | |
:roles="community.role_names" | |
:user-image="community.image_small_url" | |
:class="'user-avatar--sm'" | |
/> | |
</div> | |
<h1 class="community__name"> | |
{{ community.name }} | |
</h1> | |
<div class="tags tags--big tags--direction community__head-tags"> | |
<div class="tags__item"> | |
{{ community.direction_name }} | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="community__item"> | |
<div class="community__subtitle"> | |
О сообществе | |
</div> | |
<p class="community__text"> | |
{{ community.description }} | |
</p> | |
</div> | |
<div class="community__item"> | |
<router-link :to="`/${community.id }/themes`" class="community__subtitle community__subtitle--link"> | |
Обсуждения <span class="community__head-count">{{ community.themes_count }}</span> | |
</router-link> | |
</div> | |
<section class="discuss"> | |
<discuss-add-post :community="community" /> | |
<div class="discuss__content"> | |
<discuss-entry v-for="discussion in community.discussions" :key="discussion.id" | |
:discussion="discussion" | |
/> | |
</div> | |
</section> | |
</div> | |
</section> | |
</div> | |
</template> | |
<script> | |
import DiscussEntry from './DiscussEntry.vue'; | |
import UserAvatar from './UserAvatar.vue'; | |
import DiscussAddPost from './DiscussAddPost.vue'; | |
import { getCommunityByRouterParam } from './mixins'; | |
export default { | |
name: 'Community', | |
components: { DiscussAddPost, UserAvatar, DiscussEntry }, | |
mixins: [getCommunityByRouterParam], | |
props: { | |
id: { | |
type: String, | |
default: '0', | |
}, | |
}, | |
}; | |
</script> | |
<style lang="scss" scoped> | |
.fade-enter-active, | |
.fade-enter { | |
opacity: 0; | |
animation-name: fade-in-down; | |
animation-duration: 0.5s; | |
animation-fill-mode: both; | |
will-change: transform, opacity; | |
} | |
.fade-leave-to, | |
.fade-leave-active { | |
opacity: 0; | |
animation-name: fadeOutUp; | |
animation-duration: 0.35s; | |
animation-fill-mode: both; | |
will-change: transform, opacity; | |
} | |
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<article class="page-content__panel panel-item panel-item--community"> | |
<div class="panel-item__panel"> | |
<router-link :to="`/${community.id }`" class="panel-item__community-link"> | |
<div class="panel-item__community-head"> | |
<user-avatar | |
:roles="community.role_names" | |
:user-image="community.image_small_url" | |
:class="'user-avatar--lg'" | |
/> | |
<h2 class="panel-item__community-name"> | |
{{ community.name }} | |
</h2> | |
</div> | |
<div class="panel-item__community-info"> | |
<div class="panel-item__count-message"> | |
<span class="panel-item__number-message">{{ community.themes_count }} </span> | |
{{ getPluralDiscuss }} / <span | |
class="panel-item__number-message" | |
>{{ community.comments_total_count }} </span> {{ | |
getPluralComment }} | |
</div> | |
<div class="panel-item__last-message"> | |
Последнее сообщение: | |
<time :datetime="getFormatTime">{{ getTime }}</time> | |
</div> | |
<div class="tags tags--big tags--direction panel-item__tags"> | |
<div class="tags__item"> | |
{{ community.direction_name }} | |
</div> | |
</div> | |
</div> | |
</router-link> | |
</div> | |
</article> | |
</template> | |
<script> | |
import format from 'date-fns/format'; | |
import { noun } from 'plural-ru'; | |
import Utils from '../Utils'; | |
import UserAvatar from './UserAvatar.vue'; | |
export default { | |
name: 'CommunityListItem', | |
components: { UserAvatar }, | |
props: { | |
community: { | |
type: Object, | |
default: () => ({}), | |
}, | |
}, | |
computed: { | |
getTime() { | |
return Utils.getTime(this.community.last_updated); | |
}, | |
getFormatTime() { | |
return format(Utils.normalizeTimestamp(this.community.last_updated), | |
'YYYY-MM-DDTHH:mm',); | |
}, | |
getPluralComment() { | |
return noun( | |
this.community.comments_total_count, | |
'комментарий', | |
'комментария', | |
'комментариев', | |
); | |
}, | |
getPluralDiscuss() { | |
return noun( | |
this.community.themes_count, | |
'обсуждение', | |
'обсуждения', | |
'обсуждений', | |
); | |
}, | |
}, | |
methods: {}, | |
}; | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div> | |
<transition name="fade" mode="out-in"> | |
<!--Кнопка добавление нового обсуждения--> | |
<div v-show="!expanded" class="discuss__start"> | |
<user-avatar | |
:roles="[roleName]" | |
:user-image="avatar" | |
:class="'user-avatar--md'" | |
/> | |
<div class="discuss__start-input" @click="placeholderClick"> | |
<div class="discuss__placeholder"> | |
Добавить запись... | |
</div> | |
</div> | |
</div> | |
</transition> | |
<transition name="fade" mode="in-out"> | |
<!--Форма добавление нового обсуждения--> | |
<div v-show="expanded" class="discuss__add"> | |
<form :class="{'discuss__form--loading': loading}" action="#" method="POST" class="discuss__form form" @submit.prevent="formSubmit"> | |
<div class="form__row discuss__form-row"> | |
<div class="form__line"> | |
<div :class="{'form__wrap-input--invalid': errors.has('text')}" class="form__wrap-input discuss__wrap-textarea"> | |
<textarea ref="textarea" | |
v-model="text" | |
v-validate="'required|min:5'" | |
class="form__input form__input--shadow-grey discuss__add-area-text js-textareaAutosize" | |
name="text" | |
rows="1" | |
placeholder="Напишите..." | |
></textarea> | |
<span v-show="errors.has('text')" class="form__error-msg">{{ errors.first('text') }}</span> | |
</div> | |
</div> | |
</div> | |
<discuss-drop-zone @filesChanged="filesChanged" /> | |
<div class="discuss__form-action"> | |
<button class="btn-main btn-main--color-main" type="submit"> | |
Готово | |
</button> | |
<button class="btn-main btn-main--color-grey" type="button" @click="collapse"> | |
Отменить | |
</button> | |
</div> | |
</form> | |
</div> | |
</transition> | |
</div> | |
</template> | |
<script> | |
import get from 'lodash/get'; | |
import russian from 'vee-validate/dist/locale/ru'; | |
import swal from 'sweetalert'; | |
import UserAvatar from './UserAvatar.vue'; | |
import DiscussDropZone from './DiscussDropZone.vue'; | |
import { createNamespacedHelpers } from 'vuex'; | |
const communitiesModule = createNamespacedHelpers('communities'); | |
export default { | |
name: 'DiscussAddPost', | |
components: { UserAvatar, DiscussDropZone }, | |
props: { | |
community: { | |
type: Object, | |
default: () => ({}), | |
}, | |
theme: { | |
type: Object, | |
default: () => ({}), | |
}, | |
}, | |
data() { | |
return { | |
expanded: false, | |
loading: false, | |
files: [], | |
text: '', | |
}; | |
}, | |
computed: { | |
currentUser() { | |
return window.authUser; | |
}, | |
roleName() { | |
return get(this.currentUser, 'role.name'); | |
}, | |
avatar() { | |
return get(this.currentUser, 'avatar'); | |
}, | |
$textarea() { | |
return $(this.$refs.textarea); | |
}, | |
}, | |
created() { | |
this.$validator.localize('ru', { | |
messages: russian.messages, | |
attributes: { | |
text: 'Текст', | |
}, | |
}); | |
}, | |
methods: { | |
...communitiesModule.mapActions(['savePost']), | |
postEditCancel() { | |
this.expanded = !this.expanded; | |
}, | |
collapse() { | |
this.text = ''; | |
this.files = []; | |
this.expanded = false; | |
this.$textarea.trigger('blur'); | |
this.errors.clear(); | |
}, | |
async formSubmit(e) { | |
if (await this.$validator.validateAll()) { | |
try { | |
await this.savePost({ | |
communityId: this.community.id, | |
body: { | |
files: this.files, | |
text: this.text, | |
theme_id: this.theme.id, | |
}, | |
}); | |
this.collapse(); | |
this.loading = false; | |
} catch (e) { | |
swal({ | |
icon: 'error', | |
title: 'Ой-ой!', | |
text: 'Ошибка при сохранении', | |
}); | |
} | |
e.preventDefault(); | |
this.$textarea.trigger('blur'); | |
this.loading = true; | |
} | |
}, | |
filesChanged(files) { | |
this.files = files; | |
}, | |
placeholderClick() { | |
this.expanded = !this.expanded; | |
this.$nextTick(() => { | |
this.$textarea.trigger('focus'); | |
}); | |
}, | |
}, | |
}; | |
</script> | |
<style lang="scss" scoped> | |
.fade-enter-active, | |
.fade-enter { | |
opacity: 0; | |
animation-name: fade-in-down; | |
animation-duration: 0.5s; | |
animation-fill-mode: both; | |
will-change: transform, opacity; | |
} | |
.fade-leave-to, | |
.fade-leave-active { | |
opacity: 0; | |
animation-name: fadeOutUp; | |
animation-duration: 0.35s; | |
animation-fill-mode: both; | |
will-change: transform, opacity; | |
} | |
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div :class="cssClasses.entry"> | |
<div class="entry-discuss__header"> | |
<user-avatar | |
:url="`/page/id${user.id}`" | |
:roles="[user.role.name]" | |
:user-image="user.avatar" | |
:class="'user-avatar--md'" | |
> | |
<template slot="user-info"> | |
<div class="user-avatar__info"> | |
<div v-if="type === 'comment'" class="user-avatar__info-head"> | |
<div class="user-avatar__name"> | |
{{ user.first_name | capitalize }} {{ user.last_name | | |
capitalize }} | |
</div> | |
</div> | |
<div v-else class="user-avatar__name"> | |
{{ user.first_name | capitalize }} {{ user.last_name | | |
capitalize }} | |
</div> | |
<div class="user-avatar__sub-info"> | |
<time :datetime="getFormatTime" class="user-avatar__date">{{ getTime }}</time> | |
<template v-if="discussion.is_changed"> | |
<div v-if="isUserAdmin" | |
:class="{'user-avatar__change': true, 'user-avatar__change--original': changedHovered}" | |
@mouseover="changedMouseOver" @mouseout="changedMouseOut" @click="showOriginal" | |
> | |
{{ changedText }} | |
</div> | |
<div v-else class="user-avatar__change"> | |
Изменено | |
</div> | |
</template> | |
</div> | |
</div> | |
</template> | |
</user-avatar> | |
<div v-if="canEdit" class="entry-discuss__header-action"> | |
<button class="entry-discuss__header-btn" type="button" @click="deleteDiscussion"> | |
<svg class="entry-discuss__header-icon"> | |
<use xlink:href="#close-yellow"></use> | |
</svg> | |
</button> | |
</div> | |
<div v-if="discussion.user_to" class="entry-discuss__answer"> | |
ответила <span>{{ discussion.user_to.first_name | capitalize }}</span> | |
</div> | |
</div> | |
<body-content :text="discussionText" :files="discussion.files" :is-original="shouldShowOriginal" | |
:css-classes="{picture: cssClasses.bodyPicture}" | |
/> | |
<delete-discuss-entry v-show="deleteModalShown" | |
:type="type" | |
:loading="loading" | |
@confirm="deleteConfirm" | |
@cancel="cancelDelete" | |
/> | |
</div> | |
</template> | |
<script> | |
import format from 'date-fns/format'; | |
import get from 'lodash/get'; | |
import swal from 'sweetalert'; | |
import Utils from '../Utils'; | |
import BodyContent from './BodyContent.vue'; | |
import Likes2 from './Likes2.vue'; | |
import UserAvatar from './UserAvatar.vue'; | |
import DeleteDiscussEntry from './DeleteDiscussEntry.vue'; | |
import { getCommunityByRouterParam } from './mixins'; | |
import { createNamespacedHelpers } from 'vuex'; | |
const communitiesModule = createNamespacedHelpers('communities'); | |
export default { | |
name: 'DiscussBody', | |
components: { BodyContent, Likes2, UserAvatar, DeleteDiscussEntry }, | |
mixins: [getCommunityByRouterParam], | |
props: { | |
discussion: { | |
type: Object, | |
default: () => ({}), | |
}, | |
type: { | |
type: String, | |
default: 'post', | |
}, | |
cssClasses: { | |
type: Object, | |
default: () => ({ | |
entry: 'entry-discuss__entry', | |
bodyPicture: '', | |
}), | |
}, | |
themeId: { | |
type: Number, | |
default: null, | |
}, | |
}, | |
data() { | |
return { | |
deleteModalShown: false, | |
loading: false, | |
changedHovered: false, | |
shouldShowOriginal: false, | |
}; | |
}, | |
computed: { | |
getTime() { | |
return Utils.getTime(this.discussion.updated_at); | |
}, | |
getFormatTime() { | |
return format(Utils.normalizeTimestamp(this.discussion.updated_at), | |
'YYYY-MM-DDTHH:mm',); | |
}, | |
user() { | |
return this.discussion.user || this.discussion.user_from; | |
}, | |
isMyDiscussion() { | |
return get(window.authUser, 'id') === get(this.user, 'id'); | |
}, | |
isUserAdmin() { | |
return get(window.authUser, 'role.name') === 'superadmin'; | |
}, | |
canEdit() { | |
return this.isMyDiscussion || this.isUserAdmin; | |
}, | |
requestBody() { | |
return { | |
communityId: this.community.id, | |
[this.$props.type]: this.discussion, | |
themeId: this.themeId, | |
}; | |
}, | |
changedText() { | |
return this.changedHovered ? 'Оригинал текста' : 'Изменено'; | |
}, | |
discussionText() { | |
return this.shouldShowOriginal | |
? this.discussion.text_orig | |
: this.discussion.text; | |
}, | |
likesCount() { | |
return this.discussion.likes_count; | |
}, | |
}, | |
methods: { | |
...communitiesModule.mapActions([ | |
'deletePost', | |
'deleteComment', | |
'likePost', | |
'likeComment', | |
]), | |
deleteDiscussion() { | |
this.deleteModalShown = true; | |
}, | |
requestAction(action) { | |
return this.$props.type === 'post' | |
? action + 'Post' | |
: action + 'Comment'; | |
}, | |
requestConfirm(action) { | |
this.loading = true; | |
this[this.requestAction(action)](this.requestBody) | |
.then(() => { | |
this.loading = false; | |
this.deleteModalShown = false; | |
}) | |
.catch(() => { | |
swal({ | |
icon: 'error', | |
title: 'Ой-ой!', | |
text: 'Ошибка при удалении', | |
}); | |
}); | |
}, | |
likeDiscussion() { | |
this.requestConfirm('like'); | |
}, | |
deleteConfirm() { | |
this.requestConfirm('delete'); | |
}, | |
cancelDelete() { | |
this.deleteModalShown = false; | |
}, | |
changedMouseOver() { | |
this.changedHovered = true; | |
}, | |
changedMouseOut() { | |
if (!this.shouldShowOriginal) { | |
this.changedHovered = false; | |
} | |
}, | |
showOriginal() { | |
this.shouldShowOriginal = !this.shouldShowOriginal; | |
}, | |
reply() { | |
this.$emit('reply'); | |
}, | |
declination(user) { | |
return Utils.declination(user); | |
}, | |
}, | |
}; | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div class="entry-discuss discuss__item"> | |
<!-- Запись обсуждения --> | |
<discuss-body :discussion="discussion" | |
@reply="commentTarget(discussion.user)" | |
/> | |
<!--Комментария --> | |
<div class="entry-discuss__comments"> | |
<div v-show="showMoreVisible" class="btn-wrapper entry-discuss__btn-wrapper"> | |
<button | |
type="button" | |
class="btnV2 btnV2-grey btn-more entry-discuss__more-comment" | |
@click="loadMoreComments" | |
> | |
Показать все {{ discussion.comments_count }} {{ getPluralComment }} | |
</button> | |
</div> | |
<discuss-body | |
v-for="comment in comments" | |
:key="comment.id" | |
:discussion="comment" | |
:theme-id="discussion.theme_id" | |
:css-classes="{entry: 'entry-discuss__comment', bodyPicture: 'entry-discuss__picture--comment'}" | |
type="comment" | |
@reply="commentTarget(comment.user_from)" | |
/> | |
</div> | |
<!--Добавление комментария --> | |
<discuss-add-comment | |
ref="addComment" | |
:discussion="discussion" | |
:to-user-id="toUserId" | |
:to-user-name="toUserName" | |
@clearReply="clearTarget()" | |
/> | |
</div> | |
</template> | |
<script> | |
import { noun } from 'plural-ru'; | |
import DiscussAddComment from './DiscussAddComment.vue'; | |
import { COMMENTS_LIMIT } from './constants'; | |
import DiscussBody from './DiscussBody.vue'; | |
import { getCommunityByRouterParam } from './mixins'; | |
export default { | |
name: 'DiscussEntry', | |
components: { | |
DiscussBody, | |
DiscussAddComment, | |
}, | |
mixins: [getCommunityByRouterParam], | |
props: { | |
discussion: { | |
type: Object, | |
default: () => ({}), | |
}, | |
}, | |
data() { | |
return { | |
visibleCommentsCount: 5, | |
commentsExpanded: false, | |
toUserId: null, | |
toUserName: null, | |
}; | |
}, | |
computed: { | |
getPluralComment() { | |
return noun( | |
this.discussion.comments_count, | |
'комментарий', | |
'комментария', | |
'комментриев', | |
); | |
}, | |
hasMoreComments() { | |
return this.discussion.comments_count > COMMENTS_LIMIT; | |
}, | |
showMoreVisible() { | |
return !this.commentsExpanded && this.hasMoreComments; | |
}, | |
comments() { | |
return this.discussion.comments.slice(0, this.visibleCommentsCount); | |
}, | |
restComments() { | |
return false; | |
}, | |
}, | |
methods: { | |
loadMoreComments() { | |
this.visibleCommentsCount = 999; | |
this.commentsExpanded = true; | |
}, | |
commentTarget(user) { | |
this.toUserId = user.id; | |
this.toUserName = user.first_name; | |
this.$refs.addComment.formClick(); | |
}, | |
clearTarget() { | |
this.toUserId = null; | |
this.toUserName = null; | |
}, | |
}, | |
}; | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createNamespacedHelpers } from 'vuex'; | |
const communitiesModule = createNamespacedHelpers('communities'); | |
export const getCommunityByRouterParam = { | |
computed: { | |
...communitiesModule.mapGetters(['getCommunityById']), | |
community() { | |
return ( | |
this.getCommunityById(parseInt(this.$route.params.id, 10)) || {} | |
); | |
}, | |
}, | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<transition-group :class="mod" name="list-fade" tag="ul" class="previews dialog__previews"> | |
<li v-for="(preview, index) in allFiles" :key="index" class="working previews__item"> | |
<span v-if="isUpload" :data-index="index" class="previews__close" @click="closeClick($event, preview)">Закрыть</span> | |
<div class="swiper-preview-file"> | |
<a :href="preview.url" class="swiper-preview-file__link" @click.prevent="previewClick(preview)"> | |
<img :src="preview.src" class="previews__img" alt=""> | |
</a> | |
</div> | |
<div v-if="isUpload" id="progress" class="progress"> | |
<div class="progress__bar" style="width: 0;"></div> | |
</div> | |
</li> | |
</transition-group> | |
</template> | |
<script> | |
import { genericFancyBox } from './fancyBoxHelper'; | |
import * as globalModule from 'vuex'; | |
export default { | |
name: 'Previews', | |
props: { | |
allFiles: { | |
type: Array, | |
default: () => [], | |
}, | |
isUpload: { | |
type: Boolean, | |
default: false, | |
}, | |
mod: { | |
type: String, | |
default: '', | |
}, | |
}, | |
computed: { | |
...globalModule.mapGetters(['modalBg']), | |
}, | |
methods: { | |
...globalModule.mapActions(['setModalBg']), | |
closeClick(e, preview) { | |
if (!preview.id && preview.jqXHR) { | |
preview.jqXHR.abort(); | |
} | |
this.$emit( | |
'closePreview', e, preview | |
); | |
}, | |
previewClick(clickedPreview) { | |
const currentIndex = this.allFiles.indexOf(clickedPreview), | |
slides = this.allFiles.map(file => ({ href: file.url })); | |
genericFancyBox(slides, currentIndex, this.modalBg, color => { | |
this.setModalBg(color); | |
}); | |
}, | |
}, | |
}; | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<a v-if="url" | |
:href="url" | |
:class="outerSizeImage" | |
class="user-avatar user-avatar--link" | |
target="_blank" | |
rel="noopener noreferrer" | |
> | |
<div class="user-avatar__wrap-picture"> | |
<div class="user-avatar__picture"> | |
<img :src="userImage" alt="" class="user-avatar__img"> | |
</div> | |
<div v-if="showRoles" class="user-avatar__positions"> | |
<div v-for="(role, index) in roles" :key="index" class="user-avatar__position"> | |
<svg class="user-avatar__icon"> | |
<use :xlink:href="`#user-position-${role}`"></use> | |
</svg> | |
</div> | |
</div> | |
</div> | |
<slot name="user-info"></slot> | |
</a> | |
<div v-else :class="outerSizeImage" class="user-avatar"> | |
<div class="user-avatar__wrap-picture"> | |
<div class="user-avatar__picture"> | |
<img :src="userImage" alt="" class="user-avatar__img"> | |
</div> | |
<div v-if="showRoles" class="user-avatar__positions"> | |
<div v-for="(role, index) in roles" :key="index" class="user-avatar__position"> | |
<svg class="user-avatar__icon"> | |
<use :xlink:href="`#user-position-${role}`"></use> | |
</svg> | |
</div> | |
</div> | |
</div> | |
<slot name="user-info"></slot> | |
</div> | |
</template> | |
<script> | |
export default { | |
name: 'UserAvatar', | |
props: { | |
url: { | |
type: String, | |
default: '', | |
}, | |
outerSizeImage: { | |
type: String, | |
default: '', | |
}, | |
userImage: { | |
type: String, | |
default: '', | |
}, | |
roles: { | |
type: Array, | |
default() { | |
return []; | |
}, | |
}, | |
showRoles: { | |
type: Boolean, | |
default: true, | |
}, | |
}, | |
}; | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment