Django项目实战——9—(查询省市区数据、新增地址前后端逻辑、新增地址接口设计和定义、展示地址前后端逻辑)

1、查询省市区数据

在这里插入图片描述

请求方式

在这里插入图片描述
请求参数:查询参数

  • 如果前端没有传入area_id,表示用户需要省份数据
  • 如果前端传入了area_id,表示用户需要市或区数据
    在这里插入图片描述
    响应结果:JSON
  • 省份数据
 '''   返回给前端界面数据格式如下
            {
              "code":"0",
              "errmsg":"OK",
              "province_list":[
                  {
                      "id":110000,
                      "name":"北京市"
                  },
                  {
                      "id":120000,
                      "name":"天津市"
                  },
                  {
                      "id":130000,
                      "name":"河北省"
                  },
                  ......
              ]
            }
            '''
  • 市或区数据
{
  "code":"0",
  "errmsg":"OK",
  "sub_data":{
      "id":130000,
      "name":"河北省",
      "subs":[
          {
              "id":130100,
              "name":"石家庄市"
          },
          ......
      ]
  }
}

查询省市区数据后端逻辑实现

  • 如果前端没有传入area_id,表示用户需要省份数据
  • 如果前端传入了area_id,表示用户需要市或区数据
class AreasView(View):
    """省市区数据"""
    def get(self, request):
        """提供省市区数据"""
        area_id = request.GET.get('area_id')
        if not area_id:
            # 提供省份数据
            try:
                # 查询省份数据
                province_model_list = Area.objects.filter(parent__isnull=True)
                # 序列化省级数据
                province_list = []
                for province_model in province_model_list:
                    province_list.append({'id': province_model.id, 'name': province_model.name})
            except Exception as e:
                logger.error(e)
                return JsonResponse({'code': RETCODE.DBERR, 'errmsg': '省份数据错误'})
            # 响应省份数据
            return JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'province_list': province_list})
        else:
            # 提供市或区数据
            try:
                parent_model = Area.objects.get(id=area_id)  # 查询市或区的父级
                sub_model_list = parent_model.subs.all()
                # 序列化市或区数据
                sub_list = []
                for sub_model in sub_model_list:
                    sub_list.append({'id': sub_model.id, 'name': sub_model.name})
                sub_data = {
                    'id': parent_model.id,  # 父级pk
                    'name': parent_model.name,  # 父级name
                    'subs': sub_list  # 父级的子集
                }
            except Exception as e:
                logger.error(e)
                return JsonResponse({'code': RETCODE.DBERR, 'errmsg': '城市或区数据错误'})
            # 响应市或区数据
            return JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'sub_data': sub_data})

Vue渲染省市区数据

  • user_center_site.js
mounted() {
    // 获取省份数据
    this.get_provinces();
},
// 获取省份数据
get_provinces(){
    let url = '/areas/';
    axios.get(url, {
        responseType: 'json'
    })
        .then(response => {
            if (response.data.code == '0') {
                this.provinces = response.data.province_list;
            } else {
                console.log(response.data);
                this.provinces = [];
            }
        })
        .catch(error => {
            console.log(error.response);
            this.provinces = [];
        })
},
watch: {
    // 监听到省份id变化
    'form_address.province_id': function(){
        if (this.form_address.province_id) {
            let url = '/areas/?area_id=' + this.form_address.province_id;
            axios.get(url, {
                responseType: 'json'
            })
                .then(response => {
                    if (response.data.code == '0') {
                        this.cities = response.data.sub_data.subs;
                    } else {
                        console.log(response.data);
                        this.cities = [];
                    }
                })
                .catch(error => {
                    console.log(error.response);
                    this.cities = [];
                })
        }
    },
    // 监听到城市id变化
    'form_address.city_id': function(){
        if (this.form_address.city_id){
            let url = '/areas/?area_id='+ this.form_address.city_id;
            axios.get(url, {
                responseType: 'json'
            })
                .then(response => {
                    if (response.data.code == '0') {
                        this.districts = response.data.sub_data.subs;
                    } else {
                        console.log(response.data);
                        this.districts = [];
                    }
                })
                .catch(error => {
                    console.log(error.response);
                    this.districts = [];
                })
        }
    }
},

user_center_site.html

<div class="form_group">
    <label>*所在地区:</label>
    <select v-model="form_address.province_id">
        <option v-for="province in provinces" :value="province.id">[[ province.name ]]</option>
    </select>
    <select v-model="form_address.city_id">
        <option v-for="city in cities" :value="city.id">[[ city.name ]]</option>
    </select>
    <select v-model="form_address.district_id">
        <option v-for="district in districts" :value="district.id">[[ district.name ]]</option>
    </select>
</div>

缓存省市区数据

  • 省市区数据是我们动态查询的结果。
  • 但是省市区数据不是频繁变化的数据,所以没有必要每次都重新查询。
  • 所以我们可以选择对省市区数据进行缓存处理。

缓存工具

  • from django.core.cache import cache
  • 存储缓存数据:cache.set('key', 内容, 有效期)
  • 读取缓存数据:cache.get('key')
  • 删除缓存数据:cache.delete('key')

注意:存储进去和读取出来的数据类型相同,所以读取出来后可以直接使用。

缓存逻辑图
在这里插入图片描述
缓存逻辑实现

  • 省份缓存数据
    cache.set('province_list', province_list, 3600)
  • 市或区缓存数据
    cache.set('sub_area_' + area_id, sub_data, 3600)

项目实例代码如下:

用户地址视图文件apps/areas/views.py文件

"""
用户地址视图文件:apps/areas/views.py文件
"""
from django.shortcuts import render
from django.views import View
from django import http
from .models import Area                        # 导入地址模型
from utils.response_code import RETCODE         # 导入定义的状态码文件
from django.core.cache import cache             # 保存省市区数据到redis,默认配置的redis参数
# from django_redis import get_redis_connection   # 连接redis
import logging                                  # 错误日志


logger = logging.getLogger('django')            # 保存到‘django’名称的日志文件中,创建logger对象


