code refactoring

This commit is contained in:
Виктор Батищев 2023-10-11 15:27:06 +03:00
parent 9494762e86
commit 4b2b7f9868
2 changed files with 407 additions and 304 deletions

View File

@ -1,107 +1,122 @@
<template> <template>
<article class="news-post"> <article class="news-post">
<ImageComp class="news-post__image" @load="imageLoaded" :src="post.photo" :alt="post.title" /> <ImageComp
class="news-post__image"
@load="imageLoaded"
:src="post.photo"
:alt="post.title"
/>
<div class="news-post__container"> <div class="news-post__container">
<HeaderComp>{{ post.title }}</HeaderComp> <HeaderComp>{{ post.title }}</HeaderComp>
<MetaComp class="news-post__meta" :id="id" :category="post.category" :tags="post.tags" :published_date="post.published_date" /> <MetaComp
class="news-post__meta"
:id="id"
:category="post.category"
:tags="post.tags"
:published_date="post.published_date"
/>
<SocialComp <SocialComp
class="news-post__social" class="news-post__social"
:scope="'news'" :scope="'news'"
:id="id" :id="id"
:like="post.like" :like="post.like"
:comments_count="post.comments_count" :comments_count="post.comments_count"
:views="post.views" :views="post.views"
/> />
<div class="news-post__content" v-html="content"></div> <div class="news-post__content" v-html="content"></div>
<CommentsList :id="id" ref="refComments" /> <CommentsList :id="id" ref="refComments" />
</div> </div>
<FooterContainer /> <FooterContainer />
</article> </article>
</template> </template>
<script setup> <script setup>
import { computed, defineProps, onMounted, ref } from "vue"; import { computed, defineProps, onMounted, ref } from 'vue'
// components // components
import CommentsList from "./Comments/CommentsList.vue"; import CommentsList from './Comments/CommentsList.vue'
import FooterContainer from "@/components/Footer/FooterContainer.vue"; import FooterContainer from '@/components/Footer/FooterContainer.vue'
import MetaComp from "@/components/Common/MetaComp.vue"; import MetaComp from '@/components/Common/MetaComp.vue'
import SocialComp from "@/components/Common/SocialComp.vue"; import SocialComp from '@/components/Common/SocialComp.vue'
// composables // composables
import { useNewsApi } from "@/composables/api/news"; import { useNewsApi } from '@/composables/api/news'
const { fetchPostById } = useNewsApi(); const { fetchPostById } = useNewsApi()
// reouter // reouter
import { onBeforeRouteUpdate, useRoute, useRouter } from "vue-router"; import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router'
const route = useRoute(); const route = useRoute()
const router = useRouter(); const router = useRouter()
const props = defineProps(["id"]); const props = defineProps(['id'])
const post = ref({ title: "", news_body: "", photo: "", category: 0, tags: [], like: 0, comments_count: 0, views: 0 }); const post = ref({
title: '',
news_body: '',
photo: '',
category: 0,
tags: [],
like: 0,
comments_count: 0,
views: 0
})
const refComments = ref(null); const refComments = ref(null)
const content = computed(() => { const content = computed(() => {
if (post.value.news_body) { return post.value.news_body
let val = "<p>"; ? `<p>${post.value.news_body.replaceAll('\n', '</p><p>')}</p>`
val += post.value.news_body.replaceAll("\n", "</p><p>"); : ''
val += "</p>"; })
return val;
}
return "";
});
onMounted(init); onMounted(init)
async function init() { async function init() {
window.scroll(0, 0); window.scroll(0, 0)
let fetchedPost = await fetchPostById(props.id); post.value = await fetchPostById(props.id)
post.value = fetchedPost; document.title = `${post.value.title} - DNR.ONE`
document.title = `${post.value.title} - DNR.ONE`;
} }
function imageLoaded() { function imageLoaded() {
if (route.hash) { if (route.hash) {
scrollToComments(); scrollToComments()
} }
} }
function scrollToComments() { function scrollToComments() {
let el = refComments.value.$el; let el = refComments.value.$el
window.scrollTo({ top: el.offsetTop - 45, behavior: "smooth" }); window.scrollTo({ top: el.offsetTop - 45, behavior: 'smooth' })
router.replace({ hash: "", query: route.query }); router.replace({ hash: '', query: route.query })
} }
onBeforeRouteUpdate((to, from) => { onBeforeRouteUpdate((to, from) => {
if (to.hash === "#comments" && from.hash !== "#comments") { if (to.hash === '#comments' && from.hash !== '#comments') {
scrollToComments(); scrollToComments()
} }
}); })
</script> </script>
<style lang="scss"> <style lang="scss">
.news-post { .news-post {
width: 600px; width: 600px;
max-width: 100%; max-width: 100%;
margin: 0 auto; margin: 0 auto;
&__container { &__container {
} }
&__meta { &__meta {
margin-bottom: 5px; margin-bottom: 5px;
} }
&__social { &__social {
margin-bottom: 5px; margin-bottom: 5px;
} }
&__image { &__image {
margin: 20px 0 20px 0; margin: 20px 0 20px 0;
width: 100%; width: 100%;
} }
} }
</style> </style>

