Взаимодействие с API

remotes/origin/hasaccess
Vitaly Baev 8 years ago
parent 70f08c0d74
commit c91675b6fa
  1. 2
      project/templates/lilcity/test.html
  2. 1362
      web/build/js/app.js
  3. 2
      web/build/js/app.js.map
  4. 40813
      web/build/js/courseRedactor.js
  5. 135
      web/src/components/CourseRedactor.vue
  6. 23
      web/src/components/blocks/BlockImage.vue
  7. 23
      web/src/components/blocks/BlockImageText.vue
  8. 9
      web/src/components/blocks/BlockImages.vue
  9. 52
      web/src/components/blocks/Image.vue
  10. 2
      web/src/components/inputs/LilSelect.vue
  11. 200
      web/src/js/modules/api.js

@ -3,7 +3,7 @@
{% block title %}School LIL.CITY{% endblock title %}
{% block content %}
{{ request.user.auth_token }}
<course-redactor author-picture="http://localhost:8000/static/img/user.jpg" author-name="Vitaly Baev"></course-redactor>
<course-redactor author-picture="http://localhost:8000/static/img/user.jpg" author-name="Vitaly Baev" access-token="95a4b7ac608d5e4e0290ea93b7b0d2bf0e20bc92"></course-redactor>
{% endblock content %}
{% block foot %}
<script type="text/javascript" src={% static "js/courseRedactor.js" %}></script>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,5 +1,5 @@
<template>
<div>
<form @submit.prevent="onSubmit">
<div class="info">
<div class="info__section" :style="backgroundStyle">
<div class="info__main">
@ -31,7 +31,7 @@
<div class="info__field field field_info">
<div class="field__label field__label_gray">КАТЕГОРИЯ</div>
<div class="field__wrap">
<input type="text" class="field__input" v-model="course.category">
<lil-select :value.sync="categorySelect" :options="categoryOptions" placeholder="Выберите категорию"/>
</div>
</div>
<div class="info__field field field_info">
@ -81,11 +81,11 @@
<div class="field__label field__label_gray">ЗАПУСК</div>
<div class="field__wrap">
<label class="field__switch switch switch_lg switch_circle">
<input type="radio" :value="false" class="switch__input" v-model="course.is_delayed">
<input type="radio" :value="false" class="switch__input" v-model="course.is_deferred">
<span class="switch__content">Мгновенный</span>
</label>
<label class="field__switch switch switch_lg switch_circle">
<input type="radio" :value="true" class="switch__input" v-model="course.is_delayed">
<input type="radio" :value="true" class="switch__input" v-model="course.is_deferred">
<span class="switch__content">Отложенный</span>
</label>
</div>
@ -117,24 +117,39 @@
</div>
</div>
<div class="kit__nav">
<button class="kit__btn btn btn_lg btn_stroke">Описание курса</button>
<button class="kit__btn btn btn_lg btn_gray">Уроки</button>
<button class="kit__btn btn btn_lg btn_stroke" type="button" @click="viewSection = 'course'">Описание курса</button>
<button class="kit__btn btn btn_lg btn_gray" type="button" @click="viewSection = 'lessons'" :disabled="!course.id">Уроки</button>
</div>
<div class="kit__body">
<div v-if="viewSection === 'course'" class="kit__body">
<div v-for="block in course.blocks">
<block-text v-if="block.type === 'text'" :title.sync="block.data.title" :text.sync="block.data.text"/>
<block-image-text v-if="block.type === 'image-text'" :title.sync="block.data.title"
:text.sync="block.data.text"/>
<block-image v-if="block.type === 'image'" :title.sync="block.data.title"/>
<block-images v-if="block.type === 'images'" :title.sync="block.data.title" :text.sync="block.data.text"
:images.sync="block.data.images"/>
<block-video v-if="block.type === 'video'" :title.sync="block.data.title"
<block-text v-if="block.type === 'text'"
:title.sync="block.data.title"
:text.sync="block.data.text"
:access-token="accessToken"/>
<block-image-text v-if="block.type === 'image-text'"
:title.sync="block.data.title"
:text.sync="block.data.text"
:image-id.sync="block.data.image_id"
:image-url.sync="block.data.image_url"
:access-token="accessToken"/>
<block-image v-if="block.type === 'image'"
:title.sync="block.data.title"
:image-id.sync="block.data.image_id"
:image-url.sync="block.data.image_url"
:access-token="accessToken"/>
<block-images v-if="block.type === 'images'"
:title.sync="block.data.title"
:text.sync="block.data.text"
:images.sync="block.data.images"
:access-token="accessToken"/>
<block-video v-if="block.type === 'video'"
:title.sync="block.data.title"
:video-url.sync="block.data.video_url"/>
</div>
<div class="kit__section">
<div v-if="!is_adding_block" class="kit__add add">
<button class="add__toggle" @click="is_adding_block = true">
<button type="button" class="add__toggle" @click="is_adding_block = true">
<span class="add__circle">
<svg class="icon icon-add-plus">
<use xlink:href="/static/img/sprite.svg#icon-add-plus"></use>
@ -144,7 +159,7 @@
</button>
</div>
<div v-if="is_adding_block" class="kit__add add open">
<button class="add__toggle" @click="is_adding_block = false">
<button type="button" class="add__toggle" @click="is_adding_block = false">
<span class="add__circle">
<svg class="icon icon-add-plus">
<use xlink:href="/static/img/sprite.svg#icon-add-plus"></use>
@ -163,17 +178,17 @@
<use xlink:href="/static/img/sprite.svg#icon-image"></use>
</svg>
</button>
<button class="add__btn" @click="addBlockImageText">
<button type="button" class="add__btn" @click="addBlockImageText">
<svg class="icon icon-image-text">
<use xlink:href="/static/img/sprite.svg#icon-image-text"></use>
</svg>
</button>
<button class="add__btn" @click="addBlockImages">
<button type="button" class="add__btn" @click="addBlockImages">
<svg class="icon icon-images">
<use xlink:href="/static/img/sprite.svg#icon-images"></use>
</svg>
</button>
<button class="add__btn" @click="addBlockVideo">
<button type="button" class="add__btn" @click="addBlockVideo">
<svg class="icon icon-video-stroke">
<use xlink:href="/static/img/sprite.svg#icon-video-stroke"></use>
</svg>
@ -186,18 +201,27 @@
<vue-redactor :value.sync="course.about" />
</div>
<div class="kit__foot">
<button class="kit__submit btn btn_md">Сохранить</button>
<button type="submit" class="kit__submit btn btn_md">Сохранить</button>
</div>
</div>
<div v-if="viewSection === 'lessons'" class="kit__body">
<div class="lessons__title title">Содержание курса</div>
<div class="lessons__list">
</div>
<div class="lessons__foot">
<button class="lessons__btn btn btn_md">СОЗДАТЬ УРОК</button>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</template>
<script>
import LinkInput from './inputs/LinkInput'
import Datepicker from 'vuejs-datepicker'
import DatePicker from 'vuejs-datepicker'
import BlockText from './blocks/BlockText'
import BlockImage from './blocks/BlockImage'
import BlockImages from './blocks/BlockImages'
@ -205,27 +229,33 @@
import BlockVideo from './blocks/BlockVideo'
import VueRedactor from './redactor/VueRedactor'
import LilSelect from "./inputs/LilSelect";
import {api} from "../js/modules/api";
export default {
name: "course-redactor",
props: ["authorName", "authorPicture"],
props: ["authorName", "authorPicture", "accessToken"],
data() {
return {
viewSection: 'course',
course: {
title: 'Как просто научиться рисовать простых персонажей.',
category: 'Акварель',
category: 9,
categorySelect: null,
duration: 1,
price: 1000,
url: 'my-awesome-url',
backgroundImage: '',
is_paid: true,
is_featured: true,
is_delayed: false,
is_deferred: false,
date: '2018-03-08',
time: null,
short_description: 'Этот курс поможет детям освоить базовые навыки рисования простых персонажей',
blocks: [],
about: '<b>Главная цель курса</b> - рассказать...',
},
lessons: {
},
is_adding_block: false,
timeOptions: [
@ -265,7 +295,8 @@
'title': '18:00',
'value': '18:00',
}
]
],
categoryOptions: []
}
},
methods: {
@ -297,6 +328,8 @@
type: 'image',
data: {
title: 'тест картинка',
image_id: null,
image_url: null,
}
})
},
@ -306,6 +339,8 @@
data: {
title: 'тест картинка-текст',
text: 'текст какой-то',
image_id: null,
image_url: null,
}
})
},
@ -327,8 +362,37 @@
video_url: 'http://vimeo.com/safmklsamfk',
}
})
},
updateCategory() {
if (this.categoryOptions && Array.isArray(this.categoryOptions) && this.course.category) {
this.categoryOptions.forEach((category) => {
if (category.id === this.course.category) {
this.course.categorySelect = category;
}
});
}
},
onSubmit() {
api.saveCourse(this.course, this.accessToken)
.then((response) => {
this.course = api.convertCourseJson(response.course);
});
}
},
mounted() {
api.getCategories(this.accessToken)
.then((response) => {
if (response.data) {
this.categoryOptions = response.data.results.map((category) => {
return {
title: category.title,
value: category.id
}
});
}
this.updateCategory();
});
},
computed: {
backgroundStyle() {
return this.course.backgroundImage ? `background-image: url(${this.course.backgroundImage});` : '';
@ -340,13 +404,30 @@
set: function (value) {
this.course.price = value;
}
},
categorySelect: {
get() {
if (!this.categoryOptions || this.categoryOptions.length === 0 || !this.course || !this.course.category) {
return null;
}
let value;
this.categoryOptions.forEach((category) => {
if (category.value === this.course.category) {
value = category;
}
});
return value;
},
set(value) {
this.course.category = value.value;
}
}
},
components: {
LilSelect,
BlockText,
'link-input': LinkInput,
'vue-datepicker': Datepicker,
'vue-datepicker': DatePicker,
'block-text': BlockText,
'block-image': BlockImage,
'block-image-text': BlockImageText,

@ -10,25 +10,32 @@
</div>
</div>
<div class="kit__row">
<div class="kit__photo">
<svg class="icon icon-add-plus">
<use xlink:href="/static/img/sprite.svg#icon-add-plus"></use>
</svg>
<input type="file" class="kit__file">
</div>
<lil-image :image-id="imageId" :image-url="imageUrl" v-on:update:imageUrl="onUpdateImageUrl"
v-on:update:imageId="onUpdateImageId" :access-token="accessToken" />
</div>
</div>
</template>
<script>
import LilImage from "./Image";
export default {
name: "block-image",
props: ["title"],
props: ["title", "imageUrl", "imageId", "accessToken"],
methods: {
onTitleChange(event) {
this.$emit('update:title', event.target.value);
},
}
onUpdateImageUrl(newValue) {
this.$emit('update:imageUrl', newValue);
},
onUpdateImageId(newValue) {
this.$emit('update:imageId', newValue);
}
},
components: {
'lil-image': LilImage,
},
}
</script>