class AreasView(View):
    """省市区三级联动"""
    
    def get(self, request):
        # 判断当前是要查询省份还是市区数据
        area_id = request.GET.get('area_id')       # area_id是user_center_site.js文件中area_id被监听的数据
        
        if not area_id:                            # 数据不存在
    
            # 缓存数据中进行查询,存在就不需要重新查询省市数据信息
            province_list = cache.get('province_list')
            
            # 如果不存在缓存数据信息,再进行查询省市区数据
            if not province_list:
                try:                                                             # 异常处理
                    # 查询省级数据
                    province_model_list = Area.objects.filter(parent__isnull=True)        # parent是外键,查询出模型对象
                    print(province_model_list)                                            # 省级数据
                    # 返回字典类型的数据给前端界面
                    '''   返回给前端界面数据格式如下
                    {
                      "code":"0",
                      "errmsg":"OK",
                      "province_list":[                      # list类型
                          {
                              "id":110000,
                              "name":"北京市"
                          },
                          {
                              "id":120000,
                              "name":"天津市"
                          },
                          {
                              "id":130000,
                              "name":"河北省"
                          },
                          ......
                      ]
                    }
                    '''
                    province_list = []                            # list类型
                    for province_model in province_model_list:
                        # print(type(province_model))                     # <class 'areas.models.Area'>
                        # print(province_model)
                        province_dict = {                               # 组合数据成字典类型
                            "id": province_model.id,                    # <class 'areas.models.Area'>中的id属性
                            "name": province_model.name,                # <class 'areas.models.Area'>中的name属性
                        }
                        province_list.append(province_dict)             # 添加数据到list中
                        
                    # 保存数据到redis中,dev.py配置文件中默认的,缓存, 保存时效3600秒,保存拼接后的数据
                    cache.set("province_list", province_list, 3600)
                except Exception as e:
                    # 保存错误信息到日志文件中
                    logger.error(e)
                    return http.JsonResponse({"code": RETCODE.DBERR, "errmsg": "查询省份数据异常"})   # 异常处理
                
            # else:存在缓存数据province_list或者数据,直接返回该数据到前端界面即可
            
            # 返回数据类型到前端界面进行渲染,要与js相匹配
            # 此时是两种情况:1.缓存数据查询到了,直接返回  2.不存在缓存数据,通过查询数据库获得province_list,并返回
            return http.JsonResponse({'code': RETCODE.OK, "errmsg": "OK", "province_list": province_list})
            
        else:
            # 查询redis数据库中的数据,缓存数据
            sub_data = cache.get("sub_data_" + area_id)
            
            if not sub_data:     # 不存在redis数据库中的数据,即没有缓存数据
                try:             # 异常处理
                    # 查询市级数据
                    # print(area_id)                          # 该数据是id字段,不是parent_id字段数据
                    """ 返回给前端的市级数据的格式如下
                    {
                      "code":"0",
                      "errmsg":"OK",
                      "sub_data":{                            # 字典类型
                          "id":130000,
                          "name":"河北省",
                          "subs":[                            # list数据类型
                              {
                                  "id":130100,
                                  "name":"石家庄市"
                              },
                              ......
                          ]
                      }
                    }
                    """
                    parent_model = Area.objects.get(id=area_id)                 # 数据库中查询数据id与area_id相同的,通过area_id查询出省份的名称和id信息
                    subs_model_list = Area.objects.filter(parent__id=area_id)   # 查询省级area_id下的所有市级数据
                    # print(subs_model_list.query)                               # 查询subs_model_list的SQL语句
                    
                    subs = []                                                   # list
                    for sub_model in subs_model_list:
                        sub_dict = {                                            # 拼接数据
                            "id": sub_model.id,
                            "name": sub_model.name,
                        }
                        subs.append(sub_dict)                                   # 将数据添加到subs列表中
                        
                    # 拼接数据并传输到前端渲染
                    sub_data = {                                                # 字典类型
                        "id": parent_model.id,                                  # 省份数据的id
                        "name": parent_model.name,                              # 省份数据的name
                        "subs": subs,                                           # 使用前面拼接的数据
                    }
                    
                    # 存数据到redis中
                    cache.set("sub_data_" + area_id, sub_data, 3600)            # 存数据,字符拼接命名, 3600秒有效期
                
                except Exception as e:                                          # 异常处理
                    # 保存错误信息到日志文件中
                    logger.error(e)
                    return http.JsonResponse({"code": RETCODE.DBERR, "errmsg": "查询城市或者区县数据错误"})   # 返回错误信息

            # 返回给前端数据,此时是两种情况:1.缓存数据查询到了,直接返回  2.不存在缓存数据,通过查询数据库获得sub_data,并返回
            return http.JsonResponse({"code": RETCODE.OK, "errmsg": "OK", "sub_data": sub_data})

    

地址areas模块的路由文件apps/areas/urls.py

# -*- encoding: utf-8 -*-
"""
@File    : urls.py
@Time    : 2020/8/24 14:29
@Author  : chen

地址areas模块的路由文件:apps/areas/urls.py
"""
from django.urls import path, re_path
from . import views


app_name = 'areas'


urlpatterns = [
    # 区域, 路由与js相对应
    path('areas/', views.AreasView.as_view())
]

项目总路由文件shop/urls.py

'''
项目总路由文件:shop/urls.py
'''

from django.contrib import admin
from django.urls import path, include


urlpatterns = [
    path('admin/', admin.site.urls),
    
    path('users/', include('users.urls')),    # 如果当时注册users模块时候没有使用sys.path.insert导入路径,这里就需要改为 'apps.users.urls'
    path('', include('contents.urls')),       # 首页路由
    path('', include('verifications.urls')),  # 验证码路由
    path('', include('oauth.urls')),          # QQ登陆路由
    path('', include('areas.urls')),          # 用户地址模块路由
]

个人用户中心地址界面静态文件static/js/user_center_site.js

