Bootstrap

django+vue项目-制作一个简单的购物网站

#后端

django_rest_form的详细过程

文件配置

安装restframework

pip install djangorestframework

安装filter(在接口搜素文件的时候会用到)

pip install django-filter

安装jwt

pip install djangorestframework-jwt

安装pillow(上传照片时会用到)

pip install pillow

设置跨域请求

pip install django-cors-header

setting的配置(setting .py)

注册app

在INSTALLED_APPS添加新建的应用,如restframework

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'apps.user.apps.UserConfig',
    'apps.task.apps.TaskConfig',
    'rest_framework',
    'corsheaders',
    'rest_framework.authtoken',#jwt认证的时候要加上
    'django_filters',
]
自定义设置规则(rest_framework,jwt)
    'DEFAULT_FILTER_BACKENDS': (
        'django_filters.rest_framework.DjangoFilterBackend',
    ),
    'EXCEPTION_HANDLER': 'common.customexception.custom_exception_handler',
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',

    ),
    "DEFAULT_SCHEMA_CLASS": "rest_framework.schemas.coreapi.AutoSchema"

    # 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAuthenticated',),

}

JWT_AUTH = {
    # 自定义jwt异常
    # 'JWT_RESPONSE_PAYLOAD_ERROR_HANDLER': 'common.jwt_exception.jwt_response_payload_error_handler',
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),  # Token 过期时间为一周
    'JWT_AUTH_HEADER_PREFIX': 'JWT',  # Token的头为:JWT XXXXXXXXXXXXXXXXXXXXXX
    'JWT_ALLOW_REFRESH': True,
    # 自定义返回认证信息
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'common.jwt_utils.jwt_response_payload_handler',
    'JWT_RESPONSE_PAYLOAD_ERROR_HANDLER': 'Users.views.jwt_response_payload_error_handler',

}

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',#用在跨域请求上面,要发布放在第一个位置
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',

]
设置默认资源地址(static(用于静态资源的存放),media(用于图片,文件上传))
STATIC_URL = '/static/'
MEDIA_URL = '/media/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
# STATICFILES_DIRS = [os.path.join(BASE_DIR, 'media')]

# STATIC_ROOT = os.path.join(BASE_DIR, 'static')
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
设置默认用户列表(自定义用户列表)
AUTH_USER_MODEL = "user.MyUser"
设置跨域请求的配置
CORS_ALLOW_CREDENTIALS = True

路由的配置(urls.py)

路由分配(保证每一个接口都专门化)
    path('admin/', admin.site.urls),
    path('user/', include("apps.user.user_urls")),#用于用户相关的请求
    path('task/', include("apps.task.task_urls")),#用于任务相关的请求
    path('docs/', include_docs_urls(title="校园任务接口平台")),#接口文档
设置static,media,等资源分配的路由地址
from django.views.static import serve
from django.conf import settings
  
 re_path('static/(?P<path>.*)', serve, {"document_root": settings.STATIC_ROOT}),
 re_path('media/(?P<path>.*)', serve, {"document_root": settings.MEDIA_ROOT}),

admin文件的配置(admin.py)

from django.contrib import admin
from .models import *


# Register your models here.#注册用户自己的用户模型,方便在后台管理数据
@admin.register(MyUser)
class admin_MyUser(admin.ModelAdmin):
    list_display = ['username']

序列化文件的配置(serializer.py)
from rest_framework import serializers
from .models import *


class serializer_mark(serializers.ModelSerializer):
#可以在这里定义几个字段(在数据模型中的,方便管理,如设置只读)
xxxx=selializer.textfield(readonly=true)
    class Meta:
        model = mark
        fields = ('mark', "id",)
        # exclude = ("create_time", 'update_time', 'click_count')
        #fields = "__all__"

数据表的配置(models.py)

用户列表
from django.contrib.auth.models import AbstractUser#用于自定义用户表,或者说想要额外添加信息的话就可以用则会个类
from django.db import models


class MyUser(AbstractUser):
    username = models.CharField('姓名', blank=True, max_length=50, unique=True)
    mobile = models.CharField('手机号码', max_length=11, default="", blank=True)
    wx = models.CharField('微信号', max_length=20, default="", blank=True)
    qq = models.CharField('qq号码', max_length=20, default="", blank=True)
    mibao = models.CharField('密保问题', max_length=200, default="", blank=True)
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")

    def __str__(self):
        return self.username

定义需要的数据库模型(定义类)
class MyUser(AbstractUser):
注意要设置成是否为空
username = models.CharField('姓名', blank=True, max_length=50, unique=True)
注意外键关系(要关联那些数据模型)


