欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

使用Django实现的路飞项目重构与改编

最编程 2024-02-19 22:51:33
...

路飞项目


1.1 企业的web项目类型

  1. 商城

    1.1 B2C 直销商城 商家与会员直接交易 ( Business To Customer )

    1.2 B2B 批发商城 商家与商家直接交易

    1.3 B2B2C 购物平台 商家和会员在另一个商家提供的平台上面进行交易

    1.4 C2B 定制商城 会员向商家发起定制商品的需求,商家去完成。

    1.5 O2O 线上线下交易平台

    1.6 C2C 二手交易平台

  2. 门户网站[企业站和门户站]

  3. 社交网络

  4. 资讯论坛

  5. 内部系统

  6. 个人博客

  7. 内容收费站

1.2 企业项目开发流程

开发流程

img

1.3 立项申请阶段

立项其实就是对产品项目能不能做和怎么做,提出理论基础。大的互联网公司都有比较正规的立项流程。

img

通常公司内部要研发一款软硬件的产品之前,都要经过市场评估和调研分析,产生一份产品项目立项报告给公司。

产品项目立项报告一般包含以下内容:

项目概述需求市场需求分析和项目建设的必要性业务分析总体建设方案项目风险和风险管理可行性分析阶段

参考资料:https://blog.****.net/m0_37370820/article/details/81077886

2. 需求分析

2.1 首页

功能:导航菜单、轮播图、退出登录

1555309744206

2.2 登录注册

功能:用户登录、极验验证码、多条件登录、记住密码、短信发送、短信冷却倒计时、jwt认证

1555309859755

1555310015496

2.3 课程列表

功能:课程分类、课程列表、课程多条件筛选展示、课程分类展示、课程分页展示、课程章节课时展示、课程优惠策略

1555311221819

2.4 课程详情

功能:课程信息展示、视频播放、富文本编辑器

1555311433851

2.5 购物车

功能:购物车商品列表、添加商品、删除商品、勾选商品状态、商品结算、订单生成、唯一订单号生成

1555311588633

2.6 商品结算

功能:订单商品信息列表、订单信息展示、积分计算功能、优惠券策略、课程有效期计算、第三方支付平台接口

1555311715736

2.7 购买成功

1553138273460

2.8 个人中心

功能列表:我的订单、订单状态改变

1555312510196

2.9 视频播放

功能:视频加密播放

1553137882937

pip换源

1 pip3 install pymysql   国外很慢
2 pip3 install pymysql -i  地址
3 配置,以后pip3 install全走配好的源
	-来到C:\Users\oldboy\AppData\Roaming      %APPDATA% 
    -创建一个pip文件夹
    -新建一个文件pip.ini
    -写入
        [global]
        index-url = http://pypi.douban.com/simple
        [install]
        use-mirrors =true
        mirrors =http://pypi.douban.com/simple/
        trusted-host =pypi.douban.com

虚拟环境搭建

windows

安装
# 建议使用pip3安装到python3环境下
pip3 install virtualenv
pip3 install virtualenvwrapper-win

配置虚拟环境管理器工作目录
# 配置环境变量:
# 控制面板 => 系统和安全 => 系统 => 高级系统设置 => 环境变量 => 系统变量 => 点击新建 => 填入变量名与值
变量名:WORKON_HOME  变量值:自定义存放虚拟环境的绝对路径
eg: WORKON_HOME: D:\Virtualenvs

# 同步配置信息:
# 去向Python3的安装目录 => Scripts文件夹 => virtualenvwrapper.bat => 双击

MacOS、Linux

安装
# 建议使用pip3安装到python3环境下
pip3 install -i https://pypi.douban.com/simple virtualenv
pip3 install -i https://pypi.douban.com/simple virtualenvwrapper

工作文件
# 先找到virtualenvwrapper的工作文件 virtualenvwrapper.sh,该文件可以刷新自定义配置,但需要找到它
# MacOS可能存在的位置 /Library/Frameworks/Python.framework/Versions/版本号文件夹/bin
# Linux可能所在的位置 /usr/local/bin  |  ~/.local/bin  |  /usr/bin
# 建议不管virtualenvwrapper.sh在哪个目录,保证在 /usr/local/bin 目录下有一份
# 如果不在 /usr/local/bin 目录,如在 ~/.local/bin 目录,则复制一份到 /usr/local/bin 目录
	-- sudo cp -rf ~/.local/bin/virtualenvwrapper.sh /usr/local/bin
配置
# 在 ~/.bash_profile 完成配置,virtualenvwrapper的默认默认存放虚拟环境路径是 ~/.virtualenvs
# WORKON_HOME=自定义存放虚拟环境的绝对路径,需要自定义就解注
VIRTUALENVWRAPPER_PYTHON=/usr/local/bin/python3
source /usr/local/bin/virtualenvwrapper.sh

# 在终端让配置生效:
	-- source ~/.bash_profile

使用

# 在终端工作的命令

# 1、创建虚拟环境到配置的WORKON_HOME路径下
# 选取默认Python环境创建虚拟环境:
	-- mkvirtualenv 虚拟环境名称
# 基于某Python环境创建虚拟环境:
	-- mkvirtualenv -p python2.7 虚拟环境名称
	-- mkvirtualenv -p python3.6 虚拟环境名称

# 2、查看已有的虚拟环境
	-- workon

# 3、使用某个虚拟环境
	-- workon 虚拟环境名称
	
# 4、进入|退出 该虚拟环境的Python环境
	-- python | exit()

# 5、为虚拟环境安装模块
	-- pip或pip3 install 模块名

# 6、退出当前虚拟环境
	-- deactivate

# 7、删除虚拟环境(删除当前虚拟环境要先退出)
	-- rmvirtualenv 虚拟环境名称

pycharm使用

新建项目

img

添加环境

img

使用环境

img

后台:Django项目创建

环境

"""
为luffy项目创建一个虚拟环境
>: mkvirtualenv luffy
"""

"""
按照基础环境依赖
>: pip install django==2.0.7
>: pip install djangorestframework
>: pip install pymysql
"""

创建项目