// 个人用户中心地址界面静态文件:static/js/user_center_site.js
let vm = new Vue({
    el: '#app',
    delimiters: ['[[', ']]'],
    data: {
        username: getCookie('username'),
        is_show_edit: false,
        form_address: {
            receiver: '',
            province_id: '',
            city_id: '',
            district_id: '',
            place: '',
            mobile: '',
            tel: '',
            email: '',
        },

        provinces: [],
        cities: [],
        districts: [],
        addresses: JSON.parse(JSON.stringify(addresses)),
        default_address_id: default_address_id,
        editing_address_index: '',
        edit_title_index: '',
        new_title: '',

        error_receiver: false,
        error_place: false,
        error_mobile: false,
        error_tel: false,
        error_email: false,
    },
    mounted() {
        // 获取省份数据  页面加载完成再执行
        this.get_provinces();
    },
    watch: {
        // 监听到省份id变化
        'form_address.province_id': function(){
            if (this.form_address.province_id) {
                let url = '/areas/?area_id=' + this.form_address.province_id;    // 监听area_id的数据
                axios.get(url, {
                    responseType: 'json'
                })
                    .then(response => {
                        if (response.data.code == '0') {
                            this.cities = response.data.sub_data.subs;
                        } else {
                            console.log(response.data);
                            this.cities = [];
                        }
                    })
                    .catch(error => {
                        console.log(error.response);
                        this.cities = [];
                    })
            }
        },
        // 监听到城市id变化
        'form_address.city_id': function(){
            if (this.form_address.city_id){
                let url = '/areas/?area_id='+ this.form_address.city_id;                // 监听area_id的数据
                axios.get(url, {
                    responseType: 'json'
                })
                    .then(response => {
                        if (response.data.code == '0') {
                            this.districts = response.data.sub_data.subs;
                        } else {
                            console.log(response.data);
                            this.districts = [];
                        }
                    })
                    .catch(error => {
                        console.log(error.response);
                        this.districts = [];
                    })
            }
        }
    },
    methods: {
        // 展示新增地址弹框
        show_add_site(){
            this.is_show_edit = true;
            // 清空错误提示信息
            this.clear_all_errors();
            // 清空原有数据
            this.form_address.receiver = '';
            this.form_address.province_id = '';
            this.form_address.city_id = '';
            this.form_address.district_id = '';
            this.form_address.place = '';
            this.form_address.mobile = '';
            this.form_address.tel = '';
            this.form_address.email = '';
            this.editing_address_index = '';
        },
        // 展示编辑地址弹框
        show_edit_site(index){
            this.is_show_edit = true;
            this.clear_all_errors();
            this.editing_address_index = index.toString();
            // 只获取要编辑的数据
            this.form_address = JSON.parse(JSON.stringify(this.addresses[index]));
        },
        // 校验收货人
        check_receiver(){
            if (!this.form_address.receiver) {
                this.error_receiver = true;
            } else {
                this.error_receiver = false;
            }
        },
        // 校验收货地址
        check_place(){
            if (!this.form_address.place) {
                this.error_place = true;
            } else {
                this.error_place = false;
            }
        },
        // 校验手机号
        check_mobile(){
            let re = /^1[3-9]\d{9}$/;
            if(re.test(this.form_address.mobile)) {
                this.error_mobile = false;
            } else {
                this.error_mobile = true;
            }
        },
        // 校验固定电话
        check_tel(){
            if (this.form_address.tel) {
                let re = /^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$/;
                if (re.test(this.form_address.tel)) {
                    this.error_tel = false;
                } else {
                    this.error_tel = true;
                }
            } else {
                this.error_tel = false;
            }
        },
        // 校验邮箱
        check_email(){
            if (this.form_address.email) {
                let re = /^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$/;
                if(re.test(this.form_address.email)) {
                    this.error_email = false;
                } else {
                    this.error_email = true;
                }
            } else {
                this.error_email = false;
            }
        },
        // 清空错误提示信息
        clear_all_errors(){
            this.error_receiver = false;
            this.error_mobile = false;
            this.error_place = false;
            this.error_tel = false;
            this.error_email = false;
        },
        // 获取省份数据
        get_provinces(){
            let url = '/areas/';
            axios.get(url, {
                responseType: 'json'
            })
                .then(response => {
                    if (response.data.code == '0') {
                        this.provinces = response.data.province_list;
                    } else {
                        console.log(response.data);
                        this.provinces = [];
                    }
                })
                .catch(error => {
                    console.log(error.response);
                    this.provinces = [];
                })
        },
        // 新增地址
        save_address(){
            if (this.error_receiver || this.error_place || this.error_mobile || this.error_email || !this.form_address.province_id || !this.form_address.city_id || !this.form_address.district_id ) {
                alert('信息填写有误!');
            } else {
                // 注意:0 == '';返回true; 0 === '';返回false;
                if (this.editing_address_index === '') {
                    // 新增地址
                    let url = '/users/addresses/create/';
                    axios.post(url, this.form_address, {
                        headers: {
                            'X-CSRFToken':getCookie('csrftoken')
                        },
                        responseType: 'json'
                    })
                        .then(response => {

                            if (response.data.code == '0') {
                                // 局部刷新界面:展示所有地址信息,将新的地址添加到头部
                                this.addresses.splice(0, 0, response.data.address);
                                this.is_show_edit = false;
                            } else if (response.data.code == '4101') {
                                location.href = '/users/login/?next=/addresses/';
                            } else {
                                alert(response.data.errmsg);
                            }
                        })
                        .catch(error => {
                            console.log(error.response);
                        })
                } else {
                    // 修改地址
                    let url = '/users/addresses/' + this.addresses[this.editing_address_index].id + '/';
                    axios.put(url, this.form_address, {
                        headers: {
                            'X-CSRFToken':getCookie('csrftoken')
                        },
                        responseType: 'json'
                    })
                        .then(response => {
                            if (response.data.code == '0') {
                                this.addresses[this.editing_address_index] = response.data.address;
                                this.is_show_edit = false;
                            } else if (response.data.code == '4101') {
                                location.href = '/users/login/?next=/addresses/';
                            } else {
                                alert(response.data.errmsg);
                            }
                        })
                        .catch(error => {
                            alert(error.response);
                        })
                }
            }
        },
        // 删除地址
        delete_address(index){
            let url = '/users/addresses/' + this.addresses[index].id + '/';
            axios.delete(url, {
                headers: {
                    'X-CSRFToken':getCookie('csrftoken')
                },
                responseType: 'json'
            })
                .then(response => {
                    if (response.data.code == '0') {
                        // 删除对应的标签
                        this.addresses.splice(index, 1);
                    } else if (response.data.code == '4101') {
                        location.href = '/login/?next=/addresses/';
                    }else {
                        alert(response.data.errmsg);
                    }
                })
                .catch(error => {
                    console.log(error.response);
                })
        },
        // 设置默认地址
        set_default(index){
            let url = '/users/addresses/' + this.addresses[index].id + '/default/';
            axios.put(url, {}, {
                headers: {
                    'X-CSRFToken':getCookie('csrftoken')
                },
                responseType: 'json'
            })
                .then(response => {
                    if (response.data.code == '0') {
                        // 设置默认地址标签
                        this.default_address_id = this.addresses[index].id;
                    } else if (response.data.code == '4101') {
                        location.href = '/users/login/?next=/addresses/';
                    } else {
                        alert(response.data.errmsg);
                    }
                })
                .catch(error => {
                    console.log(error.response);
                })
        },
        // 展示地址title编辑框
        show_edit_title(index){
            this.edit_title_index = index;
        },
        // 取消保存地址title
        cancel_title(){
            this.edit_title_index = '';
            this.new_title = '';
        },
        // 修改地址title
        save_title(index){
            if (!this.new_title) {
                alert("请填写标题后再保存!");
            } else {
                let url = '/users/addresses/' + this.addresses[index].id + '/title/';
                axios.put(url, {
                    title: this.new_title
                }, {
                    headers: {
                        'X-CSRFToken':getCookie('csrftoken')
                    },
                    responseType: 'json'
                })
                    .then(response => {
                        if (response.data.code == '0') {
                            // 更新地址title
                            this.addresses[index].title = this.new_title;
                            this.cancel_title();
                        } else if (response.data.code == '4101') {
                            location.href = '/users/login/?next=/addresses/';
                        } else {
                            alert(response.data.errmsg);
                        }
                    })
                    .catch(error => {
                        console.log(error.response);
                    })
            }
        },
    }
});

