Merge branch 'dev' of gitlab.com:lilcity/backend into dev

remotes/origin/hasaccess
Ivlev Denis 8 years ago
commit abb7318a87
  1. 19
      project/templates/blocks/arts.html
  2. 2
      project/templates/lilcity/index.html
  3. 199
      web/src/components/CourseRedactor.vue
  4. 73
      web/src/components/LessonRedactor.vue
  5. BIN
      web/src/img/art01.png
  6. BIN
      web/src/img/art02.png
  7. BIN
      web/src/img/art03.png
  8. BIN
      web/src/img/art04.png
  9. BIN
      web/src/img/art05.png
  10. BIN
      web/src/img/art06.png
  11. BIN
      web/src/img/art1.png
  12. BIN
      web/src/img/art2.png
  13. BIN
      web/src/img/art3.png
  14. BIN
      web/src/img/art4.png
  15. BIN
      web/src/img/art5.png
  16. 70
      web/src/js/modules/api.js
  17. 16
      web/src/sass/_common.sass

@ -1,32 +1,37 @@
{% load static %}
<div class="section section_arts">
<div class="section__center center">
<div class="section__center">
<div class="arts">
<div class="arts__col">
<div class="arts__item" style="background-image: url({% static 'img/art1.png' %});">
<div class="arts__item" style="background-image: url({% static 'img/art01.png' %});">
<div class="arts__title">Персонаж</div>
</div>
</div>
<div class="arts__col">
<div class="arts__item" style="background-image: url({% static 'img/art2.png' %});">
<div class="arts__item" style="background-image: url({% static 'img/art02.png' %});">
<div class="arts__title">Развитие креативного мышления</div>
</div>
</div>
<div class="arts__col">
<div class="arts__item" style="background-image: url({% static 'img/art3.png' %});">
<div class="arts__title">сочная и живая акварельная живопись</div>
<div class="arts__item" style="background-image: url({% static 'img/art03.png' %});">
<div class="arts__title">Акварельная живопись</div>
</div>
</div>
<div class="arts__col">
<div class="arts__item" style="background-image: url({% static 'img/art4.png' %});">
<div class="arts__item" style="background-image: url({% static 'img/art04.png' %});">
<div class="arts__title">Пластилиновая живопись</div>
</div>
</div>
<div class="arts__col">
<div class="arts__item" style="background-image: url({% static 'img/art5.png' %});">
<div class="arts__item" style="background-image: url({% static 'img/art05.png' %});">
<div class="arts__title">История искусств</div>
</div>
</div>
<div class="arts__col">
<div class="arts__item" style="background-image: url({% static 'img/art06.png' %});">
<div class="arts__title">Коллаж</div>
</div>
</div>
</div>
</div>
</div>

@ -30,7 +30,7 @@
{% comment %} <meta property="fb:admins" content="Facebook numeric ID"> {% endcomment %}
<meta name="csrf-token" content="{{ csrf_token }}">
<link rel="stylesheet" media="all" href={% static "app.css" %}?1>
<link rel="stylesheet" media="all" href={% static "app.css" %}?2>
<link rel="shortcut icon" type="image/png" href="{% static 'img/favicon.png' %}"/>
<script>
var viewportmeta = document.querySelector('meta[name="viewport"]');