"""
前提:在目标目录新建luffy文件夹
>: cd 建立的luffy文件夹
>: django-admin startproject luffyapi

开发:用pycharm打开项目,并选择提前备好的虚拟环境
"""

重构项目目录

"""
├── luffyapi
	├── logs/				# 项目运行时/开发时日志目录 - 包
    ├── manage.py			# 脚本文件
    ├── luffyapi/      		# 项目主应用,开发时的代码保存 - 包
     	├── apps/      		# 开发者的代码保存目录,以模块[子应用]为目录保存 - 包
        ├── libs/      		# 第三方类库的保存目录[第三方组件、模块] - 包
    	├── settings/  		# 配置目录 - 包
			├── dev.py   	# 项目开发时的本地配置
			└── prod.py  	# 项目上线时的运行配置
		├── urls.py    		# 总路由
		└── utils/     		# 多个模块[子应用]的公共函数类库[自己开发的组件]
    └── scripts/       		# 保存项目运营时的脚本文件 - 文件夹
"""

配置开发环境

"""
1.修改 wsgi.py 与 manage.py 两个文件:
# manage.py
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffyapi.settings.dev')
# wsgi.py
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffyapi.settings.prod')
# manage_prod.py
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffyapi.settings.prod')

2.将settings.py删除或改名,内容拷贝到settings/dev.py中

3.修改dev.py文件内容
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = False

4.修改启动配置:见插图

5.在任何一个__init__.py文件中测试默认配置文件是否是dev.py文件
from django.conf import settings
print(settings)
"""

img

环境变量

dev

# 环境变量操作:小luffyapiBASE_DIR与apps文件夹都要添加到环境变量
import sys
sys.path.insert(0, BASE_DIR)
APPS_DIR = os.path.join(BASE_DIR, 'apps')
sys.path.insert(1, APPS_DIR)
在写项目直接导入utils文件夹也不’’错误提示’’

img

封装logger

dev.py

# 真实项目上线后,日志文件打印级别不能过低,因为一次日志记录就是一次文件io操作
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s'
        },
        'simple': {
            'format': '%(levelname)s %(module)s %(lineno)d %(message)s'
        },
    },
    'filters': {
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {
        'console': {
            # 实际开发建议使用WARNING
            'level': 'DEBUG',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'file': {
            # 实际开发建议使用ERROR
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',
            # 日志位置,日志文件名,日志保存目录必须手动创建,注:这里的文件路径要注意BASE_DIR代表的是小luffyapi
            'filename': os.path.join(os.path.dirname(BASE_DIR), "logs", "luffy.log"),
            # 日志文件的最大值,这里我们设置300M
            'maxBytes': 300 * 1024 * 1024,
            # 日志文件的数量,设置最大日志数量为10
            'backupCount': 10,
            # 日志格式:详细格式
            'formatter': 'verbose',
            # 文件内容编码
            'encoding': 'utf-8'
        },
    },
    # 日志对象
    'loggers': {
        'django': {
            'handlers': ['console', 'file'],
            'propagate': True, # 是否让日志信息继续冒泡给其他的日志处理系统
        },
    }
}
utils/logging.py
import logging
logger = logging.getLogger('django')

封装项目异常处理

utils/exception.py
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.views import Response
from rest_framework import status
from utils.logging import logger
import logging
logging.getLogger('django')
def exception_handler(exc, context):
    response = drf_exception_handler(exc, context)
    if response is None:
        # 记录服务器异常
        logger.critical('%s' % exc)
        response = Response({'detail': '服务器异常,请重试...'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
    return response

# settings
REST_FRAMEWORK = {    'EXCEPTION_HANDLER': 'utils.exception.exception_handler',}

二次封装Response模块

utils/response.py
from rest_framework.response import Response

class APIResponse(Response):
    def __init__(self, status=0, msg='ok', http_status=None, headers=None, exception=False, **kwargs):
        data = {
            'status': status,
            'msg': msg,
        }
        if kwargs:
            data.update(kwargs)
        super().__init__(data=data, status=http_status, headers=headers, exception=exception)

路由组件配置

utils/router.py
from rest_framework.routers import Route, DynamicRoute, SimpleRouter as DRFSimpleRouter

class SimpleRouter(DRFSimpleRouter):
    routes = [
        # List route.  /资源s/
        Route(
            url=r'^{prefix}{trailing_slash}$',
            mapping={
                'get': 'list',  # 群查
                'post': 'create',  # 单增、群增
                'put': 'multiple_update',  # 群整改
                'patch': 'multiple_partial_update',  # 群局改
                'delete': 'multiple_destroy',  # 群删
            },
            name='{basename}-list',
            detail=False,
            initkwargs={'suffix': 'List'}
        ),
        # Dynamically generated list routes. Generated using
        # @action(detail=False) decorator on methods of the viewset.
        DynamicRoute(
            url=r'^{prefix}/{url_path}{trailing_slash}$',
            name='{basename}-{url_name}',
            detail=False,
            initkwargs={}
        ),
        # Detail route.  /资源s/(pk)/
        Route(
            url=r'^{prefix}/{lookup}{trailing_slash}$',
            mapping={
                'get': 'retrieve',  # 单查
                'put': 'update',  # 单整改
                'patch': 'partial_update',  # 单局改
                'delete': 'destroy'  # 单删
            },
            name='{basename}-detail',
            detail=True,
            initkwargs={'suffix': 'Instance'}
        ),
        # Dynamically generated detail routes. Generated using
        # @action(detail=True) decorator on methods of the viewset.
        DynamicRoute(
            url=r'^{prefix}/{lookup}/{url_path}{trailing_slash}$',
            name='{basename}-{url_name}',
            detail=True,
            initkwargs={}
        ),
    ]

# 对外提供十大接口的router对象
router = SimpleRouter()
# eg: router.register('users', UserModelViewSet, basename='user')
"""
/users/
'get': 'list',  # 群查
'post': 'create',  # 单增、群增
'put': 'multiple_update',  # 群整改
'patch': 'multiple_partial_update',  # 群局改
'delete': 'multiple_destroy',  # 群删

/users/(pk)/
'get': 'retrieve',  # 单查
'put': 'update',  # 单整改
'patch': 'partial_update',  # 单局改
'delete': 'destroy'  # 单删

数据库配置

创建数据库
"""
1.管理员连接数据库
>: mysql -uroot -proot

2.创建数据库
>: create database luffy default charset=utf8;

3.查看用户
>: select user,host,password from mysql.user;

# 5.7往后的版本
>: select user,host,authentication_string from mysql.user;
"""
为指定数据库配置指定账户
"""
设置权限账号密码
# 授权账号命令:grant 权限(create, update) on 库.表 to '账号'@'host' identified by '密码'

1.配置任意ip都可以连入数据库的账户
>: grant all privileges on luffy.* to 'luffy'@'%' identified by 'Luffy123?';

2.由于数据库版本的问题,可能本地还连接不上,就给本地用户单独配置
>: grant all privileges on luffy.* to 'luffy'@'localhost' identified by 'Luffy123?';

3.刷新一下权限
>: flush privileges;

只能操作luffy数据库的账户
账号:luffy
密码:Luffy123?
"""
配置文件配置
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'luffy',
        'USER': 'luffy',
        'PASSWORD': 'Luffy123?',
        'HOST': 'localhost',
        'PORT': 3306
    }
}
import pymysql
pymysql.install_as_MySQLdb()

Django 2.x 一些版本pymysql兼容问题

Django不采用2.0.7版本很可能出现以下问题,需要修改源代码

img

img

创建user表

前提:在 luffy 虚拟环境下

1.终端从项目根目录进入apps目录
>: cd luffyapi & cd apps

2.创建app
>: python ../../manage.py startapp user

创建user表对应model:user/model

from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
    mobile = models.CharField(max_length=11, unique=True)
    # 需要pillow包的支持
    icon = models.ImageField(upload_to='icon', default='icon/default.png')

    class Meta:
        db_table = 'luffy_user'
        verbose_name = '用户表'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.username

注册user模块,配置User表:dev.py

INSTALLED_APPS = [    
# ...    'user',]
# 自定义User表
AUTH_USER_MODEL = 'user.User'

配置media

media配置:dev.py
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

"""
├── luffyapi
    └──	luffyapi/
       	└──	media/  	
			└──	icon 
				└── default.png
"""
主路由:luffyapi/urls.py
from django.contrib import admin
from django.urls import path, re_path, include
from django.views.static import serve
from django.conf import settings
urlpatterns = [
    path('admin/', admin.site.urls),

    path('user/', include('user.urls')),

    re_path('^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})
]
子路由:user/urls.py
from django.urls import path, include
from utils.router import router

# 注册ViewSet的路由
# router.register()

urlpatterns = [
    path('', include(router.urls)),
]

数据库迁移

"""
1)去向大luffyapi所在目录的终端

2)安装pillow模块
pip install pillow

3)数据库迁移
python manage.py makemigrations
python manage.py migrate
"""

前台搭建

vue

1.傻瓜式安装node: 
官网下载:https://nodejs.org/zh-cn/

2.安装cnpm: 
>: npm install -g cnpm --registry=https://registry.npm.taobao.org

3.安装vue最新脚手架: 
>: cnpm install -g @vue/cli

注:如果2、3步报错,清除缓存后重新走2、3步
>: npm cache clean --force
创建项目
"""
前提:在目标目录新建luffy文件夹
>: cd 建立的luffy文件夹
>: vue create luffycity
"""

img

img

重构项目目录

"""
├── luffycity
	├── public/          			# 项目共有资源
		├── favicon.ico				# 站点图标
		└── index.html				# 主页
    ├── src/      					# 项目主应用,开发时的代码保存
    	├── assets/      			# 前台静态资源总目录
    		├── css/				# 自定义css样式
    			└── global.css		# 自定义全局样式
    		├── js/					# 自定义js样式
				└── settings.js		# 自定义配置文件
			└── img/				# 前台图片资源
		├── components/    			# 小组件目录
		├── views/  				# 页面组件目录
		├── App.vue	    			# 根组件
		├── main.js	    			# 入口脚本文件
		├── router    		
			└── index.js			# 路由脚本文件
		store	    		
			└── index.js			# 仓库脚本文件
    ├── vue.config.js	    		# 项目配置文件
    └── *.*							# 其他配置文件