2、新增地址前后端逻辑

定义用户地址模型类

用户地址模型类

class Address(BaseModel):
    """用户地址"""
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='addresses', verbose_name='用户')
    title = models.CharField(max_length=20, verbose_name='地址名称')
    receiver = models.CharField(max_length=20, verbose_name='收货人')
    province = models.ForeignKey('areas.Area', on_delete=models.PROTECT, related_name='province_addresses', verbose_name='省')
    city = models.ForeignKey('areas.Area', on_delete=models.PROTECT, related_name='city_addresses', verbose_name='市')
    district = models.ForeignKey('areas.Area', on_delete=models.PROTECT, related_name='district_addresses', verbose_name='区')
    place = models.CharField(max_length=50, verbose_name='地址')
    mobile = models.CharField(max_length=11, verbose_name='手机')
    tel = models.CharField(max_length=20, null=True, blank=True, default='', verbose_name='固定电话')
    email = models.CharField(max_length=30, null=True, blank=True, default='', verbose_name='电子邮箱')
    is_deleted = models.BooleanField(default=False, verbose_name='逻辑删除')
    
    class Meta:
        db_table = 'tb_address'
        verbose_name = '用户地址'
        verbose_name_plural = verbose_name
        ordering = ['-update_time']

Address模型类说明

  • Address模型类中的外键指向areas/models里面的Area。指明外键时,可以使用应用名.模型类名来定义。
  • ordering表示在进行Address查询时,默认使用的排序方式。
    ordering = ['-update_time']: 根据更新的时间倒叙。

补充用户模型默认地址字段

class User(AbstractUser):
    """自定义用户模型类"""
    mobile = models.CharField(max_length=11, unique=True, verbose_name='手机号')
    email_active = models.BooleanField(default=False, verbose_name='邮箱验证状态')
    default_address = models.ForeignKey('Address', related_name='users', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='默认地址')
    class Meta:
        db_table = 'tb_users'
        verbose_name = '用户'
        verbose_name_plural = verbose_name
    def __str__(self):
        return self.username

映射到数据库中
在这里插入图片描述

实例代码如下

用户模型文件apps/users/models.py

"""
用户模型文件

apps/users/models.py

"""

from django.db import models
from django.contrib.auth.models import AbstractUser          # 模型继承的类
from utils.models import BaseModel                           # 模型继承的类


# objects = models.Manager()
class User(AbstractUser):
    """自定义用户模型类"""
    mobile = models.CharField(max_length=11, unique=True, verbose_name="手机号")    # 父类中没有的字段
    # 邮箱是否经过验证的字段   新添加字段
    email_active = models.BooleanField(default=False, verbose_name="邮箱验证的状态")
    
    # 默认地址字段
    default_address = models.ForeignKey('Address', related_name='users', null=True, blank=True,          # 外键Address
                                        on_delete=models.SET_NULL, verbose_name='默认地址')
    
    class Meta:
        db_table = 'tb_users'
        verbose_name = '用户'
        verbose_name_plural = verbose_name
        
    def __str__(self):
        return self.username


# 用户添加地址的模型定义
class Address(BaseModel):                #
    """用户地址"""
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='addresses', verbose_name='用户')  # user是外键,关联User模型,models.CASCADE级联删除
    title = models.CharField(max_length=20, verbose_name='地址名称')
    receiver = models.CharField(max_length=20, verbose_name='收货人')
    province = models.ForeignKey('areas.Area', on_delete=models.PROTECT, related_name='province_addresses',             # province外键
                                 verbose_name='省')
    city = models.ForeignKey('areas.Area', on_delete=models.PROTECT, related_name='city_addresses', verbose_name='市')  # city外键
    district = models.ForeignKey('areas.Area', on_delete=models.PROTECT, related_name='district_addresses',             # district外键
                                 verbose_name='区')
    place = models.CharField(max_length=50, verbose_name='地址')
    mobile = models.CharField(max_length=11, verbose_name='手机')
    tel = models.CharField(max_length=20, null=True, blank=True, default='', verbose_name='固定电话')
    email = models.CharField(max_length=30, null=True, blank=True, default='', verbose_name='电子邮箱')
    is_deleted = models.BooleanField(default=False, verbose_name='逻辑删除')                                             # 假删除字段
    
    class Meta:
        db_table = 'tb_address'
        verbose_name = '用户地址'
        verbose_name_plural = verbose_name
        ordering = ['-update_time']

3、新增地址接口设计和定义

请求方式
在这里插入图片描述
请求参数:JSON
在这里插入图片描述
响应结果:JSON
在这里插入图片描述

新增地址后端逻辑实现

用户地址数量有上限,最多20个,超过地址数量上限就返回错误信息

class CreateAddressView(LoginRequiredJsonMixin, View):
    """新增地址"""
    def post(self, request):
        """实现新增地址逻辑"""
        # 判断是否超过地址上限:最多20个
        # 接收参数
        # 校验参数
        # 保存地址信息
        # 新增地址成功,将新增的地址响应给前端实现局部刷新
        # 响应保存结果

新增地址前端逻辑实现

save_address(){
    if (this.error_receiver || this.error_place || this.error_mobile || this.error_email || !this.form_address.province_id || !this.form_address.city_id || !this.form_address.district_id ) {
        alert('信息填写有误!');
    } else {
        // 新增地址
        let url = '/addresses/create/';
        axios.post(url, this.form_address, {
            headers: {
                'X-CSRFToken':getCookie('csrftoken')
            },
            responseType: 'json'
        })
            .then(response => {
                if (response.data.code == '0') {
                    // 局部刷新界面:展示所有地址信息
                } else if (response.data.code == '4101') {
                    location.href = '/login/?next=/addresses/';
                } else {
                    alert(response.data.errmsg);
                }
            })
            .catch(error => {
                console.log(error.response);
            })
    }
},

4、展示地址前后端逻辑

展示地址接口设计和定义

  • 请求方式
    在这里插入图片描述

  • 请求参数:无

  • 响应结果:HTML
    user_center_site.html

展示地址后端逻辑实现

class AddressView(LoginRequiredMixin, View):
    """用户收货地址"""
    def get(self, request):
        """提供收货地址界面"""
        # 获取用户地址列表
        login_user = request.user
        addresses = Address.objects.filter(user=login_user, is_deleted=False)
        address_dict_list = []
        for address in addresses:
            address_dict = {
                "id": address.id,
                "title": address.title,
                "receiver": address.receiver,
                "province": address.province.name,
                "city": address.city.name,
                "district": address.district.name,
                "place": address.place,
                "mobile": address.mobile,
                "tel": address.tel,
                "email": address.email
            }
        context = {
            'default_address_id': login_user.default_address_id,
            'addresses': address_dict_list,
        }
        return render(request, 'user_center_site.html', context)