View File

@ -1,305 +1,393 @@
<template> <template>
<div class="login-register" :class="isLogin ? 'login-register--login' : 'login-register--register'"> <div
<form class="login-register__form"> class="login-register"
<input class="login-register__input" v-if="!isLogin" v-model="userData.email" type="email" placeholder="почта" name="email" /> :class="isLogin ? 'login-register--login' : 'login-register--register'"
<input class="login-register__input" v-model="userData.username" type="text" placeholder="имя пользователя" name="username" /> >
<input class="login-register__input" v-model="userData.password" type="password" placeholder="пароль" name="password" /> <form class="login-register__form">
<input <input
class="login-register__input" class="login-register__input"
v-if="!isLogin" v-if="!isLogin"
v-model="userData.password2" v-model="userData.email"
type="password" type="email"
placeholder="повторите пароль" placeholder="почта"
name="password2" name="email"
/> />
<ButtonComp class="login-register__button" :value="buttonLabel" @click="submit()" :disabled="pending" /> <input
class="login-register__input"
v-model="userData.username"
type="text"
placeholder="имя пользователя"
name="username"
/>
<input
class="login-register__input"
v-model="userData.password"
type="password"
placeholder="пароль"
name="password"
/>
<input
class="login-register__input"
v-if="!isLogin"
v-model="userData.password2"
type="password"
placeholder="повторите пароль"
name="password2"
/>
<ButtonComp
class="login-register__button"
:value="buttonLabel"
@click="submit()"
:disabled="pending"
/>
<div class="login-register__redirect"> <div class="login-register__redirect">
{{ redirectText }} {{ redirectText }}
<router-link class="login-register__redirect-link" v-if="isLogin" to="/user/register" @click="reset()"> <router-link
Регистрация class="login-register__redirect-link"
</router-link> v-if="isLogin"
<router-link class="login-register__redirect-link" v-else to="/user/login" @click="reset()">Войти</router-link> to="/user/register"
</div> @click="reset()"
</form> >
<div class="login-register__notifications"> Регистрация
<TransitionGroup name="login-register__notifications--transition"> </router-link>
<div <router-link
class="login-register__notification" class="login-register__redirect-link"
v-for="(notification, index) in notifications" v-else
:key="notification" to="/user/login"
:class="`login-register__notification--${notification.type}`" @click="reset()"
> >Войти</router-link
<font-awesome-icon >
v-if="notification.type === 'success'" </div>
class="login-register__notification-icon" </form>
:icon="['far', 'circle-check']" <div class="login-register__notifications">
/> <TransitionGroup name="login-register__notifications--transition">
<font-awesome-icon v-else class="login-register__notification-icon" :icon="['far', 'circle-xmark']" /> <div
<div class="login-register__notification-info"> class="login-register__notification"
<div class="login-register__notification-title" v-html="notification.title"></div> v-for="(notification, index) in notifications"
<div class="login-register__notification-text" v-html="notification.text"></div> :key="notification"
</div> :class="`login-register__notification--${notification.type}`"
<font-awesome-icon class="login-register__notification-close" icon="xmark" @click="removeNotification(index)" /> >
</div> <font-awesome-icon
</TransitionGroup> v-if="notification.type === 'success'"
class="login-register__notification-icon"
:icon="['far', 'circle-check']"
/>
<font-awesome-icon
v-else
class="login-register__notification-icon"
:icon="['far', 'circle-xmark']"
/>
<div class="login-register__notification-info">
<div
class="login-register__notification-title"
v-html="notification.title"
></div>
<div
class="login-register__notification-text"
v-html="notification.text"
></div>
</div>
<font-awesome-icon
class="login-register__notification-close"
icon="xmark"
@click="removeNotification(index)"
/>
</div> </div>
</TransitionGroup>
</div> </div>
</div>
</template> </template>
<script setup> <script setup>
import { ref, defineProps, computed } from "vue"; import { ref, defineProps, computed } from 'vue'
import { FieldsContainer, FieldChecks } from "@/js/validator"; import { FieldsContainer, FieldChecks } from '@/js/validator'
// composables // composables
import { useUserApi } from "@/composables/api/user"; import { useUserApi } from '@/composables/api/user'
const api = useUserApi(); const api = useUserApi()
// stores // stores
import { useUser } from "@/stores"; import { useUser } from '@/stores'
const userStore = useUser(); const userStore = useUser()
// router // router
import { onBeforeRouteLeave, useRouter } from "vue-router"; import { onBeforeRouteLeave, useRouter } from 'vue-router'
const router = useRouter(); const router = useRouter()
const props = defineProps(["type"]); const props = defineProps(['type'])
const userData = ref({ const userData = ref({
email: "", email: '',
username: "", username: '',
password: "", password: '',
password2: "", password2: ''
}); })
const notifications = ref([]); const notifications = ref([])
const pending = ref(false); const pending = ref(false)
const isLogin = computed(() => props.type === "login"); const isLogin = computed(() => props.type === 'login')
const buttonLabel = computed(() => (isLogin.value ? "Войти" : "Регистрация")); const buttonLabel = computed(() => (isLogin.value ? 'Войти' : 'Регистрация'))
const redirectText = computed(() => (isLogin.value ? "Нет аккаунта?" : "Уже есть аккаунт?")); const redirectText = computed(() =>
isLogin.value ? 'Нет аккаунта?' : 'Уже есть аккаунт?'
)
function submit() { function submit() {
notifications.value = []; notifications.value = []
let errors = getFieldsErrors(); let errors = getFieldsErrors()
if (errors.length == 0) { if (errors.length == 0) {
if (isLogin.value) { if (isLogin.value) {
login(); login()
} else {
register();
}
} else { } else {
setTimeout(() => { register()
for (let i = 0; i < errors.length; i++) {
setTimeout(() => {
notifications.value.push({ type: "error", title: errors[i].title, text: errors[i].text });
}, i * 50);
}
}, 200);
} }
} else {
setTimeout(() => {
for (let i = 0; i < errors.length; i++) {
setTimeout(() => {
notifications.value.push({
type: 'error',
title: errors[i].title,
text: errors[i].text
})
}, i * 50)
}
}, 200)
}
} }
function getFieldsErrors() { function getFieldsErrors() {
const container = new FieldsContainer(); const container = new FieldsContainer()
if (!isLogin.value) { if (!isLogin.value) {
container.addField("почта", userData.value.email, [FieldChecks.nonEmpty, FieldChecks.email]); container.addField('почта', userData.value.email, [
} FieldChecks.nonEmpty,
container.addField("имя пользователя", userData.value.username, [ FieldChecks.email
FieldChecks.nonEmpty, ])
FieldChecks.onlyLettersNumbersUnderscores, }
FieldChecks.minLength(4), container.addField('имя пользователя', userData.value.username, [
FieldChecks.maxLength(20), FieldChecks.nonEmpty,
]); FieldChecks.onlyLettersNumbersUnderscores,
container.addField("пароль", userData.value.password, [FieldChecks.nonEmpty, FieldChecks.minLength(6), FieldChecks.maxLength(32)]); FieldChecks.minLength(4),
FieldChecks.maxLength(20)
])
container.addField('пароль', userData.value.password, [
FieldChecks.nonEmpty,
FieldChecks.minLength(6),
FieldChecks.maxLength(32)
])
let errors = []; let errors = []
for (let key in container.fields) { for (let key in container.fields) {
let error = container.fields[key].tryGetFirstError(); let error = container.fields[key].tryGetFirstError()
if (error !== "") { if (error !== '') {
errors.push({ title: key, text: error }); errors.push({ title: key, text: error })
}
} }
}
if (!isLogin.value) { if (!isLogin.value) {
if (userData.value.password !== userData.value.password2) { if (userData.value.password !== userData.value.password2) {
errors.push({ title: "пароль", text: "пароли не совпадают" }); errors.push({ title: 'пароль', text: 'пароли не совпадают' })
}
} }
return errors; }
return errors
} }
async function login() { async function login() {
if (pending.value) return; if (pending.value) return
pending.value = true; pending.value = true
let resp = await api.login({ username: userData.value.username, password: userData.value.password }); let resp = await api.login({
if (resp.hasErrors) { username: userData.value.username,
for (let key in resp.error) { password: userData.value.password
notifications.value.push({ type: "error", title: key, text: resp.error[key][0] }); })
} if (resp.hasErrors) {
} else { for (let key in resp.error) {
userStore.setUserData(resp.data); notifications.value.push({
userStore.saveUserData(); type: 'error',
title: key,
notifications.value.push({ type: "success", title: "вход", text: "Вход выполнен успешно." }); text: resp.error[key][0]
redirectAfterTimeout(); })
} }
pending.value = false; } else {
userStore.setUserData(resp.data)
userStore.saveUserData()
notifications.value.push({
type: 'success',
title: 'вход',
text: 'Вход выполнен успешно.'
})
redirectAfterTimeout()
}
pending.value = false
} }
async function register() { async function register() {
if (pending.value) return; if (pending.value) return
pending.value = true; pending.value = true
let resp = await api.register({ email: userData.value.email, username: userData.value.username, password: userData.value.password }); let resp = await api.register({
if (resp.hasErrors) { email: userData.value.email,
for (let key in resp) { username: userData.value.username,
notifications.value.push({ type: "error", title: key, text: resp.error[key][0] }); password: userData.value.password
} })
} else { if (resp.hasErrors) {
userStore.setUserData(resp.data); for (let key in resp) {
userStore.saveUserData(); notifications.value.push({
type: 'error',
notifications.value.push({ type: "success", title: "регистрация", text: "Регистрация прошла успешно." }); title: key,
redirectAfterTimeout(); text: resp.error[key][0]
})
} }
pending.value = false; } else {
userStore.setUserData(resp.data)
userStore.saveUserData()
notifications.value.push({
type: 'success',
title: 'регистрация',
text: 'Регистрация прошла успешно.'
})
redirectAfterTimeout()
}
pending.value = false
} }
function reset() { function reset() {
userData.value = { userData.value = {
email: "", email: '',
username: "", username: '',
password: "", password: '',
password2: "", password2: ''
}; }
notifications.value = []; notifications.value = []
} }
function removeNotification(index) { function removeNotification(index) {
notifications.value.splice(index, 1); notifications.value.splice(index, 1)
} }
function redirectAfterTimeout() { function redirectAfterTimeout() {
setTimeout(() => { setTimeout(() => {
router.push("/"); router.push('/')
}, 1000); }, 1000)
} }
onBeforeRouteLeave(() => { onBeforeRouteLeave(() => {
reset(); reset()
}); })
</script> </script>
<style lang="scss"> <style lang="scss">
.login-register { .login-register {
$p: &;
width: 240px;
max-width: 100%;
margin: 0 auto;
padding-top: 25px;
color: color('second');
&__form {
display: flex;
flex-direction: column;
justify-content: flex-end;
height: 50vh;
gap: 5px;
}
&__input {
@include grey-input;
}
&__button {
}
&__redirect {
display: flex;
justify-content: center;
&-link {
margin-left: 5px;
color: color('second');
}
}
&__notifications {
margin-top: 5px;
&--transition {
&-enter-active,
&-leave-active {
transition: all 0.18s ease;
}
&-enter-from {
opacity: 0;
margin-top: 10px;
}
&-leave-to {
opacity: 0;
}
}
}
&__notification {
$p: &; $p: &;
width: 240px; position: relative;
max-width: 100%; display: flex;
margin: 0 auto; align-items: center;
padding-top: 25px; gap: 5px;
color: color("second"); padding: 5px 5px;
margin-bottom: 3px;
border: 1px solid color('border');
&__form { &-icon {
display: flex; width: 25px;
flex-direction: column; height: auto;
justify-content: flex-end; display: block;
height: 50vh;
gap: 5px;
} }
&__input { &-info {
@include grey-input; flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 3px;
height: 100%;
} }
&__button { &-title {
font-size: 15px;
line-height: 13px;
} }
&__redirect { &-text {
display: flex; font-size: 13px;
justify-content: center; line-height: 15px;
&-link {
margin-left: 5px;
color: color("second");
}
} }
&__notifications { &-close {
margin-top: 5px; cursor: pointer;
height: 100%;
&--transition { width: 10px;
&-enter-active,
&-leave-active {
transition: all 0.18s ease;
}
&-enter-from {
opacity: 0;
margin-top: 10px;
}
&-leave-to {
opacity: 0;
}
}
} }
&__notification { &--error {
$p: &; #{$p}-icon {
color: color('warn');
position: relative; }
display: flex;
align-items: center;
gap: 5px;
padding: 5px 5px;
margin-bottom: 3px;
border: 1px solid color("border");
&-icon {
width: 25px;
height: auto;
display: block;
}
&-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 3px;
height: 100%;
}
&-title {
font-size: 15px;
line-height: 13px;
}
&-text {
font-size: 13px;
line-height: 15px;
}
&-close {
cursor: pointer;
height: 100%;
width: 10px;
}
&--error {
#{$p}-icon {
color: color("warn");
}
}
&--success {
#{$p}-icon {
color: color("success");
}
}
} }
&--login { &--success {
#{$p}__input--email { #{$p}-icon {
display: none; color: color('success');
} }
} }
}
&--login {
#{$p}__input--email {
display: none;
}
}
} }
</style> </style>