баннер с обратным отсчетом

remotes/origin/feature/school-only-may-24-04-19
gzbender 7 years ago
parent 97edbf221a
commit 0e1b71784c
  1. 12
      api/v1/serializers/content.py
  2. 4
      api/v1/urls.py
  3. 10
      api/v1/views.py
  4. 6
      apps/content/admin.py
  5. 23
      apps/content/migrations/0024_auto_20190418_2253.py
  6. 17
      apps/content/migrations/0025_auto_20190419_0103.py
  7. 18
      apps/content/migrations/0026_banner_stretch_image.py
  8. 10
      apps/content/models.py
  9. 6
      project/context_processors.py
  10. 2
      project/settings.py
  11. 16
      project/templates/blocks/baner.html
  12. 38
      project/templates/blocks/banner.html
  13. 2
      project/templates/lilcity/index.html
  14. 104
      web/src/components/blocks/Countdown.vue
  15. 11
      web/src/js/app.js
  16. 7
      web/src/js/modules/common.js
  17. 74
      web/src/sass/_common.sass

@ -3,7 +3,7 @@ from rest_framework import serializers
from django.conf import settings from django.conf import settings
from apps.content.models import ( from apps.content.models import (
Baner, Content, Image, Text, ImageText, Video, Banner, Content, Image, Text, ImageText, Video,
Gallery, GalleryImage, ImageObject, FAQ) Gallery, GalleryImage, ImageObject, FAQ)
from . import Base64ImageField from . import Base64ImageField
@ -24,11 +24,11 @@ BASE_CONTENT_FIELDS = (
) )
class BanerSerializer(serializers.ModelSerializer): class BannerSerializer(serializers.ModelSerializer):
image = serializers.SerializerMethodField() image = serializers.SerializerMethodField()
class Meta: class Meta:
model = Baner model = Banner
fields = ( fields = (
'id', 'id',
'text', 'text',
@ -46,9 +46,9 @@ class BanerSerializer(serializers.ModelSerializer):
'update_at', 'update_at',
) )
def get_image(self, baner): def get_image(self, banner):
if baner.image: if banner.image:
return 'http://' + settings.MAIN_HOST + '/' + baner.image.url return 'http://' + settings.MAIN_HOST + '/' + banner.image.url
else: else:
return None return None