展示地址前端逻辑实现

将后端模板数据传递到Vue.js

<script type="text/javascript">
    let addresses = {{ addresses | safe }};
    let default_address_id = "{{ default_address_id }}";
</script>
data: {
    addresses: JSON.parse(JSON.stringify(addresses)),
    default_address_id: default_address_id,
},

user_center_site.html渲染地址信息

<div class="right_content clearfix" v-cloak>
    <div class="site_top_con">
        <a @click="show_add_site">新增收货地址</a>
        <span>你已创建了<b>[[ addresses.length ]]</b>个收货地址,最多可创建<b>20</b></span>
    </div>
    <div class="site_con" v-for="(address, index) in addresses">
        <div class="site_title">
            <h3>[[ address.title ]]</h3>
            <a href="javascript:;" class="edit_icon"></a>
            <em v-if="address.id===default_address_id">默认地址</em>
            <span class="del_site">×</span>
        </div>
        <ul class="site_list">
            <li><span>收货人:</span><b>[[ address.receiver ]]</b></li>
            <li><span>所在地区:</span><b>[[ address.province ]] [[address.city]] [[ address.district ]]</b></li>
            <li><span>地址:</span><b>[[ address.place ]]</b></li>
            <li><span>手机:</span><b>[[ address.mobile ]]</b></li>
            <li><span>固定电话:</span><b>[[ address.tel ]]</b></li>
            <li><span>电子邮箱:</span><b>[[ address.email ]]</b></li>
        </ul>
        <div class="down_btn">
            <a v-if="address.id!=default_address_id">设为默认</a>
            <a href="javascript:;" class="edit_icon">编辑</a>
        </div>
    </div>
</div>

完善user_center_site.js成功新增地址后的局部刷新

if (response.data.code == '0') {
    // 局部刷新界面:展示所有地址信息,将新的地址添加到头部
    this.addresses.splice(0, 0, response.data.address);
    this.is_show_edit = false;
}

项目实例代码

apps/users/views.py文件,用户后端验证视图文件

"""
apps/users/views.py文件,用户后端验证视图文件
"""
from django.shortcuts import render, redirect, reverse
from django.http import HttpResponse, JsonResponse, HttpResponseForbidden, HttpResponseServerError, HttpResponseBadRequest
from django import http
from django.views import View
from .forms import RegisterForm, LoginForm
from .models import User, Address
from django.contrib.auth import login, logout, authenticate    # authenticate封装的验证用户名和密码是否正确的方法
from django_redis import get_redis_connection
from django.contrib.auth.mixins import LoginRequiredMixin      # 验证用户是否登录的类
import json
from django.http import QueryDict      # 转换数据类型
import re                              # 正则表达式
import logging
from utils.response_code import RETCODE
from utils.views import LoginRequiredJsonMixin    # 验证用户登录的状态并返回代码
from django.core.mail import send_mail            # 发送邮件
from django.conf import settings                  # 导入设置参数
from celery_tasks.email.tasks import send_verify_emails      # celery异步发送邮件进行验证
from .utils import generate_verify_email_url, check_verify_email_url   # 生成邮箱验证的url方法和反序列化方法


logger = logging.getLogger('django')   # 记录到django的日志中


# 定义创建新的收货地址类
class CreateAddressView(LoginRequiredJsonMixin, View):
    """新增地址"""
    def post(self, request):
        """实现新增地址逻辑"""
        # 地址表单数据存储在body属性中,无法通过get获取,request.body.decode()获取表单的地址信息
        json_dict = json.loads(request.body.decode())        # json.loads是转换数据类型            接收参数
        # print("json_dict:    ", json_dict)
        
        # json_dict接收到的参数
        receiver = json_dict.get('receiver')
        province_id = json_dict.get('province_id')
        city_id = json_dict.get('city_id')
        district_id = json_dict.get('district_id')
        place = json_dict.get('place')
        mobile = json_dict.get('mobile')
        tel = json_dict.get('tel')
        email = json_dict.get('email')
        
        # 验证参数,正则表达式进行验证
        if not re.match(r"^1[3-9]\d{9}$", mobile):      # re.match正则匹配,^开始符号,$结束符号
            return http.HttpResponseForbidden('参数mobile错误')
        
        try:
            # 保存到数据库模型中,保存的字段要和模型中一样,新增地址信息
            address = Address.objects.create(
                user=request.user,   # 登录的用户
                title=receiver,      # 收货人
                receiver=receiver,
                province_id=province_id,    # province是外键,根据数据库中的字段进行添加
                city_id=city_id,            # city是外键,根据数据库中的字段进行添加
                district_id=district_id,    # district是外键,根据数据库中的字段进行添加
                place=place,
                mobile=mobile,
                tel=tel,
                email=email,
                # is_deleted=         # 使用默认值
            )

            # 如果当前用户没有默认地址,就把现在添加的地址设置为默认地址
            if not request.user.default_address_id:
                request.user.default_address_id = address                # 当前设置为默认
                request.user.save()                                      # 保存到数据库用户表中
                
        except Exception as e:
            logger.error(e)                        # 保存错误信息到日志文件中
            return http.HttpResponseServerError({"code": RETCODE.DBERR, "errmsg": "新增地址失败"})
        
        # 响应,传输给前端进行渲染
        address_dict = {
            """ 返回数据种类
                    code	状态码
                    errmsg	错误信息
                    id	地址ID
                    receiver	收货人
                    province	省份名称
                    city	城市名称
                    district	区县名称
                    place	收货地址
                    mobile	手机号
                    tel	固定电话
                    email	邮箱
            """
            "id": address.id,               # 数据库中新增收货地址的id,address是新创建的对象
            "receiver": address.receiver,
            "province": address.province.name,   # address.province显示的是省份代码,.name是外键,能够显示省份的字符串
            "city": address.city.name,           # address.city显示的是城市代码,.name是外键
            "district": address.district.name,   # address.district显示的是区县代码,.name是外键
            "place": address.place,
            "mobile": address.mobile,
            "tel": address.tel,
            "email": address.email,
        }                                       # code,errmsg在返回数据中

        return http.JsonResponse({"code": RETCODE.OK, "errmsg": "新增收货地址成功", "address": address_dict})  # address名称根据js文件设定


