diff --git a/package.json b/package.json
index 9a42306..6732083 100644
--- a/package.json
+++ b/package.json
@@ -64,6 +64,7 @@
"signale": "^1.4.0",
"vue": "^2.6.7",
"vue-carousel": "^0.18.0",
- "vue-router": "^3.0.2"
+ "vue-router": "^3.0.2",
+ "vuelidate": "^0.7.5"
}
}
diff --git a/src/admin/App.vue b/src/admin/App.vue
index e7d9d0c..c011c82 100644
--- a/src/admin/App.vue
+++ b/src/admin/App.vue
@@ -1,3 +1,130 @@
- h1 Welcome to the Vue App
-
\ No newline at end of file
+ .root-container
+ header.header-container
+ Header
+ nav.nav-container
+ Navigation
+ main.content-container
+ router-view
+
+
+
+
\ No newline at end of file
diff --git a/src/admin/components/About/About.vue b/src/admin/components/About/About.vue
new file mode 100644
index 0000000..36f7b02
--- /dev/null
+++ b/src/admin/components/About/About.vue
@@ -0,0 +1,161 @@
+
+ .about
+ .about__header
+ .container.about__header-container
+ h1.page-title.about__title Блок «Обо мне»
+ .about__header-btn
+ AddBtn(
+ text="Добавить группу"
+ size="small"
+ type='button'
+ @click="showAddSkillGroup = true")
+ .about__content
+ .container.about__content-container
+ ul.skill-group__list
+ li.skill-group__item(v-if="showAddSkillGroup")
+ SkillGroup(:value="emptySkillGroup")
+ li(
+ v-for="skillGroup in skillGroups"
+ :key="skillGroup.id"
+ ).skill-group__item
+ SkillGroup(
+ :value="skillGroup"
+ )
+
+
+
\ No newline at end of file
diff --git a/src/admin/components/About/Skill.vue b/src/admin/components/About/Skill.vue
new file mode 100644
index 0000000..ffdc6cb
--- /dev/null
+++ b/src/admin/components/About/Skill.vue
@@ -0,0 +1,216 @@
+
+ .skill
+ .skill__data(v-if="!editMode")
+ .skill__title {{skill.title}}
+ .skill__percent
+ .skill__percent-value {{skill.percent}}
+ .skill__btns
+ CardBtn(
+ icon="edit"
+ type="button"
+ @click="switchEdit"
+ ).skill__btn
+ CardBtn(
+ icon="trash"
+ type="button"
+ @click="delSkill"
+ ).skill__btn
+ form(@submit.prevent="saveSkill")(v-else)
+ .skill__data
+ .skill__field
+ CustomInput(
+ v-model="tmpSkill.title"
+ :errorText="validationMessage('title')"
+ :noSidePaddings="true"
+ )
+ .skill__field
+ CustomInput(
+ v-model="tmpSkill.percent"
+ :errorText="validationMessage('percent')"
+ :noSidePaddings="true"
+ )
+ .skill__btns.skill__btns--colored
+ CardBtn(
+ type="submit"
+ icon="confirm"
+ ).skill__btn
+ CardBtn(
+ icon="delete"
+ type="button"
+ @click="switchEdit"
+ ).skill__btn
+
+
+
\ No newline at end of file
diff --git a/src/admin/components/About/SkillGroup.vue b/src/admin/components/About/SkillGroup.vue
new file mode 100644
index 0000000..eafccff
--- /dev/null
+++ b/src/admin/components/About/SkillGroup.vue
@@ -0,0 +1,339 @@
+
+ .skill-group.card
+ .skill-group__header
+ .skill-group__header-value(v-if="!editMode")
+ .skill-group__header-title {{tmpGroup.title}}
+ CardBtn(
+ icon="edit"
+ type="button"
+ @click="switchEdit"
+ ).btn
+ .skill-group__header-form(v-else)
+ form(@submit.prevent="saveGroup").add__form.add__form--group
+ .add__form-wrap
+ .add__form-field
+ CustomInput(
+ v-model="tmpGroup.title"
+ placeholder="Название новой группы"
+ :errorText="validationMessage('tmpGroup', 'title')"
+ )
+ .add__form-btns.add__form-btns--colored
+ CardBtn(
+ icon="confirm"
+ type="submit"
+ ).btn
+ CardBtn(
+ icon="delete"
+ type="button"
+ @click="switchEdit"
+ ).btn
+ hr.divider
+ .skill-group__content
+ ul.skill-group__list
+ li(
+ v-for="skill in tmpGroup.skills"
+ :key="skill.id"
+ ).skill-group__item
+ Skill(
+ :skill="skill"
+ )
+ .skill-group__add-item(:class="blockAdding")
+ form.add__form.add__form--skill(@submit.prevent="addSkill")
+ .add__form-wrap
+ .add__form-field
+ CustomInput(
+ v-model="newSkill.title"
+ placeholder="Новый навык"
+ :errorText="validationMessage('newSkill', 'title')"
+ )
+ .add__form-field
+ CustomInput(
+ v-model="newSkill.percent"
+ placeholder="100 %"
+ :errorText="validationMessage('newSkill', 'percent')"
+ )
+ AddBtn(type="submit")
+
+
+
\ No newline at end of file
diff --git a/src/admin/components/AddBtn.vue b/src/admin/components/AddBtn.vue
new file mode 100644
index 0000000..0061bf3
--- /dev/null
+++ b/src/admin/components/AddBtn.vue
@@ -0,0 +1,137 @@
+
+ button(
+ v-if="size === 'plain'"
+ v-on="$listeners"
+ type="button"
+ ).add-new-btn.add-new-btn--plain
+ .add-new-btn__text {{text.split(' ').join('\n') }}
+ button(
+ v-else
+ v-on="$listeners"
+ :class="smallClass"
+ v-bind="$attrs"
+ ).add-new-btn {{text ? text : ''}}
+
+
+
\ No newline at end of file
diff --git a/src/admin/components/CardBtn.vue b/src/admin/components/CardBtn.vue
new file mode 100644
index 0000000..7625328
--- /dev/null
+++ b/src/admin/components/CardBtn.vue
@@ -0,0 +1,83 @@
+
+ button(
+ :class="[className, noMargin]"
+ v-on="$listeners"
+ v-bind="$attrs"
+ ).btn {{title}}
+
+
+
\ No newline at end of file
diff --git a/src/admin/components/CustomInput.vue b/src/admin/components/CustomInput.vue
new file mode 100644
index 0000000..15e1e34
--- /dev/null
+++ b/src/admin/components/CustomInput.vue
@@ -0,0 +1,199 @@
+
+ label.input(
+ v-if="fieldType === 'input'"
+ :class="[{'input_labeled' : !!title, 'no-side-paddings' : noSidePaddings}, iconClass, {'error' : !!errorText}]"
+ )
+ .input__title(v-if="title") {{title}}
+ .input__wrap
+ Icon(
+ v-if="icon"
+ className="input__icon",
+ :iconName="icon")
+ input(
+ v-bind="$attrs"
+ :value="value"
+ @input="$emit('input', $event.target.value)"
+ ).input__elem.field__elem
+ .input__error-tooltip
+ InputTooltip(
+ :errorText="errorText"
+ )
+
+ label.textarea(
+ v-else-if="fieldType === 'textarea'"
+ :class="{'error' : !!errorText}"
+ )
+ .input__title(v-if="title") {{title}}
+ textarea.textarea__elem.field__elem(
+ v-bind="$attrs"
+ :value="value"
+ @input="$emit('input', $event.target.value)"
+ )
+ .input__error-tooltip
+ InputTooltip(
+ :errorText="errorText"
+ )
+
+
+
+
+
\ No newline at end of file
diff --git a/src/admin/components/Header.vue b/src/admin/components/Header.vue
new file mode 100644
index 0000000..cc03bd5
--- /dev/null
+++ b/src/admin/components/Header.vue
@@ -0,0 +1,94 @@
+
+ .header
+ .container.header__container
+ .header__info
+ .user
+ .user__avatar
+ img(src="../../images/userfiles/admin.jpg").user__avatar-img
+ .user__name
+ span Максим Стогний
+ a.exit-btn(href="#") Выйти
+ .header__title Панель Администрирования
+ .header__btn
+ a.exit-btn(href="#") Выйти
+
+
+
\ No newline at end of file
diff --git a/src/admin/components/Icon.vue b/src/admin/components/Icon.vue
new file mode 100644
index 0000000..700e062
--- /dev/null
+++ b/src/admin/components/Icon.vue
@@ -0,0 +1,41 @@
+
+ svg(
+ :class="className"
+ :viewBox="viewBox"
+ preserveAspectRatio="none"
+ )
+ use(:xlink:href="src")
+
+
\ No newline at end of file
diff --git a/src/admin/components/InputTooltip.vue b/src/admin/components/InputTooltip.vue
new file mode 100644
index 0000000..ac870b2
--- /dev/null
+++ b/src/admin/components/InputTooltip.vue
@@ -0,0 +1,41 @@
+
+ .input__error-tooltip {{errorText}}
+
+
+
+
+
+
+
diff --git a/src/admin/components/Login.vue b/src/admin/components/Login.vue
new file mode 100644
index 0000000..8d0fe85
--- /dev/null
+++ b/src/admin/components/Login.vue
@@ -0,0 +1,173 @@
+
+ .login
+ .login__content
+ form(
+ @submit.prevent="login"
+ ).login__form
+ .login__form-title Авторизация
+ .login__row
+ CustomInput(
+ title="Логин"
+ icon="user-empty"
+ v-model="user.login"
+ :errorText="validationMessage('login')"
+ )
+ .login__row
+ CustomInput(
+ title="Пароль"
+ icon="key"
+ type="password"
+ v-model="user.password"
+ :errorText="validationMessage('password')"
+ )
+ .login__btn
+ button(
+ type="submit"
+ ).login__send-data Отправить
+
+
+
\ No newline at end of file
diff --git a/src/admin/components/Navigation.vue b/src/admin/components/Navigation.vue
new file mode 100644
index 0000000..7735a1c
--- /dev/null
+++ b/src/admin/components/Navigation.vue
@@ -0,0 +1,81 @@
+
+ .container
+ ul.nav__list
+ li.nav__item(
+ v-for="(tab, ndx) in tabs"
+ )
+ router-link(
+ :to="tab.href"
+ exact-active-class="active"
+ ).nav__link {{tab.title}}
+
+
+
\ No newline at end of file
diff --git a/src/admin/components/Reviews/Review.vue b/src/admin/components/Reviews/Review.vue
new file mode 100644
index 0000000..4374ec3
--- /dev/null
+++ b/src/admin/components/Reviews/Review.vue
@@ -0,0 +1,110 @@
+
+ .review.card
+ .review__author
+ .author
+ .author__avatar
+ img(:src="value.photo").author__avatar-img
+ .author__data
+ .author__name {{value.author}}
+ .author__desc {{value.occ}}
+ hr.divider
+ .review__content
+ .review__text
+ p {{value.text}}
+ .review__btns
+ CardBtn(title="Править" icon="edit")
+ CardBtn(title="Удалить" icon="delete")
+
+
+
\ No newline at end of file
diff --git a/src/admin/components/Reviews/ReviewEdit.vue b/src/admin/components/Reviews/ReviewEdit.vue
new file mode 100644
index 0000000..500685b
--- /dev/null
+++ b/src/admin/components/Reviews/ReviewEdit.vue
@@ -0,0 +1,345 @@
+
+ .review-edit.card
+ form(@submit.prevent="saveReview")
+ .form__container
+ .form__header {{formTitle}}
+ hr.divider
+ .form__content
+ .form__content-wrap
+ .form__avatar(:class="photoError")
+ label.form__avatar-upload
+ input(
+ type="file"
+ @change="appendFileAndRenderPhoto"
+ ).form__avatar-file
+ .form__avatar-wrap
+ img(
+ v-if="tmpReview.photo"
+ :src="tmpReview.photo"
+ ).form__avatar-img
+ Icon(
+ v-else
+ iconName="user"
+ className="form__avatar-empty-icon"
+ )
+ .form__load-text {{`${photo ? 'Изменить' : 'Добавить'} фото`}}
+ .form__error-tooltip
+ InputTooltip(
+ :errorText="validationMessage('photo')"
+ )
+ .form__review
+ .form__row
+ .form__block
+ CustomInput(
+ title="Имя автора"
+ v-model="tmpReview.author"
+ :errorText="validationMessage('author')"
+ )
+ .form__block
+ CustomInput(
+ title="Титул автора"
+ v-model="tmpReview.occ"
+ :errorText="validationMessage('occ')"
+ )
+ .form__row
+ .form__block
+ CustomInput(
+ title="Отзыв"
+ field-type="textarea"
+ v-model="tmpReview.text"
+ :errorText="validationMessage('text')"
+ )
+ .form__btns
+ button(
+ type="button"
+ @click="cancelAndHide"
+ ).form__btn.form__btn--plain Отмена
+ button(type="submit").form__btn.form__btn--big Загрузить
+
+
+
\ No newline at end of file
diff --git a/src/admin/components/Reviews/Reviews.vue b/src/admin/components/Reviews/Reviews.vue
new file mode 100644
index 0000000..dd18a66
--- /dev/null
+++ b/src/admin/components/Reviews/Reviews.vue
@@ -0,0 +1,119 @@
+
+ .reviews
+ .reviews__header
+ .container
+ h1.page-title.reviews__title Блок «Отзывы»
+ .reviews__content
+ .container.reviews__container
+ ReviewEdit(v-if="showAddReview")
+
+ ul.reviews__list
+ li.reviews__item
+ AddBtn(
+ text="Добавить отзыв"
+ size="plain"
+ @click="showAddReview = true")
+ li.reviews__item(
+ v-for="review in reviews"
+ :key="review.id"
+ )
+ review(
+ :value="review")
+
+
+
\ No newline at end of file
diff --git a/src/admin/components/Works/Work.vue b/src/admin/components/Works/Work.vue
new file mode 100644
index 0000000..e42fa63
--- /dev/null
+++ b/src/admin/components/Works/Work.vue
@@ -0,0 +1,127 @@
+
+ .work.card
+ .work__preview
+ .work__image-wrap
+ img(:src="value.image").work__image
+ .work__tags
+ ul.work__tags-list
+ li.work__tag(
+ v-for="tag in tags"
+ :key="tag"
+ )
+ .tag {{tag}}
+ .work__info
+ .work__title {{value.title}}
+ .work__decs
+ p {{value.desc}}
+ a(:href="value.link").work__link http:{{value.link}}
+ .work__btns
+ CardBtn(title="Править" icon="edit")
+ CardBtn(title="Удалить" icon="delete")
+
+
+
\ No newline at end of file
diff --git a/src/admin/components/Works/WorkEdit.vue b/src/admin/components/Works/WorkEdit.vue
new file mode 100644
index 0000000..6e43897
--- /dev/null
+++ b/src/admin/components/Works/WorkEdit.vue
@@ -0,0 +1,437 @@
+
+ .work-edit.card
+ form(@submit.prevent="saveWork")
+ .form__container
+ .form__header {{formTitle}}
+ hr.divider
+ .form__content
+ .form__content-wrap
+ .form__col
+ .form__image(v-if="tmpWork.photo")
+ img(:src="tmpWork.photo").form__image-pic
+ .form__image-btn-wrap
+ button(
+ type="button"
+ @click="showInputFile"
+ ).form__image-btn Изменить превью
+ .form__load-area(v-else)(:class="photoError")
+ .form__load-text Перетащите либо загрузите изображение
+ .form__load-btn
+ button(
+ type="button"
+ @click="showInputFile"
+ ).form__btn Загрузить
+ .form__error-tooltip
+ InputTooltip(
+ :errorText="validationMessage('photo')"
+ )
+ input(
+ type="file"
+ @change="appendFileAndRenderPhoto"
+ )#upload-pic.form__load-file
+ .form__col
+ .form__block
+ CustomInput(
+ title="Название"
+ v-model="tmpWork.title"
+ :errorText="validationMessage('title')"
+ )
+ .form__block
+ CustomInput(
+ title="Ссылка"
+ v-model="tmpWork.link"
+ :errorText="validationMessage('link')"
+ )
+ .form__block
+ CustomInput(
+ title="Описание"
+ field-type="textarea"
+ v-model="tmpWork.description"
+ :errorText="validationMessage('description')"
+ )
+ .form__block
+ CustomInput(
+ title="Добавление тэга"
+ v-model="tmpWork.techs"
+ :errorText="validationMessage('techs')"
+ )
+ ul.tags__list
+ li.tags__item(
+ v-for="(tag, ndx) in tags"
+ :key="ndx")
+ .tag
+ span {{ tag }}
+ button(
+ type="button"
+ @click="delTag(ndx)").tag__remove-btn
+ Icon(
+ iconName="cross"
+ className="tag__remove-icon"
+ )
+ .form__btns
+ button(
+ type="button"
+ @click="cancelAndHide"
+ ).form__btn.form__btn--plain Отмена
+ button(type="submit").form__btn.form__btn--big Загрузить
+
+
+
\ No newline at end of file
diff --git a/src/admin/components/Works/Works.vue b/src/admin/components/Works/Works.vue
new file mode 100644
index 0000000..ed99193
--- /dev/null
+++ b/src/admin/components/Works/Works.vue
@@ -0,0 +1,122 @@
+
+ .works
+ .works__header
+ .container
+ h1.page-title.works__title Блок «Работы»
+ .works__content
+ .container.works__container
+ WorkEdit(v-if="showAddWork")
+
+ ul.works__list
+ li.works__item
+ AddBtn(
+ text="Добавить работу"
+ size="plain"
+ @click="showAddWork = true")
+ li.works__item(
+ v-for="work in works"
+ :key="work.id"
+ )
+ work(
+ :value="work")
+
+
+
\ No newline at end of file
diff --git a/src/admin/main.js b/src/admin/main.js
index 6e47267..f4abce2 100644
--- a/src/admin/main.js
+++ b/src/admin/main.js
@@ -1,7 +1,15 @@
import Vue from 'vue';
import App from './App.vue'
+import VueRouter from 'vue-router'
+import router from './routes'
+import Vuelidate from 'vuelidate'
+
+Vue.use(Vuelidate)
+Vue.use(VueRouter)
+Vue.router = router
new Vue({
el: "#app-root",
- render: h => h(App)
+ render: h => h(App),
+ router
});
\ No newline at end of file
diff --git a/src/admin/routes.js b/src/admin/routes.js
new file mode 100644
index 0000000..69400fb
--- /dev/null
+++ b/src/admin/routes.js
@@ -0,0 +1,51 @@
+import VueRouter from 'vue-router'
+import About from './components/About/About'
+import Reviews from './components/Reviews/Reviews'
+import Works from './components/Works/Works'
+import Login from './components/Login'
+
+const routes = [
+ {
+ path: '/',
+ name: 'about',
+ component: About,
+ meta:
+ {
+ title: "Блок «Обо мне»"
+ }
+ },
+ {
+ path: '/reviews',
+ component: Reviews,
+ meta:
+ {
+ title: "Блок «Работы»"
+ }
+ },
+ {
+ path: '/works',
+ component: Works,
+ meta:
+ {
+ title: "Блок «Отзывы»"
+ }
+ },
+ {
+ path: '/login',
+ component: Login
+ },
+ {
+ path: '*',
+ redirect: 'about'
+ }
+]
+
+const router = new VueRouter({
+ routes
+})
+
+router.afterEach((to, from) => {
+ document.title = to.meta.title || ''
+})
+
+export default router
diff --git a/src/components/about.pug b/src/components/about.pug
index a2dab13..b25eaa4 100644
--- a/src/components/about.pug
+++ b/src/components/about.pug
@@ -5,7 +5,7 @@ section.about(id="about")
.user
.user__avatar
.avatar
- +image('userfiles/user.jpg', 'avatar__img')
+ +image('userfiles/admin.jpg', 'avatar__img')
.user__info
-
var info = [
diff --git a/src/components/contacts.pug b/src/components/contacts.pug
index 10a4a73..43bf798 100644
--- a/src/components/contacts.pug
+++ b/src/components/contacts.pug
@@ -2,13 +2,13 @@ section.contacts(id="contacts")
.container.contacts__container
h2.section__title.contacts__title Связаться со мной
.contacts__form
- form
+ form(novalidate)#contacts__form
.form__container
.form__row
-
var fields = [
- ["Введите ваше имя", "Иванов Иван", "user"],
- ["Введите ваш email", "example@mail.com", "envelope"]
+ ["Введите ваше имя", "Иванов Иван", "user-empty", "text"],
+ ["Введите ваш email", "example@mail.com", "envelope", "email"]
]
each field in fields
label.form__block
@@ -16,14 +16,23 @@ section.contacts(id="contacts")
.form__block-wrap
+icon(field[2],'form__block-icon')
.form__block-field
- input(placeholder=field[1]).form__block-input
+ input(type=field[3] placeholder=field[1] required).form__block-input
+ .error-tooltip
.form__row.form__row--more-margin
label.form__block
.form__block-label Сообщение к письму
.form__block-wrap.form__block-wrap--align-top
+icon('message','form__block-icon')
.form__block-field
- textarea(placeholder="Требуется ваша помощь в создании сайта. \nИнтересуют сроки и цена вопроса" rows="3").form__block-input.form__block-textarea
+ textarea(required minlength="15" placeholder="Требуется ваша помощь в создании сайта. \nИнтересуют сроки и цена вопроса" rows="3").form__block-input.form__block-textarea
+ .error-tooltip
.form__row
.form__buttons
- button(type="submit").custom-btn Отправить
\ No newline at end of file
+ button(type="submit" id="contacts__form-submit").custom-btn Отправить
+
+ script(type="template" id="modalTemplate")
+ .modal
+ .modal__container
+ .modal__content
+ .modal__content-text Письмо отправлено
+ button(type=button)#modal__close.custom-btn Закрыть
\ No newline at end of file
diff --git a/src/images/bg/bg-admin.jpg b/src/images/bg/bg-admin.jpg
new file mode 100644
index 0000000..7a833e1
Binary files /dev/null and b/src/images/bg/bg-admin.jpg differ
diff --git a/src/images/icons/key.svg b/src/images/icons/key.svg
new file mode 100644
index 0000000..ccb2678
--- /dev/null
+++ b/src/images/icons/key.svg
@@ -0,0 +1,57 @@
+
+
+
diff --git a/src/images/icons/user-empty.svg b/src/images/icons/user-empty.svg
new file mode 100644
index 0000000..2373903
--- /dev/null
+++ b/src/images/icons/user-empty.svg
@@ -0,0 +1,53 @@
+
+
+
diff --git a/src/images/icons/user.svg b/src/images/icons/user.svg
index 2373903..2c40f6f 100644
--- a/src/images/icons/user.svg
+++ b/src/images/icons/user.svg
@@ -1,24 +1,17 @@
-
+
+