@ -12,9 +12,7 @@
</div>
<div v-if="me" class="info__group info__field--light">
<div class="info__label">АВТОР</div>
<div v-if="me.role != ROLE_ADMIN" class="info__value">{{ authorName }}</div>
<div v-else class="info__value"><lil-select :value.sync="userSelect" :options="users"
placeholder="Выберите автора"/></div>
<div class="info__value">{{ authorName }}</div>
</div>
</div>
<div class="info__upload upload">
@ -246,8 +244,8 @@
</div>
</form>
<form v-if="viewSection === 'lessons-edit'" @submit.prevent="onLessonSubmit">
<lesson-redactor :lesson.sync="currentLesson" :saving="lessonSaving" :access-token="accessToken"
v-on:back="goToLessons"/>
<lesson-redactor :$v="$v" :lesson.sync="currentLesson" :saving.sync="lessonSaving" :access-token="accessToken"
v-on:back="goToLessons" />
</form>
</div>
<div v-else>
@ -304,9 +302,9 @@
status: null,
category: null,
categorySelect: null,
duration: 1,
duration: null,
author: null,
price: 0,
price: null,
url: '',
coverImage: '',
coverImageId: null,
@ -390,6 +388,10 @@
duration: "Продолжительность",
category: "Категория",
},
lessonFields: {
title: "Название урока",
short_description: "Описание урока",
},
showErrors: false,
savingTimeout: null,
savingDebounceTimeout: null,
@ -435,8 +437,16 @@
required,
numeric,
minValue: minValue(1)
}
},
},
currentLesson: {
title: {
required
},
short_description: {
required
},
}
};
}
},
@ -541,12 +551,18 @@
});
},
onLessonSubmit() {
if(!this.validateLesson()) {
return;
}
this.saveLesson();
},
saveLesson() {
this.lessonSaving = true;
const currentLessonId = this.currentLesson.id;
this.currentLesson.course_id = this.course.id;
api.saveLesson(this.currentLesson, this.accessToken)
.then((response) => {
this.lessonSaving = false;
const newLesson = api.convertLessonJson(response.data);
newLesson.course_id = this.course.id;
this.currentLesson = newLesson;
@ -556,13 +572,37 @@
if (this.lessons && Array.isArray(this.lessons)) {
this.lessons.forEach((lesson, index) => {
if (newLesson.id === lesson.id) {
this.$set('lessons', index, newLesson);
this.$set(this.lessons, index, newLesson);
}
});
}
document.getElementById('course-redactor__saving-status').innerText = 'СОХРАНЕНО';
this.savingTimeout = setTimeout(() => {
document.getElementById('course-redactor__saving-status').innerText = '';
}, 2000);
showNotification("success", 'Урок сохранён');
this.goToLessons();
this.lessonSaving = false;
})
.catch((err) => {
this.lessonSaving = false;
//console.error(err);
document.getElementById('course-redactor__saving-status').innerText = 'ОШИБКА';
this.savingTimeout = setTimeout(() => {
document.getElementById('course-redactor__saving-status').innerText = '';
}, 2000);
// alert('Произошло что-то страшное: '+err.toString());
console.log(err);
if(err.response) {
for(let i in err.response.data) {
if(typeof err.response.data[i] === "array") {
showNotification("error", this.lessonFields[i]+": "+err.response.data[i].join(', '));
}
}
}
});
},
goToLessons() {
@ -652,15 +692,32 @@
}
},
validate(silent) {
//console.log('valadte', arguments);
console.log('validate', this.$v.$invalid);
this.showErrors = true;
if (this.$v.course.$invalid) {
if(!silent) {
for(let i in this.$v.course) {
if(this.$v.course[i].$invalid) {
showNotification("error", "Ошибка валидации поля "+this.fields[i]);
}
}
}
// showNotification("error", "Заполните все необходимые поля");
return false;
}
return true;
},
validateLesson(silent) {
console.log('validate', this.$v.$invalid);
this.showErrors = true;
if (this.$v.$invalid) {
if (this.$v.currentLesson.$invalid) {
if(!silent) {
for(let i in this.$v.course) {
if(this.$v.course[i].$invalid) {
showNotification("error", "Ошибка валидации поля "+this.fields[i]);
}
}
for(let i in this.$v.currentLesson) {
if(this.$v.currentLesson[i].$invalid) {
showNotification("error", "Ошибка валидации поля "+this.lessonFields[i]);
}
}
}
// showNotification("error", "Заполните все необходимые поля");
return false;
@ -671,13 +728,18 @@
onCoursePreview() {
if(this.course.id) {
let url;
if(this.live) {
url = `/school/lessons/${this.course.id}`;
if(this.currentLesson && this.currentLesson.id) {
url = `/lesson/${this.currentLesson.id}`;
} else {
if (this.course.url) {
url = `/course/${this.course.url}`;
if(this.live) {
url = `/school/lessons/${this.course.id}`;
} else {
if (this.course.url) {
url = `/course/${this.course.url}`;
}
url = `/course/${this.course.id}`;
}
url = `/course/${this.course.id}`;
}
let newTab = window.open(url, '_blank');
@ -781,12 +843,12 @@
}, 2000);
// alert('Произошло что-то страшное: '+err.toString());
//console.log(err.response.data);
if(err.response.data) {
if(err.response) {
for(let i in err.response.data) {
showNotification("error", this.fields[i]+": "+err.response.data[i].join(', '));
if(typeof err.response.data[i] === "array") {
showNotification("error", this.fields[i] + ": " + err.response.data[i].join(', '));
}
}
} else {
showNotification("error", "Ошибка "+err.toString());
}
});
}, 500);
@ -883,28 +945,28 @@
});
}
let user = api.getCurrentUser(this.accessToken);
promises.push(user);
user.then((response) => {
if (response.data) {
this.me = response.data;
if(this.me.role == ROLE_ADMIN) {
api.getUsers({role: [ROLE_AUTHOR,ROLE_ADMIN], page_size: 1000}, this.accessToken)
.then((usersResponse) => {
if (usersResponse.data) {
this.users = usersResponse.data.results.map((user) => {
return {
title: `${user.first_name} ${user.last_name}`,
value: user.id
}
});
}
});
}
}
});
// let user = api.getCurrentUser(this.accessToken);
// promises.push(user);
//
// user.then((response) => {
// if (response.data) {
// this.me = response.data;
//
// if(this.me.role == ROLE_ADMIN) {
// api.getUsers({role: [ROLE_AUTHOR,ROLE_ADMIN], page_size: 1000}, this.accessToken)
// .then((usersResponse) => {
// if (usersResponse.data) {
// this.users = usersResponse.data.results.map((user) => {
// return {
// title: `${user.first_name} ${user.last_name}`,
// value: user.id
// }
// });
// }
// });
// }
// }
// });
// if (this.courseId) {
// this.loadCourse().then(()=>{this.updateViewSection(window.location, 'load')}).catch(()=>{
@ -967,23 +1029,23 @@
this.course.category = value.value;
}
},
userSelect: {
get() {
if (!this.users || this.users.length === 0 || !this.course || !this.course.author) {
return null;
}
let value;
this.users.forEach((user) => {
if (user.value === this.course.author) {
value = user;
}
});
return value;
},
set(value) {
this.course.author = value.value;
}
},
// userSelect: {
// get() {
// if (!this.users || this.users.length === 0 || !this.course || !this.course.author) {
// return null;
// }
// let value;
// this.users.forEach((user) => {
// if (user.value === this.course.author) {
// value = user;
// }
// });
// return value;
// },
// set(value) {
// this.course.author = value.value;
// }
// },
courseFullUrl() {
if (!this.course.url) {
return `https://lil.city/course/${this.course.id}`;
@ -1118,5 +1180,12 @@
background: white;
border-radius: 10px;
}
.course-redactor__preview-button-bg-save {
background-color: #58fffb;
}
.course-redactor__preview-button {
transition: backgroundColor 0.5s ease-in-out;
}
</style>

@ -14,52 +14,56 @@
</div>
<div class="kit__title title">{{ title }}</div>
<div class="kit__section">
<div class="kit__field field">
<div class="kit__field field"
v-bind:class="{ error: $v.currentLesson.title.$invalid }">
<div class="field__wrap">
<input type="text" class="field__input" placeholder="Название урока" v-model="lesson.title">
</div>
</div>
<div class="kit__field field">
<div class="kit__field field"
v-bind:class="{ error: $v.currentLesson.short_description.$invalid }">
<div class="field__wrap">
<textarea class="field__input" v-autosize="lesson.short_description" placeholder="Описание урока" v-model="lesson.short_description"></textarea>
</div>
</div>
</div>
<div v-for="(block, index) in lesson.content">
<block-text v-if="block.type === 'text'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-image-text v-if="block.type === 'image-text'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
:image-id.sync="block.data.image_id"
:image-url.sync="block.data.image_url"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-image v-if="block.type === 'image'"
:index="index"
:title.sync="block.data.title"
:image-id.sync="block.data.image_id"
:image-url.sync="block.data.image_url"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-images v-if="block.type === 'images'"
<vue-draggable v-model="lesson.content" @start="drag=true" @end="drag=false" :options="{ handle: '.sortable__handle' }">
<div v-for="(block, index) in lesson.content">
<block-text v-if="block.type === 'text'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
:images.sync="block.data.images"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-video v-if="block.type === 'video'"
:index="index"
:title.sync="block.data.title"
v-on:remove="onBlockRemoved"
:video-url.sync="block.data.video_url"/>
</div>
<block-image-text v-if="block.type === 'image-text'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
:image-id.sync="block.data.image_id"
:image-url.sync="block.data.image_url"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-image v-if="block.type === 'image'"
:index="index"
:title.sync="block.data.title"
:image-id.sync="block.data.image_id"
:image-url.sync="block.data.image_url"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-images v-if="block.type === 'images'"
:index="index"
:title.sync="block.data.title"
:text.sync="block.data.text"
:images.sync="block.data.images"
v-on:remove="onBlockRemoved"
:access-token="accessToken"/>
<block-video v-if="block.type === 'video'"
:index="index"
:title.sync="block.data.title"
v-on:remove="onBlockRemoved"
:video-url.sync="block.data.video_url"/>
</div>
</vue-draggable>
<block-add v-on:added="onBlockAdded" />
<div class="kit__foot">
@ -78,10 +82,12 @@
import BlockImageText from './blocks/BlockImageText'
import BlockVideo from './blocks/BlockVideo'
import {api} from "../js/modules/api";
import Draggable from 'vuedraggable';
import _ from 'lodash'
export default {
name: "lesson-redactor",
props: ["lesson", "saving", "accessToken"],
props: ["lesson", "saving", "accessToken", "$v"],
methods: {
goBack() {
this.$emit('back');
@ -113,6 +119,7 @@
'block-image-text': BlockImageText,
'block-images': BlockImages,
'block-video': BlockVideo,
'vue-draggable': Draggable,
}
}
</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

@ -1,19 +1,51 @@
import axios from 'axios';
import moment from 'moment';
import {showNotification} from "../../js/modules/notification";
axios.defaults.headers.post['Content-Type'] = 'application/json';
axios.defaults.headers.post['Accept'] = 'application/json';
export const api = {
get: (url, params) => {
let request = axios.get(url, params);
request.catch(api.errorHandler);
return request;
},
post: (url, data, params) => {
let request = axios.post(url, data, params);
request.catch(api.errorHandler);
return request;
},
put: (url, data, params) => {
let request = axios.put(url, data, params);
request.catch(api.errorHandler);
return request;
},
patch: (url, data, params) => {
let request = axios.patch(url, data, params);
request.catch(api.errorHandler);
return request;
},
delete: (url, params) => {
let request = axios.delete(url, params);
request.catch(api.errorHandler);
return request;
},
errorHandler: (error) => {
console.log('всё очень плохо', error, error.response);
if(error.response.status >= 500) {
showNotification("error", "Ошибка сервера "+error.toString());
}
},
getCurrentUser: (accessToken) => {
return axios.get('/api/v1/users/me/', {
return api.get('/api/v1/users/me/', {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
getUsers: (params, accessToken) => {
return axios.get('/api/v1/users/', {
return api.get('/api/v1/users/', {
params: params,
headers: {
'Authorization': `Token ${accessToken}`,
@ -21,14 +53,14 @@ export const api = {
});
},
getCategories: (accessToken) => {
return axios.get('/api/v1/categories/', {
return api.get('/api/v1/categories/', {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
uploadImage: (imageData, accessToken) => {
return axios.post('/api/v1/image-objects/', {
return api.post('/api/v1/image-objects/', {
image: imageData,
}, {
headers: {
@ -37,21 +69,21 @@ export const api = {
});
},
removeImage: (imageId, accessToken) => {
return axios.delete(`/api/v1/image-objects/${imageId}/`, {
return api.delete(`/api/v1/image-objects/${imageId}/`, {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
loadCourse: (courseId, accessToken) => {
return axios.get(`/api/v1/courses/${courseId}/`, {
return api.get(`/api/v1/courses/${courseId}/`, {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
loadLive: (courseId, accessToken) => {
return axios.get(`/api/v1/live-lesson/${courseId}/`, {
return api.get(`/api/v1/live-lesson/${courseId}/`, {
headers: {
'Authorization': `Token ${accessToken}`,
}
@ -360,56 +392,56 @@ export const api = {
});
},
addCourse: (courseJson, accessToken) => {
return axios.post('/api/v1/courses/', courseJson, {
return api.post('/api/v1/courses/', courseJson, {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
updateCourse: (courseId, courseJson, accessToken) => {
return axios.put(`/api/v1/courses/${courseId}/`, courseJson, {
return api.put(`/api/v1/courses/${courseId}/`, courseJson, {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
addLive: (courseJson, accessToken) => {
return axios.post('/api/v1/live-lesson/', courseJson, {
return api.post('/api/v1/live-lesson/', courseJson, {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
updateLive: (courseId, courseJson, accessToken) => {
return axios.put(`/api/v1/live-lesson/${courseId}/`, courseJson, {
return api.put(`/api/v1/live-lesson/${courseId}/`, courseJson, {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
getCourseLessons: (courseId, accessToken) => {
return axios.get(`/api/v1/lessons/?course=${courseId}`, {
return api.get(`/api/v1/lessons/?course=${courseId}`, {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
removeCourseLesson: (lessonId, accessToken) => {
return axios.delete(`/api/v1/lessons/${lessonId}/`, {
return api.delete(`/api/v1/lessons/${lessonId}/`, {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
addLesson: (lessonJson, accessToken) => {
return axios.post('/api/v1/lessons/', lessonJson, {
return api.post('/api/v1/lessons/', lessonJson, {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
updateLesson: (lessonId, lessonJson, accessToken) => {
return axios.put(`/api/v1/lessons/${lessonId}/`, lessonJson, {
return api.put(`/api/v1/lessons/${lessonId}/`, lessonJson, {
headers: {
'Authorization': `Token ${accessToken}`,
}
@ -437,28 +469,28 @@ export const api = {
if (!removeUrl) {
return;
}
return axios.delete(removeUrl, {
return api.delete(removeUrl, {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
getCourseDraft: (accessToken) => {
return axios.get('/api/v1/courses/draft/', {
return api.get('/api/v1/courses/draft/', {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
publishCourse: (courseId, accessToken) => {
return axios.patch(`/api/v1/courses/${courseId}/`, {status: 1}, {
return api.patch(`/api/v1/courses/${courseId}/`, {status: 1}, {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
getSchedule: (accessToken, params) => {
return axios.get('/api/v1/school-schedules/', {
return api.get('/api/v1/school-schedules/', {
params: params,
headers: {
'Authorization': `Token ${accessToken}`,

@ -920,7 +920,7 @@ a[name]
&_confirm
padding: 30px 0 50px
&_school
padding: 85px 0 95px
padding: 85px 0 20px
+t
padding: 65px 0 55px
+m
@ -933,7 +933,7 @@ a[name]
+m
padding: 45px 0
&_arts
padding: 95px 0 70px
padding: 70px 0 70px
+t
padding: 80px 0 70px
+m
@ -3648,18 +3648,26 @@ a.grey-link
margin-bottom: -24px
&__col
padding: 0 12px
flex: 0 0 20%
flex: 0 0 16%
+t
flex: 0 0 33.33%
flex: 0 0 28%
margin-bottom: 15px
+m
margin-bottom: 24px
flex: 0 0 50%
&:hover
.arts__title
display: none
.arts__item:before
background: rgba(255,255,255,0)
transition: all .1s ease-out 0.1s
&__item
position: relative
border-radius: 50%
background-position: 50% 50%
background-size: cover
&:before
background: rgba(255,255,255,0.8)
content: ''
display: block
padding-bottom: 100%

Loading…
Cancel
Save