# 收货地址的类
class AddressView(LoginRequiredMixin, View):            # 登录验证LoginRequiredJsonMixin类
    """用户收货地址"""
    def get(self, request):
        """提供收货地址界面"""
        # 查询用户的收货地址
        addresses = Address.objects.filter(user=request.user, is_deleted=False)     # 外键和假删除
        
        address_list = []
        for address in addresses:                       # 循环已经存在的地址
            address_dict = {
                "id": address.id,
                "title": address.title,
                "receiver": address.receiver,
                "province": address.province.name,   # address.province显示的是省份代码,.name是外键,能够显示省份的字符串
                "city": address.city.name,           # address.city显示的是城市代码,.name是外键
                "district": address.district.name,   # address.district显示的是区县代码,.name是外键
                "place": address.place,
                "mobile": address.mobile,
                "tel": address.tel,
                "email": address.email,
            }
            # 数据拼接
            address_list.append(address_dict)
        
        # 返回数据
        context = {                                         # 传输数据给前端进行渲染
            "addresses": address_list,                      # 前端的使用名称addresses,数据类型是列表中嵌套字典[ {}, {}, {} ]
            "default_address_id": request.user.default_address_id,           # 默认地址
        }
        return render(request, 'user_center_site.html', context=context)     # 返回到用户地址界面


# 邮箱验证
# 1. 生成验证邮箱的链接url   www.meiduo.com:8000/users/emails/verification/?token=xxxxx
# 2. 点击之后,将email_active改为true
class VerifyEmailView(LoginRequiredJsonMixin, View):   # 登录验证LoginRequiredJsonMixin类
    """验证邮箱"""
    def get(self, request):
        """实现邮箱验证逻辑"""
        # 接收参数
        token = request.GET.get('token')
        # 校验参数:判断token是否为空和过期,提取user
        if not token:
            return HttpResponseBadRequest('缺少token')
        
        user = check_verify_email_url(token)                  # 反序列化操作token,提取token中的用户信息
        if not user:                                          # token中无用户信息
            return HttpResponseForbidden('无效的token')
        
        # 修改email_active的值为True
        try:
            user.email_active = True                          # 修改该字段
            user.save()                                       # 保存到数据库中
        except Exception as e:
            logger.error(e)                                   # 保存到日志中
            return HttpResponseServerError('激活邮件失败')
        
        # 返回邮箱验证结果,跳转到用户个人中心
        return redirect(reverse('users:info'))
    

# 用户邮箱绑定,登录之后才能访问该方法,需要登录验证方法
class EmailView(LoginRequiredJsonMixin, View):             # 继承自LoginRequiredJsonMixin类,验证用户登录的状态问题
    """添加邮箱"""
    # 此时是put请求,一般用于更新数据
    def put(self, request):
        """添加邮箱的业务逻辑实现 """
        '''
        put 提交的数据是在body属性中,是字节数据类型Bytes
        get,post提交的数据是QueryDict的数据类型
        '''
        # print(request.body.decode())          # {"email":"xxxxx@qq.com"}
        # print(type(request.body.decode()))    # str数据类型
        # 从request中获取email
        email = json.loads(request.body.decode()).get('email')    # json.loads转换成字典类型
        # print(email)
        
        # 校验邮箱   邮箱的正则表达式
        if not re.match(r'^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((\.[a-zA-Z0-9_-]{2,3}){1,2})$', email):
            return HttpResponseForbidden('参数email错误!')
        
        try:
            # 根据当前登录的用户保存到数据库的email字段中
            request.user.email = email
            request.user.save()
        except Exception as e:
            logger.error(e)             # 记录错误信息到日志
            return HttpResponseServerError('邮箱激活失败')
        
        # 发送邮件  这种方法不是异步执行,会阻塞项目进程,需要替换成Celery异步
        '''  send_mail源码
        send_mail(subject, message, from_email, recipient_list,
                      fail_silently=False, auth_user=None, auth_password=None,
                      connection=None, html_message=None):
        '''
        # try:
        #     verify_url = 'www.baidu.com'
        #     html_message = '<p>尊敬的用户您好!</p>' \
        #                    '<p>感谢您使用商城。</p>' \
        #                    '<p>您的邮箱为:%s 。请点击此链接激活您的邮箱:</p>' \
        #                    '<p><a href="%s">%s<a></p>' % (email, verify_url, verify_url)  # verify_url是验证路由
        #
        #     send_mail(subject='LG_商城邮箱验证', message='', from_email=settings.EMAIL_FROM,
        #               recipient_list=[email], html_message=html_message)                         # [email]是列表,
        # except smtplib.SMTPServerDisconnected:
        #     print('123')

        verify_url = generate_verify_email_url(request.user)        # 生成邮箱验证的url,用户对象为request.user
        # 异步celery发送邮件进行验证   异步发送需要.delay方法
        send_verify_emails.delay(to_email=email, verify_url=verify_url)
        
        # 返回Json数据类型 传输给前端js文件进行邮件验证逻辑
        return JsonResponse({"code": RETCODE.OK, 'errmsg': "OK"})


# 个人用户中心
class UserInfoView(LoginRequiredMixin, View):
    """用户个人中心"""
    def get(self, request):
        """提供用户个人中心"""
        '''
        login_url = None
        permission_denied_message = ''
        raise_exception = False
        redirect_field_name = REDIRECT_FIELD_NAME
        '''
        # 验证用户是否登陆
        # if request.user.is_authenticated:
        #     return render(request, 'user_center_info.html')
        # else:
        #     return redirect(reverse('users:login'))           # 用户未登录,跳转至登陆界面
        # print(request.user)
        # print(request.user.username)
        # print(request.user.mobile)
        
        # 数据由Django后端来提供,前端数据的读取方式采用Vue方式读取[[ username ]]
        context = {
            'username': request.user.username,
            'mobile': request.user.mobile,
            'email': request.user.email,                             # 传输email和email_active参数到前端是为了邮箱验证的功能
            'email_active': request.user.email_active,
        }
        
        # 上面的代码后期需要复用多次,可以引入LoginRequiredMixin类封装的方法和REDIRECT_FIELD_NAME = 'next'参数来重定向
        return render(request, 'user_center_info.html', context=context)        # 重定向到个人中心


# 退出登录
class LogoutView(View):
    """退出登陆逻辑实现"""
    def get(self, request):
        """实现用户退出登录的功能"""
        # 清除状态保持信息
        logout(request)
        
        # 退出登录之后重定向到首页
        response = redirect(reverse('contents:index'))
        
        # 删除cookies中的用户名
        # result.set_cookie('username', user.username, max_age=3600*24*14)    # 保存两周
        response.delete_cookie('username')
        return response                           # 响应结果
    