class task_details(models.Model):
    title = models.CharField(max_length=30, null=True, blank=True, verbose_name="任务标题")
    catgory = models.ForeignKey(mark, on_delete=models.CASCADE, null=True, blank=True, verbose_name="任务分类")
    introduction = models.TextField(max_length=500, blank=True, null=True, verbose_name="任务介绍")
    click_count = models.IntegerField(verbose_name="任务点击数量", blank=True, null=True)
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
    user_id = models.ForeignKey(MyUser, on_delete=models.CASCADE, verbose_name="所属用户", default=5)#关联了用户自定义的用户列表

视图函数(views.py)

主要使用viewset.modelviewset类(如果额外需求可以重写他的函数)
class view_all_task(CustomModelViewSet):#这个是继承我们自己定义的(改装默认的类)
    queryset = task_details.objects.all()#填写models中的数据模型
    serializer_class = serialiser_task_details#写序列化类中的内容
    pagination_class = MyPage#写自己的自定义分页类

#tips:可以将自定义(继承子上一个类)的文件放到单数一个文件,方便以后管理
定义自定义返回类(在viewset.modelviewset的基础下)
from rest_framework import status
from rest_framework import viewsets
from common.customresponse import CustomResponse


class CustomModelViewSet(viewsets.ModelViewSet):

    # CreateModelMixin->create
    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return CustomResponse(data=serializer.data, code=201, msg="OK", status=status.HTTP_201_CREATED, headers=headers)

    # ListModelMixin->list
    def list(self, request, *args, **kwargs):
        queryset = self.get_queryset()
        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return CustomResponse(data=serializer.data, code=200, msg="OK", status=status.HTTP_200_OK)

    # RetrieveModelMixin->retrieve
    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return CustomResponse(data=serializer.data, code=200, msg="OK", status=status.HTTP_200_OK)

    # UpdateModelMixin->update
    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        return CustomResponse(data=serializer.data, code=200, msg="OK", status=status.HTTP_200_OK)

    # DestroyModelMixin->destroy
    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return CustomResponse(data=[], code=204, msg="OK", status=status.HTTP_204_NO_CONTENT)

from rest_framework.response import Response
from rest_framework.serializers import Serializer


class CustomResponse(Response):
    def __init__(self, data=None, code=None, msg=None,
                 status=None,
                 template_name=None, headers=None,
                 exception=False, content_type=None, **kwargs):
        super().__init__(None, status=status)

        if isinstance(data, Serializer):
            msg = (
                'You passed a Serializer instance as data, but '
                'probably meant to pass serialized `.data` or '
                '`.error`. representation.'
            )
            raise AssertionError(msg)

        self.data = {'code': code, 'msg': msg, 'data': data}
        self.data.update(kwargs)
        self.template_name = template_name
        self.exception = exception
        self.content_type = content_type

        if headers:
            for name, value in headers.items():
                self[name] = value

自定义异常类
from rest_framework.views import exception_handler

def custom_exception_handler(exc, context):

    response = exception_handler(exc, context)

    if response is not None:
        response.data.clear()
        #组装code、msg、data
        response.data['code'] = response.status_code

        if response.status_code == 405:
            response.data["msg"]="请求不允许"
        elif response.status_code == 401:
            response.data["msg"] = "认证未通过"
        elif response.status_code == 403:
            response.data["msg"] = "禁止访问"
        elif response.status_code == 404:
            response.data["msg"] = "未找到文件"
        elif response.status_code >= 500:
            response.data["msg"]="服务器异常"
        else:
            response.data["msg"] = "其他未知错误"

        response.data['data'] = []
    return response
自定义分页类
from rest_framework import status
from rest_framework.pagination import PageNumberPagination
from common.customresponse import CustomResponse

class MyPage(PageNumberPagination):
    page_size = 8 #每页显示数量
    max_page_size = 50 #每页最大显示数量。
    page_size_query_param = 'size' #每页数量的参数名称
    page_query_param = 'page'  #页码的参数名称

    def get_paginated_response(self, data):
        return CustomResponse(data=data,code=200,msg="OK",status=status.HTTP_200_OK, count=self.page.paginator.count,
            next=self.get_next_link(),previous=self.get_previous_link())

自定义用户列表

取代django默认的用户列表(别忘了在setting里面设置默认的用户列表)
from django.contrib.auth.models import AbstractUser
from django.db import models