@ -1,12 +1,8 @@
<template>
<div class="kit__section">
<div class="kit__row">
<div class="kit__photo">
<svg class="icon icon-add-plus">
<use xlink:href="/static/img/sprite.svg#icon-add-plus"></use>
</svg>
<input type="file" class="kit__file">
</div>
<lil-image :image-id="imageId" :image-url="imageUrl" v-on:update:imageUrl="onUpdateImageUrl"
v-on:update:imageId="onUpdateImageId" :access-token="accessToken"/>
<div class="kit__fieldset">
<div class="kit__field field">
<div class="field__wrap">
@ -31,16 +27,27 @@
</template>
<script>
import LilImage from "./Image";
export default {
name: "block-image-text",
props: ["title", "text", 'image'],
props: ["title", "text", "imageUrl", "imageId", "accessToken"],
methods: {
onTitleChange(event) {
this.$emit('update:title', event.target.value);
},
onTextChange(event) {
this.$emit('update:text', event.target.value);
},
onUpdateImageUrl(newValue) {
this.$emit('update:imageUrl', newValue);
},
onUpdateImageId(newValue) {
this.$emit('update:imageId', newValue);
}
}
},
components: {
'lil-image': LilImage,
},
}
</script>

@ -32,14 +32,11 @@
</template>
<script>
import axios from 'axios';
import {api} from "../../js/modules/api";
axios.defaults.headers.common['Authorization'] = `Token 134937192e3d8f7590ff28093e82b635cab432da`;
export default {
name: "block-images",
props: ["title", "text", "images"],
props: ["title", "text", "images", "accessToken"],
methods: {
onTitleChange(event) {
this.$emit('update:title', event.target.value);
@ -58,14 +55,14 @@
});
this.$emit('update:images', images);
api.uploadImage(reader.result, '134937192e3d8f7590ff28093e82b635cab432da')
api.uploadImage(reader.result, this.accessToken)
.then((response) => {
console.log('completed', response);
let images = this.images;
images.forEach((image, index) => {
if (image.src === reader.result) {
images[index].id = response.data.id;
images[index].img = response.data.id;
images[index].loading = false;
images[index].src = response.data.image;
}

@ -0,0 +1,52 @@
<template>
<div class="kit__photo">
<svg class="icon icon-add-plus" v-if="!imageUrl">
<use xlink:href="/static/img/sprite.svg#icon-add-plus"></use>
</svg>
<img class="kit__photo-image" v-if="imageUrl" :src="imageUrl">
<input type="file" class="kit__file" @change="onImageAdded">
</div>
</template>
<script>
import {api} from "../../js/modules/api";
export default {
name: "lil-image",
props: ["imageId", "imageUrl", "accessToken"],
data() {
return {
loading: false,
}
},
methods: {
onImageAdded(event) {
this.loading = true;
let file = event.target.files[0];
let reader = new FileReader();
reader.onload = () => {
api.uploadImage(reader.result, this.accessToken)
.then((response) => {
this.loading = false;
this.$emit('update:imageId', response.data.id);
this.$emit('update:imageUrl', response.data.image);
})
.catch((error) => {
this.loading = false;
console.log('error', error);
});
};
if (file) {
reader.readAsDataURL(file);
}
}
}
}
</script>
<style>
.kit__photo-image {
width: 140px;
height: 110px;
}
</style>

@ -17,7 +17,7 @@
props: ["options", "value", "valueKey", "placeholder"],
data() {
return {
key: this.valueKey ? this.valueKey : 'value',
key: this.valueKey ? this.valueKey : 'title',
isOpened: false,
}
},

@ -1,9 +1,17 @@
import axios from 'axios';
import moment from 'moment';
axios.defaults.headers.post['Content-Type'] = 'application/json';
axios.defaults.headers.post['Accept'] = 'application/json';
export const api = {
getCategories: (accessToken) => {
return axios.get('/api/v1/categories/', {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
uploadImage: (imageData, accessToken) => {
return axios.post('/api/v1/image-objects/', {
image: imageData,
@ -12,5 +20,195 @@ export const api = {
'Authorization': `Token ${accessToken}`,
}
});
}
},
saveCourse: (courseObject, accessToken) => {
const isAdding = (!courseObject.hasOwnProperty('id') || !courseObject.hasOwnProperty('id'));
let deferredStart = null;
if (courseObject.is_deferred) {
let deferredStartTime = moment(courseObject.time.value, "HH:mm");
let deferredStartDate = moment(courseObject.date).hour(deferredStartTime.hour());
deferredStart = deferredStartDate.format();
}
const courseJson = {
title: courseObject.title,
short_description: courseObject.short_description,
category: courseObject.category,
price: courseObject.is_paid ? courseObject.price : 0,
deferred_start_at: deferredStart,
duration: courseObject.duration,
is_featured: courseObject.is_featured,
url: courseObject.url,
cover: courseObject.cover_id ? courseObject.cover_id : null,
content: courseObject.blocks.map((block) => {
if (block.type === 'text') {
return {
'type': 'text',
'data': {
'id': block.data.id ? block.data.id : null,
'title': block.data.title,
'txt': block.data.text,
}
}
} else if (block.type === 'image') {
return {
'type': 'image',
'data': {
'id': block.data.id ? block.data.id : null,
'title': block.data.title,
'img': block.data.image_id,
}
}
} else if (block.type === 'image-text') {
return {
'type': 'image-text',
'data': {
'id': block.data.id ? block.data.id : null,
'title': block.data.title,
'img': block.data.image_id,
'txt': block.data.text,
}
}
} else if (block.type === 'images') {
return {
'type': 'images',
'data': {
'id': block.data.id ? block.data.id : null,
'title': block.data.title,
'images': block.data.images.map((galleryImage) => {
return {
'id': galleryImage.id ? galleryImage.id : null,
'img': galleryImage.img,
}
}),
}
}
} else if (block.type === 'video') {
return {
'type': 'video',
'data': {
'id': block.data.id ? block.data.id : null,
'title': block.data.title,
'url': block.data.video_url,
}
}
}
}),
};
console.log(courseJson);
let request;
if (isAdding) {
return api.addCourse(courseJson, accessToken);
} else {
return api.updateCourse(courseObject.id, courseJson, accessToken);
}
},
convertCourseJson: (courseJSON) => {
let isDeferred = false;
let deferredDate = '';
let deferredTime = '';
if (courseJSON.deferred_start_at) {
let deferredDateTime = moment(courseJSON.deferred_start_at);
isDeferred = true;
deferredDate = deferredDateTime.format('MM-DD-YYYY');
deferredTime = deferredDateTime.format('HH:mm');
}
return {
id: courseJSON.id,
title: courseJSON.title,
short_description: courseJSON.short_description,
category: courseJSON.category,
price: courseJSON.price,
is_paid: courseJSON.price === 0,
is_deferred: isDeferred,
date: deferredDate,
time: deferredTime ? {title: deferredTime, value: deferredTime} : null,
duration: courseJSON.duration,
is_featured: courseJSON.is_featured,
url: courseJSON.url,
cover_id: courseJSON.cover.id,
cover_url: courseJSON.cover.image,
content: courseJSON.content.map((contentItem) => {
if (contentItem.type === 'text') {
return {
'type': 'text',
'data': {
'id': contentItem.data.id ? contentItem.data.id : null,
'title': contentItem.data.title,
'text': contentItem.data.txt,
}
}
} else if (contentItem.type === 'image') {
return {
'type': 'image',
'data': {
'id': contentItem.data.id ? contentItem.data.id : null,
'title': contentItem.data.title,
'image_id': contentItem.data.image.id,
'image_url': contentItem.data.image.image,
}
}
} else if (contentItem.type === 'image-text') {
return {
'type': 'image-text',
'data': {
'id': contentItem.data.id ? contentItem.data.id : null,
'title': contentItem.data.title,
'image_id': contentItem.data.image.id,
'image_url': contentItem.data.image.image,
'text': contentItem.data.txt,
}
}
} else if (contentItem.type === 'images') {
return {
'type': 'images',
'data': {
'id': contentItem.data.id ? contentItem.data.id : null,
'title': contentItem.data.title,
'images': contentItem.data.images.map((galleryImage) => {
return {
'id': galleryImage.id ? galleryImage.id : null,
'image_id': galleryImage.image.id,
'image_url': galleryImage.image.url,
}
}),
}
}
} else if (contentItem.type === 'video') {
return {
'type': 'video',
'data': {
'id': contentItem.data.id ? contentItem.data.id : null,
'title': contentItem.data.title,
'video_url': contentItem.data.url,
}
}
}
}),
}
},
addCourse: (courseJson, accessToken) => {
return axios.post('/api/v1/courses/', courseJson, {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
updateCourse: (courseId, courseJson, accessToken) => {
return axios.put(`/api/v1/courses/${courseId}/`, courseJson, {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
getCourseLessons: (courseId, accessToken) => {
return axios.get(`/api/v1/lessons/?course=${courseId}`, {
headers: {
'Authorization': `Token ${accessToken}`,
}
});
},
};
Loading…
Cancel
Save