# 用户登陆
class LoginView(View):
    """用户名登陆"""
    def get(self, request):
        """  提供登陆界面
        :return: 登陆界面
        """
        return render(request, 'login.html')

    def post(self, request):
        """
        实现登录逻辑
        :param request: 请求对象
        :return: 登录结果
        """
        # 接受参数
        login_form = LoginForm(request.POST)
        
        # 校验参数
        if login_form.is_valid():
            # 接收参数
            username = login_form.cleaned_data.get('username')
            password = login_form.cleaned_data.get('password')
            remembered = request.POST.get('remembered')                 # 没经过form验证,使用request接收参数

            # 认证登录用户
            # users = User.objects.get(username=username)
            # users.check_password(password)                            # check_password验证密码封装的方法,返回值bool类型
            """  authenticate方法源码
             def authenticate(self, request, username=None, password=None, **kwargs):
                if username is None:
                    username = kwargs.get(UserModel.USERNAME_FIELD)
                try:
                    user = UserModel._default_manager.get_by_natural_key(username)
                except UserModel.DoesNotExist:
                    # Run the default password hasher once to reduce the timing
                    # difference between an existing and a nonexistent user (#20760).
                    UserModel().set_password(password)
                else:
                    if user.check_password(password) and self.user_can_authenticate(user):
                        return user
            """
            user = authenticate(username=username, password=password)   # 重构authenticate方法之后,可以验证手机号登录和用户名登录
            if user is None:
                # 用户名或者密码输入错误
                return render(request, 'login.html', {"errmsg": "用户名或者密码输入错误"})
            
            # 实现状态保持
            login(request, user)

            # 设置状态保持的周期
            if remembered != 'on':
                # 没选中记住密码,浏览器关闭就需要销毁session信息
                request.session.set_expiry(0)                  # set_expiry过期时间
            else:
                # 选中记住密码,session信息默认保存两周
                # request.session.set_expiry(60*60*24*14)
                request.session.set_expiry(None)
            
            # REDIRECT_FIELD_NAME = 'next'      LoginRequiredMixin类中源码的参数 ,用于获取登陆前的路由请求,方便登陆后直接定向到之前的请求界面
            next = request.GET.get('next')       # 获取url中的‘next’字符串参数
            if next:
                result = redirect(next)          # 如果存在next参数,则重定向到这个地址
            else:
                # 后端将用户信息存入cookie
                result = redirect(reverse('contents:index'))            # redirect方法源码中会返回一个redirect_class
            # set_cookie('key', 'value', 'erpriy')   erpriy过期时间
            result.set_cookie('username', user.username, max_age=3600*24*14)    # 保存两周
            
            # 响应登录结果    跳转到首页
            return result
        else:
            print(login_form.errors.get_json_data())
            context = {
                "form_errors": login_form.errors,
            }
            return render(request, 'login.html', context=context)
    

# 用户注册
class RegisterView(View):
    """用户注册"""
    def get(self, request):
        """提供用户的注册界面"""
        return render(request, 'register.html')
    
    def post(self, request):
        """提供用户的注册逻辑"""
        # 前端用户提交数据
        form = RegisterForm(request.POST)
        if form.is_valid():
            # 接收参数
            username = form.cleaned_data.get('username')
            password = form.cleaned_data.get('password')
            mobile = form.cleaned_data.get('mobile')
            sms_code_client = request.POST.get('sms_code')         # 验证短信验证码  sms_code是register.html 文件中命名的
            
            # 判断用户输入的短信验证码是否正确
            redis_conn = get_redis_connection('verify_code')       # 链接redis中配置的数据库
            sms_code_server = redis_conn.get('sms_%s' % mobile)    # 根据存储时候的格式写
            if sms_code_server is None:
                return render(request, 'register.html', {'sms_code_errmsg': '短信验证码已经失效'})     # 错误信息渲染到前端界面
            if sms_code_server.decode() != sms_code_client:       # sms_code_server数据类型需要转换
                return render(request, 'register.html', {'sms_code_errmsg': '短信验证码填写错误'})
            
            try:
                # user = User(username=username, password=password, mobile=mobile)
                # 下面的添加数据的方法是封装了加密等功能的函数,更安全
                users = User.objects.create_user(username=username, password=password, mobile=mobile)
            except:    # 如果保存数据失败
                return render(request, 'register.html', {'register_error_message': '注册失败'})
            
            # 保持用户登录的状态
            login(request, users)
            
            # 返回响应
            # return HttpResponse('success')
            return redirect(reverse('contents:index'))           # 注册成功,跳转到首页
        else:
            print(form.errors.get_json_data())
            # return HttpResponse("fail")
            # 返回注册错误信息到前端界面
            context = {
                'form_error': form.errors,
            }
            return render(request, 'register.html', context=context)
    

# 判断用户名是否已经存在
class UsernameExists(View):
    """ 判断用户名是否已经存在"""
    def get(self, request, username):     # username用户名
        count = User.objects.filter(username=username).count()      # 查询数据库中信息
        return JsonResponse({"code": 0, "errmsg": "OK", "count": count})   # 返回给前端界面
    


apps/users/urls.py 子路由,users模块的路由,添加用户新增收货地址路由

# -*- encoding: utf-8 -*-
"""
@File    : urls.py
@Time    : 2020/7/26 20:34
@Author  : chen


apps/users/urls.py   子路由,users模块的路由
"""
from django.urls import path, include, re_path
from . import views

app_name = 'users'

urlpatterns = [
    # 注册路由
    path('register/', views.RegisterView.as_view(), name='register'),
    
    # 判断用户名是否存在     /usernames/(?P<username>[a-zA-Z0-9_-]{5,20})/count/
    # ?P<username> 拼接用户名,[a-zA-Z0-9_-]{5,20}正则匹配
    re_path("usernames/(?P<username>[a-zA-Z0-9_-]{5,20})/count/", views.UsernameExists.as_view()),
    
    # 登陆界面url绑定
    path("login/", views.LoginView.as_view(), name='login'),
    
    # 用户退出登陆
    path("logout/", views.LogoutView.as_view(), name='logout'),
    
    # 用户个人中心路由
    path("info/", views.UserInfoView.as_view(), name='info'),
    
    # 用户添加邮箱路由
    path("emails/", views.EmailView.as_view()),
    
    # 用户验证邮箱路由     www.meiduo.site:8000/users/emails/verification/?token=xxxxx
    path("emails/verification/", views.VerifyEmailView.as_view()),
    
    # 用户收货地址路由
    path("addresses/", views.AddressView.as_view(), name='address'),
    
    # 用户新增收货地址路由
    path("addresses/create/", views.CreateAddressView.as_view())
]

个人用户中心地址界面文件templates/user_center_site.html