###在使用之前要删除以前的数据库,生成的sql语句,只能添加一些额外的个人信息(在django默认的用户列表的基础上)
class MyUser(AbstractUser):
    username = models.CharField('姓名', blank=True, max_length=50, unique=True)
    mobile = models.CharField('手机号码', max_length=11, default="", blank=True)
    wx = models.CharField('微信号', max_length=20, default="", blank=True)
    qq = models.CharField('qq号码', max_length=20, default="", blank=True)
    mibao = models.CharField('密保问题', max_length=200, default="", blank=True)
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")

    def __str__(self):
        return self.username

用户权限

目前使用的使用的是jwt认证方式
自定义jwt消息返回(在login的时候)
def jwt_response_payload_handler(token, user=None, request=None):
    return {
        "token": token,
        'id': user.id,
        'username': user.username,
        'email': user.email,
        'is_active': user.is_active,
    }

实现用户登录
from django.urls import path
from rest_framework_jwt.views import obtain_jwt_token

urlpatterns = [
    path('login', obtain_jwt_token),

]

实现用户注册

########url文件中的配置



from django.urls import path
from rest_framework_jwt.views import obtain_jwt_token
from .views import *

user_list = MyUserViewSet.as_view({
    'get': 'retrieve',
    'post': 'create',
})

user_detail = MyUserViewSet.as_view({
    'get': 'retrieve',
    'put': 'update',
    'patch': 'partial_update',
    'delete': 'destroy',
})

urlpatterns = [
    path('login', obtain_jwt_token),
    path('users/', user_list), #用于用户注册
    path('users/<pk>/', user_detail),  # 查找、更新、删除

]

####views中的配置
class MyUserViewSet(CustomModelViewSet):
    queryset = MyUser.objects.all()
    serializer_class = MyUserRegSerializer
    authentication_classes = (JSONWebTokenAuthentication,)#指定需要哪一种权限验证方式

    def get_serializer_class(self):
        if self.action == "create":
            return MyUserRegSerializer
        elif self.action == "retrieve":
            return MyUserUpdate
        elif self.action == "update":
            return MyUserUpdate
        return MyUserUpdate

    def get_permissions(self):
        if self.action == "retrieve":
            return [permissions.IsAuthenticated()]
        elif self.action == "update":
            return [permissions.IsAuthenticated()]

        else:
            # return []
            return []

    # 获取当前用户
    def get_object(self):
        return self.request.user
#######在serializer中
from .models import *
import re
from rest_framework import serializers


# 用户注册序列化类
class MyUserRegSerializer(serializers.ModelSerializer):
    class Meta:
        model = MyUser
        fields = ("id", "username", "password", "mobile", "wx", "qq", "mibao")

    def create(self, validated_data):
        user = super().create(validated_data=validated_data)
        user.set_password(validated_data["password"])
        user.save()
        return user

    def validate_username(self, username):
        # 判断用户名是否注册
        if MyUser.objects.filter(username=username).count():
            raise serializers.ValidationError("用户名已经存在,请查询")
            # return JsonResponse({'errors': serializers.errors}, status=500)
        return username

    def validate_mobile(self, mobile):
        # 判断手机号码是否注册
        if MyUser.objects.filter(mobile=mobile).count():
            raise serializers.ValidationError("手机号码已经存在,请查询")
        # 手机号码正则表达式
        REGEX_MOBILE = "^1[345789]\d{9}$|^147\d{8}$|^176\d{8}$"
        # 验证手机号码是否合法
        if not re.match(REGEX_MOBILE, mobile):
            raise serializers.ValidationError("手机号码非法")

        return mobile


# 用户资料更新
class MyUserUpdate(serializers.ModelSerializer):

    class Meta:
        model = MyUser
        fields = ("username", "mobile", "wx", "qq", "mibao")


class get_id(serializers.ModelSerializer):
    class Meta:
        model = MyUser
        fields = ("username", "wx", "qq")


#####在models中
from django.contrib.auth.models import AbstractUser
from django.db import models


class MyUser(AbstractUser):
    username = models.CharField('姓名', blank=True, max_length=50, unique=True)
    mobile = models.CharField('手机号码', max_length=11, default="", blank=True)
    wx = models.CharField('微信号', max_length=20, default="", blank=True)
    qq = models.CharField('qq号码', max_length=20, default="", blank=True)
    mibao = models.CharField('密保问题', max_length=200, default="", blank=True)
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")

    def __str__(self):
        return self.username

在试图函数里面设置权限
####在custommodelviewset里面定义权限
    permission_classes = (IsAuthenticated,)

前端

vue+element_ui_plus的详细过程

文件配置

新建一个项目(vite),在webstormzhong完成的
安装element-ui-plus,并main.js文件里引入

npm install element-plus --save

import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'

const app = createApp(App)

