接下來,咱們把註冊過程當中一些註冊信息(例如:短信驗證碼)和session緩存到redis數據庫中。css
安裝django-redis。html
pip install django-redis
在settings/dev.py配置中添加一下代碼:前端
# 設置redis緩存 CACHES = { # 默認緩存 "default": { "BACKEND": "django_redis.cache.RedisCache", # 項目上線時,須要調整這裏的路徑 "LOCATION": "redis://127.0.0.1:6379/0", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } }, # 提供給xadmin或者admin的session存儲 "session": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/1", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } }, # 提供存儲短信驗證碼 "sms_code":{ "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/2", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } } } # 設置xadmin用戶登陸時,登陸信息session保存到redis SESSION_ENGINE = "django.contrib.sessions.backends.cache" SESSION_CACHE_ALIAS = "session"
關於django-redis 的使用,說明文檔可見http://django-redis-chs.readthedocs.io/zh_CN/latest/vue
django-redis提供了get_redis_connection的方法,經過調用get_redis_connection方法傳遞redis的配置名稱可獲取到redis的鏈接對象,經過redis鏈接對象能夠執行redis命令java
https://redis-py.readthedocs.io/en/latest/python
使用範例:mysql
from django_redis import get_redis_connection // 連接redis數據庫 redis_conn = get_redis_connection("sms_code") // 操做redis redis_conn.setex("鍵","時間(秒)","值") # 這個redis類把終端命令封裝成了redis類的方法,命令的選項編程了方法的參數,因此這段代碼的操做,等同於在終端下 sexex 鍵 時間 值
在登陸後的平臺上面獲取一下信息:linux
ACCOUNT SID:8a216da86e011fa3016e4aedea792a4e AUTH TOKEN : 4fee1ea5d1024bce948db54b9161c97e AppID(默認):8a216da86e011fa3016e4aedeacb2a54 Rest URL(生產): app.cloopen.com:8883 [項目上線時使用真實短信發送服務器] Rest URL(開發): sandboxapp.cloopen.com:8883 [項目開發時使用沙箱短信發送服務器]
找到sdkdemo進行下載ios
在開發過程當中,爲了節約發送短信的成本,能夠把本身的或者同事的手機加入到測試號碼中.css3
把雲通信的sdk保存到libs目錄下, 並修改裏面的基本配置信息。
sms.py,代碼:
# -*- coding:utf-8 -*- from .CCPRestSDK import REST from django.conf import settings # 說明:主帳號,登錄雲通信網站後,可在"控制檯-應用"中看到開發者主帳號ACCOUNT SID _accountSid = settings.SMS["_accountSid"] # 說明:主帳號Token,登錄雲通信網站後,可在控制檯-應用中看到開發者主帳號AUTH TOKEN _accountToken = settings.SMS["_accountToken"] # 6dd01b2b60104b3dbc88b2b74158bac6 # 請使用管理控制檯首頁的APPID或本身建立應用的APPID _appId = settings.SMS["_appId"] # 8a216da863f8e6c20164139688400c21 # 說明:請求地址,生產環境配置成app.cloopen.com _serverIP = settings.SMS["_serverIP"] # 說明:請求端口 ,生產環境爲8883 _serverPort = settings.SMS["_serverPort"] # 說明:REST API版本號保持不變 _softVersion = '2013-12-26' # 雲通信官方提供的發送短信代碼實例 # # 發送模板短信 # # @param to 手機號碼 # # @param datas 內容數據 格式爲數組 例如:{'12','34'},如不需替換請填 '' # # @param $tempId 模板Id # # def sendTemplateSMS(to, datas, tempId): # # 初始化REST SDK # rest = REST(serverIP, serverPort, softVersion) # rest.setAccount(accountSid, accountToken) # rest.setAppId(appId) # # result = rest.sendTemplateSMS(to, datas, tempId) # for k, v in result.iteritems(): # # if k == 'templateSMS': # for k, s in v.iteritems(): # print '%s:%s' % (k, s) # else: # print '%s:%s' % (k, v) class CCP(object): """發送短信的輔助類""" def __new__(cls, *args, **kwargs): # 判斷是否存在類屬性_instance,_instance是類CCP的惟一對象,即單例 if not hasattr(CCP, "_instance"): cls._instance = super(CCP, cls).__new__(cls, *args, **kwargs) cls._instance.rest = REST(_serverIP, _serverPort, _softVersion) cls._instance.rest.setAccount(_accountSid, _accountToken) cls._instance.rest.setAppId(_appId) return cls._instance def send_template_sms(self, to, datas, temp_id): """發送模板短信""" # @param to 手機號碼 # @param datas 內容數據 格式爲數組 例如:{'12','34'},如不需替換請填 '' # @param temp_id 模板Id result = self.rest.sendTemplateSMS(to, datas, temp_id) # 若是雲通信發送短信成功,返回的字典數據result中statuCode字段的值爲"000000" return result.get("statusCode") == "000000" if __name__ == '__main__': ccp = CCP() # 注意: 測試的短信模板編號爲1[之後申請了企業帳號之後能夠有更多的模板] # 參數1: 客戶端手機號碼,測試時只能發給測試號碼 # 參數2: 短信模塊中的數據 # 短信驗證碼 # 短信驗證碼有效期提示 # 參數3: 短信模板的id,開發測試時,只能使用1 ccp.send_template_sms('17610211902', ['1234',"5分鐘"], 1)
配置文件,代碼:
# 雲通信短信 SMS = { # 說明:主帳號,登錄雲通信網站後,可在"控制檯-應用"中看到開發者主帳號ACCOUNT SID "_accountSid":'8a216da863f8e6c20164139687e80c1b', # 說明:主帳號Token,登錄雲通信網站後,可在控制檯-應用中看到開發者主帳號AUTH TOKEN "_accountToken":'4fee1ea5d1024bce948db54b9161c97e', # 請使用管理控制檯首頁的APPID或本身建立應用的APPID "_appId":'8a216da86e011fa3016e4aedeacb2a54', # 說明:請求地址,生產環境配置成app.cloopen.com "_serverIP":'sandboxapp.cloopen.com', # 說明:請求端口 ,生產環境爲8883 "_serverPort": "8883", }
視圖代碼:
from .models import User from rest_framework import status class MobileAPIView(APIView): """手機號驗證接口""" def get(self,request,mobile): """驗證手機號是否已經被註冊了 url: /users/mobile/(?P<mobile>1[3-9]\d{9})/ """ try: # 查詢出來結果有用戶,在表示已經被註冊了 User.objects.get(mobile=mobile) return Response({"status":False,"message":"當前手機號已經被註冊了"},status=status.HTTP_400_BAD_REQUEST) except User.DoesNotExist: return Response({"status":True})
路由代碼:
re_path("mobile/(?P<mobile>1[3-9]\d{9})/", views.MobileAPIView.as_view() ),
視圖代碼:
from rest_framework.views import APIView from rest_framework.response import Response from luffyapi.libs.geetest import GeetestLib from django.conf import settings #.... from luffyapi.libs.yuntongxun.sms import CCP import random from luffyapi.settings import constants class SMSAPIView(APIView): """短信接口API""" def get(self,request,mobile): """發送短信""" # 驗證碼手機號的有效性 try: User.objects.get(mobile=mobile) return Response({"status": False, "message": "當前手機號已經被註冊了"},status=status.HTTP_400_BAD_REQUEST) except User.DoesNotExist: pass from django_redis import get_redis_connection redis_conn = get_redis_connection("sms_code") # 驗證短信的發送時間是否超過了1分鐘,1分鐘內只能發送一次 if redis_conn.get("sms_interval_%s" % mobile): return Response({"status": False, "message": "請勿頻繁發送短信!"},status=status.HTTP_400_BAD_REQUEST) # 使用隨機數生成驗證碼,不足6位數字的,使用字符串格式化,在數字左邊補充"0" sms_code = "%06d" % random.randint(1000, 999999) # 把驗證碼保存到redis pipe = redis_conn.pipeline() # 建立管道對象 pipe.multi() # 開啓事務 pipe.setex("sms_%s" % mobile, constants.SMS_EXPIRE_TIME, sms_code ) pipe.setex("sms_interval_%s" % mobile, constants.SMS_INTERVAL_TIME, "_") pipe.execute() # 執行管道中全部的命令 ccp = CCP() result = ccp.send_template_sms(mobile, [sms_code, constants.SMS_EXPIRE_TIME//60 ], constants.SMS_TEMPLATE_ID) return Response({"status": result})
路由代碼:
re_path("sms/(?P<mobile>1[3-9]\d{9})/", views.SMSAPIView.as_view() ),
配置文件settings/dev.py,代碼:
# 雲通信短信 SMS = { # 說明:主帳號,登錄雲通信網站後,可在"控制檯-應用"中看到開發者主帳號ACCOUNT SID "_accountSid":'8a216da86e011fa3016e4aedea792a4e', # 說明:主帳號Token,登錄雲通信網站後,可在控制檯-應用中看到開發者主帳號AUTH TOKEN "_accountToken":'4fee1ea5d1024bce948db54b9161c97e', # 請使用管理控制檯首頁的APPID或本身建立應用的APPID "_appId":'8a216da86e011fa3016e4aedeacb2a54', # 說明:請求地址,生產環境配置成app.cloopen.com "_serverIP":'sandboxapp.cloopen.com', # 說明:請求端口 ,生產環境爲8883 "_serverPort": "8883", }
配置常量settings/constants.py,代碼:
# 驗證碼有效使用時間[單位: 秒] SMS_EXPIRE_TIME = 5 * 60 # 短信發送的間隔時間 SMS_INTERVAL_TIME = 60 # 短信模板ID SMS_TEMPLATE_ID = 1
視圖代碼:
from rest_framework.generics import CreateAPIView from .serializers import UserModelSerializer class UserAPIView(CreateAPIView): queryset = User.objects.all() serializer_class = UserModelSerializer
序列化器,代碼:
在users下建立一個serializers.py
from rest_framework import serializers from .models import User import re from django_redis import get_redis_connection import logging log = logging.getLogger("django") class UserModelSerializer(serializers.ModelSerializer): sms_code = serializers.CharField(max_length=6, min_length=4, write_only=True, help_text="短信驗證碼") token = serializers.CharField(max_length=1000, read_only=True, help_text="token令牌") class Meta: model = User fields = ["mobile", "password", "sms_code", "token", "id", "username"] extra_kwargs = { "mobile": {"write_only": True, }, "password": {"write_only": True, }, "id": {"read_only": True, }, "username": {"read_only": True, } } def validate(self, attrs): """驗證數據""" # 驗證手機號是否格式正確 mobile = attrs.get("mobile") if not re.match("^1[3-9]\d{9}$", mobile): return serializers.ValidationError("對不起,手機號碼格式不正確!") # 驗證手機號是否被註冊了 try: User.objects.get(mobile=mobile) return serializers.ValidationError("對不起,手機號碼已經被註冊了!") except User.DoesNotExist: pass # 做業: 驗證密碼的長度,必須時6-16位字符 # 驗證手機短信驗證碼是否過時了或者錯誤 sms_code = attrs.get("sms_code") redis_conn = get_redis_connection("sms_code") code_bytes = redis_conn.get("sms_%s" % mobile) if not code_bytes or code_bytes.decode() != sms_code: """若是redis不存在對應手機的短信驗證碼或者提取出來的短信驗證碼雙方不一致都是驗證失敗""" return serializers.ValidationError("對不起,驗證碼過時或錯誤!") # 刪除短信驗證碼的記錄 redis_conn.delete("sms_%s" % mobile) return attrs def create(self, validated_data): """添加用戶數據""" # 移除客戶端提交過來的短信驗證碼的信息 del validated_data["sms_code"] # 對數據進行保存和完成密碼加密 try: validated_data["username"] = validated_data["mobile"] # 添加用戶信息 user = super().create(validated_data=validated_data) # 更新密碼 user.set_password(validated_data.get("password")) user.save() except: log.error("用戶註冊信息保存失敗!") return serializers.ValidationError("對不起,用戶註冊保存信息失敗!請聯繫客服工做人員") # 在添加用戶成功之後,須要保存登陸狀態返回給客戶端[實現註冊免登陸] # 使用restframework_jwt提供的代碼 生成token from rest_framework_jwt.settings import api_settings jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER payload = jwt_payload_handler(user) user.token = jwt_encode_handler(payload) # 把token返回給客戶端 return user
users/urls.py,路由代碼:
path("", views.UserAPIView.as_view() ),
調整前端的頁面,添加一個發送短信功能,
Register/html代碼:
替換原先驗證碼代碼
<div class="sms-box"> <input v-model = "sms" type="text" placeholder="輸入驗證碼" class="user"> <div class="sms-btn" @click="smsHandle">點擊發送短信</div> </div>
css,代碼:
.sms-box{ position: relative; } .sms-btn{ font-size: 14px; color: #ffc210; letter-spacing: .26px; position: absolute; right: 16px; top: 10px; cursor: pointer; overflow: hidden; background: #fff; border-left: 1px solid #484848; padding-left: 16px; padding-bottom: 4px; }
script,代碼:
export default { name: 'Register', data(){ return { sms:"", mobile:"", password:"", interval_text: "點擊發送短信", is_send_interval:false, // 是否在60秒內發送短信的狀態,false表示沒有 } }, created(){ }, methods:{ check_mobile(){ // 驗證格式 if( ! /^1[3-9]\d{9}$/.test(this.mobile) ){ this.$message.error("對不起,手機號格式不正確!"); return ; } // 驗證是否被註冊了 this.$axios.get(`${this.$settings.Host}/user/mobile/${this.mobile}/`).then(response=>{ }).catch(error=>{ if(error.response.status === 400){ this.$message.error("對不起,當前手機已被註冊!"); }else{ this.$message.error("網絡異常,請聯繫網站客服工做人員!"); } }); }, smsHandle(){ // 判斷是否在60內曾經有發送短信的記錄 if(this.is_send_interval){ this.$message.error("請不要頻繁點擊發送短信!"); return ; } // 短信發送處理 this.$axios.get(`${this.$settings.Host}/user/sms/${this.mobile}/`).then(response=>{ if(response.data.status){ this.$message.success("短信已發送,請注意留意您的手機!"); // 更新發送短信的間隔狀態 this.is_send_interval = true; let time = 60; let t = setInterval(()=>{ if(time<1){ this.interval_text = "點擊發送短信"; this.is_send_interval = false; // 容許用戶從新點擊發送短信 clearInterval(t); // 關閉定時器 }else{ time--; this.interval_text = `${time}後點擊從新發送` } },1000); } }).catch(error=>{ this.$message.error("對不起,發送短信失敗!"); }) }, registerHandle(){ // 註冊處理 this.$axios.post(`${this.$settings.Host}/user/`, { mobile:this.mobile, sms_code: this.sms, password: this.password, }).then(response=>{ if(response.status===201){ // 註冊成功 sessionStorage.user_token = response.data.token; sessionStorage.user_id = response.data.id; sessionStorage.user_name = response.data.username; let self = this; this.$alert("註冊成功!","路飛學城",{ callback(){ self.$router.push("/"); } }) }else{ // 註冊失敗 this.$message.error("對不起,註冊用戶失敗!請從新註冊或聯繫網站客服工做人員!"); } }).catch(error=>{ this.$message.error("對不起,註冊用戶失敗!請從新註冊或聯繫網站客服工做人員!"); }) } }, };
<template> <div class="sms-box"> <input v-model = "sms" type="text" placeholder="輸入驗證碼" class="user"> <div class="sms-btn" @click="smsHandle">{{interval_text}}</div> </div> </template> <script> export default { name: 'Register', data(){ return { sms:"", mobile:"", password:"", interval_text: "點擊發送短信", is_send_interval:false, // 是否在60秒內發送短信的狀態,false表示沒有 } }, created(){ }, methods:{ check_mobile(){ // 驗證格式 if( ! /^1[3-9]\d{9}$/.test(this.mobile) ){ this.$message.error("對不起,手機號格式不正確!"); return ; } // 驗證是否被註冊了 this.$axios.get(`${this.$settings.Host}/user/mobile/${this.mobile}/`).then(response=>{ }).catch(error=>{ if(error.response.status === 400){ this.$message.error("對不起,當前手機已被註冊!"); }else{ this.$message.error("網絡異常,請聯繫網站客服工做人員!"); } }); }, smsHandle(){ // 判斷是否在60內曾經有發送短信的記錄 if(this.is_send_interval){ this.$message.error("請不要頻繁點擊發送短信!"); return ; } // 短信發送處理 this.$axios.get(`${this.$settings.Host}/user/sms/${this.mobile}/`).then(response=>{ if(response.data.status){ this.$message.success("短信已發送,請注意留意您的手機!"); // 更新發送短信的間隔狀態 this.is_send_interval = true; let time = 60; let t = setInterval(()=>{ if(time<1){ this.interval_text = "點擊發送短信"; this.is_send_interval = false; // 容許用戶從新點擊發送短信 clearInterval(t); // 關閉定時器 }else{ time--; this.interval_text = `${time}後點擊從新發送` } },1000); } }).catch(error=>{ this.$message.error("對不起,發送短信失敗!"); }) }, registerHandle(){ // 註冊處理 this.$axios.post(`${this.$settings.Host}/user/`, { mobile:this.mobile, sms_code: this.sms, password: this.password, }).then(response=>{ if(response.status===201){ // 註冊成功 sessionStorage.user_token = response.data.token; sessionStorage.user_id = response.data.id; sessionStorage.user_name = response.data.username; let self = this; this.$alert("註冊成功!","路飛學城",{ callback(){ self.$router.push("/"); } }) }else{ // 註冊失敗 this.$message.error("對不起,註冊用戶失敗!請從新註冊或聯繫網站客服工做人員!"); } }).catch(error=>{ this.$message.error("對不起,註冊用戶失敗!請從新註冊或聯繫網站客服工做人員!"); }) } }, }; </script>
在main.py主程序中對django的配置文件進行加載
pip install -U celery
建立
luffyapi/ ├── my_celery/ ├── config.py # 配置文件 ├── __init__.py ├── main.py # 主程序 └── sms/ # 一個目錄能夠放置多個任務,該目錄下存放當前任務執行時須要的模塊或依賴 └── tasks.py # 任務的文件,名稱必須是這個!!!
main.py,代碼:
# 主程序 from celery import Celery # 建立celery實例對象 app = Celery("luffy") # 把celery和django進行組合,識別和加載django的配置文件,這句話從wsgi,複製過來 import os os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffyapi.settings.dev') # 對django框架執行初始化 import django django.setup() # 經過app對象加載配置 app.config_from_object("my_celery.config") # 自動搜索並加載任務 # 參數必須必須是一個列表,裏面的每個任務都是任務的路徑名稱 # app.autodiscover_tasks(["任務1","任務2",....]) app.autodiscover_tasks(["my_celery.sms","my_celery.cache"])
config.py
# 任務隊列的連接地址 broker_url = 'redis://127.0.0.1:6379/15' # 結果隊列的連接地址 result_backend = 'redis://127.0.0.1:6379/14'
sms/tasks.py
from my_celery.main import app from .yuntongxun.sms import CCP from luffyapi.settings import constants import logging log = logging.getLogger("django") @app.task(name="send_sms") def send_sms(mobile,sms_code): """異步發送短信""" ccp = CCP() try: result = ccp.send_template_sms(mobile, [sms_code, constants.SMS_EXPIRE_TIME//60 ], constants.SMS_TEMPLATE_ID) return result except: log.error("發送短信驗證碼失敗!手機號:%s"% mobile)
啓動
# 啓動Celery的命令 # 強烈建議切換目錄到項目的根目錄下啓動celery!! # celery -A my_celery.main worker --loglevel=info
在users/views下
from rest_framework.views import APIView # Create your views here. class CaptchaAPIView(APIView): """驗證碼接口""" def get(self,request): """提供生成驗證碼流水號""" user_id = 'test' gt = GeetestLib(settings.GEETEST["pc_geetest_id"], settings.GEETEST["pc_geetest_key"]) status = gt.pre_process(user_id) # request.session[gt.GT_STATUS_SESSION_KEY] = status # request.session["user_id"] = user_id response_str = gt.get_response_str() return Response(response_str) def post(self,request): """二次驗證""" gt = GeetestLib(settings.GEETEST["pc_geetest_id"], settings.GEETEST["pc_geetest_key"]) challenge = request.data.get(gt.FN_CHALLENGE, '') validate = request.data.get(gt.FN_VALIDATE, '') seccode = request.data.get(gt.FN_SECCODE, '') # status = request.session[gt.GT_STATUS_SESSION_KEY] # user_id = request.session["user_id"] # if status: # result = gt.success_validate(challenge, validate, seccode, user_id) # else: result = gt.failback_validate(challenge, validate, seccode) result = {"status": "success"} if result else {"status": "fail"} # if result: # result = {"status": "success"} # else: # result = {"status": "fail"} return Response(result) from .models import User from rest_framework import status class MobileAPIView(APIView): """手機號驗證接口""" def get(self,request,mobile): """驗證手機號是否已經被註冊了 url: /users/mobile/(?P<mobile>1[3-9]\d{9})/ """ try: # 查詢出來結果有用戶,在表示已經被註冊了 User.objects.get(mobile=mobile) return Response({"status":False,"message":"當前手機號已經被註冊了"},status=status.HTTP_400_BAD_REQUEST) except User.DoesNotExist: return Response({"status":True}) from rest_framework.views import APIView from rest_framework.response import Response from luffyapi.libs.geetest import GeetestLib from django.conf import settings #.... import random from luffyapi.settings import constants class SMSAPIView(APIView): """短信接口API""" def get(self,request,mobile): """發送短信""" # 驗證碼手機號的有效性 try: User.objects.get(mobile=mobile) return Response({"status": False, "message": "當前手機號已經被註冊了"},status=status.HTTP_400_BAD_REQUEST) except User.DoesNotExist: pass from django_redis import get_redis_connection redis_conn = get_redis_connection("sms_code") # 驗證短信的發送時間是否超過了1分鐘,1分鐘內只能發送一次 if redis_conn.get("sms_interval_%s" % mobile): return Response({"status": False, "message": "請勿頻繁發送短信!"},status=status.HTTP_400_BAD_REQUEST) # 使用隨機數生成驗證碼,不足6位數字的,使用字符串格式化,在數字左邊補充"0" sms_code = "%06d" % random.randint(1000, 999999) # 把驗證碼保存到redis pipe = redis_conn.pipeline() # 建立管道對象 pipe.multi() # 開啓事務 pipe.setex("sms_%s" % mobile, constants.SMS_EXPIRE_TIME, sms_code ) pipe.setex("sms_interval_%s" % mobile, constants.SMS_INTERVAL_TIME, "_") pipe.execute() # 執行管道中全部的命令 # ccp = CCP() # result = ccp.send_template_sms(mobile, [sms_code, constants.SMS_EXPIRE_TIME//60 ], constants.SMS_TEMPLATE_ID) # 調用celery執行異步任務 from my_celery.sms.tasks import send_sms send_sms.delay(mobile,sms_code) return Response({"status": True}) from rest_framework.generics import CreateAPIView from .serializers import UserModelSerializer class UserAPIView(CreateAPIView): queryset = User.objects.all() serializer_class = UserModelSerializer
變更代碼
from my_celery.sms.tasks import send_sms send_sms.delay(mobile,sms_code) return Response({"status": True})
而後把課程列表頁面組件Course.vue放到前端項目中.
<template> <div class="course"> <Header></Header> <div class="main"> <!-- 篩選條件 --> <div class="condition"> <ul class="cate-list"> <li class="title">課程分類:</li> <li class="this">所有</li> <li>Python</li> <li>Linux運維</li> <li>Python進階</li> <li>開發工具</li> <li>Go語言</li> <li>機器學習</li> <li>技術生涯</li> </ul> <div class="ordering"> <ul> <li class="title">篩 選: </li> <li class="default this">默認</li> <li class="hot this">人氣</li> <li class="price this">價格</li> </ul> <p class="condition-result">共21個課程</p> </div> </div> <!-- 課程列表 --> <div class="course-list"> <div class="course-item"> <div class="course-image"> <img src="/static/image/course-cover.jpeg" alt=""> </div> <div class="course-info"> <h3><router-link to="/course/detail/1">Python開發21天入門</router-link> <span><img src="/static/image/avatar1.svg" alt="">100人已加入學習</span></h3> <p class="teather-info">Alex 金角大王 老男孩Python教學總監 <span>共154課時/更新完成</span></p> <ul class="lesson-list"> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼初識編碼初識編碼</span> <span class="free">免費</span></li> </ul> <div class="pay-box"> <span class="discount-type">限時免費</span> <span class="discount-price">¥0.00元</span> <span class="original-price">原價:9.00元</span> <span class="buy-now">當即購買</span> </div> </div> </div> <div class="course-item"> <div class="course-image"> <img src="/static/image/course-cover.jpeg" alt=""> </div> <div class="course-info"> <h3>Python開發21天入門 <span><img src="/static/image/avatar1.svg" alt="">100人已加入學習</span></h3> <p class="teather-info">Alex 金角大王 老男孩Python教學總監 <span>共154課時/更新完成</span></p> <ul class="lesson-list"> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼初識編碼初識編碼</span> <span class="free">免費</span></li> </ul> <div class="pay-box"> <span class="discount-type">限時免費</span> <span class="discount-price">¥0.00元</span> <span class="original-price">原價:9.00元</span> <span class="buy-now">當即購買</span> </div> </div> </div> <div class="course-item"> <div class="course-image"> <img src="/static/image/course-cover.jpeg" alt=""> </div> <div class="course-info"> <h3>Python開發21天入門 <span><img src="/static/image/avatar1.svg" alt="">100人已加入學習</span></h3> <p class="teather-info">Alex 金角大王 老男孩Python教學總監 <span>共154課時/更新完成</span></p> <ul class="lesson-list"> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼初識編碼初識編碼</span> <span class="free">免費</span></li> </ul> <div class="pay-box"> <span class="discount-type">限時免費</span> <span class="discount-price">¥0.00元</span> <span class="original-price">原價:9.00元</span> <span class="buy-now">當即購買</span> </div> </div> </div> <div class="course-item"> <div class="course-image"> <img src="/static/image/course-cover.jpeg" alt=""> </div> <div class="course-info"> <h3>Python開發21天入門 <span><img src="/static/image/avatar1.svg" alt="">100人已加入學習</span></h3> <p class="teather-info">Alex 金角大王 老男孩Python教學總監 <span>共154課時/更新完成</span></p> <ul class="lesson-list"> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼初識編碼初識編碼</span> <span class="free">免費</span></li> </ul> <div class="pay-box"> <span class="discount-type">限時免費</span> <span class="discount-price">¥0.00元</span> <span class="original-price">原價:9.00元</span> <span class="buy-now">當即購買</span> </div> </div> </div> </div> </div> <Footer></Footer> </div> </template> <script> import Header from "./common/Header" import Footer from "./common/Footer" export default { name: "Course", data(){ return{ category:0, } }, components:{ Header, Footer, } } </script> <style scoped> .course{ background: #f6f6f6; } .course .main{ width: 1100px; margin: 35px auto 0; } .course .condition{ margin-bottom: 35px; padding: 25px 30px 25px 20px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px 0 #f0f0f0; } .course .cate-list{ border-bottom: 1px solid #333; border-bottom-color: rgba(51,51,51,.05); padding-bottom: 18px; margin-bottom: 17px; } .course .cate-list::after{ content:""; display: block; clear: both; } .course .cate-list li{ float: left; font-size: 16px; padding: 6px 15px; line-height: 16px; margin-left: 14px; position: relative; transition: all .3s ease; cursor: pointer; color: #4a4a4a; border: 1px solid transparent; /* transparent 透明 */ } .course .cate-list .title{ color: #888; margin-left: 0; letter-spacing: .36px; padding: 0; line-height: 28px; } .course .cate-list .this{ color: #ffc210; border: 1px solid #ffc210!important; border-radius: 30px; } .course .ordering::after{ content:""; display: block; clear: both; } .course .ordering ul{ float: left; } .course .ordering ul::after{ content:""; display: block; clear: both; } .course .ordering .condition-result{ float: right; font-size: 14px; color: #9b9b9b; line-height: 28px; } .course .ordering ul li{ float: left; padding: 6px 15px; line-height: 16px; margin-left: 14px; position: relative; transition: all .3s ease; cursor: pointer; color: #4a4a4a; } .course .ordering .title{ font-size: 16px; color: #888; letter-spacing: .36px; margin-left: 0; padding:0; line-height: 28px; } .course .ordering .this{ color: #ffc210; } .course .ordering .price{ position: relative; } .course .ordering .price::before, .course .ordering .price::after{ cursor: pointer; content:""; display: block; width: 0px; height: 0px; border: 5px solid transparent; position: absolute; right: 0; } .course .ordering .price::before{ border-bottom: 5px solid #aaa; margin-bottom: 2px; top: 2px; } .course .ordering .price::after{ border-top: 5px solid #aaa; bottom: 2px; } .course .course-item:hover{ box-shadow: 4px 6px 16px rgba(0,0,0,.5); } .course .course-item{ width: 1050px; background: #fff; padding: 20px 30px 20px 20px; margin-bottom: 35px; border-radius: 2px; cursor: pointer; box-shadow: 2px 3px 16px rgba(0,0,0,.1); /* css3.0 過渡動畫 hover 事件操做 */ transition: all .2s ease; } .course .course-item::after{ content:""; display: block; clear: both; } /* 頂級元素 父級元素 當前元素{} */ .course .course-item .course-image{ float: left; width: 423px; height: 210px; margin-right: 30px; } .course .course-item .course-image img{ width: 100%; } .course .course-item .course-info{ float: left; width: 596px; } .course-item .course-info h3 { font-size: 26px; color: #333; font-weight: normal; margin-bottom: 8px; } .course-item .course-info h3 span{ font-size: 14px; color: #9b9b9b; float: right; margin-top: 14px; } .course-item .course-info h3 span img{ width: 11px; height: auto; margin-right: 7px; } .course-item .course-info .teather-info{ font-size: 14px; color: #9b9b9b; margin-bottom: 14px; padding-bottom: 14px; border-bottom: 1px solid #333; border-bottom-color: rgba(51,51,51,.05); } .course-item .course-info .teather-info span{ float: right; } .course-item .lesson-list::after{ content:""; display: block; clear: both; } .course-item .lesson-list li { float: left; width: 44%; font-size: 14px; color: #666; padding-left: 22px; /* background: url("路徑") 是否平鋪 x軸位置 y軸位置 */ background: url("/static/image/play-icon-gray.svg") no-repeat left 4px; margin-bottom: 15px; } .course-item .lesson-list li .lesson-title{ /* 如下3句,文本內容過多,會自動隱藏,並顯示省略符號 */ text-overflow: ellipsis; overflow: hidden; white-space: nowrap; display:inline-block; max-width: 200px; } .course-item .lesson-list li:hover{ background-image: url("/static/image/play-icon-yellow.svg"); color: #ffc210; } .course-item .lesson-list li .free{ width: 34px; height: 20px; color: #fd7b4d; vertical-align: super; margin-left: 10px; border: 1px solid #fd7b4d; border-radius: 2px; text-align: center; font-size: 13px; white-space: nowrap; } .course-item .lesson-list li:hover .free{ color: #ffc210; border-color: #ffc210; } .course-item .pay-box::after{ content:""; display: block; clear: both; } .course-item .pay-box .discount-type{ padding: 6px 10px; font-size: 16px; color: #fff; text-align: center; margin-right: 8px; background: #fa6240; border: 1px solid #fa6240; border-radius: 10px 0 10px 0; float: left; } .course-item .pay-box .discount-price{ font-size: 24px; color: #fa6240; float: left; } .course-item .pay-box .original-price{ text-decoration: line-through; font-size: 14px; color: #9b9b9b; margin-left: 10px; float: left; margin-top: 10px; } .course-item .pay-box .buy-now{ width: 120px; height: 38px; background: transparent; color: #fa6240; font-size: 16px; border: 1px solid #fd7b4d; border-radius: 3px; transition: all .2s ease-in-out; float: right; text-align: center; line-height: 38px; } .course-item .pay-box .buy-now:hover{ color: #fff; background: #ffc210; border: 1px solid #ffc210; } </style>
import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) // @ 表示src目錄 import Home from "@/components/Home" import Login from "@/components/Login" import Register from "@/components/Register" import Course from "@/components/Course" export default new Router({ mode:"history", routes: [ // 。。。 { path: '/courses', name: 'Courses', component: Course, } ] })
課程分類:
課程信息:
課程章節:
課時信息:
老師信息:
價格策略:(限時免費\限時折扣\限時滿減\原價\優惠券)
在apps下
python ../../manage.py startapp courses
INSTALLED_APPS = [ ... 'courses', ]
from django.db import models # Create your models here. from django.db import models from luffyapi.utils.models import BaseModel # Create your models here. class CourseCategory(BaseModel): """ 課程分類 """ name = models.CharField(max_length=64, unique=True, verbose_name="分類名稱") class Meta: db_table = "ly_course_category" verbose_name = "課程分類" verbose_name_plural = "課程分類" def __str__(self): return "%s" % self.name class Course(BaseModel): """ 實戰課程 """ course_type = ( (0, '付費'), (1, 'VIP專享'), (2, '學位課程'), ) level_choices = ( (0, '初級'), (1, '中級'), (2, '高級'), ) status_choices = ( (0, '上線'), (1, '下線'), (2, '預上線'), ) name = models.CharField(max_length=128, verbose_name="課程名稱") course_img = models.ImageField(upload_to="course", max_length=255, verbose_name="封面圖片", blank=True, null=True) course_video = models.FileField(upload_to="course_video", max_length=255, verbose_name="封面視頻", blank=True, null=True) course_type = models.SmallIntegerField(choices=course_type, default=0, verbose_name="付費類型") # 使用這個字段的緣由 brief = models.TextField(verbose_name="詳情介紹", null=True, blank=True) level = models.SmallIntegerField(choices=level_choices, default=1, verbose_name="難度等級") pub_date = models.DateField(verbose_name="發佈日期", auto_now_add=True) period = models.IntegerField(verbose_name="建議學習週期(day)", default=7) attachment_path = models.FileField(max_length=128, verbose_name="課件路徑", blank=True, null=True) status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="課程狀態") course_category = models.ForeignKey("CourseCategory", on_delete=models.CASCADE, null=True, blank=True, verbose_name="課程分類") students = models.IntegerField(verbose_name="學習人數", default=0) lessons = models.IntegerField(verbose_name="總課時數量", default=0) pub_lessons = models.IntegerField(verbose_name="課時更新數量", default=0) price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="課程原價", default=0) teacher = models.ForeignKey("Teacher", on_delete=models.DO_NOTHING, null=True, blank=True, verbose_name="授課老師") class Meta: db_table = "ly_course" verbose_name = "實戰課程" verbose_name_plural = "實戰課程" def __str__(self): return "%s" % self.name class Teacher(BaseModel): """講師、導師表""" role_choices = ( (0, '講師'), (1, '導師'), (2, '班主任'), ) name = models.CharField(max_length=32, verbose_name="講師暱稱") role = models.SmallIntegerField(choices=role_choices, default=0, verbose_name="講師身份") title = models.CharField(max_length=64, verbose_name="職位、職稱") signature = models.CharField(max_length=255, verbose_name="導師簽名", help_text="導師簽名", blank=True, null=True) image = models.ImageField(upload_to="teacher",blank=True, null=True, verbose_name="講師封面") brief = models.TextField(max_length=1024, verbose_name="講師描述") class Meta: db_table = "ly_teacher" verbose_name = "講師導師" verbose_name_plural = "講師導師" def __str__(self): return "%s" % self.name class CourseChapter(BaseModel): """課程章節""" course = models.ForeignKey("Course", related_name='chapters', on_delete=models.CASCADE, verbose_name="課程名稱") chapter = models.SmallIntegerField(verbose_name="第幾章", default=1) name = models.CharField(max_length=128, verbose_name="章節標題") summary = models.TextField(verbose_name="章節介紹", blank=True, null=True) pub_date = models.DateField(verbose_name="發佈日期", auto_now_add=True) class Meta: db_table = "ly_course_chapter" verbose_name = "課程章節" verbose_name_plural = "課程章節" def __str__(self): return "%s:(第%s章)%s" % (self.course, self.chapter, self.name) class CourseLesson(BaseModel): """課程課時""" lesson_type_choices = ( (0, '文檔'), (1, '練習'), (2, '視頻') ) chapter = models.ForeignKey("CourseChapter", related_name='lessons', on_delete=models.CASCADE, verbose_name="章節") course = models.ForeignKey("Course", related_name="lesson_list", on_delete=models.CASCADE, verbose_name="課程") name = models.CharField(max_length=128, verbose_name="課時標題") lesson_type = models.SmallIntegerField(default=2, choices=lesson_type_choices, verbose_name="課時種類") lesson_link = models.CharField(max_length=255, blank=True, null=True, verbose_name="課時連接", help_text="如果video,填vid,如果文檔,填link") duration = models.CharField(verbose_name="視頻時長", blank=True, null=True, max_length=32) # 僅在前端展現使用 pub_date = models.DateTimeField(verbose_name="發佈時間", auto_now_add=True) free_trail = models.BooleanField(verbose_name="是否可試看", default=False) recomment = models.BooleanField(verbose_name="是否推薦到課程列表") class Meta: db_table = "ly_course_lesson" verbose_name = "課程課時" verbose_name_plural = "課程課時" def __str__(self): return "%s-%s" % (self.chapter, self.name)
python manage.py makemigrations
python manage.py migrate
把當前新增的課程模型註冊到xadmin裏面.
coursers/adminx.py,代碼:
import xadmin from .models import CourseCategory class CourseCategoryModelAdmin(object): """課程分類模型管理類""" pass xadmin.site.register(CourseCategory, CourseCategoryModelAdmin) from .models import Course class CourseModelAdmin(object): """課程模型管理類""" pass xadmin.site.register(Course, CourseModelAdmin) from .models import Teacher class TeacherModelAdmin(object): """老師模型管理類""" pass xadmin.site.register(Teacher, TeacherModelAdmin) from .models import CourseChapter class CourseChapterModelAdmin(object): """課程章節模型管理類""" pass xadmin.site.register(CourseChapter, CourseChapterModelAdmin) from .models import CourseLesson class CourseLessonModelAdmin(object): """課程課時模型管理類""" pass xadmin.site.register(CourseLesson, CourseLessonModelAdmin)
能夠採用第三方模塊Faker來完成數據的模擬添加
可使用python腳本或者shell腳原本完成
這裏咱們學習第三種方法.那麼,咱們就要學習怎麼編寫一個python終端腳本或者shell腳本。
要編寫一個python或者shell腳本,就要清楚一件事情,就是咱們能夠根據對應的語言來編寫對應的終端代碼,可是必須在首行的位置聲明執行這些代碼的解析器是誰?
#! /usr/bin/python3 # 註釋,後續的代碼必需要符合python的語法 print("hello world")
#! /bin/bash # 註釋,後續的代碼必需要符合shell的語法,或者能夠填寫linux命令,例如,chmod,cd, copy
編寫完成腳本之後,腳本自己由於操做系統默認會取消它的執行權限,因此咱們經過如下命令來增長執行的權限
chmod +x 對應的文件名 # 例如:有一個python腳本在桌面,咱們須要賦予執行的權限。文件名假設爲:demo.py cd ~/Desktop chmod +x demo.py # 例如:有一個shell腳本在桌面,咱們須要賦予執行的權限,文件名假設爲:demo.sh cd ~/Desktop chmod +x demo.sh
賦予了權限之後,就能夠執行腳本了。
# 例如,上面的demo.py cd ~/Desktop ./demo.py
1. 編寫一個sql語句的文件test_data.sql
# 關閉原來表中的主外鍵約束功能 set FOREIGN_KEY_CHECKS=0; # 清空原有的課程分類表信息 truncate table ly_course_category; # 添加導航信息 insert into ly_course_category (name,orders,is_show,is_delete,create_time,update_time) VALUES ("python",1,1,0,"2019-11-11 12:00:00","2019-11-11 12:00:00"), ("python進階",2,1,0,"2019-11-11 12:00:00","2019-11-11 12:00:00"), ("java",3,1,0,"2019-11-11 12:00:00","2019-11-11 12:00:00"), ("前端開發",4,1,0,"2019-11-11 12:00:00","2019-11-11 12:00:00"); # 開啓原來表中的主外鍵約束功能 set FOREIGN_KEY_CHECKS=1;
2. 編寫一個shell腳本test_data.sh來執行上面的文件
#! /bin/bash mysql -uroot -p456 luffy < ./test_data.sql
3. 賦予create_data.sh執行的權限
chmod +x test_data.sh
from rest_framework import serializers from .models import CourseCategory class CourseCategoryModelSerializer(serializers.ModelSerializer): class Meta: model = CourseCategory fields = ["id","name"]
courses/views.py
from rest_framework.generics import ListAPIView from .models import CourseCategory from .serializers import CourseCategoryModelSerializer class CourseCategoryListAPIView(ListAPIView): queryset = CourseCategory.objects.filter(is_show=True, is_delete=False).order_by("orders","-id") serializer_class = CourseCategoryModelSerializer # 設置不分頁,提早寫這段代碼的做用就是防止未來若是進行全局分頁,能夠不被它干擾到 pagination_class = None
# 子應用路由 from django.urls import path from . import views urlpatterns = [ path("category/",views.CourseCategoryListAPIView.as_view()), ] # 總路由 path('courses/', include("courses.urls") ),
course
<template> <div class="course"> <Header></Header> <div class="main"> <!-- 篩選條件 --> <div class="condition"> <ul class="cate-list"> <li class="title">課程分類:</li> <li class="this">所有</li> <li v-for="category in category_list">{{category.name}}</li> </ul> <div class="ordering"> <ul> <li class="title">篩 選: </li> <li class="default this">默認</li> <li class="hot this">人氣</li> <li class="price this">價格</li> </ul> <p class="condition-result">共21個課程</p> </div> </div> <!-- 課程列表 --> <div class="course-list"> <div class="course-item"> <div class="course-image"> <img src="/static/image/course-cover.jpeg" alt=""> </div> <div class="course-info"> <h3><router-link to="/course/detail/1">Python開發21天入門</router-link> <span><img src="/static/image/avatar1.svg" alt="">100人已加入學習</span></h3> <p class="teather-info">Alex 金角大王 老男孩Python教學總監 <span>共154課時/更新完成</span></p> <ul class="lesson-list"> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼初識編碼初識編碼</span> <span class="free">免費</span></li> </ul> <div class="pay-box"> <span class="discount-type">限時免費</span> <span class="discount-price">¥0.00元</span> <span class="original-price">原價:9.00元</span> <span class="buy-now">當即購買</span> </div> </div> </div> <div class="course-item"> <div class="course-image"> <img src="/static/image/course-cover.jpeg" alt=""> </div> <div class="course-info"> <h3>Python開發21天入門 <span><img src="/static/image/avatar1.svg" alt="">100人已加入學習</span></h3> <p class="teather-info">Alex 金角大王 老男孩Python教學總監 <span>共154課時/更新完成</span></p> <ul class="lesson-list"> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼初識編碼初識編碼</span> <span class="free">免費</span></li> </ul> <div class="pay-box"> <span class="discount-type">限時免費</span> <span class="discount-price">¥0.00元</span> <span class="original-price">原價:9.00元</span> <span class="buy-now">當即購買</span> </div> </div> </div> <div class="course-item"> <div class="course-image"> <img src="/static/image/course-cover.jpeg" alt=""> </div> <div class="course-info"> <h3>Python開發21天入門 <span><img src="/static/image/avatar1.svg" alt="">100人已加入學習</span></h3> <p class="teather-info">Alex 金角大王 老男孩Python教學總監 <span>共154課時/更新完成</span></p> <ul class="lesson-list"> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼初識編碼初識編碼</span> <span class="free">免費</span></li> </ul> <div class="pay-box"> <span class="discount-type">限時免費</span> <span class="discount-price">¥0.00元</span> <span class="original-price">原價:9.00元</span> <span class="buy-now">當即購買</span> </div> </div> </div> <div class="course-item"> <div class="course-image"> <img src="/static/image/course-cover.jpeg" alt=""> </div> <div class="course-info"> <h3>Python開發21天入門 <span><img src="/static/image/avatar1.svg" alt="">100人已加入學習</span></h3> <p class="teather-info">Alex 金角大王 老男孩Python教學總監 <span>共154課時/更新完成</span></p> <ul class="lesson-list"> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼初識編碼初識編碼</span> <span class="free">免費</span></li> </ul> <div class="pay-box"> <span class="discount-type">限時免費</span> <span class="discount-price">¥0.00元</span> <span class="original-price">原價:9.00元</span> <span class="buy-now">當即購買</span> </div> </div> </div> </div> </div> <Footer></Footer> </div> </template> <script> import Header from "./common/Header" import Footer from "./common/Footer" export default { name: "Course", data(){ return{ category:0, category_list: [], } }, created(){ this.get_course_category(); }, methods:{ get_course_category(){ // 獲取課程分類 this.$axios.get(`${this.$settings.Host}/courses/category/`).then(response=>{ this.category_list = response.data; }).catch(error=>{ this.$message.error("沒法獲取課程分類信息!"); }) } }, components:{ Header, Footer, } } </script> <style scoped> .course{ background: #f6f6f6; } .course .main{ width: 1100px; margin: 35px auto 0; } .course .condition{ margin-bottom: 35px; padding: 25px 30px 25px 20px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px 0 #f0f0f0; } .course .cate-list{ border-bottom: 1px solid #333; border-bottom-color: rgba(51,51,51,.05); padding-bottom: 18px; margin-bottom: 17px; } .course .cate-list::after{ content:""; display: block; clear: both; } .course .cate-list li{ float: left; font-size: 16px; padding: 6px 15px; line-height: 16px; margin-left: 14px; position: relative; transition: all .3s ease; cursor: pointer; color: #4a4a4a; border: 1px solid transparent; /* transparent 透明 */ } .course .cate-list .title{ color: #888; margin-left: 0; letter-spacing: .36px; padding: 0; line-height: 28px; } .course .cate-list .this{ color: #ffc210; border: 1px solid #ffc210!important; border-radius: 30px; } .course .ordering::after{ content:""; display: block; clear: both; } .course .ordering ul{ float: left; } .course .ordering ul::after{ content:""; display: block; clear: both; } .course .ordering .condition-result{ float: right; font-size: 14px; color: #9b9b9b; line-height: 28px; } .course .ordering ul li{ float: left; padding: 6px 15px; line-height: 16px; margin-left: 14px; position: relative; transition: all .3s ease; cursor: pointer; color: #4a4a4a; } .course .ordering .title{ font-size: 16px; color: #888; letter-spacing: .36px; margin-left: 0; padding:0; line-height: 28px; } .course .ordering .this{ color: #ffc210; } .course .ordering .price{ position: relative; } .course .ordering .price::before, .course .ordering .price::after{ cursor: pointer; content:""; display: block; width: 0px; height: 0px; border: 5px solid transparent; position: absolute; right: 0; } .course .ordering .price::before{ border-bottom: 5px solid #aaa; margin-bottom: 2px; top: 2px; } .course .ordering .price::after{ border-top: 5px solid #aaa; bottom: 2px; } .course .course-item:hover{ box-shadow: 4px 6px 16px rgba(0,0,0,.5); } .course .course-item{ width: 1050px; background: #fff; padding: 20px 30px 20px 20px; margin-bottom: 35px; border-radius: 2px; cursor: pointer; box-shadow: 2px 3px 16px rgba(0,0,0,.1); /* css3.0 過渡動畫 hover 事件操做 */ transition: all .2s ease; } .course .course-item::after{ content:""; display: block; clear: both; } /* 頂級元素 父級元素 當前元素{} */ .course .course-item .course-image{ float: left; width: 423px; height: 210px; margin-right: 30px; } .course .course-item .course-image img{ width: 100%; } .course .course-item .course-info{ float: left; width: 596px; } .course-item .course-info h3 { font-size: 26px; color: #333; font-weight: normal; margin-bottom: 8px; } .course-item .course-info h3 span{ font-size: 14px; color: #9b9b9b; float: right; margin-top: 14px; } .course-item .course-info h3 span img{ width: 11px; height: auto; margin-right: 7px; } .course-item .course-info .teather-info{ font-size: 14px; color: #9b9b9b; margin-bottom: 14px; padding-bottom: 14px; border-bottom: 1px solid #333; border-bottom-color: rgba(51,51,51,.05); } .course-item .course-info .teather-info span{ float: right; } .course-item .lesson-list::after{ content:""; display: block; clear: both; } .course-item .lesson-list li { float: left; width: 44%; font-size: 14px; color: #666; padding-left: 22px; /* background: url("路徑") 是否平鋪 x軸位置 y軸位置 */ background: url("/static/image/play-icon-gray.svg") no-repeat left 4px; margin-bottom: 15px; } .course-item .lesson-list li .lesson-title{ /* 如下3句,文本內容過多,會自動隱藏,並顯示省略符號 */ text-overflow: ellipsis; overflow: hidden; white-space: nowrap; display:inline-block; max-width: 200px; } .course-item .lesson-list li:hover{ background-image: url("/static/image/play-icon-yellow.svg"); color: #ffc210; } .course-item .lesson-list li .free{ width: 34px; height: 20px; color: #fd7b4d; vertical-align: super; margin-left: 10px; border: 1px solid #fd7b4d; border-radius: 2px; text-align: center; font-size: 13px; white-space: nowrap; } .course-item .lesson-list li:hover .free{ color: #ffc210; border-color: #ffc210; } .course-item .pay-box::after{ content:""; display: block; clear: both; } .course-item .pay-box .discount-type{ padding: 6px 10px; font-size: 16px; color: #fff; text-align: center; margin-right: 8px; background: #fa6240; border: 1px solid #fa6240; border-radius: 10px 0 10px 0; float: left; } .course-item .pay-box .discount-price{ font-size: 24px; color: #fa6240; float: left; } .course-item .pay-box .original-price{ text-decoration: line-through; font-size: 14px; color: #9b9b9b; margin-left: 10px; float: left; margin-top: 10px; } .course-item .pay-box .buy-now{ width: 120px; height: 38px; background: transparent; color: #fa6240; font-size: 16px; border: 1px solid #fd7b4d; border-radius: 3px; transition: all .2s ease-in-out; float: right; text-align: center; line-height: 38px; } .course-item .pay-box .buy-now:hover{ color: #fff; background: #ffc210; border: 1px solid #ffc210; } </style>
<template> <div class="course"> <Header></Header> <div class="main"> <!-- 篩選條件 --> <div class="condition"> <ul class="cate-list"> <li class="title">課程分類:</li> <li :class="category==0?'this':''" @click="category=0">所有</li> <li :class="category==item.id?'this':''" v-for="item in category_list" @click="category=item.id">{{item.name}}</li> </ul> <div class="ordering"> <ul> <li class="title">篩 選: </li> <li class="default this">默認</li> <li class="hot this">人氣</li> <li class="price this">價格</li> </ul> <p class="condition-result">共21個課程</p> </div> </div> <!-- 課程列表 --> <div class="course-list"> <div class="course-item"> <div class="course-image"> <img src="/static/image/course-cover.jpeg" alt=""> </div> <div class="course-info"> <h3><router-link to="/course/detail/1">Python開發21天入門</router-link> <span><img src="/static/image/avatar1.svg" alt="">100人已加入學習</span></h3> <p class="teather-info">Alex 金角大王 老男孩Python教學總監 <span>共154課時/更新完成</span></p> <ul class="lesson-list"> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼初識編碼初識編碼</span> <span class="free">免費</span></li> </ul> <div class="pay-box"> <span class="discount-type">限時免費</span> <span class="discount-price">¥0.00元</span> <span class="original-price">原價:9.00元</span> <span class="buy-now">當即購買</span> </div> </div> </div> <div class="course-item"> <div class="course-image"> <img src="/static/image/course-cover.jpeg" alt=""> </div> <div class="course-info"> <h3>Python開發21天入門 <span><img src="/static/image/avatar1.svg" alt="">100人已加入學習</span></h3> <p class="teather-info">Alex 金角大王 老男孩Python教學總監 <span>共154課時/更新完成</span></p> <ul class="lesson-list"> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼初識編碼初識編碼</span> <span class="free">免費</span></li> </ul> <div class="pay-box"> <span class="discount-type">限時免費</span> <span class="discount-price">¥0.00元</span> <span class="original-price">原價:9.00元</span> <span class="buy-now">當即購買</span> </div> </div> </div> <div class="course-item"> <div class="course-image"> <img src="/static/image/course-cover.jpeg" alt=""> </div> <div class="course-info"> <h3>Python開發21天入門 <span><img src="/static/image/avatar1.svg" alt="">100人已加入學習</span></h3> <p class="teather-info">Alex 金角大王 老男孩Python教學總監 <span>共154課時/更新完成</span></p> <ul class="lesson-list"> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼初識編碼初識編碼</span> <span class="free">免費</span></li> </ul> <div class="pay-box"> <span class="discount-type">限時免費</span> <span class="discount-price">¥0.00元</span> <span class="original-price">原價:9.00元</span> <span class="buy-now">當即購買</span> </div> </div> </div> <div class="course-item"> <div class="course-image"> <img src="/static/image/course-cover.jpeg" alt=""> </div> <div class="course-info"> <h3>Python開發21天入門 <span><img src="/static/image/avatar1.svg" alt="">100人已加入學習</span></h3> <p class="teather-info">Alex 金角大王 老男孩Python教學總監 <span>共154課時/更新完成</span></p> <ul class="lesson-list"> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼初識編碼初識編碼</span> <span class="free">免費</span></li> </ul> <div class="pay-box"> <span class="discount-type">限時免費</span> <span class="discount-price">¥0.00元</span> <span class="original-price">原價:9.00元</span> <span class="buy-now">當即購買</span> </div> </div> </div> </div> </div> <Footer></Footer> </div> </template> <script> import Header from "./common/Header" import Footer from "./common/Footer" export default { name: "Course", data(){ return{ category: 0, // 當前點擊的課程分類ID,0表示默認值,所有 category_list: [], } }, created(){ this.get_course_category(); }, methods:{ get_course_category(){ // 獲取課程分類 this.$axios.get(`${this.$settings.Host}/courses/category/`).then(response=>{ this.category_list = response.data; }).catch(error=>{ this.$message.error("沒法獲取課程分類信息!"); }) } }, components:{ Header, Footer, } } </script> <style scoped> .course{ background: #f6f6f6; } .course .main{ width: 1100px; margin: 35px auto 0; } .course .condition{ margin-bottom: 35px; padding: 25px 30px 25px 20px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px 0 #f0f0f0; } .course .cate-list{ border-bottom: 1px solid #333; border-bottom-color: rgba(51,51,51,.05); padding-bottom: 18px; margin-bottom: 17px; } .course .cate-list::after{ content:""; display: block; clear: both; } .course .cate-list li{ float: left; font-size: 16px; padding: 6px 15px; line-height: 16px; margin-left: 14px; position: relative; transition: all .3s ease; cursor: pointer; color: #4a4a4a; border: 1px solid transparent; /* transparent 透明 */ } .course .cate-list .title{ color: #888; margin-left: 0; letter-spacing: .36px; padding: 0; line-height: 28px; } .course .cate-list .this{ color: #ffc210; border: 1px solid #ffc210!important; border-radius: 30px; } .course .ordering::after{ content:""; display: block; clear: both; } .course .ordering ul{ float: left; } .course .ordering ul::after{ content:""; display: block; clear: both; } .course .ordering .condition-result{ float: right; font-size: 14px; color: #9b9b9b; line-height: 28px; } .course .ordering ul li{ float: left; padding: 6px 15px; line-height: 16px; margin-left: 14px; position: relative; transition: all .3s ease; cursor: pointer; color: #4a4a4a; } .course .ordering .title{ font-size: 16px; color: #888; letter-spacing: .36px; margin-left: 0; padding:0; line-height: 28px; } .course .ordering .this{ color: #ffc210; } .course .ordering .price{ position: relative; } .course .ordering .price::before, .course .ordering .price::after{ cursor: pointer; content:""; display: block; width: 0px; height: 0px; border: 5px solid transparent; position: absolute; right: 0; } .course .ordering .price::before{ border-bottom: 5px solid #aaa; margin-bottom: 2px; top: 2px; } .course .ordering .price::after{ border-top: 5px solid #aaa; bottom: 2px; } .course .course-item:hover{ box-shadow: 4px 6px 16px rgba(0,0,0,.5); } .course .course-item{ width: 1050px; background: #fff; padding: 20px 30px 20px 20px; margin-bottom: 35px; border-radius: 2px; cursor: pointer; box-shadow: 2px 3px 16px rgba(0,0,0,.1); /* css3.0 過渡動畫 hover 事件操做 */ transition: all .2s ease; } .course .course-item::after{ content:""; display: block; clear: both; } /* 頂級元素 父級元素 當前元素{} */ .course .course-item .course-image{ float: left; width: 423px; height: 210px; margin-right: 30px; } .course .course-item .course-image img{ width: 100%; } .course .course-item .course-info{ float: left; width: 596px; } .course-item .course-info h3 { font-size: 26px; color: #333; font-weight: normal; margin-bottom: 8px; } .course-item .course-info h3 span{ font-size: 14px; color: #9b9b9b; float: right; margin-top: 14px; } .course-item .course-info h3 span img{ width: 11px; height: auto; margin-right: 7px; } .course-item .course-info .teather-info{ font-size: 14px; color: #9b9b9b; margin-bottom: 14px; padding-bottom: 14px; border-bottom: 1px solid #333; border-bottom-color: rgba(51,51,51,.05); } .course-item .course-info .teather-info span{ float: right; } .course-item .lesson-list::after{ content:""; display: block; clear: both; } .course-item .lesson-list li { float: left; width: 44%; font-size: 14px; color: #666; padding-left: 22px; /* background: url("路徑") 是否平鋪 x軸位置 y軸位置 */ background: url("/static/image/play-icon-gray.svg") no-repeat left 4px; margin-bottom: 15px; } .course-item .lesson-list li .lesson-title{ /* 如下3句,文本內容過多,會自動隱藏,並顯示省略符號 */ text-overflow: ellipsis; overflow: hidden; white-space: nowrap; display:inline-block; max-width: 200px; } .course-item .lesson-list li:hover{ background-image: url("/static/image/play-icon-yellow.svg"); color: #ffc210; } .course-item .lesson-list li .free{ width: 34px; height: 20px; color: #fd7b4d; vertical-align: super; margin-left: 10px; border: 1px solid #fd7b4d; border-radius: 2px; text-align: center; font-size: 13px; white-space: nowrap; } .course-item .lesson-list li:hover .free{ color: #ffc210; border-color: #ffc210; } .course-item .pay-box::after{ content:""; display: block; clear: both; } .course-item .pay-box .discount-type{ padding: 6px 10px; font-size: 16px; color: #fff; text-align: center; margin-right: 8px; background: #fa6240; border: 1px solid #fa6240; border-radius: 10px 0 10px 0; float: left; } .course-item .pay-box .discount-price{ font-size: 24px; color: #fa6240; float: left; } .course-item .pay-box .original-price{ text-decoration: line-through; font-size: 14px; color: #9b9b9b; margin-left: 10px; float: left; margin-top: 10px; } .course-item .pay-box .buy-now{ width: 120px; height: 38px; background: transparent; color: #fa6240; font-size: 16px; border: 1px solid #fd7b4d; border-radius: 3px; transition: all .2s ease-in-out; float: right; text-align: center; line-height: 38px; } .course-item .pay-box .buy-now:hover{ color: #fff; background: #ffc210; border: 1px solid #ffc210; } </style>
序列化器,代碼:
from rest_framework import serializers from .models import CourseCategory class CourseCategoryModelSerializer(serializers.ModelSerializer): class Meta: model = CourseCategory fields = ["id","name"] from .models import Course class CourseModelSerializer(serializers.ModelSerializer): """課程""" class Meta: model = Course fields = ["id","name","course_img","students","lessons","pub_lessons","price","teacher"]
視圖代碼:
# Create your views here. from rest_framework.generics import ListAPIView from .models import CourseCategory,Course from .serializers import CourseCategoryModelSerializer,CourseModelSerializer class CourseCategoryListAPIView(ListAPIView): """課程分類列表接口""" // .... class CourseListAPIView(ListAPIView): """課程列表""" queryset = Course.objects.filter(is_show=True, is_delete=False).order_by("orders", "-id") serializer_class = CourseModelSerializer
路由:
# 子應用路由 from django.urls import path,re_path from . import views urlpatterns = [ path("category/",views.CourseCategoryListAPIView.as_view()), path("", views.CourseListAPIView.as_view()), ]
訪問效果
上面雖然返回了課程基本信息,可是這裏Teacher字段返回的是一個模型對象,這個模型對象被做爲字符串輸出時,默認輸出的是主鍵ID,因此咱們接下來須要對這個外檢字段Teacher進行從新調整,讓他能夠輸出老師的其餘信息,而不只是主鍵ID。
這裏有兩種解決方案:
1.在模型中經過給模型增長自定義字段方法來返回多個字段數據
# 模型代碼: class Course(BaseModel): @property def teacher_name(self): return self.teacher.name # 序列化器代碼: class CourseModelSerializer(serializers.ModelSerializer): """課程""" class Meta: model = Course fields = ["id","name","course_img","students","lessons","pub_lessons","price","teacher","teacher_name"]
2.
from .models import CourseCategory,Course,Teacher class TeacherModelSerializer(serializers.ModelSerializer): """課程列表的老師信息""" class Meta: model = Teacher fields = ["id","name","title","signature"] class CourseModelSerializer(serializers.ModelSerializer): """課程列表的課程基本信息""" # 序列化器嵌套[被嵌套的序列化器必須聲明對應的字段爲模型原有的外鍵字段,同時這個被嵌套的序列化器必須先聲明才能進行調用!] # 若是嵌套的序列化器數據有多條,則須要在調用序列化器時須要聲明 many=True teacher = TeacherModelSerializer() class Meta: model = Course fields = ["id","name","course_img","students","lessons","pub_lessons","price","teacher"]
服務端提供role_name字段,models.py代碼:
class Teacher(BaseModel): """講師、導師表""" role_choices = ( (0, '講師'), (1, '導師'), (2, '班主任'), ) name = models.CharField(max_length=32, verbose_name="講師暱稱") role = models.SmallIntegerField(choices=role_choices, default=0, verbose_name="講師身份") title = models.CharField(max_length=64, verbose_name="職位、職稱") signature = models.CharField(max_length=255, verbose_name="導師簽名", help_text="導師簽名", blank=True, null=True) image = models.ImageField(upload_to="teacher", null=True, verbose_name = "講師封面") brief = models.TextField(max_length=1024, verbose_name="講師描述") class Meta: db_table = "ly_teacher" verbose_name = "講師導師" verbose_name_plural = "講師導師" @property def role_name(self): return self.role_choices[self.role][1]
serializers.py
from .models import Teacher class TeacherModelSerializer(serializers.ModelSerializer): """講師導師序列化器""" class Meta: model = Teacher fields = ["id","name","role_name","title","signature"]
Course.vue,代碼:
<template> <div class="course"> <Header></Header> <div class="main"> <!-- 篩選條件 --> <div class="condition"> <ul class="cate-list"> <li class="title">課程分類:</li> <li :class="category==0?'this':''" @click="category=0">所有</li> <li :class="category==item.id?'this':''" v-for="item in category_list" @click="category=item.id">{{item.name}}</li> </ul> <div class="ordering"> <ul> <li class="title">篩 選: </li> <li class="default this">默認</li> <li class="hot this">人氣</li> <li class="price this">價格</li> </ul> <p class="condition-result">共21個課程</p> </div> </div> <!-- 課程列表 --> <div class="course-list"> <div class="course-item" v-for="course in course_list"> <div class="course-image"> <img :src="course.course_img" alt=""> </div> <div class="course-info"> <h3><router-link to="/course/detail/1">{{course.name}}</router-link> <span><img src="/static/image/avatar1.svg" alt="">{{course.students}}人已加入學習</span></h3> <p class="teather-info">{{course.teacher.role_name}} {{course.teacher.name}} {{course.teacher.signature}} {{course.teacher.title}} <span>共{{course.lessons}}課時/{{course.lessons==course.pub_lessons?'更新完成':'已更新'+course.pub_lessons+'課時'}}</span></p> <ul class="lesson-list"> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼</span> <span class="free">免費</span></li> <li><span class="lesson-title">01 | 第1節:初識編碼初識編碼初識編碼初識編碼</span> <span class="free">免費</span></li> </ul> <div class="pay-box"> <span class="discount-type">限時免費</span> <span class="discount-price">¥0.00元</span> <span class="original-price">原價:{{course.price}}元</span> <span class="buy-now">當即購買</span> </div> </div> </div> </div> </div> <Footer></Footer> </div> </template> <script> import Header from "./common/Header" import Footer from "./common/Footer" export default { name: "Course", data(){ return{ category: 0, // 當前點擊的課程分類ID,0表示默認值,所有 category_list: [], // 課程分類列表 course_list: [ { teacher: {}, } ], } }, created(){ this.get_course_category(); this.get_course(); }, methods:{ get_course_category(){ // 獲取課程分類 this.$axios.get(`${this.$settings.Host}/courses/category/`).then(response=>{ this.category_list = response.data; }).catch(error=>{ this.$message.error("沒法獲取課程分類信息!"); }) }, get_course(){ // 獲取課程信息 this.$axios.get(`${this.$settings.Host}/courses/`).then(response=>{ this.course_list = response.data; }).catch(error=>{ this.$message.error("沒法獲取課程信息!"); }) } }, components:{ Header, Footer, } } </script> <style scoped> .course{ background: #f6f6f6; } .course .main{ width: 1100px; margin: 35px auto 0; } .course .condition{ margin-bottom: 35px; padding: 25px 30px 25px 20px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px 0 #f0f0f0; } .course .cate-list{ border-bottom: 1px solid #333; border-bottom-color: rgba(51,51,51,.05); padding-bottom: 18px; margin-bottom: 17px; } .course .cate-list::after{ content:""; display: block; clear: both; } .course .cate-list li{ float: left; font-size: 16px; padding: 6px 15px; line-height: 16px; margin-left: 14px; position: relative; transition: all .3s ease; cursor: pointer; color: #4a4a4a; border: 1px solid transparent; /* transparent 透明 */ } .course .cate-list .title{ color: #888; margin-left: 0; letter-spacing: .36px; padding: 0; line-height: 28px; } .course .cate-list .this{ color: #ffc210; border: 1px solid #ffc210!important; border-radius: 30px; } .course .ordering::after{ content:""; display: block; clear: both; } .course .ordering ul{ float: left; } .course .ordering ul::after{ content:""; display: block; clear: both; } .course .ordering .condition-result{ float: right; font-size: 14px; color: #9b9b9b; line-height: 28px; } .course .ordering ul li{ float: left; padding: 6px 15px; line-height: 16px; margin-left: 14px; position: relative; transition: all .3s ease; cursor: pointer; color: #4a4a4a; } .course .ordering .title{ font-size: 16px; color: #888; letter-spacing: .36px; margin-left: 0; padding:0; line-height: 28px; } .course .ordering .this{ color: #ffc210; } .course .ordering .price{ position: relative; } .course .ordering .price::before, .course .ordering .price::after{ cursor: pointer; content:""; display: block; width: 0px; height: 0px; border: 5px solid transparent; position: absolute; right: 0; } .course .ordering .price::before{ border-bottom: 5px solid #aaa; margin-bottom: 2px; top: 2px; } .course .ordering .price::after{ border-top: 5px solid #aaa; bottom: 2px; } .course .course-item:hover{ box-shadow: 4px 6px 16px rgba(0,0,0,.5); } .course .course-item{ width: 1050px; background: #fff; padding: 20px 30px 20px 20px; margin-bottom: 35px; border-radius: 2px; cursor: pointer; box-shadow: 2px 3px 16px rgba(0,0,0,.1); /* css3.0 過渡動畫 hover 事件操做 */ transition: all .2s ease; } .course .course-item::after{ content:""; display: block; clear: both; } /* 頂級元素 父級元素 當前元素{} */ .course .course-item .course-image{ float: left; width: 423px; height: 210px; margin-right: 30px; } .course .course-item .course-image img{ width: 100%; } .course .course-item .course-info{ float: left; width: 596px; } .course-item .course-info h3 { font-size: 26px; color: #333; font-weight: normal; margin-bottom: 8px; } .course-item .course-info h3 span{ font-size: 14px; color: #9b9b9b; float: right; margin-top: 14px; } .course-item .course-info h3 span img{ width: 11px; height: auto; margin-right: 7px; } .course-item .course-info .teather-info{ font-size: 14px; color: #9b9b9b; margin-bottom: 14px; padding-bottom: 14px; border-bottom: 1px solid #333; border-bottom-color: rgba(51,51,51,.05); } .course-item .course-info .teather-info span{ float: right; } .course-item .lesson-list::after{ content:""; display: block; clear: both; } .course-item .lesson-list li { float: left; width: 44%; font-size: 14px; color: #666; padding-left: 22px; /* background: url("路徑") 是否平鋪 x軸位置 y軸位置 */ background: url("/static/image/play-icon-gray.svg") no-repeat left 4px; margin-bottom: 15px; } .course-item .lesson-list li .lesson-title{ /* 如下3句,文本內容過多,會自動隱藏,並顯示省略符號 */ text-overflow: ellipsis; overflow: hidden; white-space: nowrap; display:inline-block; max-width: 200px; } .course-item .lesson-list li:hover{ background-image: url("/static/image/play-icon-yellow.svg"); color: #ffc210; } .course-item .lesson-list li .free{ width: 34px; height: 20px; color: #fd7b4d; vertical-align: super; margin-left: 10px; border: 1px solid #fd7b4d; border-radius: 2px; text-align: center; font-size: 13px; white-space: nowrap; } .course-item .lesson-list li:hover .free{ color: #ffc210; border-color: #ffc210; } .course-item .pay-box::after{ content:""; display: block; clear: both; } .course-item .pay-box .discount-type{ padding: 6px 10px; font-size: 16px; color: #fff; text-align: center; margin-right: 8px; background: #fa6240; border: 1px solid #fa6240; border-radius: 10px 0 10px 0; float: left; } .course-item .pay-box .discount-price{ font-size: 24px; color: #fa6240; float: left; } .course-item .pay-box .original-price{ text-decoration: line-through; font-size: 14px; color: #9b9b9b; margin-left: 10px; float: left; margin-top: 10px; } .course-item .pay-box .buy-now{ width: 120px; height: 38px; background: transparent; color: #fa6240; font-size: 16px; border: 1px solid #fd7b4d; border-radius: 3px; transition: all .2s ease-in-out; float: right; text-align: center; line-height: 38px; } .course-item .pay-box .buy-now:hover{ color: #fff; background: #ffc210; border: 1px solid #ffc210; } </style>
能夠經過序列化器嵌套來完成,可是查詢過程的數量很差控制。如下代碼僅供參考
from .models import Course class CourseModelSerializer(serializers.ModelSerializer): """課程""" teacher = TeacherModelSerializer() class Meta: model = Course fields = ["id","name","course_img","students","lessons","pub_lessons","price","teacher","recomment_lessons"]
models
增長方法 recomment_lessons
class Course(BaseModel): """ 實戰課程 """ course_type = ( (0, '付費'), (1, 'VIP專享'), (2, '學位課程'), ) level_choices = ( (0, '初級'), (1, '中級'), (2, '高級'), ) status_choices = ( (0, '上線'), (1, '下線'), (2, '預上線'), ) name = models.CharField(max_length=128, verbose_name="課程名稱") course_img = models.ImageField(upload_to="course", max_length=255, verbose_name="封面圖片", blank=True, null=True) course_video = models.FileField(upload_to="course_video", max_length=255, verbose_name="封面視頻", blank=True, null=True) course_type = models.SmallIntegerField(choices=course_type, default=0, verbose_name="付費類型") # 使用這個字段的緣由 brief = models.TextField(verbose_name="詳情介紹", null=True, blank=True) level = models.SmallIntegerField(choices=level_choices, default=1, verbose_name="難度等級") pub_date = models.DateField(verbose_name="發佈日期", auto_now_add=True) period = models.IntegerField(verbose_name="建議學習週期(day)", default=7) attachment_path = models.FileField(max_length=128, verbose_name="課件路徑", blank=True, null=True) status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="課程狀態") course_category = models.ForeignKey("CourseCategory", on_delete=models.CASCADE, null=True, blank=True, verbose_name="課程分類") students = models.IntegerField(verbose_name="學習人數", default=0) lessons = models.IntegerField(verbose_name="總課時數量", default=0) pub_lessons = models.IntegerField(verbose_name="課時更新數量", default=0) price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="課程原價", default=0) teacher = models.ForeignKey("Teacher", on_delete=models.DO_NOTHING, null=True, blank=True, verbose_name="授課老師") class Meta: db_table = "ly_course" verbose_name = "實戰課程" verbose_name_plural = "實戰課程" def teacher_name(self): return self.teacher.name @property def recomment_lessons(self): """課程列表的推薦課時""" lesson_list = self.lesson_list.filter(recomment=True, is_show=True, is_delete=False).order_by("orders","id") print(lesson_list,111111) # data = [] # for lesson in lesson_list: # data.append({ # "id": lesson.id, # "name": lesson.name, # "free_trail": lesson.free_trail, # }) # return data # 列表生成式 return [{"id":lesson.id, "name": lesson.name, "free_trail": lesson.free_trail} for lesson in lesson_list] def __str__(self): return "%s" % self.name
<template> <div class="course"> <Header></Header> <div class="main"> <!-- 篩選條件 --> <div class="condition"> <ul class="cate-list"> <li class="title">課程分類:</li> <li :class="category==0?'this':''" @click="category=0">所有</li> <li :class="category==item.id?'this':''" v-for="item in category_list" @click="category=item.id">{{item.name}}</li> </ul> <div class="ordering"> <ul> <li class="title">篩 選: </li> <li class="default this">默認</li> <li class="hot this">人氣</li> <li class="price this">價格</li> </ul> <p class="condition-result">共21個課程</p> </div> </div> <!-- 課程列表 --> <div class="course-list"> <div class="course-item" v-for="course in course_list"> <div class="course-image"> <img :src="course.course_img" alt=""> </div> <div class="course-info"> <h3><router-link to="/course/detail/1">{{course.name}}</router-link> <span><img src="/static/image/avatar1.svg" alt="">{{course.students}}人已加入學習</span></h3> <p class="teather-info">{{course.teacher.role_name}} {{course.teacher.name}} {{course.teacher.signature}} {{course.teacher.title}} <span>共{{course.lessons}}課時/{{course.lessons==course.pub_lessons?'更新完成':'已更新'+course.pub_lessons+'課時'}}</span></p> <ul class="lesson-list"> <li v-for="lesson,key in course.recomment_lessons"><span class="lesson-title">0{{key+1}} | 第{{lesson.id}}節:{{lesson.name}}</span> <span class="free" v-if="lesson.free_trail">免費</span></li> </ul> <div class="pay-box"> <span class="discount-type">限時免費</span> <span class="discount-price">¥0.00元</span> <span class="original-price">原價:{{course.price}}元</span> <span class="buy-now">當即購買</span> </div> </div> </div> </div> </div> <Footer></Footer> </div> </template> <script> import Header from "./common/Header" import Footer from "./common/Footer" export default { name: "Course", data(){ return{ category: 0, // 當前點擊的課程分類ID,0表示默認值,所有 category_list: [], // 課程分類列表 course_list: [ { teacher: {}, } ], } }, created(){ this.get_course_category(); this.get_course(); }, methods:{ get_course_category(){ // 獲取課程分類 this.$axios.get(`${this.$settings.Host}/courses/category/`).then(response=>{ this.category_list = response.data; }).catch(error=>{ this.$message.error("沒法獲取課程分類信息!"); }) }, get_course(){ // 獲取課程信息 this.$axios.get(`${this.$settings.Host}/courses/`).then(response=>{ this.course_list = response.data; }).catch(error=>{ this.$message.error("沒法獲取課程信息!"); }) } }, components:{ Header, Footer, } } </script> <style scoped> .course{ background: #f6f6f6; } .course .main{ width: 1100px; margin: 35px auto 0; } .course .condition{ margin-bottom: 35px; padding: 25px 30px 25px 20px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px 0 #f0f0f0; } .course .cate-list{ border-bottom: 1px solid #333; border-bottom-color: rgba(51,51,51,.05); padding-bottom: 18px; margin-bottom: 17px; } .course .cate-list::after{ content:""; display: block; clear: both; } .course .cate-list li{ float: left; font-size: 16px; padding: 6px 15px; line-height: 16px; margin-left: 14px; position: relative; transition: all .3s ease; cursor: pointer; color: #4a4a4a; border: 1px solid transparent; /* transparent 透明 */ } .course .cate-list .title{ color: #888; margin-left: 0; letter-spacing: .36px; padding: 0; line-height: 28px; } .course .cate-list .this{ color: #ffc210; border: 1px solid #ffc210!important; border-radius: 30px; } .course .ordering::after{ content:""; display: block; clear: both; } .course .ordering ul{ float: left; } .course .ordering ul::after{ content:""; display: block; clear: both; } .course .ordering .condition-result{ float: right; font-size: 14px; color: #9b9b9b; line-height: 28px; } .course .ordering ul li{ float: left; padding: 6px 15px; line-height: 16px; margin-left: 14px; position: relative; transition: all .3s ease; cursor: pointer; color: #4a4a4a; } .course .ordering .title{ font-size: 16px; color: #888; letter-spacing: .36px; margin-left: 0; padding:0; line-height: 28px; } .course .ordering .this{ color: #ffc210; } .course .ordering .price{ position: relative; } .course .ordering .price::before, .course .ordering .price::after{ cursor: pointer; content:""; display: block; width: 0px; height: 0px; border: 5px solid transparent; position: absolute; right: 0; } .course .ordering .price::before{ border-bottom: 5px solid #aaa; margin-bottom: 2px; top: 2px; } .course .ordering .price::after{ border-top: 5px solid #aaa; bottom: 2px; } .course .course-item:hover{ box-shadow: 4px 6px 16px rgba(0,0,0,.5); } .course .course-item{ width: 1050px; background: #fff; padding: 20px 30px 20px 20px; margin-bottom: 35px; border-radius: 2px; cursor: pointer; box-shadow: 2px 3px 16px rgba(0,0,0,.1); /* css3.0 過渡動畫 hover 事件操做 */ transition: all .2s ease; } .course .course-item::after{ content:""; display: block; clear: both; } /* 頂級元素 父級元素 當前元素{} */ .course .course-item .course-image{ float: left; width: 423px; height: 210px; margin-right: 30px; } .course .course-item .course-image img{ width: 100%; } .course .course-item .course-info{ float: left; width: 596px; } .course-item .course-info h3 { font-size: 26px; color: #333; font-weight: normal; margin-bottom: 8px; } .course-item .course-info h3 span{ font-size: 14px; color: #9b9b9b; float: right; margin-top: 14px; } .course-item .course-info h3 span img{ width: 11px; height: auto; margin-right: 7px; } .course-item .course-info .teather-info{ font-size: 14px; color: #9b9b9b; margin-bottom: 14px; padding-bottom: 14px; border-bottom: 1px solid #333; border-bottom-color: rgba(51,51,51,.05); } .course-item .course-info .teather-info span{ float: right; } .course-item .lesson-list::after{ content:""; display: block; clear: both; } .course-item .lesson-list li { float: left; width: 44%; font-size: 14px; color: #666; padding-left: 22px; /* background: url("路徑") 是否平鋪 x軸位置 y軸位置 */ background: url("/static/image/play-icon-gray.svg") no-repeat left 4px; margin-bottom: 15px; } .course-item .lesson-list li .lesson-title{ /* 如下3句,文本內容過多,會自動隱藏,並顯示省略符號 */ text-overflow: ellipsis; overflow: hidden; white-space: nowrap; display:inline-block; max-width: 200px; } .course-item .lesson-list li:hover{ background-image: url("/static/image/play-icon-yellow.svg"); color: #ffc210; } .course-item .lesson-list li .free{ width: 34px; height: 20px; color: #fd7b4d; vertical-align: super; margin-left: 10px; border: 1px solid #fd7b4d; border-radius: 2px; text-align: center; font-size: 13px; white-space: nowrap; } .course-item .lesson-list li:hover .free{ color: #ffc210; border-color: #ffc210; } .course-item .pay-box::after{ content:""; display: block; clear: both; } .course-item .pay-box .discount-type{ padding: 6px 10px; font-size: 16px; color: #fff; text-align: center; margin-right: 8px; background: #fa6240; border: 1px solid #fa6240; border-radius: 10px 0 10px 0; float: left; } .course-item .pay-box .discount-price{ font-size: 24px; color: #fa6240; float: left; } .course-item .pay-box .original-price{ text-decoration: line-through; font-size: 14px; color: #9b9b9b; margin-left: 10px; float: left; margin-top: 10px; } .course-item .pay-box .buy-now{ width: 120px; height: 38px; background: transparent; color: #fa6240; font-size: 16px; border: 1px solid #fd7b4d; border-radius: 3px; transition: all .2s ease-in-out; float: right; text-align: center; line-height: 38px; } .course-item .pay-box .buy-now:hover{ color: #fff; background: #ffc210; border: 1px solid #ffc210; } </style>
在當前項目中安裝 字段過濾排序
pip install django-filter
在settings/dev.py配置文件中增長過濾後端的設置:
INSTALLED_APPS = [ ... 'django_filters', # 須要註冊應用, ]
在視圖中設置容許過濾的字段名和引入過濾字段核心類
from rest_framework.generics import ListAPIView from .models import Course from django_filters.rest_framework import DjangoFilterBackend from rest_framework.filters import OrderingFilter class CourseListAPIView(ListAPIView): """課程列表""" queryset = Course.objects.filter(is_show=True, is_delete=False).order_by("orders", "-id") serializer_class = CourseModelSerializer filter_backends = [DjangoFilterBackend,OrderingFilter ] filter_fields = ('course_category',) ordering_fields =('id','students','price')
<template> <div class="course"> <Header></Header> <div class="main"> <!-- 篩選條件 --> <div class="condition"> <ul class="cate-list"> <li class="title">課程分類:</li> <li :class="filters.category==0?'this':''" @click="filters.category=0">所有</li> <li :class="filters.category==item.id?'this':''" v-for="item in category_list" @click="filters.category=item.id">{{item.name}}</li> </ul> <div class="ordering"> <ul> <li class="title">篩 選: </li> <li class="default this">默認</li> <li class="hot this">人氣</li> <li class="price this">價格</li> </ul> <p class="condition-result">共21個課程</p> </div> </div> <!-- 課程列表 --> <div class="course-list"> <div class="course-item" v-for="course in course_list"> <div class="course-image"> <img :src="course.course_img" alt=""> </div> <div class="course-info"> <h3><router-link to="/course/detail/1">{{course.name}}</router-link> <span><img src="/static/image/avatar1.svg" alt="">{{course.students}}人已加入學習</span></h3> <p class="teather-info">{{course.teacher.role_name}} {{course.teacher.name}} {{course.teacher.signature}} {{course.teacher.title}} <span>共{{course.lessons}}課時/{{course.lessons==course.pub_lessons?'更新完成':'已更新'+course.pub_lessons+'課時'}}</span></p> <ul class="lesson-list"> <li v-for="lesson,key in course.recomment_lessons"><span class="lesson-title">0{{key+1}} | 第{{lesson.id}}節:{{lesson.name}}</span> <span class="free" v-if="lesson.free_trail">免費</span></li> </ul> <div class="pay-box"> <span class="discount-type">限時免費</span> <span class="discount-price">¥0.00元</span> <span class="original-price">原價:{{course.price}}元</span> <span class="buy-now">當即購買</span> </div> </div> </div> </div> </div> <Footer></Footer> </div> </template> <script> import Header from "./common/Header" import Footer from "./common/Footer" export default { name: "Course", data(){ return{ filters:{ category: 0, // 當前點擊的課程分類ID,0表示默認值,所有 }, category_list: [], // 課程分類列表 course_list: [ { teacher: {}, } ], } }, created(){ this.get_course_category(); this.get_course(); }, watch:{ // 每次分類ID進行l修改,都要從新獲取一次課程別彪信息 "filters.category":function () { this.get_course(); }, }, methods:{ get_course_category(){ // 獲取課程分類 this.$axios.get(`${this.$settings.Host}/courses/category/`).then(response=>{ this.category_list = response.data; }).catch(error=>{ this.$message.error("沒法獲取課程分類信息!"); }) }, get_course(){ // 獲取課程列表 let filters = { }; // 判斷課程分類ID不是0,則須要指定分類到後端查詢對應分類的數據,拼接參數 if(this.filters.category != 0){ filters.course_category = this.filters.category; } // 獲取課程信息 this.$axios.get(`${this.$settings.Host}/courses/`,{ params:filters }).then(response=>{ this.course_list = response.data; }).catch(error=>{ this.$message.error("沒法獲取課程信息!"); }) } }, components:{ Header, Footer, } } </script> <style scoped> .course{ background: #f6f6f6; } .course .main{ width: 1100px; margin: 35px auto 0; } .course .condition{ margin-bottom: 35px; padding: 25px 30px 25px 20px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px 0 #f0f0f0; } .course .cate-list{ border-bottom: 1px solid #333; border-bottom-color: rgba(51,51,51,.05); padding-bottom: 18px; margin-bottom: 17px; } .course .cate-list::after{ content:""; display: block; clear: both; } .course .cate-list li{ float: left; font-size: 16px; padding: 6px 15px; line-height: 16px; margin-left: 14px; position: relative; transition: all .3s ease; cursor: pointer; color: #4a4a4a; border: 1px solid transparent; /* transparent 透明 */ } .course .cate-list .title{ color: #888; margin-left: 0; letter-spacing: .36px; padding: 0; line-height: 28px; } .course .cate-list .this{ color: #ffc210; border: 1px solid #ffc210!important; border-radius: 30px; } .course .ordering::after{ content:""; display: block; clear: both; } .course .ordering ul{ float: left; } .course .ordering ul::after{ content:""; display: block; clear: both; } .course .ordering .condition-result{ float: right; font-size: 14px; color: #9b9b9b; line-height: 28px; } .course .ordering ul li{ float: left; padding: 6px 15px; line-height: 16px; margin-left: 14px; position: relative; transition: all .3s ease; cursor: pointer; color: #4a4a4a; } .course .ordering .title{ font-size: 16px; color: #888; letter-spacing: .36px; margin-left: 0; padding:0; line-height: 28px; } .course .ordering .this{ color: #ffc210; } .course .ordering .price{ position: relative; } .course .ordering .price::before, .course .ordering .price::after{ cursor: pointer; content:""; display: block; width: 0px; height: 0px; border: 5px solid transparent; position: absolute; right: 0; } .course .ordering .price::before{ border-bottom: 5px solid #aaa; margin-bottom: 2px; top: 2px; } .course .ordering .price::after{ border-top: 5px solid #aaa; bottom: 2px; } .course .course-item:hover{ box-shadow: 4px 6px 16px rgba(0,0,0,.5); } .course .course-item{ width: 1050px; background: #fff; padding: 20px 30px 20px 20px; margin-bottom: 35px; border-radius: 2px; cursor: pointer; box-shadow: 2px 3px 16px rgba(0,0,0,.1); /* css3.0 過渡動畫 hover 事件操做 */ transition: all .2s ease; } .course .course-item::after{ content:""; display: block; clear: both; } /* 頂級元素 父級元素 當前元素{} */ .course .course-item .course-image{ float: left; width: 423px; height: 210px; margin-right: 30px; } .course .course-item .course-image img{ width: 100%; } .course .course-item .course-info{ float: left; width: 596px; } .course-item .course-info h3 { font-size: 26px; color: #333; font-weight: normal; margin-bottom: 8px; } .course-item .course-info h3 span{ font-size: 14px; color: #9b9b9b; float: right; margin-top: 14px; } .course-item .course-info h3 span img{ width: 11px; height: auto; margin-right: 7px; } .course-item .course-info .teather-info{ font-size: 14px; color: #9b9b9b; margin-bottom: 14px; padding-bottom: 14px; border-bottom: 1px solid #333; border-bottom-color: rgba(51,51,51,.05); } .course-item .course-info .teather-info span{ float: right; } .course-item .lesson-list::after{ content:""; display: block; clear: both; } .course-item .lesson-list li { float: left; width: 44%; font-size: 14px; color: #666; padding-left: 22px; /* background: url("路徑") 是否平鋪 x軸位置 y軸位置 */ background: url("/static/image/play-icon-gray.svg") no-repeat left 4px; margin-bottom: 15px; } .course-item .lesson-list li .lesson-title{ /* 如下3句,文本內容過多,會自動隱藏,並顯示省略符號 */ text-overflow: ellipsis; overflow: hidden; white-space: nowrap; display:inline-block; max-width: 200px; } .course-item .lesson-list li:hover{ background-image: url("/static/image/play-icon-yellow.svg"); color: #ffc210; } .course-item .lesson-list li .free{ width: 34px; height: 20px; color: #fd7b4d; vertical-align: super; margin-left: 10px; border: 1px solid #fd7b4d; border-radius: 2px; text-align: center; font-size: 13px; white-space: nowrap; } .course-item .lesson-list li:hover .free{ color: #ffc210; border-color: #ffc210; } .course-item .pay-box::after{ content:""; display: block; clear: both; } .course-item .pay-box .discount-type{ padding: 6px 10px; font-size: 16px; color: #fff; text-align: center; margin-right: 8px; background: #fa6240; border: 1px solid #fa6240; border-radius: 10px 0 10px 0; float: left; } .course-item .pay-box .discount-price{ font-size: 24px; color: #fa6240; float: left; } .course-item .pay-box .original-price{ text-decoration: line-through; font-size: 14px; color: #9b9b9b; margin-left: 10px; float: left; margin-top: 10px; } .course-item .pay-box .buy-now{ width: 120px; height: 38px; background: transparent; color: #fa6240; font-size: 16px; border: 1px solid #fd7b4d; border-radius: 3px; transition: all .2s ease-in-out; float: right; text-align: center; line-height: 38px; } .course-item .pay-box .buy-now:hover{ color: #fff; background: #ffc210; border: 1px solid #ffc210; } </style>
course.vu
<template> <div class="course"> <Header></Header> <div class="main"> <!-- 篩選條件 --> <div class="condition"> <ul class="cate-list"> <li class="title">課程分類:</li> <li :class="filters.category==0?'this':''" @click="filters.category=0">所有</li> <li :class="filters.category==item.id?'this':''" v-for="item in category_list" @click="filters.category=item.id">{{item.name}}</li> </ul> <div class="ordering"> <ul> <li class="title">篩 選: </li> <li class="default" :class="filters.ordering=='id'?'this asc': (filters.ordering=='-id'?'this desc':'')" @click="select_ordering('id')">默認</li> <li class="hot" :class="filters.ordering=='students'?'this asc': (filters.ordering=='-students'?'this desc':'')" @click="select_ordering('students')">人氣</li> <li class="price" :class="filters.ordering=='price'?'this asc': (filters.ordering=='-price'?'this desc':'')" @click="select_ordering('price')">價格</li> </ul> <p class="condition-result">共21個課程</p> </div> </div> <!-- 課程列表 --> <div class="course-list"> <div class="course-item" v-for="course in course_list"> <div class="course-image"> <img :src="course.course_img" alt=""> </div> <div class="course-info"> <h3><router-link to="/course/detail/1">{{course.name}}</router-link> <span><img src="/static/image/avatar1.svg" alt="">{{course.students}}人已加入學習</span></h3> <p class="teather-info">{{course.teacher.role_name}} {{course.teacher.name}} {{course.teacher.signature}} {{course.teacher.title}} <span>共{{course.lessons}}課時/{{course.lessons==course.pub_lessons?'更新完成':'已更新'+course.pub_lessons+'課時'}}</span></p> <ul class="lesson-list"> <li v-for="lesson,key in course.recomment_lessons"><span class="lesson-title">0{{key+1}} | 第{{lesson.id}}節:{{lesson.name}}</span> <span class="free" v-if="lesson.free_trail">免費</span></li> </ul> <div class="pay-box"> <span class="discount-type">限時免費</span> <span class="discount-price">¥0.00元</span> <span class="original-price">原價:{{course.price}}元</span> <span class="buy-now">當即購買</span> </div> </div> </div> </div> </div> <Footer></Footer> </div> </template> <script> import Header from "./common/Header" import Footer from "./common/Footer" export default { name: "Course", data(){ return{ filters:{ category: 0, // 當前點擊的課程分類ID,0表示默認值,所有 ordering: "id", // id默認,students人氣, price價格,若是值的左邊有-減號,則表示倒敘,不然是正序 }, category_list: [], // 課程分類列表 course_list: [ { teacher: {}, } ], } }, created(){ this.get_course_category(); this.get_course(); }, watch:{ // 每次分類ID進行l修改,都要從新獲取一次課程別彪信息 "filters.category":function () { this.get_course(); }, "filters.ordering": function(){ this.get_course(); }, }, methods:{ get_course_category(){ // 獲取課程分類 this.$axios.get(`${this.$settings.Host}/courses/category/`).then(response=>{ this.category_list = response.data; }).catch(error=>{ this.$message.error("沒法獲取課程分類信息!"); }) }, get_course(){ // 獲取課程列表 let filters = { "ordering": this.filters.ordering, }; // 判斷課程分類ID不是0,則須要指定分類到後端查詢對應分類的數據,拼接參數 if(this.filters.category != 0){ filters.course_category = this.filters.category; } // 獲取課程信息 this.$axios.get(`${this.$settings.Host}/courses/`,{ params:filters }).then(response=>{ this.course_list = response.data; }).catch(error=>{ this.$message.error("沒法獲取課程信息!"); }) }, select_ordering(ordering){ // 判斷切換不一樣的排序方式 if(ordering=="id"){ // 點擊了默認 if(this.filters.ordering=="id"){ this.filters.ordering="-id"; }else{ this.filters.ordering="id"; } }else if(ordering=="students"){ // 點擊了人氣 if(this.filters.ordering=="students"){ this.filters.ordering="-students"; }else{ this.filters.ordering="students"; } }else if(ordering=="price"){ // 點擊了價格 if(this.filters.ordering=="price"){ this.filters.ordering="-price"; }else{ this.filters.ordering="price"; } } }, }, components:{ Header, Footer, } } </script> <style scoped> .course{ background: #f6f6f6; } .course .main{ width: 1100px; margin: 35px auto 0; } .course .condition{ margin-bottom: 35px; padding: 25px 30px 25px 20px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px 0 #f0f0f0; } .course .cate-list{ border-bottom: 1px solid #333; border-bottom-color: rgba(51,51,51,.05); padding-bottom: 18px; margin-bottom: 17px; } .course .cate-list::after{ content:""; display: block; clear: both; } .course .cate-list li{ float: left; font-size: 16px; padding: 6px 15px; line-height: 16px; margin-left: 14px; position: relative; transition: all .3s ease; cursor: pointer; color: #4a4a4a; border: 1px solid transparent; /* transparent 透明 */ } .course .cate-list .title{ color: #888; margin-left: 0; letter-spacing: .36px; padding: 0; line-height: 28px; } .course .cate-list .this{ color: #ffc210; border: 1px solid #ffc210!important; border-radius: 30px; } .course .ordering::after{ content:""; display: block; clear: both; } .course .ordering ul{ float: left; } .course .ordering ul::after{ content:""; display: block; clear: both; } .course .ordering .condition-result{ float: right; font-size: 14px; color: #9b9b9b; line-height: 28px; } .course .ordering ul li{ float: left; padding: 6px 15px; line-height: 16px; margin-left: 14px; position: relative; transition: all .3s ease; cursor: pointer; color: #4a4a4a; } .course .ordering .title{ font-size: 16px; color: #888; letter-spacing: .36px; margin-left: 0; padding:0; line-height: 28px; } .course .ordering .this{ color: #ffc210; } .course .ordering .price{ position: relative; } .course .ordering .this::before, .course .ordering .this::after{ cursor: pointer; content:""; display: block; width: 0px; height: 0px; border: 5px solid transparent; position: absolute; right: 0; } .course .ordering .this::before{ border-bottom: 5px solid #aaa; margin-bottom: 2px; top: 2px; } .course .ordering .this::after{ border-top: 5px solid #aaa; bottom: 2px; } .course .ordering .desc::after{ border-top: 5px solid #ffc210; } .course .ordering .asc::before{ border-bottom: 5px solid #ffc210; } .course .course-item:hover{ box-shadow: 4px 6px 16px rgba(0,0,0,.5); } .course .course-item{ width: 1050px; background: #fff; padding: 20px 30px 20px 20px; margin-bottom: 35px; border-radius: 2px; cursor: pointer; box-shadow: 2px 3px 16px rgba(0,0,0,.1); /* css3.0 過渡動畫 hover 事件操做 */ transition: all .2s ease; } .course .course-item::after{ content:""; display: block; clear: both; } /* 頂級元素 父級元素 當前元素{} */ .course .course-item .course-image{ float: left; width: 423px; height: 210px; margin-right: 30px; } .course .course-item .course-image img{ width: 100%; } .course .course-item .course-info{ float: left; width: 596px; } .course-item .course-info h3 { font-size: 26px; color: #333; font-weight: normal; margin-bottom: 8px; } .course-item .course-info h3 span{ font-size: 14px; color: #9b9b9b; float: right; margin-top: 14px; } .course-item .course-info h3 span img{ width: 11px; height: auto; margin-right: 7px; } .course-item .course-info .teather-info{ font-size: 14px; color: #9b9b9b; margin-bottom: 14px; padding-bottom: 14px; border-bottom: 1px solid #333; border-bottom-color: rgba(51,51,51,.05); } .course-item .course-info .teather-info span{ float: right; } .course-item .lesson-list::after{ content:""; display: block; clear: both; } .course-item .lesson-list li { float: left; width: 44%; font-size: 14px; color: #666; padding-left: 22px; /* background: url("路徑") 是否平鋪 x軸位置 y軸位置 */ background: url("/static/image/play-icon-gray.svg") no-repeat left 4px; margin-bottom: 15px; } .course-item .lesson-list li .lesson-title{ /* 如下3句,文本內容過多,會自動隱藏,並顯示省略符號 */ text-overflow: ellipsis; overflow: hidden; white-space: nowrap; display:inline-block; max-width: 200px; } .course-item .lesson-list li:hover{ background-image: url("/static/image/play-icon-yellow.svg"); color: #ffc210; } .course-item .lesson-list li .free{ width: 34px; height: 20px; color: #fd7b4d; vertical-align: super; margin-left: 10px; border: 1px solid #fd7b4d; border-radius: 2px; text-align: center; font-size: 13px; white-space: nowrap; } .course-item .lesson-list li:hover .free{ color: #ffc210; border-color: #ffc210; } .course-item .pay-box::after{ content:""; display: block; clear: both; } .course-item .pay-box .discount-type{ padding: 6px 10px; font-size: 16px; color: #fff; text-align: center; margin-right: 8px; background: #fa6240; border: 1px solid #fa6240; border-radius: 10px 0 10px 0; float: left; } .course-item .pay-box .discount-price{ font-size: 24px; color: #fa6240; float: left; } .course-item .pay-box .original-price{ text-decoration: line-through; font-size: 14px; color: #9b9b9b; margin-left: 10px; float: left; margin-top: 10px; } .course-item .pay-box .buy-now{ width: 120px; height: 38px; background: transparent; color: #fa6240; font-size: 16px; border: 1px solid #fd7b4d; border-radius: 3px; transition: all .2s ease-in-out; float: right; text-align: center; line-height: 38px; } .course-item .pay-box .buy-now:hover{ color: #fff; background: #ffc210; border: 1px solid #ffc210; } </style>
在當前子應用courses下建立一個分頁器的文件paginations.py,代碼:
from rest_framework.pagination import PageNumberPagination class CoursePageNumberPagination(PageNumberPagination): """課程列表數據的分頁器""" page_query_param = "page" # 頁碼參數 page_size_query_param = 'size' # 單頁數據量 page_size = 5 # 默認的單頁數據量,就是每一頁顯示5個課程信息 max_page_size = 20 # 容許客戶端設置的單頁數據量
視圖中加載聲明的分頁器,代碼:
courses/views.py
from .models import Course from .serializers import CourseModelSerializer from django_filters.rest_framework import DjangoFilterBackend from rest_framework.filters import OrderingFilter from .paginations import CoursePageNumberPagination class CourseListAPIView(ListAPIView): """課程列表""" queryset = Course.objects.filter(is_show=True, is_delete=False).order_by("orders", "-id") serializer_class = CourseModelSerializer pagination_class = CoursePageNumberPagination filter_backends = [DjangoFilterBackend, OrderingFilter] filter_fields = ['course_category'] ordering_fields = ['id', 'students', 'price']
客戶端請求後端發送數據
Course.vue
<template> <div class="course"> <Header></Header> <div class="main"> <!-- 篩選條件 --> <div class="condition"> <ul class="cate-list"> <li class="title">課程分類:</li> <li :class="filters.category==0?'this':''" @click="filters.category=0">所有</li> <li :class="filters.category==item.id?'this':''" v-for="item in category_list" @click="filters.category=item.id">{{item.name}}</li> </ul> <div class="ordering"> <ul> <li class="title">篩 選: </li> <li class="default" :class="filters.ordering=='id'?'this asc': (filters.ordering=='-id'?'this desc':'')" @click="select_ordering('id')">默認</li> <li class="hot" :class="filters.ordering=='students'?'this asc': (filters.ordering=='-students'?'this desc':'')" @click="select_ordering('students')">人氣</li> <li class="price" :class="filters.ordering=='price'?'this asc': (filters.ordering=='-price'?'this desc':'')" @click="select_ordering('price')">價格</li> </ul> <p class="condition-result">共{{course_total}}個課程</p> </div> </div> <!-- 課程列表 --> <div class="course-list"> <div class="course-item" v-for="course in course_list"> <div class="course-image"> <img :src="course.course_img" alt=""> </div> <div class="course-info"> <h3><router-link to="/course/detail/1">{{course.name}}</router-link> <span><img src="/static/image/avatar1.svg" alt="">{{course.students}}人已加入學習</span></h3> <p class="teather-info">{{course.teacher.role_name}} {{course.teacher.name}} {{course.teacher.signature}} {{course.teacher.title}} <span>共{{course.lessons}}課時/{{course.lessons==course.pub_lessons?'更新完成':'已更新'+course.pub_lessons+'課時'}}</span></p> <ul class="lesson-list"> <li v-for="lesson,key in course.recomment_lessons"><span class="lesson-title">0{{key+1}} | 第{{lesson.id}}節:{{lesson.name}}</span> <span class="free" v-if="lesson.free_trail">免費</span></li> </ul> <div class="pay-box"> <span class="discount-type">限時免費</span> <span class="discount-price">¥0.00元</span> <span class="original-price">原價:{{course.price}}元</span> <span class="buy-now">當即購買</span> </div> </div> </div> </div> <el-pagination v-if="course_total>1" background :page-size="filters.size" :page-sizes="[2, 5, 10, 20]" layout="sizes, prev, pager, next" @size-change="handleSizeChange" :current-page="filters.page" @current-change="handleCurrentChange" :total="course_total"> </el-pagination> </div> <Footer></Footer> </div> </template> <script> import Header from "./common/Header" import Footer from "./common/Footer" export default { name: "Course", data(){ return{ filters:{ page: 1, size: 5, category: 0, // 當前點擊的課程分類ID,0表示默認值,所有 ordering: "id", // id默認,students人氣, price價格,若是值的左邊有-減號,則表示倒敘,不然是正序 }, category_list: [], // 課程分類列表 course_total: 0, // 當前課程的總數量 course_list: [ { teacher: {}, } ], } }, created(){ this.get_course_category(); this.get_course(); }, watch:{ // 每次分類ID進行了修改,都要從新獲取一次課程列表信息 "filters.category": function(){ this.filters.page = 1; this.get_course(); }, "filters.ordering": function(){ this.get_course(); }, "filters.page": function(){ this.get_course(); }, "filters.size": function(){ this.filters.page = 1; // this.get_course(); }, }, methods:{ get_course_category(){ // 獲取課程分類 this.$axios.get(`${this.$settings.Host}/courses/category/`).then(response=>{ this.category_list = response.data; }).catch(error=>{ this.$message.error("沒法獲取課程分類信息!"); }) }, get_course(){ // 獲取課程列表 let filters = { "ordering": this.filters.ordering, }; // 判斷課程分類ID不是0,則須要指定分類到後端查詢對應分類的數據,拼接參數 if(this.filters.category != 0){ filters.course_category = this.filters.category; } filters.page = this.filters.page; filters.size = this.filters.size; // 獲取課程信息 this.$axios.get(`${this.$settings.Host}/courses/`,{ params: filters }).then(response=>{ this.course_list = response.data.results; // 課程列表 this.course_total = response.data.count; // 課程總數 }).catch(error=>{ this.$message.error("沒法獲取課程信息!"); }) }, select_ordering(ordering){ // 判斷切換不一樣的排序方式 if(ordering=="id"){ // 點擊了默認 if(this.filters.ordering=="id"){ this.filters.ordering="-id"; }else{ this.filters.ordering="id"; } }else if(ordering=="students"){ // 點擊了人氣 if(this.filters.ordering=="students"){ this.filters.ordering="-students"; }else{ this.filters.ordering="students"; } }else if(ordering=="price"){ // 點擊了價格 if(this.filters.ordering=="price"){ this.filters.ordering="-price"; }else{ this.filters.ordering="price"; } } }, handleCurrentChange(page){ // 切換頁碼 this.filters.page = page; }, handleSizeChange(size){ // 切換單頁顯示數據量 this.filters.size = size; } }, components:{ Header, Footer, } } </script> <style scoped> .course{ background: #f6f6f6; } .course .main{ width: 1100px; margin: 35px auto 0; } .course .condition{ margin-bottom: 35px; padding: 25px 30px 25px 20px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px 0 #f0f0f0; } .course .cate-list{ border-bottom: 1px solid #333; border-bottom-color: rgba(51,51,51,.05); padding-bottom: 18px; margin-bottom: 17px; } .course .cate-list::after{ content:""; display: block; clear: both; } .course .cate-list li{ float: left; font-size: 16px; padding: 6px 15px; line-height: 16px; margin-left: 14px; position: relative; transition: all .3s ease; cursor: pointer; color: #4a4a4a; border: 1px solid transparent; /* transparent 透明 */ } .course .cate-list .title{ color: #888; margin-left: 0; letter-spacing: .36px; padding: 0; line-height: 28px; } .course .cate-list .this{ color: #ffc210; border: 1px solid #ffc210!important; border-radius: 30px; } .course .ordering::after{ content:""; display: block; clear: both; } .course .ordering ul{ float: left; } .course .ordering ul::after{ content:""; display: block; clear: both; } .course .ordering .condition-result{ float: right; font-size: 14px; color: #9b9b9b; line-height: 28px; } .course .ordering ul li{ float: left; padding: 6px 15px; line-height: 16px; margin-left: 14px; position: relative; transition: all .3s ease; cursor: pointer; color: #4a4a4a; } .course .ordering .title{ font-size: 16px; color: #888; letter-spacing: .36px; margin-left: 0; padding:0; line-height: 28px; } .course .ordering .this{ color: #ffc210; } .course .ordering .price{ position: relative; } .course .ordering .this::before, .course .ordering .this::after{ cursor: pointer; content:""; display: block; width: 0px; height: 0px; border: 5px solid transparent; position: absolute; right: 0; } .course .ordering .this::before{ border-bottom: 5px solid #aaa; margin-bottom: 2px; top: 2px; } .course .ordering .this::after{ border-top: 5px solid #aaa; bottom: 2px; } .course .ordering .desc::after{ border-top: 5px solid #ffc210; } .course .ordering .asc::before{ border-bottom: 5px solid #ffc210; } .course .course-item:hover{ box-shadow: 4px 6px 16px rgba(0,0,0,.5); } .course .course-item{ width: 1050px; background: #fff; padding: 20px 30px 20px 20px; margin-bottom: 35px; border-radius: 2px; cursor: pointer; box-shadow: 2px 3px 16px rgba(0,0,0,.1); /* css3.0 過渡動畫 hover 事件操做 */ transition: all .2s ease; } .course .course-item::after{ content:""; display: block; clear: both; } /* 頂級元素 父級元素 當前元素{} */ .course .course-item .course-image{ float: left; width: 423px; height: 210px; margin-right: 30px; } .course .course-item .course-image img{ width: 100%; } .course .course-item .course-info{ float: left; width: 596px; } .course-item .course-info h3 { font-size: 26px; color: #333; font-weight: normal; margin-bottom: 8px; } .course-item .course-info h3 span{ font-size: 14px; color: #9b9b9b; float: right; margin-top: 14px; } .course-item .course-info h3 span img{ width: 11px; height: auto; margin-right: 7px; } .course-item .course-info .teather-info{ font-size: 14px; color: #9b9b9b; margin-bottom: 14px; padding-bottom: 14px; border-bottom: 1px solid #333; border-bottom-color: rgba(51,51,51,.05); } .course-item .course-info .teather-info span{ float: right; } .course-item .lesson-list::after{ content:""; display: block; clear: both; } .course-item .lesson-list li { float: left; width: 44%; font-size: 14px; color: #666; padding-left: 22px; /* background: url("路徑") 是否平鋪 x軸位置 y軸位置 */ background: url("/static/image/play-icon-gray.svg") no-repeat left 4px; margin-bottom: 15px; } .course-item .lesson-list li .lesson-title{ /* 如下3句,文本內容過多,會自動隱藏,並顯示省略符號 */ text-overflow: ellipsis; overflow: hidden; white-space: nowrap; display:inline-block; max-width: 200px; } .course-item .lesson-list li:hover{ background-image: url("/static/image/play-icon-yellow.svg"); color: #ffc210; } .course-item .lesson-list li .free{ width: 34px; height: 20px; color: #fd7b4d; vertical-align: super; margin-left: 10px; border: 1px solid #fd7b4d; border-radius: 2px; text-align: center; font-size: 13px; white-space: nowrap; } .course-item .lesson-list li:hover .free{ color: #ffc210; border-color: #ffc210; } .course-item .pay-box::after{ content:""; display: block; clear: both; } .course-item .pay-box .discount-type{ padding: 6px 10px; font-size: 16px; color: #fff; text-align: center; margin-right: 8px; background: #fa6240; border: 1px solid #fa6240; border-radius: 10px 0 10px 0; float: left; } .course-item .pay-box .discount-price{ font-size: 24px; color: #fa6240; float: left; } .course-item .pay-box .original-price{ text-decoration: line-through; font-size: 14px; color: #9b9b9b; margin-left: 10px; float: left; margin-top: 10px; } .course-item .pay-box .buy-now{ width: 120px; height: 38px; background: transparent; color: #fa6240; font-size: 16px; border: 1px solid #fd7b4d; border-radius: 3px; transition: all .2s ease-in-out; float: right; text-align: center; line-height: 38px; } .course-item .pay-boxthis .buy-now:hover{ color: #fff; background: #ffc210; border: 1px solid #ffc210; } </style>
修改分頁樣式中的字體大小和居中,static/reset.css,代碼:
.el-pagination{ text-align: center; margin-top: 18px; margin-bottom: 28px; }