@ -9,7 +9,7 @@ from drf_yasg import openapi
from .auth import ObtainToken, ObtainTempToken from .auth import ObtainToken, ObtainTempToken
from .views import ( from .views import (
AuthorBalanceViewSet, AuthorRequestViewSet, AuthorBalanceViewSet, AuthorRequestViewSet,
BanerViewSet, ConfigViewSet, CategoryViewSet, BannerViewSet, ConfigViewSet, CategoryViewSet,
CourseViewSet, CommentViewSet, CourseViewSet, CommentViewSet,
MaterialViewSet, LikeViewSet, MaterialViewSet, LikeViewSet,
ImageViewSet, TextViewSet, ImageViewSet, TextViewSet,
@ -24,7 +24,7 @@ from .views import (
router = DefaultRouter() router = DefaultRouter()
router.register(r'author-requests', AuthorRequestViewSet, base_name='author-requests') router.register(r'author-requests', AuthorRequestViewSet, base_name='author-requests')
router.register(r'author-balance', AuthorBalanceViewSet, base_name='author-balance') router.register(r'author-balance', AuthorBalanceViewSet, base_name='author-balance')
router.register(r'baners', BanerViewSet, base_name='baners') router.register(r'baners', BannerViewSet, base_name='banners')
router.register(r'categories', CategoryViewSet, base_name='categories') router.register(r'categories', CategoryViewSet, base_name='categories')
router.register(r'courses', CourseViewSet, base_name='courses') router.register(r'courses', CourseViewSet, base_name='courses')
router.register(r'comments', CommentViewSet, base_name='comments') router.register(r'comments', CommentViewSet, base_name='comments')

@ -23,7 +23,7 @@ from .serializers.course import (
LikeCreateSerializer, CourseCommentSerializer, LessonCommentSerializer, LikeCreateSerializer, CourseCommentSerializer, LessonCommentSerializer,
LiveLessonCommentSerializer,) LiveLessonCommentSerializer,)
from .serializers.content import ( from .serializers.content import (
BanerSerializer, BannerSerializer,
ImageSerializer, ImageCreateSerializer, ImageSerializer, ImageCreateSerializer,
TextSerializer, TextCreateSerializer, TextSerializer, TextCreateSerializer,
ImageTextSerializer, ImageTextCreateSerializer, ImageTextSerializer, ImageTextCreateSerializer,
@ -63,7 +63,7 @@ from apps.course.models import (
LiveLessonComment) LiveLessonComment)
from apps.config.models import Config from apps.config.models import Config
from apps.content.models import ( from apps.content.models import (
Baner, Image, Text, ImageText, Video, Banner, Image, Text, ImageText, Video,
Gallery, GalleryImage, ImageObject, Gallery, GalleryImage, ImageObject,
Contest, ContestWork, FAQ) Contest, ContestWork, FAQ)
from apps.payment.models import ( from apps.payment.models import (
@ -132,9 +132,9 @@ class AuthorBalanceUsersViewSet(views.APIView):
return Response(UserSerializer(users, many=True).data) return Response(UserSerializer(users, many=True).data)
class BanerViewSet(ExtendedModelViewSet): class BannerViewSet(ExtendedModelViewSet):
queryset = Baner.objects.all() queryset = Banner.objects.all()
serializer_class = BanerSerializer serializer_class = BannerSerializer
permission_classes = (IsAdmin,) permission_classes = (IsAdmin,)
filter_fields = ('use',) filter_fields = ('use',)
ordering_fields = ('created_at', 'update_at',) ordering_fields = ('created_at', 'update_at',)

@ -6,14 +6,14 @@ from polymorphic.admin import (
) )
from apps.content.models import ( from apps.content.models import (
Baner, Content, Image, Text, ImageText, Video, Banner, Content, Image, Text, ImageText, Video,
Gallery, GalleryImage, ImageObject, Gallery, GalleryImage, ImageObject,
Contest, ContestWork, FAQ, Contest, ContestWork, FAQ,
) )
@admin.register(Baner) @admin.register(Banner)
class BanerAdmin(admin.ModelAdmin): class BannerAdmin(admin.ModelAdmin):
list_display = ( list_display = (
'text', 'text',
'button_text', 'button_text',

@ -0,0 +1,23 @@
# Generated by Django 2.0.7 on 2019-04-18 22:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('content', '0023_faq'),
]
operations = [
migrations.AddField(
model_name='baner',
name='color',
field=models.CharField(blank=True, default='', max_length=7),
),
migrations.AddField(
model_name='baner',
name='future_date',
field=models.DateTimeField(blank=True, null=True),
),
]

@ -0,0 +1,17 @@
# Generated by Django 2.0.7 on 2019-04-19 01:03
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('content', '0024_auto_20190418_2253'),
]
operations = [
migrations.RenameModel(
old_name='Baner',
new_name='Banner',
),
]

@ -0,0 +1,18 @@
# Generated by Django 2.0.7 on 2019-04-19 15:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('content', '0025_auto_20190419_0103'),
]
operations = [
migrations.AddField(
model_name='banner',
name='stretch_image',
field=models.BooleanField(default=True),
),
]

@ -132,13 +132,15 @@ class GalleryImage(models.Model):
ordering = ('-created_at',) ordering = ('-created_at',)
class Baner(models.Model): class Banner(models.Model):
text = models.TextField() text = models.TextField()
button_text = models.CharField(max_length=50) button_text = models.CharField(max_length=50)
url = models.URLField() url = models.URLField()
image = models.ImageField() image = models.ImageField()
use = models.BooleanField(default=False) use = models.BooleanField(default=False)
color = models.CharField(max_length=7, blank=True, default='')
stretch_image = models.BooleanField(default=True)
future_date = models.DateTimeField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True) update_at = models.DateTimeField(auto_now=True)
@ -149,7 +151,7 @@ class Baner(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self.use: if self.use:
Baner.objects.filter(use=True).update(use=False) Banner.objects.filter(use=True).update(use=False)
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
@ -168,7 +170,7 @@ class Contest(models.Model):
date_start = models.DateField('Дата начала', null=True, blank=True) date_start = models.DateField('Дата начала', null=True, blank=True)
date_end = models.DateField('Дата окончания', null=True, blank=True) date_end = models.DateField('Дата окончания', null=True, blank=True)
active = models.BooleanField(default=True) active = models.BooleanField(default=True)
# TODO? baner # TODO? banner
@property @property
def finished(self): def finished(self):

@ -4,7 +4,7 @@ from paymentwall.pingback import Pingback
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.conf import settings from django.conf import settings
from apps.config.models import Config from apps.config.models import Config
from apps.content.models import Baner from apps.content.models import Banner
from apps.payment.models import SchoolPayment from apps.payment.models import SchoolPayment
User = get_user_model() User = get_user_model()
@ -14,8 +14,8 @@ def config(request):
return {"config": Config.load()} return {"config": Config.load()}
def baner(request): def banner(request):
return {'baner': Baner.objects.filter(use=True).first()} return {'banner': Banner.objects.filter(use=True).first()}
def school_purchased(request): def school_purchased(request):

@ -95,7 +95,7 @@ TEMPLATES = [
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
'project.context_processors.config', 'project.context_processors.config',
'project.context_processors.baner', 'project.context_processors.banner',
'project.context_processors.school_purchased', 'project.context_processors.school_purchased',
'project.context_processors.referrer', 'project.context_processors.referrer',
'project.context_processors.settings', 'project.context_processors.settings',

@ -1,16 +0,0 @@
{% if baner %}
<div class="banner" data-banner="{{baner.id}}"
style="{% if not request.user_agent.is_mobile %}background-image: url({{ baner.image.url }});{% endif %} display: none;">
{% if request.user_agent.is_mobile %}
<a href="{{ baner.url }}" class="banner__image-wrap">
<img class="banner__image" src="{{ baner.image.url }}" />
</a>
{% else %}
<div class="banner__content">
<span class="banner__text">{{ baner.text }}</span>
<a href="{{ baner.url }}" class="banner__link">{{ baner.button_text }}</a>
</div>
{% endif %}
<a href="#" class="banner__hide">Скрыть баннер</a>
</div>
{% endif %}

@ -0,0 +1,38 @@
{% if banner %}
<div class="banner {% if banner.color %}banner_colored{% endif %} {% if banner.future_date %}banner_countdown{% endif %}"
data-banner="{{banner.id}}"
style="
{% if not request.user_agent.is_mobile %}
background-image: url({{ banner.image.url }});
{% endif %}
{% if banner.stretch_image %}
background-position: center;
background-size: cover;
{% else %}
background-position: center bottom;
{% endif %}
display: none;
background-color: {{ banner.color|default:'white' }};">
{# if request.user_agent.is_mobile #}
<!--<a href="{{ banner.url }}" class="banner__image-wrap">-->
<!--<img class="banner__image" src="{{ banner.image.url }}" />-->
<!--</a>-->
{# else #}
<div class="banner__content">
<div class="banner__content-center">
<div class="banner__text-column">
<div class="banner__text">{{ banner.text }}</div>
<a href="{{ banner.url }}" class="banner__link">{{ banner.button_text }}</a>
</div>
{% if banner.future_date %}
<div class="banner__countdown-column">
<div class="banner__countdown-title">До конца акции осталось</div>
<countdown date="{{ banner.future_date|date:'Y-m-d H:i:s' }}"></countdown>
</div>
{% endif %}
</div>
</div>
{# endif #}
<a href="#" class="banner__hide">Скрыть баннер</a>
</div>
{% endif %}

@ -32,7 +32,7 @@
{% block layer_body %} {% block layer_body %}
{% include "templates/blocks/social.html" %} {% include "templates/blocks/social.html" %}
{% include "templates/blocks/baner.html" %} {% include "templates/blocks/banner.html" %}
<div class="outer js-outer"> <div class="outer js-outer">
{% include "templates/blocks/header.html" %} {% include "templates/blocks/header.html" %}
<div id="lilcity-vue-app" class="container"> <div id="lilcity-vue-app" class="container">

@ -0,0 +1,104 @@
<template>
<div class="banner__countdown" style="opacity: 0;">
<div>
<div class="banner__countdown-nums">
<div>{{ days[0] }}</div>
<div>{{ days[1] }}</div>
</div>
<div class="banner__countdown-descr">
Дней
</div>
</div>
<div class="banner__countdown-delim"></div>
<div>
<div class="banner__countdown-nums">
<div>{{ hours[0] }}</div>
<div>{{ hours[1] }}</div>
</div>
<div class="banner__countdown-descr">
Часов
</div>
</div>
<div class="banner__countdown-delim">:</div>
<div>
<div class="banner__countdown-nums">
<div>{{ minutes[0] }}</div>
<div>{{ minutes[1] }}</div>
</div>
<div class="banner__countdown-descr">
Минут
</div>
</div>
<div class="banner__countdown-delim">:</div>
<div>
<div class="banner__countdown-nums">
<div>{{ seconds[0] }}</div>
<div>{{ seconds[1] }}</div>
</div>
<div class="banner__countdown-descr">
Секунд
</div>
</div>
</div>
</template>
<script>
import moment from 'moment';
export default {
name: "Countdown",
props: ['date', 'format'],
data: () => ({
days: '00',
hours: '00',
minutes: '00',
seconds: '00',
i: 0,
}),
beforeDestroy(){
if ( window['cancelAnimationFrame'] ) {
window.cancelAnimationFrame(this.frame);
}
},
mounted() {
if ( window['requestAnimationFrame'] ) {
this.countdown = moment(this.date, this.format).toDate();
this.update();
}
},
methods: {
zerofill(value) {
return (value < 10 && value > -1 ? '0' : '') + value;
},
update() {
this.frame = window.requestAnimationFrame(this.update.bind(this));
if(this.i++ % 10 || this.$el.offsetParent === null) {
return;
}
this.$el.style.opacity = 1;
const t = this.countdown - (new Date());
if(t < 0){
this.days = this.hours = this.minutes = this.seconds = '00';
return;
}
this.days = this.zerofill(Math.floor(t / (1000 * 60 * 60 * 24)));
this.hours = this.zerofill(Math.floor((t / (1000 * 60 * 60)) % 24));
this.minutes = this.zerofill(Math.floor((t / 1000 / 60) % 60));
this.seconds = this.zerofill(Math.floor((t / 1000) % 60));
}
}
}
</script>
<style scoped>
</style>

@ -27,6 +27,7 @@ import DatePicker from 'vuejs-datepicker'
import Comments from '../components/Comments'; import Comments from '../components/Comments';
import Likes from '../components/blocks/Likes.vue'; import Likes from '../components/blocks/Likes.vue';
import FAQ from '../components/FAQ.vue'; import FAQ from '../components/FAQ.vue';
import Countdown from '../components/blocks/Countdown.vue';
Vue.use(Vuelidate); Vue.use(Vuelidate);
Vue.use(VueAutosize); Vue.use(VueAutosize);
@ -54,3 +55,13 @@ const app = new Vue({
}, },
components: components components: components
}); });
const bannerApp = new Vue({
el: '.banner',
data() {
return {
store: window.LIL_STORE,
}
},
components: {'countdown': Countdown,}
});

@ -5,6 +5,7 @@ import SmoothScroll from 'smooth-scroll/dist/js/smooth-scroll';
import baguetteBox from 'baguettebox.js' import baguetteBox from 'baguettebox.js'
import createHistory from 'history/createBrowserHistory' import createHistory from 'history/createBrowserHistory'
import Cookies from 'js-cookie' import Cookies from 'js-cookie'
import moment from 'moment'
window.$ = window.jQuery = jQuery = $; window.$ = window.jQuery = jQuery = $;
window.Inputmask = Inputmask; window.Inputmask = Inputmask;
@ -32,6 +33,7 @@ $(document).ready(function () {
//===========BANNERS=============== //===========BANNERS===============
const $banner = $('[data-banner]'); const $banner = $('[data-banner]');
const bannerId = $banner.data('banner') + ''; const bannerId = $banner.data('banner') + '';
const futureDate = $banner.data('future-date') + '';
if(Cookies.get('hide_banner') !== bannerId){ if(Cookies.get('hide_banner') !== bannerId){
$banner.show(); $banner.show();
} }
@ -43,6 +45,11 @@ $(document).ready(function () {
Cookies.set('hide_banner', bannerId); Cookies.set('hide_banner', bannerId);
}); });
if(futureDate){
}
$banner.find('.banner__countdown')
//===========REVIEWS=============== //===========REVIEWS===============
if(window.LIL_STORE.isIndexPage){ if(window.LIL_STORE.isIndexPage){
const $reviews = $('.reviews'); const $reviews = $('.reviews');

@ -4388,10 +4388,7 @@ a
height: 140px height: 140px
text-align: center text-align: center
color: #fff color: #fff
background-color: white
background-repeat: no-repeat background-repeat: no-repeat
background-position: center
background-size: cover
+m +m
height: auto height: auto
&__image-wrap &__image-wrap
@ -4405,15 +4402,33 @@ a
+m +m
width: 100% width: 100%
display: block display: block
margin: 0 auto
&_colored &__image
+m
max-width: 100%
width: auto
opactiy: 0.8
&__content &__content
position: absolute position: absolute
width: 100% width: 100%
margin-top: 50px margin-top: 50px
+m +m
margin-top: -110px margin-top: -110px
&_countdown &__content
margin-top: 15px
&__content-center
width: 1024px
margin: 0 auto
display: flex
padding: 0 40px
&__text &__text
font-size: 30px font-size: 30px
text-shadow: 0px 0px 3px rgba(0, 0, 0, 1) text-shadow: 0px 0px 3px rgba(0, 0, 0, 1)
&_countdown &__text
font-size: 22px
text-shadow: none
color: black
width: 300px
&__link &__link
display: block display: block
color: #fff color: #fff
@ -4422,6 +4437,20 @@ a
margin-top: 20px margin-top: 20px
+m +m
margin-top: 0px margin-top: 0px
&_countdown &__link
font-size: 15px
color: black
text-shadow: none
background: white
font-weight: bold
padding: 7px 20px
border-radius: 20px
border: 1px solid #ddd
text-align: center
margin-top: 10px
display: inline-block
&_countdown &__link:hover
background: #ddd
&__hide &__hide
position: absolute position: absolute
right: 5px right: 5px
@ -4434,6 +4463,45 @@ a
opacity: 0.3 opacity: 0.3
+m +m
//margin-top: 0px //margin-top: 0px
&__text-column
flex: 1
&_countdown &__text-column
text-align: left
&__countdown-column
padding: 0 20px
+m
border-radius: 3px
background: rgba(255, 255, 255, 0.3)
&__countdown-title
color: black
font-size: 15px
margin-bottom: 5px
&__countdown
display: flex
color: black
transition: 0.5s opacity
> div
font-size: 27px
display: flex
flex-direction: column
&-nums
display: flex
& > div
margin: 1px
width: 27px
border-radius: 5px
background: #000000ba
text-align: center
padding-top: 2px
color: white
&-delim
width: 10px
color: black
&-descr
text-align: center
font-size: 11px
margin-top: 3px
.anchor .anchor
padding-top: 100px padding-top: 100px

Loading…
Cancel
Save