"""

文件修订:目录中非配置文件的多余文件可以移除

App.vue

<template>
    <div id="app">
        <router-view/>
    </div>
</template>
router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter);

const routes = [
    {
        path: '/',
        name: 'Home',
        component: Home
    },
    {
        path: '/home',
        redirect: '/',
    },
];

const router = new VueRouter({
    mode: 'history',
    base: process.env.BASE_URL,
    routes
})

export default router
Home.vue
<template>
    <div class="home">
    </div>
</template>

<script>
    export default {
        name: 'home',
        components: {
        },
    }
</script>

全局配置:全局样式、配置文件

global.css
/* 声明全局样式和项目的初始化样式 */
body, h1, h2, h3, h4, h5, h6, p, table, tr, td, ul, li, a, form, input, select, option, textarea {
    margin: 0;
    padding: 0;
    font-size: 15px;
}

a {
    text-decoration: none;
    color: #333;
}

ul {
    list-style: none;
}

table {
    border-collapse: collapse; /* 合并边框 */
}
settings.js
export default {
    base_url: 'http://127.0.0.1:8000'
}
main.js
// 配置全局样式
import '@/assets/css/global.css'

// 配置全局自定义设置
import settings from '@/assets/js/settings'
Vue.prototype.$settings = settings;
// 在所有需要与后台交互的组件中:this.$settings.base_url + '再拼接具体后台路由'

目录介绍

public
    -favicon.ico   # 
    -index.html    #整个项目的单页面
src
    -assets  #静态文件,js,css,img
    -components # 小组件,头部组件,尾部组件
    -router     # 路由相关
    -store      # vuex相关,状态管理器,临时存储数据的地方
    -views      # 页面组件
    -App.vue    # 根组件
    -main.js    # 配置文件(跟django的setting一样)
    
    
    
#任何一个组件都有三部分
	<template>
    	#html相关
	</template>
    <style>
		# css相关
	</style>

    <script>
		# js相关
    </script>

后台Response和异常和日志封装

# utils/response

from rest_framework.response import  Response


class APIResponse(Response):
    def __init__(self,code=1,msg='成功',result=None,status=None,headers=None,content_type=None,**kwargs):
        dic={
            'code':code,
            'msg':msg

             }
        if result:
            dic['result']=result
        dic.update(kwargs)

        #对象来调用对象的绑定方法,会自动传值
        super().__init__(data=dic,status=status,headers=headers,content_type=content_type)

        # 类来调用对象的绑定方法,这个方法就是一个普通函数,有几个参数就要传几个参数
        # Response.__init__(data=dic,status=status,headers=headers,content_type=content_type)
#utils/exceptions.py

#方法,加日志

from rest_framework.views import exception_handler
# from luffyapi.utils import response
from .response import APIResponse
from .logger import log
def common_exception_handler(exc, context):
    log.error('view是:%s ,错误是%s'%(context['view'].__class__.__name__,str(exc)))
    #context['view']  是TextView的对象,想拿出这个对象对应的类名
    print(context['view'].__class__.__name__)
    ret=exception_handler(exc, context)  # 是Response对象,它内部有个data

    if not ret:  #drf内置处理不了,丢给django 的,我们自己来处理
        # 好多逻辑,更具体的捕获异常
        if isinstance(exc,KeyError):
            return APIResponse(code=0, msg='key error')

        return APIResponse(code=0,msg='error',result=str(exc))
    else:
        return APIResponse(code=0,msg='error',result=ret.data)



# 封装logger
# setting.py
#日志的配置
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s'
        },
        'simple': {
            'format': '%(levelname)s %(module)s %(lineno)d %(message)s'
        },
    },
    'filters': {
        'require_debug_true': {
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {
        'console': {
            # 实际开发建议使用WARNING
            'level': 'DEBUG',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'file': {
            # 实际开发建议使用ERROR
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',
            # 日志位置,日志文件名,日志保存目录必须手动创建,注:这里的文件路径要注意BASE_DIR代表的是小luffyapi
            'filename': os.path.join(os.path.dirname(BASE_DIR), "logs", "luffy.log"),
            # 日志文件的最大值,这里我们设置300M
            'maxBytes': 300 * 1024 * 1024,
            # 日志文件的数量,设置最大日志数量为10
            'backupCount': 100,
            # 日志格式:详细格式
            'formatter': 'verbose',
            # 文件内容编码
            'encoding': 'utf-8'
        },
    },
    # 日志对象
    'loggers': {
        'django': {
            'handlers': ['console', 'file'],
            'propagate': True, # 是否让日志信息继续冒泡给其他的日志处理系统
        },
    }
}
#utils/logger

import logging
# log=logging.getLogger('名字') # 跟配置文件中loggers日志对象下的名字对应
log=logging.getLogger('django')


# 以后使用,导入直接用
from luffyapi.utils.logger import log
log.info('xxxxxx')  # 不要写print

2 跨域问题及解决

# xss:跨站脚本攻击,cors:跨域资源共享,csrf:跨站请求伪造

# 1 同源策略:请求的url地址,必须与浏览器上的url地址处于同域上,也就是域名,端口,协议相同.
# 2 CORS:跨域资源共享,允许不同的域来我的服务器拿数据
# 3 CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)
	只要同时满足以下两大条件,就属于简单请求
    (1) 请求方法是以下三种方法之一:
        HEAD
        GET
        POST
     (2)HTTP的头信息不超出以下几种字段:
        Accept
        Accept-Language
        Content-Language
        Last-Event-ID
        Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
        
      #如果发送post请求,数据格式是json---》非简单请求,非简单请求发两次,一次OPTIONS请求,一次真正的请求
# 4 后端处理,开启cors,跨域资源共享(在中间中写)
class MyMiddle(MiddlewareMixin):
    def process_response(self, request, response):
        response['Access-Control-Allow-Origin'] = '*'
        if request.method == "OPTIONS":
            # 可以加*
            response["Access-Control-Allow-Headers"] = "Content-Type"
            response["Access-Control-Allow-Headers"] = "authorization"

        return response
#5 在setting的中间件中配置


#6使用第三方,django-cors-headers
-pip install django-cors-headers
-注册app:'corsheaders',
-配置中间件:corsheaders.middleware.CorsMiddleware
-setting中配置:
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_METHODS = (
	'DELETE',
	'GET',
	'OPTIONS',
	'PATCH',
	'POST',
	'PUT',
	'VIEW',
)
CORS_ALLOW_HEADERS = (
	'authorization',
	'content-type',
)


3 前后台打通

#1 前台可以发送ajax的请求,axios
#2 cnpm install axios
#3 配置在main.js中
import axios from 'axios'   //导入安装的axios
//相当于把axios这个对象放到了vue对象中,以后用  vue对象.$axios
Vue.prototype.$axios = axios;
	
    
#4 使用(某个函数中)
      this.$axios.get('http://127.0.0.1:8000/home/home/'). 向某个地址发送get请求
      then(function (response) {  如果请求成功,返回的数据再response中
        console.log(response)
      }).catch(function (error) {
        console.log(error)
      })
        
#5 es6的箭头函数
function (response) { console.log(response)}
response=>{ console.log(response)}

4 xadmin后台管理

# 1 安装 pip install https://codeload.github.com/sshwsfc/xadmin/zip/django2
# 2 在app中注册
	 # xadmin主体模块
    'xadmin',
    # 渲染表格模块
    'crispy_forms',
    # 为模型通过版本控制,可以回滚数据
    'reversion',
    
# 3 数据迁移
python manage.py makemigrations
python manage.py migrate
# 4 主路由替换掉admin:主urls.py
    # xadmin的依赖
    import xadmin
    xadmin.autodiscover()
    # xversion模块自动注册需要版本控制的 Model
    from xadmin.plugins import xversion
    xversion.register_models()

    urlpatterns = [
        # ...
        path(r'xadmin/', xadmin.site.urls),
    ]
# 5 # 在项目根目录下的终端
python manage.py createsuperuser
# 账号密码设置:admin | Admin123

路飞项目头部组件

1.1 路由跳转的方式

#html中路由跳转
<router-link to="/">
<img src="../assets/img/head-logo.svg" alt="">
</router-link>
#js中控制路由跳转
this.$router.push('/');

1.2 头部组件vue代码

# 在components内新建Head.vue
<template>
    <div class="header">
        <div class="slogan">
            <p>老男孩IT教育 | 帮助有志向的年轻人通过努力学习获得体面的工作和生活</p>
        </div>
        <div class="nav">
            <ul class="left-part">
                <li class="logo">
                    <router-link to="/">
                        <img src="../assets/img/head-logo.svg" alt="">
                    </router-link>
                </li>
                <li class="ele">
                    <span @click="goPage('/free-course')" :class="{active: url_path === '/free-course'}">免费课</span>
                </li>
                <li class="ele">
                    <span @click="goPage('/actual-course')" :class="{active: url_path === '/actual-course'}">实战课</span>
                </li>
                <li class="ele">
                    <span @click="goPage('/light-course')" :class="{active: url_path === '/light-course'}">轻课</span>
                </li>
            </ul>

            <div class="right-part">
                <div>
                    <span>登录</span>
                    <span class="line">|</span>
                    <span>注册</span>
                </div>
    		</div>
        </div>
    </div>

</template>

<script>

    export default {
        name: "Header",
        data() {
            return {
                url_path: sessionStorage.url_path || '/',
            }
        },
        methods: {
            goPage(url_path) {
                // 已经是当前路由就没有必要重新跳转
                if (this.url_path !== url_path) {
                    this.$router.push(url_path);
                }
                sessionStorage.url_path = url_path;
            },
        },
        created() {
            sessionStorage.url_path = this.$route.path;
            this.url_path = this.$route.path;
        }
    }
</script>

<style scoped>
    .header {
        background-color: white;
        box-shadow: 0 0 5px 0 #aaa;
    }

    .header:after {
        content: "";
        display: block;
        clear: both;
    }

    .slogan {
        background-color: #eee;
        height: 40px;
    }

    .slogan p {
        width: 1200px;
        margin: 0 auto;
        color: #aaa;
        font-size: 13px;
        line-height: 40px;
    }

    .nav {
        background-color: white;
        user-select: none;
        width: 1200px;
        margin: 0 auto;

    }

    .nav ul {
        padding: 15px 0;
        float: left;
    }

    .nav ul:after {
        clear: both;
        content: '';
        display: block;
    }

    .nav ul li {
        float: left;
    }

    .logo {
        margin-right: 20px;
    }

    .ele {
        margin: 0 20px;
    }

    .ele span {
        display: block;
        font: 15px/36px '微软雅黑';
        border-bottom: 2px solid transparent;
        cursor: pointer;
    }

    .ele span:hover {
        border-bottom-color: orange;
    }

    .ele span.active {
        color: orange;
        border-bottom-color: orange;
    }

    .right-part {
        float: right;
    }

    .right-part .line {
        margin: 0 10px;
    }

    .right-part span {
        line-height: 68px;
        cursor: pointer;
    }
</style>

1.3 配置全局css和setting

# 在main.js中配置
// 配置全局样式 @ 符号,代指src路径
import '@/assets/css/global.css'
// 配置全局自定义设置
import settings from '@/assets/js/settings'
Vue.prototype.$settings = settings;
// 在所有需要与后台交互的组件中:this.$settings.base_url + '再拼接具体后台路由'

# 在assets下的css中加入global.css
/* 声明全局样式和项目的初始化样式 */
body, h1, h2, h3, h4, h5, h6, p, table, tr, td, ul, li, a, form, input, select, option, textarea {
    margin: 0;
    padding: 0;
    font-size: 15px;
}

a {
    text-decoration: none;
    color: #333;
}

ul {
    list-style: none;
}

table {
    border-collapse: collapse; /* 合并边框 */
}


# 在assets下的js中加入settings.js
export default {
    base_url: 'http://127.0.0.1:8000'
}
 

1.4 前台配置

# 安装
cnpm install axios
cnpm install vue-cookies
cnpm install element-ui
cnpm install jquery
cnpm install bootstrap@3

#在main.js中配置
#axios配置
import axios from 'axios'
Vue.prototype.$axios = axios;
# vue-cookies配置
import cookies from 'vue-cookies'
Vue.prototype.$cookies = cookies;
# ElementUI的配置
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
# bootstrap配置
import 'bootstrap'
import 'bootstrap/dist/css/bootstrap.min.css'

# jquery的配置,不太一样
项目根路径创一个 vue.config.js
const webpack = require("webpack");

module.exports = {
    configureWebpack: {
        plugins: [
            new webpack.ProvidePlugin({
                $: "jquery",
                jQuery: "jquery",
                "window.jQuery": "jquery",
                "window.$": "jquery",
                Popper: ["popper.js", "default"]
            })
        ]
    }
};

2 路飞项目尾部组件

Footer.vue

<template>
    <div class="footer">
        <ul>
            <li>关于我们</li>
            <li>联系我们</li>
            <li>商务合作</li>
            <li>帮助中心</li>
            <li>意见反馈</li>
            <li>新手指南</li>
        </ul>
        <p>Copyright © luffycity.com版权所有 | 京ICP备17072161号-1</p>
    </div>
</template>

<script>
    export default {
        name: "Footer"
    }
</script>

<style scoped>
    .footer {
        width: 100%;
        height: 128px;
        background: #25292e;
        color: #fff;
    }

    .footer ul {
        margin: 0 auto 16px;
        padding-top: 38px;
        width: 810px;
    }

    .footer ul li {
        float: left;
        width: 112px;
        margin: 0 10px;
        text-align: center;
        font-size: 14px;
    }

    .footer ul::after {
        content: "";
        display: block;
        clear: both;
    }

    .footer p {
        text-align: center;
        font-size: 12px;
    }
</style>

3 轮播图接口

####1  urls.py 
from rest_framework.routers import  SimpleRouter
router=SimpleRouter()
router.register('banner',views.BannerView,'banner')
urlpatterns = [
    # path('banner/', views.BannerView.as_view()),
    path('', include(router.urls)),
]

####2 views.py
from django.conf import settings
class BannerView(GenericViewSet,ListModelMixin):
    # 无论有多少条待展示的数据,最多就展示3条
    queryset = models.Banner.objects.filter(is_delete=False,is_show=True).order_by('display_order')[:settings.BANNER_COUNTER]
    serializer_class = serializer.BannerModelSerilaizer
    
### 3 serializer.py
from rest_framework import serializers
from . import models
class BannerModelSerilaizer(serializers.ModelSerializer):
    class Meta:
        model=models.Banner
        fields=['name','link','img']
        
### 4 models.py
from utils.models import BaseModel


class Banner(BaseModel):
    name=models.CharField(max_length=32,verbose_name='图片名字')
    img=models.ImageField(upload_to='banner',verbose_name='轮播图',help_text='图片尺寸必须是:3840*800',null=True)
    link=models.CharField(max_length=32,verbose_name='跳转连接')
    info=models.TextField(verbose_name='图片简介')
    # type=models.IntegerField(choices=)

    def __str__(self):
        return self.name
####5 utils/models.py

class BaseModel(models.Model):
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    update_time = models.DateTimeField(auto_now=True, verbose_name='最后更新时间')
    is_delete = models.BooleanField(default=False, verbose_name='是否删除')
    is_show = models.BooleanField(default=True, verbose_name='是否展示')
    display_order = models.IntegerField()
    class Meta:
        abstract=True  # 一定不要忘了
        
### 6 settings/const.py
# 首页轮播图个数
BANNER_COUNTER=3

#### 7 settings/dev.py
from .const import *

4 轮播图组件

Banner.vue

<template>

    <div id="banner">

        <el-carousel height="400px">
            <el-carousel-item v-for="item in banner_list">
                <!--<img src="../assets/img/banner1.png" alt="">-->
                <router-link :to="item.link">
                    <img :src="item.img" :alt="item.name">
                </router-link>

            </el-carousel-item>
        </el-carousel>
    </div>

</template>

<script>
    export default {
        name: "Banner",
        // data:function(){},
        data() {
            return {
                banner_list: []
            }
        },
        created() {
            //当banner组件一创建,就向后台发请求,拿回轮播图数据
            this.$axios.get(this.$settings.base_url + '/home/banner/').then(response => {
                console.log(response.data)
                this.banner_list=response.data
            }).catch(error => {
            })
        },

    }
</script>

<style scoped>
    .el-carousel__item {
        height: 400px;
        min-width: 1200px;
    }

    .el-carousel__item img {
        height: 400px;
        /*margin-left: 20px;*/
        /*margin-left: calc(50% - 1920px / 2);*/
    }
</style>

前端路由配置

const routes = [
    {
        path: '/',
        name: 'Home',
        component: Home
    },
    {
        path: '/free-course',
        name: 'FreeCourse',
        component: FreeCourse
    }, {
        path: '/light-course',
        name: 'LightCourse',
        component: LightCourse
    }, {
        path: '/actual-course',
        name: 'ActualCourse',
        component: ActualCourse
    },

]

5 git的使用

#1  协同开发,版本管理
#2 svn(集中式管理),git(分布式管理)
#3 git装完,既有客户端,又有服务的
#4 git工作流程
	-工作区,暂存区,版本库
#5 远程仓库:github,码云,公司内部(gitlab)
	
# 6 安装:一路下一步
# 7 右键--git bash here

# 8 git 命令
	-初始化:git init 文件夹名
    -初始化:git init   #当前路径全被管理

	-git status
    -git add a.txt  # 把a提交到暂存区
	-git add .
    -git commit -m '注释,我新增了a'      # 把暂存区的所有都提交到版本库
    -需要增加作者信息
      git config --global user.email "lqz@qq.com"
  	  git config --global user.name "lqz"
    
      git config  user.email "egon@qq.com"
  	  git config  user.name "egon"

	-把a的新增提交到版本管理
    -新建b,在a中新增一行
    -git checkout .   # 回复到提交版本的位置,a是空的,b没有被git管理,所有,是什么样,还是什么样
    -git log   # 查看版本管理的日志
    -git reflog # 查看日志,条数更多,内容更少
	-git reset --hard 版本号
# 红色表示未被管理
# 绿色表示提交到暂存区了

# 忽略文件
	-空文件夹不被管理
	-指定某些文件或者文件夹不被git管理
    -在项目根路径,跟.git文件夹一个路径,新建.gitignore.,在里面配置
    - 语法:
    	# 号是注释,没有用
        文件夹名字,表示文件夹忽略,不被管理
        /dist 表示根路径下的dist文件夹,不被管理
        *.py   表示后缀名为py的文件,都被忽略
        *.log*
# 分支操作
	-查看分支 git branch   查看所有分支,分支是绿的,表示在当前分支上
    -创建分支 git branch dev
    -创建并切换到 git checkout -b dev
    -删除分支 git branch -d dev
    -切换分支 git checkout dev
    -合并分支 git merge 分支名  # 把dev分支合并到master分支:切换到master分支,执行合并dev分支的命令
    

it远程连接

# 1 码云(国内,快)
# 2 新建仓库的时候,不要勾选Readme初始化这个而仓库
# 3 现在什么都没有,新建仓库
    mkdir lqz_test
    cd lqz_test
    git init
    touch a.txt
    git add a.txt
    git commit -m "first commit"
    git remote add origin https://gitee.com/liuqingzheng/lqz_test.git  # 连接远程
    git push  origin master
# 4 已经有了仓库
    cd b
    git remote add origin https://gitee.com/liuqingzheng/lqz_test.git
    git push origin master
    
    
    
# 5 git 远程操作命令
	-git remote # 查看远程仓库(没有就看不到)
    -git remote add origin https://gitee.com/liuqingzheng/lqz_test.git # 跟远程仓库建立连接
    -git push origin master  # 把本地的master分支提交到远程的origin,需要输入用户名和密码(之前存的需要删掉)

2 git项目创始者和开发者

# 1 项目创始者,如上
# 2 项目开发者,参与者(换了一台电脑),把代码拉下来继续开发
	-git clone https://gitee.com/liuqingzheng/lqz_test.git
    -能看到完整的版本和日志控制
    -可以回复到任意版本
    	-git rest --hard 版本号
    -git checkout . # 表示回到指针指向的版本,因为已经用git rest --hard把指针移动了,所以checkout .就是当前再的版本上的东西
# 3 本地新增c.txt文件
	-提交到暂存区
    -提交到版本库(没有提交到远程,远程看不到)
    -提交到远程:git push origin master
    
    
# 4 让a文件夹中的代码成为最新的
	-git pull origin master
    
# 5 重点:每次再提交代码之前,一定要先更新代码(拉),如果不拉提不上去

3 ssh连接和https连接

# 1 公司内部大部分用ssh连接
	-领导给你一个git地址(项目地址)
    -你 git clone 地址  到本地
    -改代码,改完了---》提交(禁止)
    -ssh配置,以后都不用输密码了
# 2 配置如何做
	-对称加密(加密和解密用同一套秘密)
    -非对称加密(公钥和私钥),公钥加密,私钥解密
    
    -生成一对公钥和私钥(用命令)
    	-https://gitee.com/help/articles/4181
        -ssh-keygen -t rsa -C "lqz@qq.com" 生成到用户家目录的.ssh文件夹下(一个公钥,一个私钥)  
        -把公钥复制出来,再码云上配置
 
# 跟远程操作有三个命令
git pull
git push
-git feacth(一般不用,他跟pull是一个东西,当作不知道)

1595315082819

pycharm操作git

# 1 安装git
# 2 再pycharm中配置,setting---》git--->git.exe的地址
# 3 git clone --->等同于下图
# 4 使用pycharm创建本地分支(见下图)
# 5 拉取代码,如下图
# 6 push代码,如下图

1595301976791

1595314048881

1595314084159

1595314121443

1595314167062

1595314245430

4 协同开发

# 1 协同开发出现冲突,如何解决
# 2 git pull origin master  拉下远程代码,同事和你修改了同一个位置,会冲突,如下
<<<<<<< HEAD
你的代码
=======
别人代码
>>>>>>> origin/master
# 3 处理方案
    -删除你的代码
    -删除同时代码
    -合并你们的代码

5 合并分支出现冲突

# 1 分支:本地分支,远程分支

# 2 创建本地分支
	-git checkout -b dev
# 3 把本地分支提交到远程
	-git push origin dev  

5 线上分支合并

1595315172363

1595315293642

# 如果看到可自动合并,表示合并后不会有冲突,正常操作即可
# 测试人员点通过,直接合并即可

6 线上回滚

# 1 切换到master分支
# 2 回滚打某个版本  git reset --hard  版本号
# 3 强制提交代码 git push origin master -f

7 分支合并出现冲突解决

# 1 远端创建一个dev分支
	-在远端直接创建
    -本地创建,提交到远端
    git checkout -b dev
    git push origin dev
# 2 本地新建一个dev_bug分支
	-dev_bug分支改了文件
    -dev分支改了同样的文件
    -合并就出冲突
    -git merge dev_bug (在dev分支上操作)
    -解决冲突(删你的,同事的,合并起来)
    -git add .  git commit 
    -正常了,冲突解决
 # 3 手动线下合并代码并提交到远程
	-git checkout master
    -git merge dev 
    -如果出冲突,解决
    -git add .
    -git commit 
    -git push..

7 vue登录页面

# Login.vue
<template>
    <div class="login">
    <span @click="close_login">X</span>
    </div>
</template>

<script>
    export default {
        name: "Login",
        methods:{
            close_login(){
                //子传父组件 this.$emit,给父组件传递一个事件
                this.$emit("close")
            },

        },

    }
</script>

<style scoped>
.login{
    width: 100vw;
    height: 100vw;
    position: fixed;
    left: 0;
    top: 0;
    z-index: 666;
    background-color: rgba(0,0,0,0.3);
}
    span{
        font-size: 30px;
        cursor: pointer;
    }
</style>

# Head.vue
#template中
<div class="right-part">
<div>
<span @click="pull_login">登录</span>
<span class="line">|</span>
<span>注册</span>
</div>
</div>
</div>

<Login v-if="is_login" @close="close"/>
<!--is_login是True就显示,false就不显示-->
</div>
# js中
pull_login(){
    this.is_login=true
},
close(){
    this.is_login=false
},

补充

1 pycharm找回误删,修改的东西

1595314881189

2 git的变基

git rebase

登陆注册模态框

# Login.vue
<template>
    <div class="login">
        <div class="box">
            <i class="el-icon-close" @click="close_login"></i>
            <div class="content">
                <div class="nav">
                    <span :class="{active: login_method === 'is_pwd'}"
                          @click="change_login_method('is_pwd')">密码登录</span>
                    <span :class="{active: login_method === 'is_sms'}"
                          @click="change_login_method('is_sms')">短信登录</span>
                </div>
                <el-form v-if="login_method === 'is_pwd'">
                    <el-input
                            placeholder="用户名/手机号/邮箱"
                            prefix-icon="el-icon-user"
                            v-model="username"
                            clearable>
                    </el-input>
                    <el-input
                            placeholder="密码"
                            prefix-icon="el-icon-key"
                            v-model="password"
                            clearable
                            show-password>
                    </el-input>
                    <el-button type="primary">登录</el-button>
                </el-form>
                <el-form v-if="login_method === 'is_sms'">
                    <el-input
                            placeholder="手机号"
                            prefix-icon="el-icon-phone-outline"
                            v-model="mobile"
                            clearable
                            @blur="check_mobile">
                    </el-input>
                    <el-input
                            placeholder="验证码"
                            prefix-icon="el-icon-chat-line-round"
                            v-model="sms"
                            clearable>
                        <template slot="append">
                            <span class="sms" @click="send_sms">{{ sms_interval }}</span>
                        </template>
                    </el-input>
                    <el-button type="primary">登录</el-button>
                </el-form>
                <div class="foot">
                    <span @click="go_register">立即注册</span>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        name: "Login",
        data() {
            return {
                username: '',
                password: '',
                mobile: '',
                sms: '',
                login_method: 'is_pwd',
                sms_interval: '获取验证码',
                is_send: false,
            }
        },
        methods: {
            close_login() {
                this.$emit('close')
            },
            go_register() {
                this.$emit('go')
            },
            change_login_method(method) {
                this.login_method = method;
            },
            check_mobile() {
                if (!this.mobile) return;
                if (!this.mobile.match(/^1[3-9][0-9]{9}$/)) {
                    this.$message({
                        message: '手机号有误',
                        type: 'warning',
                        duration: 1000,
                        onClose: () => {
                            this.mobile = '';
                        }
                    });
                    return false;
                }
                this.is_send = true;
            },
            send_sms() {

                if (!this.is_send) return;
                this.is_send = false;
                let sms_interval_time = 60;
                this.sms_interval = "发送中...";
                let timer = setInterval(() => {
                    if (sms_interval_time <= 1) {
                        clearInterval(timer);
                        this.sms_interval = "获取验证码";
                        this.is_send = true; // 重新回复点击发送功能的条件
                    } else {
                        sms_interval_time -= 1;
                        this.sms_interval = `${sms_interval_time}秒后再发`;
                    }
                }, 1000);
            }
        }
    }
</script>

<style scoped>
    .login {
        width: 100vw;
        height: 100vh;
        position: fixed;
        top: 0;
        left: 0;
        z-index: 10;
        background-color: rgba(0, 0, 0, 0.3);
    }

    .box {
        width: 400px;
        height: 420px;
        background-color: white;
        border-radius: 10px;
        position: relative;
        top: calc(50vh - 210px);
        left: calc(50vw - 200px);
    }

    .el-icon-close {
        position: absolute;
        font-weight: bold;
        font-size: 20px;
        top: 10px;
        right: 10px;
        cursor: pointer;
    }

    .el-icon-close:hover {
        color: darkred;
    }

    .content {
        position: absolute;
        top: 40px;
        width: 280px;
        left: 60px;
    }

    .nav {
        font-size: 20px;
        height: 38px;
        border-bottom: 2px solid darkgrey;
    }

    .nav > span {
        margin: 0 20px 0 35px;
        color: darkgrey;
        user-select: none;
        cursor: pointer;
        padding-bottom: 10px;
        border-bottom: 2px solid darkgrey;
    }

    .nav > span.active {
        color: black;
        border-bottom: 3px solid black;
        padding-bottom: 9px;
    }

    .el-input, .el-button {
        margin-top: 40px;
    }

    .el-button {
        width: 100%;
        font-size: 18px;
    }

    .foot > span {
        float: right;
        margin-top: 20px;
        color: orange;
        cursor: pointer;
    }

    .sms {
        color: orange;
        cursor: pointer;
        display: inline-block;
        width: 70px;
        text-align: center;
        user-select: none;
    }
</style>
# Register.vue
<template>
    <div class="register">
        <div class="box">
            <i class="el-icon-close" @click="close_register"></i>
            <div class="content">
                <div class="nav">
                    <span class="active">新用户注册</span>
                </div>
                <el-form>
                    <el-input
                            placeholder="手机号"
                            prefix-icon="el-icon-phone-outline"
                            v-model="mobile"
                            clearable
                            @blur="check_mobile">
                    </el-input>
                    <el-input
                            placeholder="密码"
                            prefix-icon="el-icon-key"
                            v-model="password"
                            clearable
                            show-password>
                    </el-input>
                    <el-input
                            placeholder="验证码"
                            prefix-icon="el-icon-chat-line-round"
                            v-model="sms"
                            clearable>
                        <template slot="append">
                            <span class="sms" @click="send_sms">{{ sms_interval }}</span>
                        </template>
                    </el-input>
                    <el-button type="primary">注册</el-button>
                </el-form>
                <div class="foot">
                    <span @click="go_login">立即登录</span>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        name: "Register",
        data() {
            return {
                mobile: '',
                password: '',
                sms: '',
                sms_interval: '获取验证码',
                is_send: false,
            }
        },
        methods: {
            close_register() {
                this.$emit('close', false)
            },
            go_login() {
                this.$emit('go')
            },
            check_mobile() {
                if (!this.mobile) return;
                if (!this.mobile.match(/^1[3-9][0-9]{9}$/)) {
                    this.$message({
                        message: '手机号有误',
                        type: 'warning',
                        duration: 1000,
                        onClose: () => {
                            this.mobile = '';
                        }
                    });
                    return false;
                }
                this.is_send = true;
            },
            send_sms() {
                if (!this.is_send) return;
                this.is_send = false;
                let sms_interval_time = 60;
                this.sms_interval = "发送中...";
                let timer = setInterval(() => {
                    if (sms_interval_time <= 1) {
                        clearInterval(timer);
                        this.sms_interval = "获取验证码";
                        this.is_send = true; // 重新回复点击发送功能的条件
                    } else {
                        sms_interval_time -= 1;
                        this.sms_interval = `${sms_interval_time}秒后再发`;
                    }
                }, 1000);
            }
        }
    }
</script>

<style scoped>
    .register {
        width: 100vw;
        height: 100vh;
        position: fixed;
        top: 0;
        left: 0;
        z-index: 10;
        background-color: rgba(0, 0, 0, 0.3);
    }

    .box {
        width: 400px;
        height: 480px;
        background-color: white;
        border-radius: 10px;
        position: relative;
        top: calc(50vh - 240px);
        left: calc(50vw - 200px);
    }

    .el-icon-close {
        position: absolute;
        font-weight: bold;
        font-size: 20px;
        top: 10px;
        right: 10px;
        cursor: pointer;
    }

    .el-icon-close:hover {
        color: darkred;
    }

    .content {
        position: absolute;
        top: 40px;
        width: 280px;
        left: 60px;
    }

    .nav {
        font-size: 20px;
        height: 38px;
        border-bottom: 2px solid darkgrey;
    }

    .nav > span {
        margin-left: 90px;
        color: darkgrey;
        user-select: none;
        cursor: pointer;
        padding-bottom: 10px;
        border-bottom: 2px solid darkgrey;
    }

    .n					

推荐阅读