{# 个人用户中心地址界面文件:templates/user_center_site.html #}
{% load static %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
	<title>LG商城-用户中心</title>
	<link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}">
	<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
    <script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
</head>
<body>
	<div id="app">
	<div class="header_con">
		<div class="header" v-cloak>
			<div class="welcome fl">欢迎来到LG商城!</div>
			<div class="fr">
                <div v-if="username" class="login_btn fl">
                    欢迎您:<em>[[ username ]]</em>
                    <span>|</span>
                    <a href="{% url 'users:logout' %}">退出</a>
                </div>
                <div v-else class="login_btn fl">
                    <a href="{% url 'users:login' %}">登录</a>
                    <span>|</span>
                    <a href="{% url 'users:register' %}">注册</a>
                </div>
				<div class="user_link fl">
					<span>|</span>
					<a href="{% url 'users:info' %}">用户中心</a>
					<span>|</span>
					<a href="cart.html">我的购物车</a>
					<span>|</span>
					<a href="user_center_order.html">我的订单</a>
				</div>
			</div>
		</div>
	</div>
	<div class="search_bar clearfix">
		<a href="{% url 'contents:index' %}" class="logo fl"><img src="{% static 'images/logo.png' %}"></a>
		<div class="search_wrap fl">
			<form method="get" action="/search/" class="search_con">
                <input type="text" class="input_text fl" name="q" placeholder="搜索商品">
                <input type="submit" class="input_btn fr" name="" value="搜索">
            </form>
			<ul class="search_suggest fl">
				<li><a href="#">索尼微单</a></li>
				<li><a href="#">优惠15元</a></li>
				<li><a href="#">美妆个护</a></li>
				<li><a href="#">买2免1</a></li>
			</ul>
		</div>
	</div>
	<div class="main_con clearfix">
		<div class="left_menu_con clearfix">
			<h3>用户中心</h3>
			<ul>
				<li><a href="{% url 'users:info' %}">· 个人信息</a></li>
				<li><a href="{% url 'users:address' %}">· 收货地址</a></li>
                <li><a href="user_center_order.html">· 全部订单</a></li>
				<li><a href="user_center_pass.html">· 修改密码</a></li>
			</ul>
		</div>
        <div class="right_content clearfix" v-cloak>
            <div class="site_top_con">
                <a @click="show_add_site">新增收货地址</a>

                <span>你已创建了<b>[[ addresses.length ]]</b>个收货地址,最多可创建<b>20</b></span>
            </div>
            <div class="site_con" v-for="(address, index) in addresses">
                <div class="site_title">
                    <div v-if="edit_title_index===index">
                        <input v-model="new_title" type="text" name="">
                        <input @click="save_title(index)" type="button" name="" value="保 存">
                        <input @click="cancel_title(index)" type="reset" name="" value="取 消">
                    </div>
                    <div>
                        <h3>[[ address.title ]]</h3>
                        <a @click="show_edit_title(index)" class="edit_title"></a>
                    </div>
                    <em v-if="address.id===default_address_id">默认地址</em>
                    <span @click="delete_address(index)">×</span>
                </div>
                <ul class="site_list">
                    <li><span>收货人:</span><b>[[ address.receiver ]]</b></li>
                    <li><span>所在地区:</span><b>[[ address.province ]] [[address.city]] [[ address.district ]]</b></li>
                    <li><span>地址:</span><b>[[ address.place ]]</b></li>
                    <li><span>手机:</span><b>[[ address.mobile ]]</b></li>
                    <li><span>固定电话:</span><b>[[ address.tel ]]</b></li>
                    <li><span>电子邮箱:</span><b>[[ address.email ]]</b></li>
                </ul>
                <div class="down_btn">
                    <a v-if="address.id!=default_address_id" @click="set_default(index)">设为默认</a>
                    <a @click="show_edit_site(index)" class="edit_icon">编辑</a>
                </div>
            </div>
        </div>
	</div>
	<div class="footer">
		<div class="foot_link">
			<a href="#">关于我们</a>
			<span>|</span>
			<a href="#">联系我们</a>
			<span>|</span>
			<a href="#">招聘人才</a>
			<span>|</span>
			<a href="#">友情链接</a>
		</div>
		<p>CopyRight © 2016 xxx商业股份有限公司 All Rights Reserved</p>
		<p>电话:010-****888    京ICP备*******8号</p>
	</div>
	<div class="pop_con" v-show="is_show_edit" v-cloak>
		<div class="site_con site_pop">
            <div class="site_pop_title">
                <h3>新增收货地址</h3>
                <a @click="is_show_edit=false">×</a>
            </div>
            <form>
                <div class="form_group">
                    <label>*收货人:</label>
                    <input v-model="form_address.receiver" @blur="check_receiver" type="text" class="receiver">
                    <span v-show="error_receiver" class="receiver_error">请填写收件人</span>
                </div>
                <div class="form_group">
                    <label>*所在地区:</label>
                    <select v-model="form_address.province_id">
                        <option v-for="province in provinces" :value="province.id">[[ province.name ]]</option>
                    </select>
                    <select v-model="form_address.city_id">
                        <option v-for="city in cities" :value="city.id">[[ city.name ]]</option>
                    </select>
                    <select v-model="form_address.district_id">
                        <option v-for="district in districts" :value="district.id">[[ district.name ]]</option>
                    </select>
                </div>
                <div class="form_group">
                    <label>*详细地址:</label>
                    <input v-model="form_address.place" @blur="check_place" type="text" class="place">
                    <span v-show="error_place" class="place_error">请填写地址信息</span>
                </div>
                <div class="form_group">
                    <label>*手机:</label>
                    <input v-model="form_address.mobile" @blur="check_mobile" type="text" class="mobile">
                    <span v-show="error_mobile" class="mobile_error">手机信息有误</span>
                </div>
                <div class="form_group">
                    <label>固定电话:</label>
                    <input v-model="form_address.tel" @blur="check_tel" type="text" class="tel">
                    <span v-show="error_tel" class="tel_error">固定电话有误</span>
                </div>
                <div class="form_group">
                    <label>邮箱:</label>
                    <input v-model="form_address.email" @blur="check_email" type="text" class="email">
                    <span v-show="error_email" class="email_error">邮箱信息有误</span>
                </div>
                <input @click="save_address" type="button" name="" value="新 增" class="info_submit">
                <input @click="is_show_edit=false" type="reset" name="" value="取 消" class="info_submit info_reset">
            </form>
		</div>
		<div class="mask"></div>
	</div>
	</div>
    <script type="text/javascript">
{#          addresses | safe  因为addresses是列表中嵌套字典的数据类型,添加safe是防止 js不识别      #}
		 let addresses = {{ addresses | safe }};
		 let default_address_id = {{ default_address_id }};
    </script>
	<script type="text/javascript" src="{% static 'js/common.js' %}"></script>
	<script type="text/javascript" src="{% static 'js/user_center_site.js' %}"></script>
</body>
</html>

实现效果如下:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值