app.use(ElementPlus)
app.mount('#app')
安装router,并在main.js文件里引入

npm install vue-router --save

import router from "./router/index";

app.use(router)
安装vuex,并在main.js文件里引入

npm install vuex --save

import store from "@/vuex/store";
app.use(store)
安装axios,并做到所有请求统一管理

npm install axios --save

import axios from 'axios'

var URL = ''
// 创建 axios 实例
const service = axios.create({
    baseURL: URL,
    timeout: 6000 // 请求超时时间
})

// request拦截器
service.interceptors.request.use(config => {

}
)
// response拦截器
service.interceptors.response.use({}
)

export default service

目录介绍

src
src/api
专门统一化前端请求,在这里面模块画axios请求,例如
import request from '@/utils/request'

// 封装请求的方式
export function get_home_task(page) {
    return request({
        url: 'api/task/get/all?page='+page,
        method: 'get',
    })
}
//在组件里面这样用
import {get_home_task} from "@/api/task";
get_home_task().then((response) => {
        this.mark_list = response.data.data;
      });
src/assets

存放前端要用到的各种资源,包括图片,js,css,音频文件等

src/components

在里面存放组件,例如导航栏,侧栏,卡片之类的,就相当于html页面

src/router

有index.js文件,相当于django中的urls文件,用于路由分配,切换组件等

使用方法
import {createRouter, createWebHistory} from "vue-router";
const routes = [
    #路由地址
{
        path: "/us",
        name: 'us',
        component: us
    }
]
const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL),
    routes: routes
});
export default router;

src/utils

有requests,js,存放axios的请求拦截器,响应拦截器

使用用法
import axios from 'axios'

var URL = ''
// 创建 axios 实例
const service = axios.create({
    baseURL: URL,
    timeout: 6000 // 请求超时时间
})

// request拦截器
service.interceptors.request.use(config => {
        const {url} = config
        //指定页面访问需要JWT认证。
        if (url.indexOf('api/task/img/picture/') !== -1 || url.indexOf('api/user/users/') !== -1 || url.indexOf('api/task/person') !== -1 || url === 'api/task/person/task/' || url === 'api/task/img/picture/') {
            var jwt = localStorage.getItem('token');
            config.headers.Authorization = 'JWT  ' + jwt;
        }
        return config
    },
    error => {
        Promise.reject(error)
    }
)
// response拦截器
service.interceptors.response.use({}
)

export default service

src/vuex

里面有store.js文件,可以配置一些变量,方便在组件之间公用数据

import Vuex from 'vuex'

//定义便变量
const state = {
    notice: "打开",
    userinfo: localStorage["userinfo"],
    token: localStorage["token"],
    name: localStorage["name"],
    id: localStorage["id"]

}

//定义同步方法
const mutations = {
    saveUser(state, value) {
        state.userinfo = value, localStorage.setItem('userinfo', value)
    },
    save_user_token(state, token) {
        state.token = token, localStorage.setItem('token', token)


    },
    save_user_name(state, name) {
        state.name = name, localStorage.setItem('name', name)
    },
    save_user_id(state, id) {
        state.id = id, localStorage.setItem('id', id)
    },
    delUser(state) {
        state.userinfo = '';
        state.token = '';
        state.name = '';
        state.id = '';
        localStorage['userinfo'] = '';
        localStorage['token'] = '';
        localStorage['name'] = '';
        localStorage['token'] = '';
        localStorage['id'] = '';

    }
}

//用于取值
const getters = {
    get_user_info(state) {
        return state.userinfo
    },
    get_user_name(state){
        return state.name
    },
    get_user_id(state){
        return state.id
    }
}

//注册前面几个东西
const store = new Vuex.Store({
    state, mutations, getters
})


export default store
//在组件中的用法
import {mapGetters, mapMutations} from "vuex";

this.$store.status.xxxx

method:{
 ...mapMutations({
      save_user_info: "saveUser"
    })
}
computed: {
    ...mapGetters({
      userinfo: 'get_user_info'
    })
  }

代理(解决前后端数据交互问题)

在vite.config.js文件里面

import {fileURLToPath, URL} from 'node:url'

import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({

    plugins: [vue()],
    resolve: {
        alias: {
            '@': fileURLToPath(new URL('./src', import.meta.url))
        }
    },
    server: {
        // hmr: {overlay: false},
        proxy: {
            '/api': {
                target: 'http://127.0.0.1:8000',//替换的服务端地址
                changeOrigin: true,//开启代理,允许跨域
                rewrite: path => path.replace(/^\/api/, "")//设置重写的路径
            }
        }
    },


})


项目打包

npm run build

;