豆约翰

豆约翰 查看完整档案

填写现居城市  |  填写毕业院校home  |  teacher 编辑 www.songbo.info 编辑
编辑

闲看庭前花开花落 漫随天外云卷云舒

个人动态

豆约翰 发布了文章 · 2020-12-24

从零开始手把手教你使用javascript+canvas开发一个塔防游戏01地图创建

项目演示

tf.gif

项目演示地址:

体验一下

项目源码:

项目源码

代码结构

本节做完效果

游戏主页面

index.html

<!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">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" />
<title>塔防</title>

</head>
<body>

<img data-original="img/enemy.png" id="enemy_img" style="display:none;" />
<img data-original="img/tower.png" id="tower_img" style="display:none;" />
<img data-original="img/bullet.png" id="bullet_img" style="display:none;" />
<img data-original="img/btn.png" id="btn_img" style="display:none;" />

<div id="game" style="position:relative;float:left;width:600px;height:500px;">
    <canvas id="map" width="500" height="500" style="background:url(img/bg.png) repeat;position:absolute;left:0;top:0;z-index:99;"></canvas>
    <canvas id="main" width="500" height="500" style="position:absolute;left:0;top:0;z-index:100;"></canvas>
    <canvas id="tower" width="500" height="500" style="position:absolute;left:0;top:0;z-index:100;"></canvas>
    <canvas id="select" width="500" height="500" style="position:absolute;left:0;top:0;z-index:101;"></canvas>
    <canvas id="info" width="100" height="500" style="background-color:black;position:absolute;left:500px;top:0;z-index:102;"></canvas>
</div>

<div style="float:left;width:500px;margin-left:50px;">
    
    <p>
        说明:每种塔都可以升级到3级,升级的价格与本身建造相同,卖掉的话就是总额的一半。<br/><br/>
        第一种塔:升级到3级,有小偷功能,每攻击一次会偷取1块钱。<br/>
        第二种塔:减速攻击,3级时可以同时减速两个。<br/>
        第三种塔:多重攻击。攻击多个目标。<br/>
        第四种塔:穿刺攻击,攻击一条线上,攻击力最高。<br/>
        第五种塔:秒杀攻击,有一定几率把敌人秒杀。<br/><br />
        选择地图<select id="select_map"><option value="1">地图一</option><option value="2">地图二</option></select>,然后按<input type="button" value="开始" onclick="startGame()" />开始游戏!
    </p>
    
</div>


<script type="text/javascript" data-original="js/tools.js"></script>
<script type="text/javascript" data-original="js/MapData.js"></script>
<script type="text/javascript" data-original="js/Map.js"></script>
<script type="text/javascript" data-original="js/Info.js"></script>
<script type="text/javascript" data-original="js/Enemy.js"></script>
<script type="text/javascript" data-original="js/Tower.js"></script>
<script type="text/javascript" data-original="js/Bullet.js"></script>
<script type="text/javascript" data-original="js/Game.js"></script>

<script type="text/javascript">
    
    var isStart = false;
    
    function startGame(){
        
        if(!isStart)Game.start();
        else Game.restart();
        
        isStart = true;
    }
    
</script>

</body>
</html>

游戏主模块

game.js

//游戏数据控制类
var Game = {
    //图片列表信息
    imgList : {},
    //画布列表信息
    canvasList : {},

    //初始化
    init : function(){
        this.initImg();
        this.initCanvas();
    },
    //初始化图片
    initImg : function(){

        this.imgList = {

            enemy_img : document.getElementById("enemy_img"),
            tower_img : document.getElementById("tower_img"),
            bullet_img : document.getElementById("bullet_img"),
            btn_img : document.getElementById("btn_img")
        }
    },
    //初始化画布
    initCanvas : function(){

        this.canvasList = {

            map : document.getElementById("map").getContext("2d"),
            main : document.getElementById("main").getContext("2d"),
            info : document.getElementById("info").getContext("2d"),
            select : document.getElementById("select").getContext("2d"),
            tower : document.getElementById("tower").getContext("2d")
        }
    },

    //开始
    start : function(){

        switch(document.getElementById("select_map").value){
            case "1":
                MapData = MapOne;
                break;
            case "2":
                MapData = MapTwo;
                break;
            default:
                MapData = MapOne;
                break;
        }
        Map.draw(this.canvasList.map);
        this.timer = setInterval(Game.loop,20);
    },
    //循环体
    loop : function(){
        Canvas.clear(Game.canvasList.main,500,500);
    }
}

Game.init();

代码不言自明。
简单说明一下:
Game.init()加载图片和Canvas对象,本游戏有5个Canvas对象,具体用途我们在后面图层章节进行说明。
点击网页上的开始按钮,调用start方法。start方法加载地图,并画到canvas上。

地图数据

0是墙壁,1是可以走的路径。可以简单的改变数据,生成不同形状的地图。
MapData.js

//地图类
var MapData = [];

//地图二数据
var MapTwo = [[0,0,0,0,0,0,0,0,0,0],
    [1,1,1,1,1,1,1,1,1,0],
    [0,0,0,0,0,0,0,0,1,0],
    [0,0,0,0,0,0,0,0,1,0],
    [0,1,1,1,1,1,1,1,1,0],
    [0,1,0,0,0,0,0,0,0,0],
    [0,1,0,0,0,0,0,0,0,0],
    [0,1,1,1,1,1,1,1,1,0],
    [0,0,0,0,0,0,0,0,1,0],
    [0,0,0,0,0,0,0,0,1,0]];
//地图一数据
var MapOne = [[0,0,0,0,0,0,0,0,0,0],
    [1,1,1,1,1,1,1,1,1,0],
    [0,0,0,0,0,0,0,0,1,0],
    [0,0,0,0,0,0,0,0,1,0],
    [0,0,0,0,0,0,0,0,1,0],
    [0,1,1,1,1,1,1,1,1,0],
    [0,1,0,0,0,0,0,0,0,0],
    [0,1,0,0,0,0,0,0,0,0],
    [0,1,0,0,0,0,0,0,0,0],
    [0,1,0,0,0,0,0,0,0,0]];

画地图

Map.js

//地图绘制类
var Map = {
    //画出地图
    draw : function(map){

        var i,j;

        for(i = 0; i < 10;i++){

            for(j = 0;j<10;j++){
                //画背景地图
                if(MapData[i][j] == 0)Canvas.drawRect(map,i*50,j*50,50,50,'red');
                //画可以走的路
                else Canvas.fillRect(map,i*50,j*50,50,50,'black');
            }
        }
    }
}

很简单,看注释即可明白。

游戏循环

    //开始
    start : function(){

        switch(document.getElementById("select_map").value){
            case "1":
                MapData = MapOne;
                break;
            case "2":
                MapData = MapTwo;
                break;
            default:
                MapData = MapOne;
                break;
        }
        Map.draw(this.canvasList.map);
        this.timer = setInterval(Game.loop,20);
    },
    //循环体
    loop : function(){
        Canvas.clear(Game.canvasList.main,500,500);
    }

每隔20毫秒,刷新一下界面。
That's All

项目源码:

项目源码

查看原文

赞 0 收藏 0 评论 0

豆约翰 发布了文章 · 2020-12-21

Python实现超级玛丽游戏系列教程05添加地面,管道和阶梯冲突检测

配套视频教程

配套视频教程

项目代码

项目代码

冲突检测

添加地面冲突

添加管道冲突



添加阶梯冲突

添加冲突完成

  def setup_ground(self):
        """Create collideable rects for each section of the ground"""

        self.ground_rect1 = collider.Collider(0, c.GROUND_HEIGHT, 2953, 60)
        self.ground_rect2 = collider.Collider(3048, c.GROUND_HEIGHT, 635, 60)
        self.ground_rect3 = collider.Collider(3819, c.GROUND_HEIGHT, 2735, 60)
        self.ground_rect4 = collider.Collider(6647, c.GROUND_HEIGHT, 2300, 60)

        self.ground_group = pg.sprite.Group(self.ground_rect1,
                                            self.ground_rect2,
                                            self.ground_rect3,
                                            self.ground_rect4)

    def setup_pipes(self):
        """Create collideable rects for all the pipes"""

        self.pipe1 = collider.Collider(1202, 452, 83, 82)
        self.pipe2 = collider.Collider(1631, 409, 83, 140)
        self.pipe3 = collider.Collider(1973, 366, 83, 170)
        self.pipe4 = collider.Collider(2445, 366, 83, 170)
        self.pipe5 = collider.Collider(6989, 452, 83, 82)
        self.pipe6 = collider.Collider(7675, 452, 83, 82)

        self.pipe_group = pg.sprite.Group(self.pipe1, self.pipe2,
                                          self.pipe3, self.pipe4,
                                          self.pipe5, self.pipe6)

    def setup_steps(self):
        """Create collideable rects for all the steps"""

        self.step1 = collider.Collider(5745, 495, 40, 44)
        self.step2 = collider.Collider(5788, 452, 40, 44)
        self.step3 = collider.Collider(5831, 409, 40, 44)
        self.step4 = collider.Collider(5874, 366, 40, 176)

        self.step5 = collider.Collider(6001, 366, 40, 176)
        self.step6 = collider.Collider(6044, 408, 40, 40)
        self.step7 = collider.Collider(6087, 452, 40, 40)
        self.step8 = collider.Collider(6130, 495, 40, 40)

        self.step9 = collider.Collider(6345, 495, 40, 40)
        self.step10 = collider.Collider(6388, 452, 40, 40)
        self.step11 = collider.Collider(6431, 409, 40, 40)
        self.step12 = collider.Collider(6474, 366, 40, 40)
        self.step13 = collider.Collider(6517, 366, 40, 176)

        self.step14 = collider.Collider(6644, 366, 40, 176)
        self.step15 = collider.Collider(6687, 408, 40, 40)
        self.step16 = collider.Collider(6728, 452, 40, 40)
        self.step17 = collider.Collider(6771, 495, 40, 40)

        self.step18 = collider.Collider(7760, 495, 40, 40)
        self.step19 = collider.Collider(7803, 452, 40, 40)
        self.step20 = collider.Collider(7845, 409, 40, 40)
        self.step21 = collider.Collider(7888, 366, 40, 40)
        self.step22 = collider.Collider(7931, 323, 40, 40)
        self.step23 = collider.Collider(7974, 280, 40, 40)
        self.step24 = collider.Collider(8017, 237, 40, 40)
        self.step25 = collider.Collider(8060, 194, 40, 40)
        self.step26 = collider.Collider(8103, 194, 40, 360)

        self.step27 = collider.Collider(8488, 495, 40, 40)

        self.step_group = pg.sprite.Group(self.step1, self.step2,
                                          self.step3, self.step4,
                                          self.step5, self.step6,
                                          self.step7, self.step8,
                                          self.step9, self.step10,
                                          self.step11, self.step12,
                                          self.step13, self.step14,
                                          self.step15, self.step16,
                                          self.step17, self.step18,
                                          self.step19, self.step20,
                                          self.step21, self.step22,
                                          self.step23, self.step24,
                                          self.step25, self.step26,
                                          self.step27)

查看原文

赞 0 收藏 0 评论 0

豆约翰 发布了文章 · 2020-12-11

通过游戏学javascript系列第一节Canvas游戏开发基础

本节教程通过一个简单的游戏小例子,讲解Canvas的基础知识。

最终效果:

点击移动的方块,方块上的分数会增加,方块的行进方向会改变,并且方块的速度会增加。

在线演示

源码

HTML5引入了canvas元素。canvas元素为我们提供了一块空白画布。我们可以使用此画布来绘制和绘制我们想要的任何东西。JavaScript为我们提供了动态制作动画并绘制到画布上所需的工具。它不仅提供绘图和动画系统,还可以处理用户交互。在本教程中,我们将使用纯JavaScript制作基本的HTML5 Canvas框架,该框架可用于制作真实的游戏。在本教程的结尾创建了一个非常简单的游戏,以演示HTML5 Canvas与JavaScript结合的优势。

HTML5 Canvas基本游戏框架

让我们围绕canvas元素创建一个基本的游戏框架。我们需要一个HTML5文件和一个JavaScript文件。HTML5文件应包含canvas元素和对JavaScript文件的引用。JavaScript文件包含将代码绘制到canvas元素的代码。

这是HTML5文件index.html:

<head>
<meta charset="UTF-8">
<title>Canvas Example</title>
<script type="text/javascript" data-original="framework.js"></script>
</head>
<body>
<canvas id="viewport" width="640" height="480"></canvas>
</body>
</html> 

如您所见,JavaScript文件game.js包含在html文件的头部。画布元素以名称“ viewport”定义,其宽度为640像素,高度为480像素。在我们的framework.js中,我们需要使用其名称查找canvas元素,以便可以在其上进行绘制。我们正在创建的框架应支持渲染循环以及玩家与鼠标的交互。对于渲染循环,我们将使用Window.requestAnimationFrame()。通过添加鼠标事件侦听器来启用鼠标交互。

这是JavaScript文件framework.js:

// The function gets called when the window is fully loaded
window.onload = function() {
    // Get the canvas and context
    var canvas = document.getElementById("viewport"); 
    var context = canvas.getContext("2d");

    // Timing and frames per second
    var lastframe = 0;
    var fpstime = 0;
    var framecount = 0;
    var fps = 0;
    
    // Initialize the game
    function init() {
        // Add mouse events
        canvas.addEventListener("mousemove", onMouseMove);
        canvas.addEventListener("mousedown", onMouseDown);
        canvas.addEventListener("mouseup", onMouseUp);
        canvas.addEventListener("mouseout", onMouseOut);
    
        // Enter main loop
        main(0);
    }
    
    // Main loop
    function main(tframe) {
        // Request animation frames
        window.requestAnimationFrame(main);
        
        // Update and render the game
        update(tframe);
        render();
    }
    
    // Update the game state
    function update(tframe) {
        var dt = (tframe - lastframe) / 1000;
        lastframe = tframe;
        
        // Update the fps counter
        updateFps(dt);
    }
    
    function updateFps(dt) {
        if (fpstime > 0.25) {
            // Calculate fps
            fps = Math.round(framecount / fpstime);
            
            // Reset time and framecount
            fpstime = 0;
            framecount = 0;
        }
        
        // Increase time and framecount
        fpstime += dt;
        framecount++;
    }
    
    // Render the game
    function render() {
        // Draw the frame
        drawFrame();
    }
    
    // Draw a frame with a border
    function drawFrame() {
        // Draw background and a border
        context.fillStyle = "#d0d0d0";
        context.fillRect(0, 0, canvas.width, canvas.height);
        context.fillStyle = "#e8eaec";
        context.fillRect(1, 1, canvas.width-2, canvas.height-2);
        
        // Draw header
        context.fillStyle = "#303030";
        context.fillRect(0, 0, canvas.width, 65);
        
        // Draw title
        context.fillStyle = "#ffffff";
        context.font = "24px Verdana";
        context.fillText("HTML5 Canvas Basic Framework ", 10, 30);
        
        // Display fps
        context.fillStyle = "#ffffff";
        context.font = "12px Verdana";
        context.fillText("Fps: " + fps, 13, 50);
    }
    
    // Mouse event handlers
    function onMouseMove(e) {}
    function onMouseDown(e) {}
    function onMouseUp(e) {}
    function onMouseOut(e) {}
    
    // Get the mouse position
    function getMousePos(canvas, e) {
        var rect = canvas.getBoundingClientRect();

        return {
            x: Math.round((e.clientX - rect.left)/(rect.right - rect.left)*canvas.width),
            y: Math.round((e.clientY - rect.top)/(rect.bottom - rect.top)*canvas.height)
        };
    }
    
    // Call init to start the game
    init();
};

上面的代码绘制了一个带有边框,标题和每秒帧数的简单框架。这是代码生成的内容

带有弹跳方块的游戏

现在我们有了一个框架,让我们用它创建一个简单的游戏。我们将创建一个在屏幕上具有反弹方块的游戏。当玩家单击它时,方块上的分数会增加,方块的行进方向会改变,并且方块的速度会增加。

首先,我们定义一些对象和属性。该级别定义了方块可以反弹的区域。方块本身具有位置,尺寸和运动属性。最后,有一个分数。

    // Level properties
    var level = {
        x: 1,
        y: 65,
        width: canvas.width - 2,
        height: canvas.height - 66
    };
 
    // Square
    var square = {
        x: 0,
        y: 0,
        width: 0,
        height: 0,
        xdir: 0,
        ydir: 0,
        speed: 0
    }
 
    // Score
    var score = 0;

我们需要在init()函数中初始化对象和属性。

    // Initialize the game
    function init() {
        // Add mouse events
        canvas.addEventListener("mousemove", onMouseMove);
        canvas.addEventListener("mousedown", onMouseDown);
        canvas.addEventListener("mouseup", onMouseUp);
        canvas.addEventListener("mouseout", onMouseOut);
 
        // Initialize the square
        square.width = 100;
        square.height = 100;
        square.x = level.x + (level.width - square.width) / 2;
        square.y = level.y + (level.height - square.height) / 2;
        square.xdir = 1;
        square.ydir = 1;
        square.speed = 200;
 
        // Initialize the score
        score = 0;
 
        // Enter main loop
        main(0);
    }

这些对象需要更新,因此让我们修改update()函数。方块需要移动,并且应该检测并解决与标高边缘的碰撞。

    // Update the game state
    function update(tframe) {
        var dt = (tframe - lastframe) / 1000;
        lastframe = tframe;
 
        // Update the fps counter
        updateFps(dt);
 
        // Move the square, time-based
        square.x += dt * square.speed * square.xdir;
        square.y += dt * square.speed * square.ydir;
 
        // Handle left and right collisions with the level
        if (square.x <= level.x) {
            // Left edge
            square.xdir = 1;
            square.x = level.x;
        } else if (square.x + square.width >= level.x + level.width) {
            // Right edge
            square.xdir = -1;
            square.x = level.x + level.width - square.width;
        }
 
        // Handle top and bottom collisions with the level
        if (square.y <= level.y) {
            // Top edge
            square.ydir = 1;
            square.y = level.y;
        } else if (square.y + square.height >= level.y + level.height) {
            // Bottom edge
            square.ydir = -1;
            square.y = level.y + level.height - square.height;
        }
    }

我们需要绘制方块和分数。这需要在render()函数中完成。

    // Render the game
    function render() {
        // Draw the frame
        drawFrame();
 
        // Draw the square
        context.fillStyle = "#ff8080";
        context.fillRect(square.x, square.y, square.width, square.height);
 
        // Draw score inside the square
        context.fillStyle = "#ffffff";
        context.font = "38px Verdana";
        var textdim = context.measureText(score);
        context.fillText(score, square.x+(square.width-textdim.width)/2, square.y+65);
    }

最后一步是添加鼠标交互。让我们将代码添加到onMouseDown()函数中。

   function onMouseDown(e) {
        // Get the mouse position
        var pos = getMousePos(canvas, e);
 
        // Check if we clicked the square
        if (pos.x >= square.x && pos.x < square.x + square.width &&
            pos.y >= square.y && pos.y < square.y + square.height) {
 
            // Increase the score
            score += 1;
 
            // Increase the speed of the square by 10 percent
            square.speed *= 1.1;
 
            // Give the square a random position
            square.x = Math.floor(Math.random()*(level.x+level.width-square.width));
            square.y = Math.floor(Math.random()*(level.y+level.height-square.height));
 
            // Give the square a random direction
            square.xdir = Math.floor(Math.random() * 2) * 2 - 1;
            square.ydir = Math.floor(Math.random() * 2) * 2 - 1;
        }
    }

这是通过基本框架和一些修改而成的最终游戏。单击方块以增加您的分数并前进到下一个方块。

查看原文

赞 0 收藏 0 评论 0

豆约翰 赞了文章 · 2020-11-23

python3 + Django + uwsgi + nginx 配置部署笔记

本文环境

操作系统: Ubuntu 16.04.3
Python版本: 3.5.2
Django版本: 2.0.4
nginx版本: 1.10.3

本文平台为腾讯云1核1G系统
我的项目文件名为: dgutpsy

安装pip3(python3 的pip)

sudo apt-get install python3-pip

2018-04-10-20-07-25.png

安装成功后运行pip3将会出现

2018-04-10-20-09-19.png

安装uwsgi

pip install uwsgi

2018-04-10-20-17-19.png

测试uwsgi运行状态

新建文件test.py
def application(env, start_response):
    start_response('200 OK', [('Content-Type','text/html')])
    return [b"Hello World"]
有些教程说是 return "hello world"
但是这样其实会出现访问空白的情况, 我的是Python3的环境,需要对hello world进行编码.
而如果你是Python2的环境,你应该写return "hello world"

使用uwsgi运行该文件

uwsgi --http :8000 --wsgi-file test.py
此语句的意思是,使用uwsgi运行test.py文件, 采用http模式, 端口8000

2018-04-10-20-44-49.png

访问页面

好啦,可以看到亲切的hello world 就说明uwsgi运行成功了

2018-04-10-20-39-21.png

TIPS: 如果你访问不了,请先检查腾讯云安全组端口是否开放8000端口

安装Django

pip3 install Django
如在python3里面import django没有报错则安装成功.

2018-04-10-20-55-49.png

上传项目代码

现在我们开始上传项目代码

2018-04-10-20-42-25.png

使用MobaXterm的sftp工具拖进来,[笑着哭]不要问我为什么不用ftp,这个方便.

确认代码运行正常

切进项目目录,运行
python3 manage.py runserver 0.0.0.0:8000

2018-04-10-20-59-03.png

访问页面

2018-04-10-21-01-32.png

不太对?这是因为设置了ALLOWED_HOSTS的原因
我们在setting.py里设置一下
ALLOWED_HOSTS = ['*']

再次访问页面

2018-04-10-21-05-34.png

OK!

uswgi运行项目

 uwsgi --http :8000 --chdir /home/ubuntu/dgutpsy  --wsgi dgutpsy.wsgi
这个语句的意思与上相似,只不过是多了一个工作路径和wsgi文件
2018-04-10-21-05-34.png
开什么玩笑? 你觉得没变化?

至此

我们可以暂定以下内容
  1. 项目没问题.
  2. 项目配置没问题
  3. uwsgi没问题
  4. 对项目的支持环境没问题

接下来, 我们开始配置nginx

安装nginx

sudo apt-get install nginx

测试nginx运行状态

2018-04-10-21-18-53.png

nginx 安装成功

修改nginx配置

文件路径"/etc/nginx/sites-enabled/default"

别再费力找nginx.conf啦,新版本已经没有这个文件的.

我的
upstream django {
        server 127.0.0.1:8001; #web的socket端口
    }
server {
    listen 80 default_server;
    listen [::]:80 default_server;
 
    index index.html index.htm index.nginx-debian.html;

    server_name _;

    location / {
        root /home/ubuntu/dgutpsy; #项目目录
        uwsgi_pass django;
        include /home/ubuntu/dgutpsy/uwsgi_params; #uwsgi_params文件的地址
    }
}

完整的uwsgi_params文件内容应该是

uwsgi_param  QUERY_STRING       $query_string;
uwsgi_param  REQUEST_METHOD     $request_method;
uwsgi_param  CONTENT_TYPE       $content_type;
uwsgi_param  CONTENT_LENGTH     $content_length;

uwsgi_param  REQUEST_URI        $request_uri;
uwsgi_param  PATH_INFO          $document_uri;
uwsgi_param  DOCUMENT_ROOT      $document_root;
uwsgi_param  SERVER_PROTOCOL    $server_protocol;
uwsgi_param  REQUEST_SCHEME     $scheme;
uwsgi_param  HTTPS              $https if_not_empty;

uwsgi_param  REMOTE_ADDR        $remote_addr;
uwsgi_param  REMOTE_PORT        $remote_port;
uwsgi_param  SERVER_PORT        $server_port;
uwsgi_param  SERVER_NAME        $server_name;
没有这个文件,或者这个文件写错是会报错的哦.

访问测试502

2018-04-10-21-37-49.png

这是因为我们的socket通信还没开始呢!

nginx 与 uwsgi通信

先使用hello world测试
uwsgi --socket :8001 --wsgi-file test.py

访问测试页面

图片描述


至此: nginx 与 uwsgi通信成功!

TIPS: 这里你是无法通过在浏览器里面访问8001端口来判断是否启动的,因为它是socket协议

让uwsgi后台运行

这样虽然看似可以, 但是当你关闭ssh时,又会出现502了!

这是因为当你关闭这个ssh进程时,uwsgi进程也被终止了.
而且,每次启动都要输一串好长的命令,好不麻烦!

这个时候,我们需要用到一个东西, 那就是ini配置文件启动.
其中daemonize = /home/ubuntu/dgutpsy/test.log的意思就是后台运行并规定日志输出目录。

新建test.ini

[uwsgi]
socket = 127.0.0.1:8001
wsgi-file = /home/ubuntu/dgutpsy/test.py
daemonize = /home/ubuntu/dgutpsy/test.log 
uwsgi --ini test.ini

niginx与Django项目通信

新建dgutpsy.ini

[uwsgi]
socket = 127.0.0.1:8001
chdir           = /home/ubuntu/dgutpsy
module          = dgutpsy.wsgi
master          = true
processes       = 1 

threads = 2 
max-requests = 6000

daemonize = /home/ubuntu/dgutpsy/run.log

然后运行

uwsgi --ini dgutpsy.ini

域名映射

添加域名>解析consult.psyannabel.cn

访问页面

后记:

Q&A:
Q: 能用manage.py运行的,为什么要用uwsgi?

A: 单进程的manage.py, 能和web服务器uwsgi比ma

Q: 能用uwsgi的,为什么还要用nginx?

A: uwsgi虽然是web服务器, 但是它在处理静态文件时, 并没有nginx来得优秀.

Q: 大致流程?

A: nginx运行web处理http请求, nginx使用socket与uwsgi通信,将动态部分转让交给uwsgi处理

Q: 为啥我的test.py运行不了?

A: 如果端口开放没有问题,那么请检查Python版本对应的return
查看原文

赞 14 收藏 7 评论 9

豆约翰 赞了文章 · 2020-11-16

你真的了解mongoose吗?

引言

继上篇文章「Koa2+MongoDB+JWT实战--Restful API最佳实践」后,收到许多小伙伴的反馈,表示自己对于mongoose不怎么了解,上手感觉有些难度,看官方文档又基本都是英文(宝宝心里苦,但宝宝不说)。

为了让各位小伙伴快速上手,加深对于 mongoose 的了解,我特地结合之前的项目整理了一下关于 mongoose 的一些基础知识,这些对于实战都是很有用的。相信看了这篇文章,一定会对你快速上手,了解使用 mongoose 有不小的帮助。

mongoose 涉及到的概念和模块还是很多的,大体有下面这些:

本篇文章并不会逐个去展开详细讲解,主要是讲述在实战中比较重要的几个模块:模式(schemas)模式类型(SchemaTypes)连接(Connections)模型(Models)联表(Populate)

模式(schemas)

定义你的 schema

Mongoose的一切都始于一个Schema。每个 schema 映射到 MongoDB 的集合(collection)和定义该集合(collection)中的文档的形式。

const mongoose = require("mongoose");

const { Schema, model } = mongoose;

const userSchema = new Schema(
  {
    __v: { type: Number, select: false },
    name: { type: String, required: true },
    password: { type: String, required: true, select: false },
    avatar_url: { type: String },
    gender: {
      type: String,
      enum: ["male", "female"],
      default: "male",
      required: true
    },
    headline: { type: String },
  },
  { timestamps: true }
);

module.exports = model("User", userSchema);
这里的__vversionKey。该 versionKey 是每个文档首次创建时,由 mongoose 创建的一个属性。包含了文档的内部修订版。此文档属性是可配置的。默认值为__v。如果不需要该版本号,在 schema 中添加{ versionKey: false}即可。

创建模型

使用我们的 schema 定义,我们需要将我们的userSchema转成我们可以用的模型。也就是mongoose.model(modelName, schema) 。也就是上面代码中的:

module.exports = model("User", userSchema);

选项(options)

Schemas 有几个可配置的选项,可以直接传递给构造函数或设置:

new Schema({..}, options);

// or

var schema = new Schema({..});
schema.set(option, value);

可用选项:

  • autoIndex
  • bufferCommands
  • capped
  • collection
  • id
  • _id
  • minimize
  • read
  • shardKey
  • strict
  • toJSON
  • toObject
  • typeKey
  • validateBeforeSave
  • versionKey
  • skipVersioning
  • timestamps

这里我只是列举了常用的配置项,完整的配置项可查看官方文档https://mongoosejs.com/docs/guide.html#options

这里我主要说一下versionKeytimestamps:

  • versionKey(上文有提到) 是 Mongoose 在文件创建时自动设定的。 这个值包含文件的内部修订号。 versionKey 是一个字符串,代表版本号的属性名, 默认值为 __v
  • 如果设置了 timestamps 选项, mongoose 会在你的 schema 自动添加 createdAtupdatedAt 字段, 其类型为 Date

到这里,已经基本介绍完了Schema,接下来看一下SchemaTypes

模式类型(SchemaTypes)

SchemaTypes为查询和其他处理路径默认值,验证,getter,setter,字段选择默认值,以及字符串和数字的特殊字符。 在 mongoose 中有效的 SchemaTypes 有:

  • String
  • Number
  • Date
  • Buffer
  • Boolean
  • Mixed
  • ObjectId
  • Array
  • Decimal128
  • Map

看一个简单的示例:

const answerSchema = new Schema(
  {
    __v: { type: Number, select: false },
    content: { type: String, required: true },
    answerer: {
      type: Schema.Types.ObjectId,
      ref: "User",
      required: true,
      select: false
    },
    questionId: { type: String, required: true },
    voteCount: { type: Number, required: true, default: 0 }
  },
  { timestamps: true }
);

所有的 Schema 类型

  • required: 布尔值或函数,如果为 true,则为此属性添加必须的验证。
  • default: 任意类型或函数,为路径设置一个默认的值。如果值是一个函数,则函数的返回值用作默认值。
  • select: 布尔值 指定 query 的默认 projections
  • validate: 函数,对属性添加验证函数。
  • get: 函数,使用 Object.defineProperty() 定义自定义 getter
  • set: 函数,使用 Object.defineProperty() 定义自定义 setter
  • alias: 字符串,只对mongoose>=4.10.0有效。定义一个具有给定名称的虚拟属性,该名称可以获取/设置这个路径

索引

你可以用 schema 类型选项声明 MongoDB 的索引。

  • index: 布尔值,是否在属性中定义一个索引。
  • unique: 布尔值,是否在属性中定义一个唯一索引。
  • sparse: 布尔值,是否在属性中定义一个稀疏索引。
var schema2 = new Schema({
  test: {
    type: String,
    index: true,
    unique: true // 如果指定`unique`为true,则为唯一索引
  }
});

字符串

  • lowercase: 布尔值,是否在保存前对此值调用toLowerCase()
  • uppercase: 布尔值,是否在保存前对此值调用toUpperCase()
  • trim: 布尔值,是否在保存前对此值调用trim()
  • match: 正则,创建一个验证器,验证值是否匹配给定的正则表达式
  • enum: 数组,创建一个验证器,验证值是否是给定数组中的元素

数字

  • min: 数字,创建一个验证器,验证值是否大于等于给定的最小值
  • max: 数字,创建一个验证器,验证值是否小于等于给定的最大的值

日期

  • min: Date
  • max: Date

现在已经介绍完Schematype,接下来让我们看一下Connections

连接(Connections)

我们可以通过利用mongoose.connect()方法连接 MongoDB 。

mongoose.connect('mongodb://localhost:27017/myapp');

这是连接运行在本地myapp数据库最小的值(27017)。如果连接失败,尝试用127.0.0.1代替localhost

当然,你可在 uri 中指定更多的参数:

mongoose.connect('mongodb://username:password@host:port/database?options...');

操作缓存

意思就是我们不必等待连接建立成功就可以使用 models,mongoose 会先缓存 model 操作

let TestModel = mongoose.model('Test', new Schema({ name: String }));
// 连接成功前操作会被挂起
TestModel.findOne(function(error, result) { /* ... */ });

setTimeout(function() {
  mongoose.connect('mongodb://localhost/myapp');
}, 60000);

如果要禁用缓存,可修改bufferCommands配置,也可以全局禁用 bufferCommands

mongoose.set('bufferCommands', false);

选项

connect 方法也接收一个 options 对象:

mongoose.connect(uri, options);

这里我列举几个在日常使用中比较重要的选项,完整的连接选项看这里

  • bufferCommands:这是 mongoose 中一个特殊的选项(不传递给 MongoDB 驱动),它可以禁用 mongoose 的缓冲机制
  • user/pass:身份验证的用户名和密码。这是 mongoose 中特殊的选项,它们可以等同于 MongoDB 驱动中的auth.userauth.password选项。
  • dbName:指定连接哪个数据库,并覆盖连接字符串中任意的数据库。
  • useNewUrlParser:底层 MongoDB 已经废弃当前连接字符串解析器。因为这是一个重大的改变,添加了 useNewUrlParser 标记如果在用户遇到 bug 时,允许用户在新的解析器中返回旧的解析器。
  • poolSize:MongoDB 驱动将为这个连接保持的最大 socket 数量。默认情况下,poolSize 是 5。
  • useUnifiedTopology:默认情况下为false。设置为 true 表示选择使用 MongoDB 驱动程序的新连接管理引擎。您应该将此选项设置为 true,除非极少数情况会阻止您保持稳定的连接。

示例:

const options = {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  autoIndex: false, // 不创建索引
  reconnectTries: Number.MAX_VALUE, // 总是尝试重新连接
  reconnectInterval: 500, // 每500ms重新连接一次
  poolSize: 10, // 维护最多10个socket连接
  // 如果没有连接立即返回错误,而不是等待重新连接
  bufferMaxEntries: 0,
  connectTimeoutMS: 10000, // 10s后放弃重新连接
  socketTimeoutMS: 45000, // 在45s不活跃后关闭sockets
  family: 4 // 用IPv4, 跳过IPv6
};
mongoose.connect(uri, options);

回调

connect()函数也接收一个回调参数,其返回一个 promise。

mongoose.connect(uri, options, function(error) {
  // 检查错误,初始化连接。回调没有第二个参数。
});

// 或者用promise
mongoose.connect(uri, options).then(
  () => { /** ready to use. The `mongoose.connect()` promise resolves to undefined. */ },
  err => { /** handle initial connection error */ }
);

说完Connections,下面让我们来看一个重点Models

模型(Models)

Models 是从 Schema 编译来的构造函数。 它们的实例就代表着可以从数据库保存和读取的 documents。 从数据库创建和读取 document 的所有操作都是通过 model 进行的。

const mongoose = require("mongoose");

const { Schema, model } = mongoose;

const answerSchema = new Schema(
  {
    __v: { type: Number, select: false },
    content: { type: String, required: true },
  },
  { timestamps: true }
);

module.exports = model("Answer", answerSchema);

定义好 model 之后,就可以进行一些增删改查操作了

创建

如果是Entity,使用save方法;如果是Model,使用create方法或insertMany方法。

// save([options], [options.safe], [options.validateBeforeSave], [fn])
let Person = mongoose.model("User", userSchema);
let person1 = new Person({ name: '森林' });
person1.save()

// 使用save()方法,需要先实例化为文档,再使用save()方法保存文档。而create()方法,则直接在模型Model上操作,并且可以同时新增多个文档
// Model.create(doc(s), [callback])
Person.create({ name: '森林' }, callback)

// Model.insertMany(doc(s), [options], [callback])
Person.insertMany([{ name: '森林' }, { name: '之晨' }], function(err, docs) {

})

说到这里,我们先要补充说明一下 mongoose 里面的三个概念:schemamodelentity:

  • schema: 一种以文件形式存储的数据库模型骨架,不具备数据库的操作能力
  • model: 由 schema 发布生成的模型,具有抽象属性和行为的数据库操作对
  • entity: 由 Model 创建的实体,他的操作也会影响数据库
Schema、Model、Entity 的关系请牢记: Schema生成Model,Model创造Entity,Model 和 Entity 都可对数据库操作造成影响,但 Model 比 Entity 更具操作性。

查询

对于 Mongoosecha 的查找文档很容易,它支持丰富的查询 MongoDB 语法。包括findfindByIdfindOne等。

find()

第一个参数表示查询条件,第二个参数用于控制返回的字段,第三个参数用于配置查询参数,第四个参数是回调函数,回调函数的形式为function(err,docs){}

Model.find(conditions, [projection], [options], [callback])

下面让我们依次看下 find()的各个参数在实际场景中的应用:

  • conditions

    • 查找全部
    Model.find({})
    • 精确查找
    Model.find({name:'森林'})
    • 使用操作符

对比相关操作符

compareOp.png

Model.find({ age: { $in: [18, 24]} })

返回 age 字段等于 18 或者 24 的所有 document。

逻辑相关操作符
logicOp.png

// 返回 age 字段大于 24 或者 age 字段不存在的文档
Model.find( { age: { $not: { $lte: 24 }}})

字段相关操作符
fieldOp.png

数组字段的查找
arrFieldOp.png

// 使用 $all 查找同时存在 18 和 20 的 document
Model.find({ age: { $all: [ 18, 20 ] } });
  • projection

    指定要包含或排除哪些 document 字段(也称为查询“投影”),必须同时指定包含或同时指定排除,不能混合指定,_id除外。

    在 mongoose 中有两种指定方式,字符串指定对象形式指定

    字符串指定时在排除的字段前加 - 号,只写字段名的是包含。

    Model.find({},'age');
    Model.find({},'-name');

    对象形式指定时,1 是包含,0 是排除。

    Model.find({}, { age: 1 });
    Model.find({}, { name: 0 });
  • options

    // 三种方式实现
    Model.find(filter,null,options)
    Model.find(filter).setOptions(options)
    Model.find(filter).<option>(xxx)

    options 选项见官方文档 Query.prototype.setOptions()

    这里我们只列举常用的:

    • sort: 按照排序规则根据所给的字段进行排序,值可以是 asc, desc, ascending, descending, 1, 和 -1。
    • limit: 指定返回结果的最大数量
    • skip: 指定要跳过的文档数量
    • lean: 返回普通的 js 对象,而不是 Mongoose Documents。建议不需要 mongoose 特殊处理就返给前端的数据都最好使用该方法转成普通 js 对象。
    // sort 两种方式指定排序
    Model.find().sort('age -name'); // 字符串有 - 代表 descending 降序
    Model.find().sort({age:'asc', name:-1});

sortlimit 同时使用时,调用的顺序并不重要,返回的数据都是先排序后限制数量。

// 效果一样
Model.find().limit(2).sort('age');
Model.find().sort('age').limit(2);
  • callback

    Mongoose 中所有传入 callback 的查询,其格式都是 callback(error, result) 这种形式。如果出错,则 error 是出错信息,result 是 null;如果查询成功,则 error 是 null, result 是查询结果,查询结果的结构形式是根据查询方法的不同而有不同形式的。

    find() 方法的查询结果是数组,即使没查询到内容,也会返回 [] 空数组。

findById

Model.findById(id,[projection],[options],[callback])

Model.findById(id) 相当于 Model.findOne({ _id: id })

看一下官方对于findOnefindById的对比:

不同之处在于处理 id 为 undefined 时的情况。findOne({ _id: undefined }) 相当于 findOne({}),返回任意一条数据。而 findById(undefined) 相当于 findOne({ _id: null }),返回 null

查询结果:

  • 返回数据的格式是 {} 对象形式。
  • id 为 undefinednull,result 返回 null
  • 没符合查询条件的数据,result 返回 null

findOne

该方法返回查找到的所有实例的第一个

Model.findOne(conditions, [projection], [options], [callback])

如果查询条件是 _id,建议使用 findById()

查询结果:

  • 返回数据的格式是 {} 对象形式。
  • 有多个数据满足查询条件的,只返回第一条。
  • 查询条件 conditions 为 {}、 null 或 undefined,将任意返回一条数据。
  • 没有符合查询条件的数据,result 返回 null。

更新

每个模型都有自己的更新方法,用于修改数据库中的文档,不将它们返回到您的应用程序。常用的有findOneAndUpdate()findByIdAndUpdate()update()updateMany()等。

findOneAndUpdate()

Model.findOneAndUpdate(filter, update, [options], [callback])
  • filter

    查询语句,和find()一样。

    filter 为{},则只更新第一条数据。

  • update

    {operator: { field: value, ... }, ... }
    必须使用 update 操作符。如果没有操作符或操作符不是 update 操作符,统一被视为 $set 操作(mongoose 特有)

    字段相关操作符

    符号描述
    $set设置字段值
    $currentDate设置字段值为当前时间,可以是 Date 或时间戳格式。
    $min只有当指定值小于当前字段值时更新
    $max只有当指定值大于当前字段值时更新
    $inc将字段值增加指定数量指定数量可以是负数,代表减少。
    $mul将字段值乘以指定数量
    \$unset删除指定字段,数组中的值删后改为 null。

    数组字段相关操作符

    符号描述
    \$充当占位符,用来表示匹配查询条件的数组字段中的第一个元素 {operator:{ "arrayField.$" : value }}
    \$addToSet向数组字段中添加之前不存在的元素 { $addToSet: {arrayField: value, ... }},value 是数组时可与 $each 组合使用。
    \$push向数组字段的末尾添加元素 { $push: { arrayField: value, ... } },value 是数组时可与 $each 等修饰符组合使用
    \$pop移除数组字段中的第一个或最后一个元素 { $pop: {arrayField: -1(first) / 1(last), ... } }
    \$pull移除数组字段中与查询条件匹配的所有元素 { $pull: {arrayField: value / condition, ... } }
    \$pullAll从数组中删除所有匹配的值 { $pullAll: { arrayField: [value1, value2 ... ], ... } }

    修饰符

    符号描述
    \$each修饰 $push$addToSet 操作符,以便为数组字段添加多个元素。
    \$position修饰 $push 操作符以指定要添加的元素在数组中的位置。
    \$slice修饰 $push 操作符以限制更新后的数组的大小。
    \$sort修饰 $push 操作符来重新排序数组字段中的元素。

    修饰符执行的顺序(与定义的顺序无关):

    • 在指定的位置添加元素以更新数组字段
    • 按照指定的规则排序
    • 限制数组大小
    • 存储数组
  • options

    • lean: true 返回普通的 js 对象,而不是 Mongoose Documents
    • new: 布尔值,true 返回更新后的数据,false (默认)返回更新前的数据。
    • fields/select:指定返回的字段。
    • sort:如果查询条件找到多个文档,则设置排序顺序以选择要更新哪个文档。
    • maxTimeMS:为查询设置时间限制。
    • upsert:布尔值,如果对象不存在,则创建它。默认值为 false
    • omitUndefined:布尔值,如果为 true,则在更新之前删除值为 undefined 的属性。
    • rawResult:如果为 true,则返回来自 MongoDB 的原生结果。
  • callback

    • 没找到数据返回 null
    • 更新成功返回更新前的该条数据( {} 形式)
    • options{new:true},更新成功返回更新后的该条数据( {} 形式)
    • 没有查询条件,即 filter 为空,则更新第一条数据

findByIdAndUpdate()

Model.findByIdAndUpdate(id, update, options, callback)

Model.findByIdAndUpdate(id, update) 相当于 Model.findOneAndUpdate({ _id: id }, update)

result 查询结果:

  • 返回数据的格式是 {} 对象形式。
  • id 为 undefinednull,result 返回 null
  • 没符合查询条件的数据,result 返回 null

update()

Model.update(filter, update, options, callback)
  • options

    • multi: 默认 false,只更新第一条数据;为 true 时,符合查询条件的多条文档都会更新。
    • overwrite:默认为 false,即 update 参数如果没有操作符或操作符不是 update 操作符,将会默认添加 $set;如果为 true,则不添加 $set,视为覆盖原有文档。

updateMany()

Model.updateMany(filter, update, options, callback)

更新符合查询条件的所有文档,相当于 Model.update(filter, update, { multi: true }, callback)

删除

删除常用的有findOneAndDelete()findByIdAndDelete()deleteMany()findByIdAndRemove()等。

findOneAndDelete()

Model.findOneAndDelete(filter, options, callback)
  • filter
    查询语句和 find() 一样
  • options

    • sort:如果查询条件找到多个文档,则设置排序顺序以选择要删除哪个文档。
    • select/projection:指定返回的字段。
    • rawResult:如果为 true,则返回来自 MongoDB 的原生结果。
  • callback

    • 没有符合 filter 的数据时,返回 null
    • filter 为空或 {} 时,删除第一条数据。
    • 删除成功返回 {} 形式的原数据。

findByIdAndDelete()

Model.findByIdAndDelete(id, options, callback)

Model.findByIdAndDelete(id) 相当于 Model.findOneAndDelete({ _id: id })

  • callback

    • 没有符合 id 的数据时,返回 null
    • id 为空或 undefined 时,返回 null
    • 删除成功返回 {} 形式的原数据。

deleteMany()

Model.deleteMany(filter, options, callback)
  • filter
    删除所有符合 filter 条件的文档。

deleteOne()

Model.deleteOne(filter, options, callback)
  • filter
    删除符合 filter 条件的第一条文档。

findOneAndRemove()

Model.findOneAndRemove(filter, options, callback)

用法与 findOneAndDelete() 一样,一个小小的区别是 findOneAndRemove() 会调用 MongoDB 原生的 findAndModify() 命令,而不是 findOneAndDelete() 命令。

建议使用 findOneAndDelete() 方法。

findByIdAndRemove()

Model.findByIdAndRemove(id, options, callback)

Model.findByIdAndRemove(id) 相当于 Model.findOneAndRemove({ _id: id })

remove()

Model.remove(filter, options, callback)

从集合中删除所有匹配 filter 条件的文档。要删除第一个匹配条件的文档,可将 single 选项设置为 true

看完Models,最后让我们来看下在实战中比较有用的Populate

联表(Populate)

Mongoose 的 populate() 可以连表查询,即在另外的集合中引用其文档。

Populate() 可以自动替换 document 中的指定字段,替换内容从其他 collection 中获取。

refs

创建 Model 的时候,可给该 Model 中关联存储其它集合 _id 的字段设置 ref 选项。ref 选项告诉 Mongoose 在使用 populate() 填充的时候使用哪个 Model

const mongoose = require("mongoose");

const { Schema, model } = mongoose;

const answerSchema = new Schema(
  {
    __v: { type: Number, select: false },
    content: { type: String, required: true },
    answerer: {
      type: Schema.Types.ObjectId,
      ref: "User",
      required: true,
      select: false
    },
    questionId: { type: String, required: true },
    voteCount: { type: Number, required: true, default: 0 }
  },
  { timestamps: true }
);

module.exports = model("Answer", answerSchema);

上例中 Answer model 的 answerer 字段设为 ObjectId 数组。 ref 选项告诉 Mongoose 在填充的时候使用 User model。所有储存在 answerer 中的 _id 都必须是 User model 中 document_id

ObjectIdNumberString 以及 Buffer 都可以作为 refs 使用。 但是最好还是使用 ObjectId

在创建文档时,保存 refs 字段与保存普通属性一样,把 _id 的值赋给它就好了。

const Answer = require("../models/answers");

async create(ctx) {
  ctx.verifyParams({
    content: { type: "string", required: true }
  });
  const answerer = ctx.state.user._id;
  const { questionId } = ctx.params;
  const answer = await new Answer({
    ...ctx.request.body,
    answerer,
    questionId
  }).save();
  ctx.body = answer;
}

populate(path,select)

填充document

const Answer = require("../models/answers");

const answer = await Answer.findById(ctx.params.id)
      .select(selectFields)
      .populate("answerer");

被填充的 answerer 字段已经不是原来的 _id,而是被指定的 document 代替。这个 document 由另一条 query 从数据库返回。

返回字段选择

如果只需要填充 document 中一部分字段,可给 populate() 传入第二个参数,参数形式即 返回字段字符串,同 Query.prototype.select()

const answer = await Answer.findById(ctx.params.id)
      .select(selectFields)
      .populate("answerer", "name -_id");

populate 多个字段

const populateStr =
      fields &&
      fields
        .split(";")
        .filter(f => f)
        .map(f => {
          if (f === "employments") {
            return "employments.company employments.job";
          }
          if (f === "educations") {
            return "educations.school educations.major";
          }
          return f;
        })
        .join(" ");
const user = await User.findById(ctx.params.id)
      .select(selectFields)
      .populate(populateStr);

最后

到这里本篇文章也就结束了,这里主要是结合我平时的项目(https://github.com/Cosen95/rest_node_api)中对于mongoose的使用做的简单的总结。希望能给你带来帮助!

同时你可以关注我的同名公众号【前端森林】,这里我会定期发一些大前端相关的前沿文章和日常开发过程中的实战总结。

image

查看原文

赞 10 收藏 5 评论 0

豆约翰 发布了文章 · 2020-10-23

05.vue路由

一.路由的概念与原理

路由的本质就是一种对应关系,比如我们在url地址中输入我们要访问的url地址之后,浏览器要去请求这个url地址对应的资源。
那么url地址和真实的资源之间就有一种对应的关系,就是路由。

路由分为前端路由后端路由

1.1 后端路由

后端路由是根据不同的用户URL请求,返回不同的内容,本质就是URL请求地址与服务器资源之间的对应关系。
由服务器端进行实现,并完成资源的分发。

image

1.2 前端路由

前端路由依靠hash值(锚链接)的变化进行实现。

后端路由性能相对前端路由来说较低,所以,我们接下来主要学习的是前端路由。
基本概念:根据不同的事件来显示不同的页面内容,即用户事件事件处理函数之间的对应关系。
前端路由主要做的事情就是监听事件并分发执行事件处理函数。

image

1.3 SPA(Single Page Application)

  • 后端渲染(存在性能问题)
  • Ajax前端渲染(前端渲染提高性能,但是不支持浏览器的后退操作)
  • SPA单页面应用程序:整个网站只有一个页面,内容的变化通过Ajax局部更新实现,同时支持浏览器地址栏的前进和后退操作
  • SPA的实现原理之一:基于URL地址的hash(hash的变化会导致浏览器访问历史记录的变化,但hash的变化不会触发新的URL请求)
  • 在实现SPA的过程中,最核心的技术点就是前端路由

二、前端路由的初体验

前端路由是基于hash值的变化进行实现的(比如点击页面中的菜单或者按钮改变URL的hash值,根据hash值的变化来控制组件的切换)
核心实现依靠一个事件,即监听hash值变化的事件

window.onhashchange = function(){
    //location.hash可以获取到最新的hash值
    location.hash
}

前端路由实现tab栏切换:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <!-- 导入 vue 文件 -->
    <script data-original="./lib/vue_2.5.22.js"></script>
</head>
<body>
    <!-- 被 vue 实例控制的 div 区域 -->
    <div id="app">
        <!-- 切换组件的超链接 -->
        <a href="#/zhuye">主页</a> 
        <a href="#/keji">科技</a> 
        <a href="#/caijing">财经</a>
        <a href="#/yule">娱乐</a>

        <!-- 根据 :is 属性指定的组件名称,把对应的组件渲染到 component 标签所在的位置 -->
        <!-- 可以把 component 标签当做是【组件的占位符】 -->
        <component :is="comName"></component>
    </div>

    <script>
        // #region 定义需要被切换的 4 个组件
        // 主页组件
        const zhuye = {
            template: '<h1>主页信息</h1>'
        }
        // 科技组件
        const keji = {
            template: '<h1>科技信息</h1>'
        }
        // 财经组件
        const caijing = {
            template: '<h1>财经信息</h1>'
        }
        // 娱乐组件
        const yule = {
            template: '<h1>娱乐信息</h1>'
        }
        // #endregion

        // #region vue 实例对象
        const vm = new Vue({
            el: '#app',
            data: {
                comName: 'zhuye'
            },
            // 注册私有组件
            components: {
                zhuye,
                keji,
                caijing,
                yule
            }
        })
        // #endregion

        // 监听 window 的 onhashchange 事件,根据获取到的最新的 hash 值,切换要显示的组件的名称
        window.onhashchange = function() {
            // 通过 location.hash 获取到最新的 hash 值
            console.log(location.hash); // #/zhuye
            vm.comName = location.hash.slice(2)
        }
    </script>
</body>
</html>

案例效果图:

image

点击每个超链接之后,会进行相应的内容切换,如下:

image

核心思路:
在页面中有一个vue实例对象,vue实例对象中有四个组件,分别是tab栏切换需要显示的组件内容
在页面中有四个超链接,如下:

<a href="#/zhuye">主页</a> 
<a href="#/keji">科技</a> 
<a href="#/caijing">财经</a>
<a href="#/yule">娱乐</a>

当我们点击这些超链接的时候,就会改变url地址中的hash值,当hash值被改变时,就会触发onhashchange事件,然后得到当前hash值,根据这个hash值来让不同的组件进行显示。

三、Vue Router简介

它是一个Vue.js官方提供的路由管理器。是一个功能更加强大的前端路由器,推荐使用。
Vue Router和Vue.js的核心深度集成,因此非常契合,可以一起方便的实现SPA单页应用程序的开发。
Vue Router依赖于Vue,所以需要先引入Vue,再引入Vue Router

Vue Router的特性:

  • 支持H5历史模式或者hash模式
  • 支持嵌套路由
  • 支持路由参数
  • 支持编程式路由
  • 支持命名路由
  • 支持路由导航守卫
  • 支持路由过渡动画特效
  • 支持路由懒加载
  • 支持路由滚动行为

四、Vue Router的使用步骤(★★★)

A.导入js文件
<script data-original="lib/vue_2.5.22.js"></script>
为全局window对象挂载VueRouter构造函数
<script data-original="lib/vue-router_3.0.2.js"></script>
B.添加路由链接
<router-link>是vue中提供的标签,默认会被渲染为a标签,
to属性默认被渲染为href属性,
to属性的值会被渲染为#开头的hash地址
<router-link to="/user">User</router-link>
<router-link to="/login">Login</router-link>
C.添加路由填充位(路由占位符)
<router-view></router-view>
最后路由展示的组件就会在占位符的位置显示
D.定义路由组件
var User = { template:"<div>This is User</div>" }
var Login = { template:"<div>This is Login</div>" }
E.配置路由规则并创建路由实例

var myRouter = new VueRouter({
    //routes是路由规则数组
    routes:[
        //每一个路由规则都是一个对象,对象中至少包含path和component两个属性
        //path表示  路由匹配的hash地址,component表示路由规则对应要展示的组件对象
        {path:"/user",component:User},
        {path:"/login",component:Login}
    ]
})

F.将路由挂载到Vue实例中

new Vue({
    el:"#app",
    //通过router属性挂载路由对象
    router:myRouter
})

补充
路由重定向:可以通过路由重定向为页面设置默认展示的组件
在路由规则中添加一条路由规则即可,如下:

var myRouter = new VueRouter({
    //routes是路由规则数组
    routes: [
        //path设置为/表示页面最初始的地址 / ,redirect表示要被重定向的新地址,设置为一个路由即可
        { path:"/", redirect:"/user"},
        { path: "/user", component: User },
        { path: "/login", component: Login }
    ]
})

五、嵌套路由,动态路由的实现方式

A.嵌套路由的概念(★★★)

当我们进行路由的时候显示的组件中还有新的子级路由链接以及内容。

嵌套路由最关键的代码在于理解子级路由的概念:
比如我们有一个/login的路由
那么/login下面还可以添加子级路由,如:
/login/account
/login/phone

参考代码如下:

     var User = {template: "<div>This is User</div>"}
    //Login组件中的模板代码里面包含了子级路由链接以及子级路由的占位符
    var Login = {
        template: `<div>
        <h1>This is Login</h1>
        <hr>
        <router-link to="/login/account">账号密码登录</router-link>
        <router-link to="/login/phone">扫码登录</router-link>
            <router-view></router-view>
            </div>`
    }

    //定义两个子级路由组件
    var account = {template: "<div>账号:<input><br>密码:<input></div>"};
    var phone = {template: "<h1>扫我二维码</h1>"};
    var myRouter = new VueRouter({
        //routes是路由规则数组
        routes: [
            {path: "/", redirect: "/user"},
            {path: "/user", component: User},
            {
                path: "/login",
                component: Login,
                //通过children属性为/login添加子路由规则
                children: [
                    {path: "/login/account", component: account},
                    {path: "/login/phone", component: phone},
                ]
            }
        ]
    })

    var vm = new Vue({
        el: '#app',
        data: {},
        methods: {},
        router: myRouter
    });

页面效果大致如下:

image

B.动态路由匹配(★★★)

  <router-link to="/user/22">User</router-link>
var User = { template:"<div>用户:{{$route.params.id}}</div>"}

var myRouter = new VueRouter({
    //routes是路由规则数组
    routes: [
        //通过 /:参数名  的形式传递参数 
        { path: "/user/:id", component: User },     
    ]
})

可以在路由对应的组件中使用$route.params.id来获取路径传参的id参数值,但是这种方式不够灵活。今后可以尽量使用props将组件和路由解耦。
1.通过props来接收参数

var User = { 
    props:["id"], //使用props接收id参数
    template:"<div>用户:{{id}}</div>"
}

var myRouter = new VueRouter({
    //routes是路由规则数组
    routes: [
        //通过/:参数名  的形式传递参数 
        //如果props设置为true,route.params将会被设置为组件属性
        { path: "/user/:id", component: User, props:true },

    ]
})

2.还有一种情况,我们可以将props设置为对象,直接将静态数据传递给组件进行使用。
但是这样id的值就访问不到了。

var User = { 
    props:["username","pwd"],
    template:"<div>用户:{{username}}---{{pwd}}</div>"
    }

var myRouter = new VueRouter({
    //routes是路由规则数组
    routes: [
        //通过 /:参数名  的形式传递参数 
        //如果props设置为对象,则传递的是对象中的数据给组件
        { path: "/user/:id", component: User, props:{username:"jack",pwd:123} },

    ]
})

3.如果既想要获取路由传递的参数值,又想获取传递的对象数据,那么props应该设置为函数形式。

var User = { 
    props:["username","pwd","id"],
    template:"<div>用户:{{id}} -> {{username}}---{{pwd}}</div>"
    }

var myRouter = new VueRouter({
    //routes是路由规则数组
    routes: [
        //通过 /:参数名  的形式传递参数 
        //如果props设置为函数,则通过函数的第一个参数获取路由对象
        //并可以通过路由对象的params属性获取传递的参数
        //
        { path: "/user/:id", component: User,props:(route)=>{
            return {username:"jack",pwd:123,id:route.params.id}
            } 
        },

    ]
})

六、命名路由以及编程式导航

A.命名路由:用name属性值给路由取别名

案例:

var User = { template:"<div>用户:{{$route.params.id}}</div>"}

var myRouter = new VueRouter({
    //routes是路由规则数组
    routes: [
        //通过name属性为路由添加一个别名
        { path: "/user/:id", component: User, name:"user"},        
    ]
})

添加了别名之后,可以使用别名进行跳转(给to属性绑定对象的形式)
<router-link to="/user/123">User</router-link>
等价于
<router-link :to="{ name:'user' , params: {id:123} }">User</router-link>

还可以编程式导航
myRouter.push( { name:'user' , params: {id:123} } )

   var vm = new Vue({
        el: '#app',
        data: {},
        methods: {},
        router: myRouter,
        mounted:function () {
            myRouter.push( { name:'user' , params: {id:123} } )
        }
    });

B.编程式导航(★★★)

页面导航的两种方式:
A.声明式导航:通过点击链接的方式实现的导航
B.编程式导航:调用js的api方法实现导航,比如调用location.href就是

Vue-Router中常见的导航方式:
this.$router.push("hash地址");
this.$router.push("/login");
this.$router.push({ name:'user' , params: {id:123} });
this.$router.push({ path:"/login" });
this.$router.push({ path:"/login",query:{username:"jack"} });

this.$router.go(n); n为数字,参考history.go
this.$router.go(-1);

七、实现后台管理案例(★★★)

案例效果:

image.png

点击左侧的"用户管理","权限管理","商品管理","订单管理","系统设置"都会出现对应的组件并展示内容

其中"用户管理"组件展示的效果如上图所示,在用户管理区域中的详情链接也是可以点击的,点击之后将会显示用户详情信息。

案例思路:
1).先将以下代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>基于vue-router的案例</title>
    <style type="text/css">
      html,
      body,
      #app {
        margin: 0;
        padding: 0px;
        height: 100%;
      }
      .header {
        height: 50px;
        background-color: #545c64;
        line-height: 50px;
        text-align: center;
        font-size: 24px;
        color: #fff;
      }
      .footer {
        height: 40px;
        line-height: 40px;
        background-color: #888;
        position: absolute;
        bottom: 0;
        width: 100%;
        text-align: center;
        color: #fff;
      }
      .main {
        display: flex;
        position: absolute;
        top: 50px;
        bottom: 40px;
        width: 100%;
      }
      .content {
        flex: 1;
        text-align: center;
        height: 100%;
      }
      .left {
        flex: 0 0 20%;
        background-color: #545c64;
      }
      .left a {
        color: white;
        text-decoration: none;
      }
      .right {
        margin: 5px;
      }
      .btns {
        width: 100%;
        height: 35px;
        line-height: 35px;
        background-color: #f5f5f5;
        text-align: left;
        padding-left: 10px;
        box-sizing: border-box;
      }
      button {
        height: 30px;
        background-color: #ecf5ff;
        border: 1px solid lightskyblue;
        font-size: 12px;
        padding: 0 20px;
      }
      .main-content {
        margin-top: 10px;
      }
      ul {
        margin: 0;
        padding: 0;
        list-style: none;
      }
      ul li {
        height: 45px;
        line-height: 45px;
        background-color: #a0a0a0;
        color: #fff;
        cursor: pointer;
        border-bottom: 1px solid #fff;
      }

      table {
        width: 100%;
        border-collapse: collapse;
      }

      td,
      th {
        border: 1px solid #eee;
        line-height: 35px;
        font-size: 12px;
      }

      th {
        background-color: #ddd;
      }
    </style>
  </head>
  <body>
    <div>
      <!-- 头部区域 -->
      <header class="header">后台管理系统</header>
      <!-- 中间主体区域 -->
      <div class="main">
        <!-- 左侧菜单栏 -->
        <div class="content left">
          <ul>
            <li>用户管理</li>
            <li>权限管理</li>
            <li>商品管理</li>
            <li>订单管理</li>
            <li>系统设置</li>
          </ul>
        </div>
        <!-- 右侧内容区域 -->
        <div class="content right"><div class="main-content">添加用户表单</div></div>
      </div>
      <!-- 尾部区域 -->
      <footer class="footer">版权信息</footer>
    </div>
  </body>
</html>

复制到我们自己的文件夹中。
看一下这个文件中的代码编写了一些什么内容,
这个页面已经把后台管理页面的基本布局实现了
2).在页面中引入vue,vue-router
3).创建Vue实例对象,准备开始编写代码实现功能
4).希望是通过组件的形式展示页面的主体内容,而不是写死页面结构,所以我们可以定义一个根组件:

//只需要把原本页面中的html代码设置为组件中的模板内容即可
const app = {
    template:`<div>
        <!-- 头部区域 -->
        <header class="header">后台管理系统</header>
        <!-- 中间主体区域 -->
        <div class="main">
          <!-- 左侧菜单栏 -->
          <div class="content left">
            <ul>
              <li>用户管理</li>
              <li>权限管理</li>
              <li>商品管理</li>
              <li>订单管理</li>
              <li>系统设置</li>
            </ul>
          </div>
          <!-- 右侧内容区域 -->
          <div class="content right">
            <div class="main-content">添加用户表单</div>
          </div>
        </div>
        <!-- 尾部区域 -->
        <footer class="footer">版权信息</footer>
      </div>`
  }

5).当我们访问页面的时候,默认需要展示刚刚创建的app根组件,我们可以
创建一个路由对象来完成这个事情,然后将路由挂载到Vue实例对象中即可

const myRouter = new VueRouter({
    routes:[
        {path:"/",component:app}
    ]
})

const vm = new Vue({
    el:"#app",
    data:{},
    methods:{},
    router:myRouter
})

补充:到此为止,基本的js代码都处理完毕了,我们还需要设置一个路由占位符

<body>
  <div id="app">
    <router-view></router-view>
  </div>
</body>

6).此时我们打开页面应该就可以得到一个VueRouter路由出来的根组件了
我们需要在这个根组件中继续路由实现其他的功能子组件
先让我们更改根组件中的模板:更改左侧li为子级路由链接,并在右侧内容区域添加子级组件占位符

const app = {
    template:`<div>
        ........
        <div class="main">
          <!-- 左侧菜单栏 -->
          <div class="content left">
            <ul>
              <!-- 注意:我们把所有li都修改为了路由链接 -->
              <li><router-link to="/users">用户管理</router-link></li>
              <li><router-link to="/accesses">权限管理</router-link></li>
              <li><router-link to="/goods">商品管理</router-link></li>
              <li><router-link to="/orders">订单管理</router-link></li>
              <li><router-link to="/systems">系统设置</router-link></li>
            </ul>
          </div>
          <!-- 右侧内容区域 -->
          <div class="content right">
            <div class="main-content">
                <!-- 在 -->
                <router-view></router-view> 
            </div>
          </div>
        </div>
        .......
      </div>`
  }

然后,我们要为子级路由创建并设置需要显示的子级组件

//建议创建的组件首字母大写,和其他内容区分
const Users = {template:`<div>
    <h3>用户管理</h3>
</div>`}
const Access = {template:`<div>
    <h3>权限管理</h3>
</div>`}
const Goods = {template:`<div>
    <h3>商品管理</h3>
</div>`}
const Orders = {template:`<div>
    <h3>订单管理</h3>
</div>`}
const Systems = {template:`<div>
    <h3>系统管理</h3>
</div>`}

//添加子组件的路由规则
const myRouter = new VueRouter({
    routes:[
        {path:"/",component:app , children:[
            { path:"/users",component:Users },
            { path:"/accesses",component:Access },
            { path:"/goods",component:Goods },
            { path:"/orders",component:Orders },
            { path:"/systems",component:Systems },
        ]}
    ]
})

const vm = new Vue({
    el:"#app",
    data:{},
    methods:{},
    router:myRouter
})

7).展示用户信息列表:
A.为Users组件添加私有数据,并在模板中循环展示私有数据

const Users = {
    data(){
        return {
            userList:[
                {id:1,name:"zs",age:18},
                {id:2,name:"ls",age:19},
                {id:3,name:"wang",age:20},
                {id:4,name:"jack",age:21},
            ]
        }
    },
    template:`<div>
        <h3>用户管理</h3>
        <table>
            <thead>
                <tr>
                    <th>编号</th>
                    <th>姓名</th>
                    <th>年龄</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                <tr :key="item.id" v-for="item in userList">
                    <td>{{item.id}}</td>
                    <td>{{item.name}}</td>
                    <td>{{item.age}}</td>
                    <td><a href="javascript:;">详情</a></td>
                </tr>
            </tbody>
        </table>
    </div>`}

8.当用户列表展示完毕之后,我们可以点击列表中的详情来显示用户详情信息,首先我们需要创建一个组件,用来展示详情信息

const UserInfo = {
    props:["id"],
    template:`<div>
      <h5>用户详情</h5>
      <p>查看 {{id}} 号用户信息</p>
      <button @click="goBack">返回用户详情页</button>
    </div> `,
    methods:{
      goBack(){
        //当用户点击按钮,后退一页
        this.$router.go(-1);
      }
    }
  }

然后我们需要设置这个组件的路由规则

const myRouter = new VueRouter({
    routes:[
        {path:"/",component:app , children:[
            { path:"/users",component:Users },
            //添加一个/userinfo的路由规则
            { path:"/userinfo/:id",component:UserInfo,props:true},
            { path:"/accesses",component:Access },
            { path:"/goods",component:Goods },
            { path:"/orders",component:Orders },
            { path:"/systems",component:Systems },
        ]}
    ]
})

const vm = new Vue({
    el:"#app",
    data:{},
    methods:{},
    router:myRouter
})

再接着给用户列表中的详情a连接添加事件

const Users = {
    data(){
        return {
            userList:[
                {id:1,name:"zs",age:18},
                {id:2,name:"ls",age:19},
                {id:3,name:"wang",age:20},
                {id:4,name:"jack",age:21},
            ]
        }
    },
    template:`<div>
        <h3>用户管理</h3>
        <table>
            <thead>
                <tr>
                    <th>编号</th>
                    <th>姓名</th>
                    <th>年龄</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                <tr :key="item.id" v-for="item in userList">
                    <td>{{item.id}}</td>
                    <td>{{item.name}}</td>
                    <td>{{item.age}}</td>
                    <td><a href="javascript:;" @click="goDetail(item.id)">详情</a></td>
                </tr>
            </tbody>
        </table>
    </div>`,
    methods:{
        goDetail(id){
            this.$router.push("/userinfo/"+id);
        }
    }
}
查看原文

赞 0 收藏 0 评论 0

豆约翰 发布了文章 · 2020-10-23

04.Vue之前后端交互

接口调用方式

  • 原生ajax
  • 基于jQuery的ajax
  • fetch
  • axios

异步

  • JavaScript的执行环境是「单线程」
  • 所谓单线程,是指JS引擎中负责解释和执行JavaScript代码的线程只有一个,也就是一次只能完成一项任务,这个任务执行完后才能执行下一个,它会「阻塞」其他任务。这个任务可称为主线程
  • 异步模式可以一起执行多个任务
  • JS中常见的异步调用

    • 定时任何
    • ajax
    • 事件函数

promise

  • 主要解决异步深层嵌套的问题
  • promise 提供了简洁的API 使得异步操作更加容易
 
  <script type="text/javascript">
    /*
     1. Promise基本使用
           我们使用new来构建一个Promise  Promise的构造函数接收一个参数,是函数,并且传入两个参数:           resolve,reject, 分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数
    */


    var p = new Promise(function(resolve, reject){
      //2. 这里用于实现异步任务  setTimeout
      setTimeout(function(){
        var flag = false;
        if(flag) {
          //3. 正常情况
          resolve('hello');
        }else{
          //4. 异常情况
          reject('出错了');
        }
      }, 100);
    });
    //  5 Promise实例生成以后,可以用then方法指定resolved状态和reject状态的回调函数 
    //  在then方法中,你也可以直接return数据而不是Promise对象,在后面的then中就可以接收到数据了  
    p.then(function(data){
      console.log(data)
    },function(info){
      console.log(info)
    });
  </script>

基于Promise发送Ajax请求

 
  <script type="text/javascript">
    /*
      基于Promise发送Ajax请求
    */
    function queryData(url) {
     #   1.1 创建一个Promise实例
      var p = new Promise(function(resolve, reject){
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function(){
          if(xhr.readyState != 4) return;
          if(xhr.readyState == 4 && xhr.status == 200) {
            # 1.2 处理正常的情况
            resolve(xhr.responseText);
          }else{
            # 1.3 处理异常情况
            reject('服务器错误');
          }
        };
        xhr.open('get', url);
        xhr.send(null);
      });
      return p;
    }
    # 注意:  这里需要开启一个服务 
    # 在then方法中,你也可以直接return数据而不是Promise对象,在后面的then中就可以接收到数据了
    queryData('http://localhost:3000/data')
      .then(function(data){
        console.log(data)
        #  1.4 想要继续链式编程下去 需要 return  
        return queryData('http://localhost:3000/data1');
      })
      .then(function(data){
        console.log(data);
        return queryData('http://localhost:3000/data2');
      })
      .then(function(data){
        console.log(data)
      });
  </script>

服务器端
pip install

Flask==1.1.1
Flask-Cors==3.0.8
from flask import Flask, jsonify, request
from flask_cors import CORS

# 创建应用程序对象
app = Flask(__name__)
# pip install Flask-Cors
CORS(app)

@app.route('/data')
def hello():
    '''
    hello请求
    '''
    # 直接返回字符串
    return 'Hello, data'
@app.route('/data1')
def hello1():
    '''
    hello请求
    '''
    # 直接返回字符串
    return 'Hello, data1'
@app.route('/data2')
def hello2():
    '''
    hello请求
    '''
    # 直接返回字符串
    return 'Hello, data2'

if __name__ == '__main__':
    # 以debug模式启动程序
    app.run(debug = True)

Promise 基本API

实例方法

.then()
  • 得到异步任务正确的结果
.catch()
  • 获取异常信息
.finally()
  • 成功与否都会执行(不是正式标准)
  
  <script type="text/javascript">
    /*
      Promise常用API-实例方法
    */
    // console.dir(Promise);
    function foo() {
      return new Promise(function(resolve, reject){
        setTimeout(function(){
          // resolve(123);
          reject('error');
        }, 100);
      })
    }
    // foo()
    //   .then(function(data){
    //     console.log(data)
    //   })
    //   .catch(function(data){
    //     console.log(data)
    //   })
    //   .finally(function(){
    //     console.log('finished')
    //   });

    // --------------------------
    // 两种写法是等效的
    foo()
      .then(function(data){
        # 得到异步任务正确的结果
        console.log(data)
      },function(data){
        # 获取异常信息
        console.log(data)
      })
      # 成功与否都会执行(不是正式标准) 
      .finally(function(){
        console.log('finished')
      });
  </script>

静态方法

.all()
  • Promise.all方法接受一个数组作参数,数组中的对象(p1、p2、p3)均为promise实例(如果不是一个promise,该项会被用Promise.resolve转换为一个promise)。它的状态由这三个promise实例决定
.race()
  • Promise.race方法同样接受一个数组作参数。当p1, p2, p3中有一个实例的状态发生改变(变为fulfilledrejected),p的状态就跟着改变。并把第一个改变状态的promise的返回值,传给p的回调函数

  <script type="text/javascript">
    /*
      Promise常用API-对象方法
    */
    // console.dir(Promise)
    function queryData(url) {
      return new Promise(function(resolve, reject){
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function(){
          if(xhr.readyState != 4) return;
          if(xhr.readyState == 4 && xhr.status == 200) {
            // 处理正常的情况
            resolve(xhr.responseText);
          }else{
            // 处理异常情况
            reject('服务器错误');
          }
        };
        xhr.open('get', url);
        xhr.send(null);
      });
    }

    var p1 = queryData('http://localhost:3000/a1');
    var p2 = queryData('http://localhost:3000/a2');
    var p3 = queryData('http://localhost:3000/a3');
     Promise.all([p1,p2,p3]).then(function(result){
       //   all 中的参数  [p1,p2,p3]   和 返回的结果一 一对应["HELLO TOM", "HELLO JERRY", "HELLO SPIKE"]
       console.log(result) //["HELLO TOM", "HELLO JERRY", "HELLO SPIKE"]
     })
    Promise.race([p1,p2,p3]).then(function(result){
      // 由于p1执行较快,Promise的then()将获得结果'P1'。p2,p3仍在继续执行,但执行结果将被丢弃。
      console.log(result) // "HELLO TOM"
    })
  </script>

fetch

  • Fetch API是新的ajax解决方案 Fetch会返回Promise
  • fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象
  • fetch(url, options).then()
  <script type="text/javascript">
    /*
      Fetch API 基本用法
          fetch(url).then()
         第一个参数请求的路径   Fetch会返回Promise   所以我们可以使用then 拿到请求成功的结果 
    */
    fetch('http://localhost:3000/fdata').then(function(data){
      // text()方法属于fetchAPI的一部分,它返回一个Promise实例对象,用于获取后台返回的数据
      return data.text();
    }).then(function(data){
      //   在这个then里面我们能拿到最终的数据  
      console.log(data);
    })
  </script>

fetch API 中的 HTTP 请求

  • fetch(url, options).then()
  • HTTP协议,它给我们提供了很多的方法,如POST,GET,DELETE,UPDATE,PATCH和PUT

    • 默认的是 GET 请求
    • 需要在 options 对象中 指定对应的 method method:请求使用的方法
    • post 和 普通 请求的时候 需要在options 中 设置 请求头 headers 和 body
   <script type="text/javascript">
           /*
              Fetch API 调用接口传递参数
        */
    fetch('http://localhost:5000/books?id=123', {
    method: 'get'
    })
    .then(function(data) {
        return data.text();
    }).then(function(data) {
        console.log(data)
    });

    fetch('http://localhost:5000/books/456', {
    method: 'get'
    })
    .then(function(data) {
        return data.text();
    }).then(function(data) {
        console.log(data)
    });
    //
    fetch('http://localhost:5000/books/789', {
        method: 'delete'
    })
        .then(function(data) {
            return data.text();
        }).then(function(data) {
        console.log(data)
    });
    //
    fetch('http://localhost:5000/books', {
        method: 'post',
    body: 'uname=lisi&pwd=123',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
    }
    })
    .then(function(data) {
        return data.text();
    }).then(function(data) {
        console.log(data)
    });
    //
    fetch('http://localhost:5000/books_json', {
        method: 'post',
        body: JSON.stringify({
            uname: '张三',
            pwd: '456'
        }),
        headers: {
            'Content-Type': 'application/json'
        }
    })
        .then(function(data) {
            return data.text();
        }).then(function(data) {
        console.log(data)
    });
    //
    fetch('http://localhost:5000/books/123', {
        method: 'put',
        body: JSON.stringify({
            uname: '张三',
            pwd: '789'
        }),
        headers: {
            'Content-Type': 'application/json'
        }
    })
        .then(function(data) {
            return data.text();
        }).then(function(data) {
        console.log(data)
    });
    </script>

后台代码:

from flask import Flask, jsonify, request
from flask_cors import CORS

# 创建应用程序对象
app = Flask(__name__)
# pip install Flask-Cors
CORS(app)

@app.route('/books',methods=["GET","POST"])
def hello():
    '''
    hello请求
    '''
    # 直接返回字符串
    if request.method == 'GET':
        return request.args['id']
    elif request.method == 'POST':
        return request.form['uname']

@app.route('/books/<string:id>')
def hello1(id):
    '''
    hello请求
    '''
    # 直接返回字符串
    return id
@app.route('/books/<string:id>',methods=["DELETE"])
def hello2(id):
    '''
    hello请求
    '''
    # 直接返回字符串
    return id

@app.route('/books_json',methods=["POST"])
def books_json():
    '''
    hello请求
    '''
    # 直接返回字符串
    if request.method == 'POST':
        return request.json

@app.route('/books/<string:id>',methods=["PUT"])
def books_put_json(id):
    '''
    hello请求
    '''
    # 直接返回字符串
    return request.json

if __name__ == '__main__':
    # 以debug模式启动程序
    app.run(debug = True)

fetchAPI 中 响应格式

  • 用fetch来获取数据,如果响应正常返回,我们首先看到的是一个response对象,其中包括返回的一堆原始字节,这些字节需要在收到后,需要我们通过调用方法将其转换为相应格式的数据,比如JSONBLOB或者TEXT等等

    /*
      Fetch响应结果的数据格式
    */
    fetch('http://localhost:3000/json').then(function(data){
      // return data.json();   //  将获取到的数据使用 json 转换对象
      return data.text(); //  //  将获取到的数据 转换成字符串 
    }).then(function(data){
      // console.log(data.uname)
      // console.log(typeof data)
      var obj = JSON.parse(data);
      console.log(obj.uname,obj.age,obj.gender)
    })

axios

  • 基于promise用于浏览器和node.js的http客户端
  • 支持浏览器和node.js
  • 支持promise
  • 能拦截请求和响应
  • 自动转换JSON数据
  • 能转换请求和响应数据

axios基础用法

  • get和 delete请求传递参数

    • 通过传统的url 以 ? 的形式传递参数
    • restful 形式传递参数
    • 通过params 形式传递参数
  • post 和 put 请求传递参数

    • 通过选项传递参数
    • 通过 URLSearchParams 传递参数
    # 1. 发送get 请求 
    axios.get('http://localhost:3000/adata').then(function(ret){ 
      #  拿到 ret 是一个对象      所有的对象都存在 ret 的data 属性里面
      // 注意data属性是固定的用法,用于获取后台的实际数据
      // console.log(ret.data)
      console.log(ret)
    })
    # 2.  get 请求传递参数
    # 2.1  通过传统的url  以 ? 的形式传递参数
    axios.get('http://localhost:3000/axios?id=123').then(function(ret){
      console.log(ret.data)
    })
    # 2.2  restful 形式传递参数 
    axios.get('http://localhost:3000/axios/123').then(function(ret){
      console.log(ret.data)
    })
    # 2.3  通过params  形式传递参数 
    axios.get('http://localhost:3000/axios', {
      params: {
        id: 789
      }
    }).then(function(ret){
      console.log(ret.data)
    })
    #3 axios delete 请求传参     传参的形式和 get 请求一样
    axios.delete('http://localhost:3000/axios', {
      params: {
        id: 111
      }
    }).then(function(ret){
      console.log(ret.data)
    })

    # 4  axios 的 post 请求
    # 4.1  通过选项传递参数
    axios.post('http://localhost:3000/axios', {
      uname: 'lisi',
      pwd: 123
    }).then(function(ret){
      console.log(ret.data)
    })
    # 4.2  通过 URLSearchParams  传递参数 
    var params = new URLSearchParams();
    params.append('uname', 'zhangsan');
    params.append('pwd', '111');
    axios.post('http://localhost:3000/axios', params).then(function(ret){
      console.log(ret.data)
    })

     #5  axios put 请求传参   和 post 请求一样 
    axios.put('http://localhost:3000/axios/123', {
      uname: 'lisi',
      pwd: 123
    }).then(function(ret){
      console.log(ret.data)
    })

axios 全局配置

#  配置公共的请求头 
axios.defaults.baseURL = 'https://api.example.com';
#  配置 超时时间
axios.defaults.timeout = 2500;
#  配置公共的请求头
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
# 配置公共的 post 的 Content-Type
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

axios 拦截器

  • 请求拦截器

    • 请求拦截器的作用是在请求发送前进行一些操作

      • 例如在每个请求体里加上token,统一做了处理如果以后要改也非常容易
  • 响应拦截器

    • 响应拦截器的作用是在接收到响应后进行一些操作

      • 例如在服务器返回登录状态失效,需要重新登录的时候,跳转到登录页
    # 1. 请求拦截器 
    axios.interceptors.request.use(function(config) {
      console.log(config.url)
      # 1.1  任何请求都会经过这一步   在发送请求之前做些什么   
      config.headers.mytoken = 'nihao';
      # 1.2  这里一定要return   否则配置不成功  
      return config;
    }, function(err){
       #1.3 对请求错误做点什么    
      console.log(err)
    })
    #2. 响应拦截器 
    axios.interceptors.response.use(function(res) {
      #2.1  在接收响应做些什么  
      var data = res.data;
      return data;
    }, function(err){
      #2.2 对响应错误做点什么  
      console.log(err)
    })

async 和 await

  • async作为一个关键字放到函数前面

    • 任何一个async函数都会隐式返回一个promise
  • await关键字只能在使用async定义的函数中使用

    • ​ await后面可以直接跟一个 Promise实例对象
    • ​ await函数不能单独使用
  • async/await 让异步代码看起来、表现起来更像同步代码
     # 1.  async 基础用法
    # 1.1 async作为一个关键字放到函数前面
    async function queryData() {
      # 1.2 await关键字只能在使用async定义的函数中使用      await后面可以直接跟一个 Promise实例对象
      var ret = await new Promise(function(resolve, reject){
        setTimeout(function(){
          resolve('nihao')
        },1000);
      })
      // console.log(ret.data)
      return ret;
    }
    # 1.3 任何一个async函数都会隐式返回一个promise   我们可以使用then 进行链式编程
    queryData().then(function(data){
      console.log(data)
    })

    #2.  async    函数处理多个异步函数
    axios.defaults.baseURL = 'http://localhost:3000';

    async function queryData() {
      # 2.1  添加await之后 当前的await 返回结果之后才会执行后面的代码   
      
      var info = await axios.get('async1');
      #2.2  让异步代码看起来、表现起来更像同步代码
      var ret = await axios.get('async2?info=' + info.data);
      return ret.data;
    }

    queryData().then(function(data){
      console.log(data)
    })

图书列表案例

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <link rel="stylesheet" type="text/css" href="css/index.css">
</head>
<body>
  <div id="app">
    <div class="grid">
      <div>
        <h1>图书管理</h1>
        <div class="book">
          <div>
            <label for="id">
              编号:
            </label>
            <input type="text" id="id" v-model='id' disabled="false" v-focus>
            <label for="name">
              名称:
            </label>
            <input type="text" id="name" v-model='name'>
            <button @click='handle' :disabled="submitFlag">提交</button>
          </div>
        </div>
      </div>
      <div class="total">
        <span>图书总数:</span>
        <span>{{total}}</span>
      </div>
      <table>
        <thead>
          <tr>
            <th>编号</th>
            <th>名称</th>
            <th>时间</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          <tr :key='item.id' v-for='item in books'>
            <td>{{item.id}}</td>
            <td>{{item.name}}</td>
            <td>{{item.date | format}}</td>
            <td>
              <a href="" @click.prevent='toEdit(item.id)'>修改</a>
              <span>|</span>
              <a href="" @click.prevent='deleteBook(item.id)'>删除</a>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
  <script type="text/javascript" data-original="js/vue.js"></script>
  <script type="text/javascript" data-original="js/axios.js"></script>
  <script type="text/javascript">
    /*
      图书管理-添加图书
    */
    axios.defaults.baseURL = 'http://localhost:5000/';
    axios.interceptors.response.use(function(res){
      return res.data;
    }, function(error){
      console.log(error)
    });
    Vue.directive('focus', {
      inserted: function (el) {
        el.focus();
      }
    });

    Vue.filter('format',(dataStr) => {
        var time = new Date(dataStr);
        function timeAdd0(str) {
            if(str<10){
                str='0'+str;
            }
            return str
        }
        var y = time.getFullYear();
        var m = time.getMonth()+1;
        var d = time.getDate();
        var h = time.getHours();
        var mm = time.getMinutes();
        var s = time.getSeconds();
        return y+'-'+timeAdd0(m)+'-'+timeAdd0(d)+' '+timeAdd0(h)+':'+timeAdd0(mm)+':'+timeAdd0(s);
    })


    var vm = new Vue({
      el: '#app',
      data: {
        flag: false,
        submitFlag: false,
        id: '',
        name: '',
        books: []
      },
      methods: {
        handle: async function(){
          if(this.flag) {
            // 编辑图书
            var ret = await axios.put('books/' + this.id, {
              name: this.name
            });
            if(ret.status == 200){
              // 重新加载列表数据
              this.queryData();
            }
            this.flag = false;
          }else{
            // 添加图书
            var ret = await axios.post('books', {
              name: this.name
            })
            if(ret.status == 200) {
              // 重新加载列表数据
              this.queryData();
            }
          }
          // 清空表单
          this.id = '';
          this.name = '';
        },
        toEdit: async function(id){
          // flag状态位用于区分编辑和添加操作
          this.flag = true;
          // 根据id查询出对应的图书信息
          var ret = await axios.get('books/' + id);
          this.id = ret.id;
          this.name = ret.name;
        },
        deleteBook: async function(id){
          // 删除图书
          var ret = await axios.delete('books/' + id);
           console.log(ret)
          if(ret.status == 200) {
            // 重新加载列表数据
            this.queryData();
          }
        },
        queryData: async function(){
          // 调用后台接口获取图书列表数据
          // var ret = await axios.get('books');
          // this.books = ret.data;

          this.books = await axios.get('books');
        }
      },
      computed: {
        total: function(){
          // 计算图书的总数
          return this.books.length;
        }
      },
      watch: {
        name: async function(val) {
          // 验证图书名称是否已经存在
          var ret = await axios.get('/books/book/' + this.name);
          // console.log(ret)
          if(ret.status == 1) {
            // 图书名称存在
            this.submitFlag = true;
          }else{
            // 图书名称不存在
            this.submitFlag = false;
          }
        }
      },
      mounted: function(){
        this.queryData();
      }
    });
  </script>
</body>
</html>

.grid {
  margin: auto;
  width: 530px;
  text-align: center;
}
.grid table {
  border-top: 1px solid #C2D89A;
  width: 100%;
  border-collapse: collapse;
}
.grid th,td {
  padding: 10;
  border: 1px dashed #F3DCAB;
  height: 35px;
  line-height: 35px;
}
.grid th {
  background-color: #F3DCAB;
}
.grid .book {
  padding-bottom: 10px;
  padding-top: 5px;
  background-color: #F3DCAB;
}
.grid .total {
  height: 30px;
  line-height: 30px;
  background-color: #F3DCAB;
  border-top: 1px solid #C2D89A;
}

后台代码:

# coding:utf-8
import time

from flask import Flask, jsonify, request
from flask_cors import CORS

bookdata = [
    {
        "id": 1,
        "name": "红楼梦",
        "date": 2525609975000
    },
    {
        "name": "三国演义",
        "date": 2525609975000,
        "id": 2
    },
    {
        "name": "水浒传",
        "date": 2525609975000,
        "id": 3
    },

    {
        "name": "西游记",
        "date": 2525609975000,
        "id": 4
    }
]
last_id = len(bookdata) + 1
# 创建应用程序对象
app = Flask(__name__)
# pip install Flask-Cors
CORS(app)
@app.route('/')
def hello():
    '''
    hello请求
    '''
    # 直接返回字符串
    return 'Hello, Flask World!'

@app.route('/books', methods=["GET","POST"])
def books():
    global last_id
    if request.method == 'GET':
        # 直接返回字符串
        return jsonify(bookdata)
    elif request.method == 'POST':
        body = request.json
        print(body['name'])
        book = {}
        book['id'] = last_id
        book['name'] = body['name']
        book['date'] = int(time.time()*1000)
        bookdata.append(book)
        last_id+=1
        return jsonify({'status': 200})

@app.route('/books/book/<string:name>', methods=["GET"])
def check_bookname(name):
    print('check_bookname' + name)
    if name in [book['name'] for book in bookdata]:
        return jsonify({'status': 1})
    else:
        return jsonify({'status': 0})

@app.route('/books/book/', methods=["GET"])
def check_empty_bookname():
    print('check_empty_bookname')
    return jsonify({'status': 0})


@app.route('/books/<string:id>', methods=["GET","PUT","DELETE"])
def manage_bookinfo(id):
    global bookdata
    if request.method == 'GET':
        books = list(filter(lambda x: x['id'] == int(id), bookdata))
        return jsonify(books[0])
    elif request.method == 'PUT':
        body = request.json
        books = list(filter(lambda x: x['id'] == int(id), bookdata))
        book = books[0]
        book['name'] = body['name']
        return jsonify({'status': 200})
    elif request.method == 'DELETE':
        body = request.json
        books = list(filter(lambda x: x['id'] != int(id), bookdata))
        bookdata = books
        return jsonify({'status': 200})

if __name__ == '__main__':
    # 以debug模式启动程序
    app.run(debug = True)
查看原文

赞 0 收藏 0 评论 0

豆约翰 发布了文章 · 2020-10-23

03.vue组件

一、组件

  • 组件 (Component) 是 Vue.js 最强大的功能之一
  • 组件可以扩展 HTML 元素,封装可重用的代码

image.png

二、 组件注册

2.1 全局注册

  • Vue.component(组件名称, { data: 组件数据, template: 组件模板内容 })
  • 全局组件注册后,任何vue实例都可以用
组件基础用法

<组件名称></组件名称>

<div id="example">
  <!-- 2、 组件使用 组件名称 是以HTML标签的形式使用  -->  
  <my-component></my-component>
</div>
<script>
    //   注册组件 
    // 1、 my-component 就是组件中自定义的标签名
    Vue.component('my-component', {
      template: '<div>A custom component!</div>'
    })

    // 创建根实例
    new Vue({
      el: '#example'
    })

</script>

2.2 组件注意事项

  • 组件参数的data值必须是函数,同时这个函数要求返回一个对象
  • 组件模板必须是单个根元素
  • 组件模板的内容可以是模板字符串(用反引号包含起来)
  <div id="app">
     <!-- 
        4、组件可以重复使用多次 
          因为data中返回的是一个对象所以每个组件中的数据是私有的
          即每个实例可以维护一份被返回对象的独立的拷贝   
    --> 
    <button-counter></button-counter>
    <button-counter></button-counter>
    <button-counter></button-counter>
      <!-- 8、必须使用短横线的方式使用组件 -->
     <hello-world></hello-world>
  </div>
<script type="text/javascript">
    //5  如果使用驼峰式命名组件,那么在使用组件的时候,只能在字符串模板中用驼峰的方式使用组件,
    // 7、但是在普通的标签模板中,必须使用短横线的方式使用组件
    Vue.component('HelloWorld', {
      data: function(){
        return {
          msg: 'HelloWorld'
        }
      },
      template: '<div>{{msg}}</div>'
    });

    Vue.component('button-counter', {
      // 1、组件参数的data值必须是函数 
      // 同时这个函数要求返回一个对象  
      data: function(){
        return {
          count: 0
        }
      },
      //  2、组件模板必须是单个根元素
      //  3、组件模板的内容可以是模板字符串  
      template: `
        <div>
          <button @click="handle">点击了{{count}}次</button>
          <button>测试123</button>
            #  6 在字符串模板中可以使用驼峰的方式使用组件   
           <HelloWorld></HelloWorld>
        </div>
      `,
      methods: {
        handle: function(){
          this.count += 2;
        }
      }
    })
    var vm = new Vue({
      el: '#app',
      data: {

      }
    });
  </script>

2.3 命名方式

  • 短横线方式(推荐)
    Vue.component('my-component', { /* ... */ })
  • 驼峰方式
    Vue.component('MyComponent, { /* ... */ })
    注意:驼峰方式命名的组件 <my-component> 和 <MyComponent> 一般情况都是可接受的,比如在字符串模版中使用。但直接在 DOM 中使用时只有 <my-component>是有效的。

2.4 局部注册

  • 只能在当前注册它的vue实例中使用
  <div id="app">
      <my-component></my-component>
  </div>

<script>
    // 定义组件的模板
    var Child = {
      data: function(){
          return {
              msg: 'Hello'
          }
      },
      template: '<div>A custom component!{{msg}}</div>'
    }
    new Vue({
      el: '#app',
      //局部注册组件  
      components: {
        // <my-component> 将只在父模板可用  一定要在实例上注册了才能在html文件中使用
        'my-component': Child
      }
    })
 </script>

三、Vue 调试工具

链接:https://pan.baidu.com/s/139hspAnspD7bJbo81xigmg 密码:1hsv

四、 Vue组件之间传值(数据交互)

4.1 父组件向子组件传值

  1. 子组件内部通过props属性接收传递过来的值
Vue.component(‘menu-item', {
    props: ['title'],
    template: '<div>{{ title }}</div>'
})
  1. 父组件通过属性将值传递给子组件
可以传递静态数据
<menu-item title="来自父组件的数据"></menu-item> 
也可以动态绑定
<menu-item :title="title"></menu-item>
  1. 在props中定义要接收的属性名时使用驼峰形式,在DOM中直接引入组件时需要使用短横线的形式,字符串形式的模板中则没有这个限制
Vue.component('third-com', {
    // 在 JavaScript 中是驼峰式的
    props: ['testTitle'],
    template: '<div>{{ testTitle }}'
})
Vue.component('menu-item', {
    // 在 JavaScript 中是驼峰式的
    props: ['menuTitle'],
    template: '<div>{{ menuTitle }}<third-com testTitle="hello"></div>'
    //字符串模版内驼峰式使用属性没有影响
})
<!– 在html中是短横线方式的 -->
<menu-item menu-title=“nihao"></menu-item>
  1. 传值类型
    如果子组件需要得到原始类型,比如数字型、布尔型,在父组件中推荐使用:属性=属性值的形式进行绑定,否则都会传递字符串类型。
    <div id="app">
        <div>{{pmsg}}</div>
        <menu-item :pstr='pstr' :pnum='12' :pboo='true' :parr='parr' :pobj='pobj'></menu-item>
    </div>
    Vue.component('menu-item', {
            props: ['pstr', 'pnum', 'pboo', 'parr', 'pobj'],
            template: `
        <div>
          <div>{{pstr}}</div>
          <div>{{12 + pnum}}</div>
          <div>{{typeof pboo}}</div>
          <ul>
            <li :key='index' v-for='(item,index) in parr'>{{item}}</li>
          </ul>
            <span>{{pobj.name}}</span>
            <span>{{pobj.age}}</span>
          </div>
        </div>
      `
        });
        var vm = new Vue({
            el: '#app',
            data: {
                pmsg: '父组件中内容',
                pstr: 'hello',
                parr: ['apple', 'orange', 'banana'],
                pobj: {
                    name: 'lisi',
                    age: 12
                }
            }
        });

实例

  <div id="app">
    <div>{{pmsg}}</div>
     <!--1、menu-item  在 APP中嵌套着 故 menu-item   为  子组件      -->
     <!-- 给子组件传入一个静态的值 -->
    <menu-item title='来自父组件的值'></menu-item>
    <!-- 2、 需要动态的数据的时候 需要属性绑定的形式设置 此时 ptitle  来自父组件data 中的数据 . 
          传的值可以是数字、对象、数组等等
    -->
    <menu-item :title='ptitle' content='hello'></menu-item>
  </div>

  <script type="text/javascript">
    Vue.component('menu-item', {
      // 3、 子组件用属性props接收父组件传递过来的数据  
      props: ['title', 'content'],
      data: function() {
        return {
          msg: '子组件本身的数据'
        }
      },
      template: '<div>{{msg + "----" + title + "-----" + content}}</div>'
    });
    var vm = new Vue({
      el: '#app',
      data: {
        pmsg: '父组件中内容',
        ptitle: '动态绑定属性'
      }
    });
  </script>

4.2 子组件向父组件传值

  1. 子组件通过自定义事件向父组件传递信息,用$emit()触发事件
    <button @click='$emit("自定义事件名称","需要传递的数据")'>扩大字体</button>
  2. 父组件用v-on监听子组件的事件
    直接写事件处理逻辑时使用数据
    <menu-item v-on:自定义事件名称='fontSize += $event'></menu-item>
    绑定函数时使用
    <menu-item @自定义事件名称='函数名($event)'></menu-item>

 <div id="app">
    <div :style='{fontSize: fontSize + "px"}'>{{pmsg}}</div>
     <!-- 2 父组件用v-on 监听子组件的事件
        这里 enlarge-text  是从 $emit 中的第一个参数对应   handle 为对应的事件处理函数 
    --> 
    <menu-item :parr='parr' @enlarge-text='handle($event)'></menu-item>
  </div>
  <script type="text/javascript" data-original="js/vue.js"></script>
  <script type="text/javascript">
    /* 子组件向父组件传值-携带参数*/
    Vue.component('menu-item', {
      props: ['parr'],
      template: `
        <div>
          <ul>
            <li :key='index' v-for='(item,index) in parr'>{{item}}</li>
          </ul>
            ###  1、子组件用$emit()触发事件
            ### 第一个参数为 自定义的事件名称   第二个参数为需要传递的数据  
          <button @click='$emit("enlarge-text", 5)'>扩大父组件中字体大小</button>
          <button @click='$emit("enlarge-text", 10)'>扩大父组件中字体大小</button>
        </div>
      `
    });
    var vm = new Vue({
      el: '#app',
      data: {
        pmsg: '父组件中内容',
        parr: ['apple','orange','banana'],
        fontSize: 10
      },
      methods: {
        handle: function(val){
          // 扩大字体大小
          this.fontSize += val;
        }
      }
    });
  </script>

4.3 兄弟之间的传递

兄弟之间传递数据需要借助于事件中心,通过事件中心传递数据

  1. 提供事件中心
    var eventHub = new Vue()
  2. 接收数据方,通过mounted(){} 钩子中设置监听(注册)事件
    eventHub.$on('方法名', 数据形参)
  3. 传递数据方触发事件
    eventHub.$emit(‘方法名', 传递的数据)
  4. 销毁事件,通过eventhub.$off()销毁之后,无法进行传递数据
    eventHub.$off('方法名')
 <div id="app">
    <div>父组件</div>
    <div>
      <button @click='handle'>销毁事件</button>
    </div>
    <test-tom></test-tom>
    <test-jerry></test-jerry>
  </div>
  <script type="text/javascript" data-original="js/vue.js"></script>
  <script type="text/javascript">
    /*
      兄弟组件之间数据传递
    */
    //1、 提供事件中心
    var hub = new Vue();

    Vue.component('test-tom', {
      data: function(){
        return {
          num: 0
        }
      },
      template: `
        <div>
          <div>TOM:{{num}}</div>
          <div>
            <button @click='handle'>点击</button>
          </div>
        </div>
      `,
      methods: {
        handle: function(){
          //2、传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)   触发兄弟组件的事件
          hub.$emit('jerry-event', 2);
        }
      },
      mounted: function() {
       // 3、接收数据方,通过mounted(){} 钩子中  触发hub.$on(方法名
        hub.$on('tom-event', (val) => {
          this.num += val;
        });
      }
    });
    Vue.component('test-jerry', {
      data: function(){
        return {
          num: 0
        }
      },
      template: `
        <div>
          <div>JERRY:{{num}}</div>
          <div>
            <button @click='handle'>点击</button>
          </div>
        </div>
      `,
      methods: {
        handle: function(){
          //2、传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)   触发兄弟组件的事件
          hub.$emit('tom-event', 1);
        }
      },
      mounted: function() {
        // 3、接收数据方,通过mounted(){} 钩子中  触发hub.$on()方法名
        hub.$on('jerry-event', (val) => {
          this.num += val;
        });
      }
    });
    var vm = new Vue({
      el: '#app',
      data: {        
      },
      methods: {
        handle: function(){
          //4、销毁事件 通过hub.$off()方法名销毁之后无法进行传递数据  
          hub.$off('tom-event');
          hub.$off('jerry-event');
        }
      }
    });
  </script>

五、 组件插槽

指父组件向子组件传递模版内容,子组件预留的位置(template中的slot)就叫插槽。而传递给slot的内容就是组件标签之间的内容。
组件的最大特性就是复用性,而用好插槽能大大提高组件的可复用能力

5.1 匿名插槽

  <div id="app">
    <!-- 这里的所有组件标签中嵌套的内容会替换掉slot  如果不传值 则使用 slot 中的默认值  -->  
    <alert-box>有bug发生</alert-box>
    <alert-box>有一个警告</alert-box>
    <alert-box></alert-box>
  </div>

  <script type="text/javascript">
    /*组件插槽:父组件向子组件传递内容 */
    Vue.component('alert-box', {
      template: `
        <div>
          <strong>ERROR:</strong>
        # 当组件渲染的时候,这个 <slot> 元素将会被替换为“组件标签中嵌套的内容”。
        # 插槽内可以包含任何模板代码,包括 HTML
          <slot>默认内容</slot>
        </div>
      `
    });
    var vm = new Vue({
      el: '#app',
      data: {

      }
    });
  </script>
</body>
</html>

5.2 具名插槽

  • 具有名字的插槽
  • 使用 <slot> 中的 "name" 属性绑定元素
  <div id="app">
    <base-layout>
       <!-- 2、组件使用时,有slot属性的标签以及里面的内容会放在slot属性匹配上的插槽位置
                如果没有匹配到 则放到匿名的插槽中   --> 
      <p slot='header'>标题信息</p>
      <p>主要内容1</p>
      <p>主要内容2</p>
      <p slot='footer'>底部信息信息</p>
    </base-layout>

    <base-layout>
      <!-- template临时的包裹标签最终不会渲染到页面上,这种方式可以渲染传递多个标签的内容  -->  
      <template slot='header'>
        <p>标题信息1</p>
        <p>标题信息2</p>
      </template>
      <p>主要内容1</p>
      <p>主要内容2</p>
      <template slot='footer'>
        <p>底部信息信息1</p>
        <p>底部信息信息2</p>
      </template>
    </base-layout>
  </div>
  <script type="text/javascript" data-original="js/vue.js"></script>
  <script type="text/javascript">
    /*具名插槽 */
    Vue.component('base-layout', {
      template: `
        <div>
          <header>
            ### 1、 使用 <slot> 中的 "name" 属性绑定元素 指定当前插槽的名字
            <slot name='header'></slot>
          </header>
          <main>
            <slot></slot>
          </main>
          <footer>
            ###  注意点: 
            ###  具名插槽的渲染顺序,完全取决于模板,而不是取决于父组件中元素的顺序
            <slot name='footer'></slot>
          </footer>
        </div>
      `
    });
    var vm = new Vue({
      el: '#app',
      data: {
      }
    });
  </script>
</body>
</html>

5.3 作用域插槽

  • 父组件可以对子组件内容加工处理
  • 既可以复用子组件的slot,又可以使slot内容不一致
  <div id="app">
    <!-- 
        1、当我们希望li的样式由外部使用组件的地方定义,因为可能有多种地方要使用该组件,
        但样式希望不一样 这个时候我们需要使用作用域插槽    
    -->  
    <fruit-list :list='list'>
       <!-- 2、父组件中使用了<template>元素,而且包含scope="slotProps",
            slotProps在这里只是临时变量,名称是自定义的   
        --->    
      <template slot-scope='slotProps'>
        <strong v-if='slotProps.info.id==3' class="current">
            {{slotProps.info.name}}              
         </strong>
        <span v-else>{{slotProps.info.name}}</span>
      </template>
    </fruit-list>
  </div>
  <script type="text/javascript" data-original="js/vue.js"></script>
  <script type="text/javascript">
    /*作用域插槽*/
    Vue.component('fruit-list', {
      props: ['list'],
      template: `
        <div>
          <li :key='item.id' v-for='item in list'>
            ###  3、 在子组件模板中,<slot>元素上有一个类似props传递数据给组件的写法msg="xxx",
            ###   插槽可以提供一个默认内容,如果如果父组件没有为这个插槽提供了内容,会显示默认的内容。
                如果父组件为这个插槽提供了内容,则默认的内容会被替换掉
            <slot :info='item'>{{item.name}}</slot>
          </li>
        </div>
      `
    });
    var vm = new Vue({
      el: '#app',
      data: {
        list: [{
          id: 1,
          name: 'apple'
        },{
          id: 2,
          name: 'orange'
        },{
          id: 3,
          name: 'banana'
        }]
      }
    });
  </script>
</body>
</html>

六、 基于组件的购物车案例

6.1. 实现组件化布局

  • 把静态页面转换成组件化模式
  • 把组件渲染到页面上
 <div id="app">
    <div class="container">
      <!-- 2、把组件渲染到页面上 --> 
      <my-cart></my-cart>
    </div>
  </div>
  <script type="text/javascript" data-original="js/vue.js"></script>
  <script type="text/javascript">
    # 1、 把静态页面转换成组件化模式
    # 1.1  标题组件 
    var CartTitle = {
      template: `
        <div class="title">我的商品</div>
      `
    }
    # 1.2  商品列表组件 
    var CartList = {
      #  注意点 :  组件模板必须是单个根元素  
      template: `
        <div>
          <div class="item">
            <img data-original="img/a.jpg"/>
            <div class="name"></div>
            <div class="change">
              <a href="">-</a>
              <input type="text" class="num" />
              <a href="">+</a>
            </div>
            <div class="del">×</div>
          </div>
          <div class="item">
            <img data-original="img/b.jpg"/>
            <div class="name"></div>
            <div class="change">
              <a href="">-</a>
              <input type="text" class="num" />
              <a href="">+</a>
            </div>
            <div class="del">×</div>
          </div>
          <div class="item">
            <img data-original="img/c.jpg"/>
            <div class="name"></div>
            <div class="change">
              <a href="">-</a>
              <input type="text" class="num" />
              <a href="">+</a>
            </div>
            <div class="del">×</div>
          </div>
          <div class="item">
            <img data-original="img/d.jpg"/>
            <div class="name"></div>
            <div class="change">
              <a href="">-</a>
              <input type="text" class="num" />
              <a href="">+</a>
            </div>
            <div class="del">×</div>
          </div>
          <div class="item">
            <img data-original="img/e.jpg"/>
            <div class="name"></div>
            <div class="change">
              <a href="">-</a>
              <input type="text" class="num" />
              <a href="">+</a>
            </div>
            <div class="del">×</div>
          </div>
        </div>
      `
    }
    # 1.3  商品结算组件 
    var CartTotal = {
      template: `
        <div class="total">
          <span>总价:123</span>
          <button>结算</button>
        </div>
      `
    }
    ## 1.4  定义一个全局组件 my-cart
    Vue.component('my-cart',{
      ##  1.6 引入子组件  
      template: `
        <div class='cart'>
          <cart-title></cart-title>
          <cart-list></cart-list>
          <cart-total></cart-total>
        </div>
      `,
      # 1.5  注册子组件   
      components: {
        'cart-title': CartTitle,
        'cart-list': CartList,
        'cart-total': CartTotal
      }
    });
    var vm = new Vue({
      el: '#app',
      data: {

      }
    });

  </script>

6.2、实现 标题和结算功能组件

  • 标题组件实现动态渲染

    • 从父组件把标题数据传递过来 即 父向子组件传值
    • 把传递过来的数据渲染到页面上
  • 结算功能组件

    • 从父组件把商品列表list 数据传递过来 即 父向子组件传值
    • 把传递过来的数据计算最终价格渲染到页面上
 <div id="app">
    <div class="container">
      <my-cart></my-cart>
    </div>
  </div>
  <script type="text/javascript" data-original="js/vue.js"></script>
  <script type="text/javascript">
     # 2.2  标题组件     子组件通过props形式接收父组件传递过来的uname数据
    var CartTitle = {
      props: ['uname'],
      template: `
        <div class="title">{{uname}}的商品</div>
      `
    }
    # 2.3  商品结算组件  子组件通过props形式接收父组件传递过来的list数据   
    var CartTotal = {
      props: ['list'],
      template: `
        <div class="total">
          <span>总价:{{total}}</span>
          <button>结算</button>
        </div>
      `,
      computed: {
        # 2.4    计算商品的总价  并渲染到页面上 
        total: function() {
          var t = 0;
          this.list.forEach(item => {
            t += item.price * item.num;
          });
          return t;
        }
      }
    }
    Vue.component('my-cart',{
      data: function() {
        return {
          uname: '张三',
          list: [{
            id: 1,
            name: 'TCL彩电',
            price: 1000,
            num: 1,
            img: 'img/a.jpg'
          },{
            id: 2,
            name: '机顶盒',
            price: 1000,
            num: 1,
            img: 'img/b.jpg'
          },{
            id: 3,
            name: '海尔冰箱',
            price: 1000,
            num: 1,
            img: 'img/c.jpg'
          },{
            id: 4,
            name: '小米手机',
            price: 1000,
            num: 1,
            img: 'img/d.jpg'
          },{
            id: 5,
            name: 'PPTV电视',
            price: 1000,
            num: 2,
            img: 'img/e.jpg'
          }]
        }
      },
      #  2.1  父组件向子组件以属性传递的形式 传递数据
      #   向 标题组件传递 uname 属性   向 商品结算组件传递 list  属性  
      template: `
        <div class='cart'>
          <cart-title :uname='uname'></cart-title>
          <cart-list></cart-list>
          <cart-total :list='list'></cart-total>
        </div>
      `,
      components: {
        'cart-title': CartTitle,
        'cart-list': CartList,
        'cart-total': CartTotal
      }
    });
    var vm = new Vue({
      el: '#app',
      data: {

      }
    });
  </script>

6.3. 实现列表组件删除功能

  • 从父组件把商品列表list 数据传递过来 即 父向子组件传值
  • 把传递过来的数据渲染到页面上
  • 点击删除按钮的时候删除对应的数据

    • 给按钮添加点击事件把需要删除的id传递过来

      • 子组件中不推荐操作父组件的数据有可能多个子组件使用父组件的数据 我们需要把数据传递给父组件让父组件操作数据
      • 父组件删除对应的数据
 <div id="app">
    <div class="container">
      <my-cart></my-cart>
    </div>
  </div>
  <script type="text/javascript" data-original="js/vue.js"></script>
  <script type="text/javascript">

    var CartTitle = {
      props: ['uname'],
      template: `
        <div class="title">{{uname}}的商品</div>
      `
    }
    #  3.2 把列表数据动态渲染到页面上  
    var CartList = {
      props: ['list'],
      template: `
        <div>
          <div :key='item.id' v-for='item in list' class="item">
            <img :data-original="item.img"/>
            <div class="name">{{item.name}}</div>
            <div class="change">
              <a href="">-</a>
              <input type="text" class="num" />
              <a href="">+</a>
            </div>
            # 3.3  给按钮添加点击事件把需要删除的id传递过来
            <div class="del" @click='del(item.id)'>×</div>
          </div>
        </div>
      `,
      methods: {
        del: function(id){
           # 3.4 子组件中不推荐操作父组件的数据有可能多个子组件使用父组件的数据 
          #       我们需要把数据传递给父组件 让父组件操作数据 
          this.$emit('cart-del', id);
        }
      }
    }
    var CartTotal = {
      props: ['list'],
      template: `
        <div class="total">
          <span>总价:{{total}}</span>
          <button>结算</button>
        </div>
      `,
      computed: {
        total: function() {
          // 计算商品的总价
          var t = 0;
          this.list.forEach(item => {
            t += item.price * item.num;
          });
          return t;
        }
      }
    }
    Vue.component('my-cart',{
      data: function() {
        return {
          uname: '张三',
          list: [{
            id: 1,
            name: 'TCL彩电',
            price: 1000,
            num: 1,
            img: 'img/a.jpg'
          },{
            id: 2,
            name: '机顶盒',
            price: 1000,
            num: 1,
            img: 'img/b.jpg'
          },{
            id: 3,
            name: '海尔冰箱',
            price: 1000,
            num: 1,
            img: 'img/c.jpg'
          },{
            id: 4,
            name: '小米手机',
            price: 1000,
            num: 1,
            img: 'img/d.jpg'
          },{
            id: 5,
            name: 'PPTV电视',
            price: 1000,
            num: 2,
            img: 'img/e.jpg'
          }]
        }
      },
      # 3.1 从父组件把商品列表list 数据传递过来 即 父向子组件传值  
      template: `
        <div class='cart'>
          <cart-title :uname='uname'></cart-title>
          #  3.5  父组件通过事件绑定 接收子组件传递过来的数据 
          <cart-list :list='list' @cart-del='delCart($event)'></cart-list>
          <cart-total :list='list'></cart-total>
        </div>
      `,
      components: {
        'cart-title': CartTitle,
        'cart-list': CartList,
        'cart-total': CartTotal
      },
      methods: {
        # 3.6    根据id删除list中对应的数据        
        delCart: function(id) {
          // 1、找到id所对应数据的索引
          var index = this.list.findIndex(item=>{
            return item.id == id;
          });
          // 2、根据索引删除对应数据
          this.list.splice(index, 1);
        }
      }
    });
    var vm = new Vue({
      el: '#app',
      data: {

      }
    });
  </script>
</body>
</html>

6.4. 实现组件更新数据功能 (上)

  • 将输入框中的默认数据动态渲染出来
  • 输入框失去焦点的时候 更改商品的数量
  • 子组件中不推荐操作数据 把这些数据传递给父组件 让父组件处理这些数据
  • 父组件中接收子组件传递过来的数据并处理
 <div id="app">
    <div class="container">
      <my-cart></my-cart>
    </div>
  </div>
  <script type="text/javascript" data-original="js/vue.js"></script>
  <script type="text/javascript">

    var CartTitle = {
      props: ['uname'],
      template: `
        <div class="title">{{uname}}的商品</div>
      `
    }
    var CartList = {
      props: ['list'],
      template: `
        <div>
          <div :key='item.id' v-for='item in list' class="item">
            <img :data-original="item.img"/>
            <div class="name">{{item.name}}</div>
            <div class="change">
              <a href="">-</a>
                # 1\. 将输入框中的默认数据动态渲染出来
                # 2\. 输入框失去焦点的时候 更改商品的数量  需要将当前商品的id 传递过来
              <input type="text" class="num" :value='item.num' @blur='changeNum(item.id, $event)'/>
              <a href="">+</a>
            </div>
            <div class="del" @click='del(item.id)'>×</div>
          </div>
        </div>
      `,
      methods: {
        changeNum: function(id, event){
          # 3 子组件中不推荐操作数据  因为别的组件可能也引用了这些数据
          #  把这些数据传递给父组件 让父组件处理这些数据
          this.$emit('change-num', {
            id: id,
            num: event.target.value
          });
        },
        del: function(id){
          // 把id传递给父组件
          this.$emit('cart-del', id);
        }
      }
    }
    var CartTotal = {
      props: ['list'],
      template: `
        <div class="total">
          <span>总价:{{total}}</span>
          <button>结算</button>
        </div>
      `,
      computed: {
        total: function() {
          // 计算商品的总价
          var t = 0;
          this.list.forEach(item => {
            t += item.price * item.num;
          });
          return t;
        }
      }
    }
    Vue.component('my-cart',{
      data: function() {
        return {
          uname: '张三',
          list: [{
            id: 1,
            name: 'TCL彩电',
            price: 1000,
            num: 1,
            img: 'img/a.jpg'
          }]
      },
      template: `
        <div class='cart'>
          <cart-title :uname='uname'></cart-title>
            # 4  父组件中接收子组件传递过来的数据 
          <cart-list :list='list' @change-num='changeNum($event)' @cart-del='delCart($event)'></cart-list>
          <cart-total :list='list'></cart-total>
        </div>
      `,
      components: {
        'cart-title': CartTitle,
        'cart-list': CartList,
        'cart-total': CartTotal
      },
      methods: {
        changeNum: function(val) {
          //4.1 根据子组件传递过来的数据,跟新list中对应的数据
          this.list.some(item=>{
            if(item.id == val.id) {
              item.num = val.num;
              // 终止遍历
              return true;
            }
          });
        },
        delCart: function(id) {
          // 根据id删除list中对应的数据
          // 1、找到id所对应数据的索引
          var index = this.list.findIndex(item=>{
            return item.id == id;
          });
          // 2、根据索引删除对应数据
          this.list.splice(index, 1);
        }
      }
    });
    var vm = new Vue({
      el: '#app',
      data: {

      }
    });
  </script>

6.5. 实现组件更新数据功能 (下)

  • 子组件通过一个标识符来标记对用的用户点击 + - 或者输入框输入的内容
  • 父组件拿到标识符更新对应的组件
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style type="text/css">
    .container {
    }
    .container .cart {
      width: 300px;
      margin: auto;
    }
    .container .title {
      background-color: lightblue;
      height: 40px;
      line-height: 40px;
      text-align: center;
      /*color: #fff;*/  
    }
    .container .total {
      background-color: #FFCE46;
      height: 50px;
      line-height: 50px;
      text-align: right;
    }
    .container .total button {
      margin: 0 10px;
      background-color: #DC4C40;
      height: 35px;
      width: 80px;
      border: 0;
    }
    .container .total span {
      color: red;
      font-weight: bold;
    }
    .container .item {
      height: 55px;
      line-height: 55px;
      position: relative;
      border-top: 1px solid #ADD8E6;
    }
    .container .item img {
      width: 45px;
      height: 45px;
      margin: 5px;
    }
    .container .item .name {
      position: absolute;
      width: 90px;
      top: 0;left: 55px;
      font-size: 16px;
    }

    .container .item .change {
      width: 100px;
      position: absolute;
      top: 0;
      right: 50px;
    }
    .container .item .change a {
      font-size: 20px;
      width: 30px;
      text-decoration:none;
      background-color: lightgray;
      vertical-align: middle;
    }
    .container .item .change .num {
      width: 40px;
      height: 25px;
    }
    .container .item .del {
      position: absolute;
      top: 0;
      right: 0px;
      width: 40px;
      text-align: center;
      font-size: 40px;
      cursor: pointer;
      color: red;
    }
    .container .item .del:hover {
      background-color: orange;
    }
  </style>
</head>
<body>
  <div id="app">
    <div class="container">
      <my-cart></my-cart>
    </div>
  </div>
  <script type="text/javascript" data-original="js/vue.js"></script>
  <script type="text/javascript">

    var CartTitle = {
      props: ['uname'],
      template: `
        <div class="title">{{uname}}的商品</div>
      `
    }
    var CartList = {
      props: ['list'],
      template: `
        <div>
          <div :key='item.id' v-for='item in list' class="item">
            <img :data-original="item.img"/>
            <div class="name">{{item.name}}</div>
            <div class="change">
              # 1\.  + - 按钮绑定事件 
              <a href="" @click.prevent='sub(item.id)'>-</a>
              <input type="text" class="num" :value='item.num' @blur='changeNum(item.id, $event)'/>
              <a href="" @click.prevent='add(item.id)'>+</a>
            </div>
            <div class="del" @click='del(item.id)'>×</div>
          </div>
        </div>
      `,
      methods: {
            delCart: function(id) {
                // 根据id删除list中对应的数据
                // 1、找到id所对应数据的索引
                var index = this.list.findIndex(item=>{
                    return item.id == id;
                });
                // 2、根据索引删除对应数据
                this.list.splice(index, 1);
            },
        changeNum: function(id, event){
          this.$emit('change-num', {
            id: id,
            type: 'change',
            num: event.target.value
          });
        },
        sub: function(id){
          # 2 数量的增加和减少通过父组件来计算   每次都是加1 和 减1 不需要传递数量   父组件需要一个类型来判断 是 加一 还是减1  以及是输入框输入的数据  我们通过type 标识符来标记 不同的操作   
          this.$emit('change-num', {
            id: id,
            type: 'sub'
          });
        },
        add: function(id){
         # 2 数量的增加和减少通过父组件来计算   每次都是加1 和 减1 不需要传递数量   父组件需要一个类型来判断 是 加一 还是减1  以及是输入框输入的数据  我们通过type 标识符来标记 不同的操作
          this.$emit('change-num', {
            id: id,
            type: 'add'
          });
        },
        del: function(id){
          // 把id传递给父组件
          this.$emit('cart-del', id);
        }
      }
    }
    var CartTotal = {
      props: ['list'],
      template: `
        <div class="total">
          <span>总价:{{total}}</span>
          <button>结算</button>
        </div>
      `,
      computed: {
        total: function() {
          // 计算商品的总价
          var t = 0;
          this.list.forEach(item => {
            t += item.price * item.num;
          });
          return t;
        }
      }
    }
    Vue.component('my-cart',{
      data: function() {
        return {
          uname: '张三',
          list: [{
            id: 1,
            name: 'TCL彩电',
            price: 1000,
            num: 1,
            img: 'img/a.jpg'
          },{
            id: 2,
            name: '机顶盒',
            price: 1000,
            num: 1,
            img: 'img/b.jpg'
          },{
            id: 3,
            name: '海尔冰箱',
            price: 1000,
            num: 1,
            img: 'img/c.jpg'
          },{
            id: 4,
            name: '小米手机',
            price: 1000,
            num: 1,
            img: 'img/d.jpg'
          },{
            id: 5,
            name: 'PPTV电视',
            price: 1000,
            num: 2,
            img: 'img/e.jpg'
          }]
        }
      },
      template: `
        <div class='cart'>
          <cart-title :uname='uname'></cart-title>  
        # 3 父组件通过事件监听   接收子组件的数据  
          <cart-list :list='list' @change-num='changeNum($event)' @cart-del='delCart($event)'></cart-list>
          <cart-total :list='list'></cart-total>
        </div>
      `,
      components: {
        'cart-title': CartTitle,
        'cart-list': CartList,
        'cart-total': CartTotal
      },
      methods: {
        changeNum: function(val) {
          #4 分为三种情况:输入框变更、加号变更、减号变更
          if(val.type=='change') {
            // 根据子组件传递过来的数据,跟新list中对应的数据
            this.list.some(item=>{
              if(item.id == val.id) {
                item.num = val.num;
                // 终止遍历
                return true;
              }
            });
          }else if(val.type=='sub'){
            // 减一操作
            this.list.some(item=>{
              if(item.id == val.id) {
                item.num -= 1;
                // 终止遍历
                return true;
              }
            });
          }else if(val.type=='add'){
            // 加一操作
            this.list.some(item=>{
              if(item.id == val.id) {
                item.num += 1;
                // 终止遍历
                return true;
              }
            });
          }
        }
      }
    });
    var vm = new Vue({
      el: '#app',
      data: {

      }
    });
  </script>
</body>
</html>
查看原文

赞 0 收藏 0 评论 0

豆约翰 发布了文章 · 2020-10-23

02.Vue的常用特性

一、表单基本操作

都是通过v-model

  • 单选框
1、 两个单选框需要同时通过v-model 双向绑定 一个值 
2、 每一个单选框必须要有value属性  且value值不能一样 
3、 当某一个单选框选中的时候 v-model  会将当前的 value值 改变 data 中的 数据

gender 的值就是选中的值,我们只需要实时监控他的值就可以了

<input type="radio" id="male" value="1" v-model='gender'>
<label for="male">男</label>
<input type="radio" id="female" value="2" v-model='gender'>
<label for="female">女</label>

<script>
    new  Vue({
        el: '#app',
        data: {
            // 默认会让当前的 value 值为 2 的单选框选中
            gender: 1,
        },
    })
</script>
  • 复选框

    • 复选框 checkbox 这种的组合时 data 中的 hobby 我们要定义成数组 否则无法实现多选
    1、 复选框需要同时通过v-model 双向绑定 一个值 
    2、 每一个复选框必须要有value属性  且value值不能一样 
    3、 当某一个单选框选中的时候 v-model  会将当前的 value值 改变 data 中的 数据
    hobby 的值就是选中的值,我们只需要实时监控他的值就可以了
    
    <div>
       <span>爱好:</span>
       <input type="checkbox" id="ball" value="1" v-model='hobby'>
       <label for="ball">篮球</label>
       <input type="checkbox" id="sing" value="2" v-model='hobby'>
       <label for="sing">唱歌</label>
       <input type="checkbox" id="code" value="3" v-model='hobby'>
       <label for="code">写代码</label>
    </div>
    <script>
        new Vue({
             data: {
                 // 默认会让当前的 value 值为 2 和 3 的复选框选中
                 hobby: ['2', '3'],
             },
        })
    </script>
    
  • 下拉框和文本框

       <div>
          <span>职业:</span>
           <!--
              1、 需要给select  通过v-model 双向绑定 一个值 
              2、 每一个option  必须要有value属性  且value 值不能一样 
              3、 当某一个option选中的时候 v-model  会将当前的 value值 改变 data 中的 数据
               occupation 的值就是选中的值,我们只需要实时监控他的值就可以了
          -->
           <!-- multiple  多选 -->
          <select v-model='occupation' multiple>
              <option value="0">请选择职业...</option>
              <option value="1">教师</option>
              <option value="2">软件工程师</option>
              <option value="3">律师</option>
          </select>
          <!-- textarea 是 一个双标签   不需要绑定value 属性的  -->
          <textarea v-model='desc'></textarea>
      </div>
    <script>
        new Vue({
             data: {
                 // 默认会让当前的 value 值为 2 和 3 的下拉框选中
                 occupation: ['2', '3'],
                 desc: 'nihao'
             },
        })
    </script>
    
表单域修饰符
  • .number 将值转换为数值类型

    • 如过输入以数字开头的值,后面有夹杂非数组形式的字符串(如字母中文和其他符号)的话,属性值得到则是第一个非数字部分前面的所有数字。
    • 如果输入的值以字符串开头,则无法转换成数字。
  • .trim 自动过滤用户输入的首尾空白字符

    • 只能去掉首尾的,不能去除中间的空格
  • .lazy 将input事件切换成change事件

    • .lazy修饰符延迟了同步更新属性值的时机。将v-model原本绑定在 input 事件的同步逻辑转变为绑定在 change 事件上。
    • 在失去焦点 或者 按下回车键时才更新
    <!-- 自动将用户的输入值转为数值类型 -->
    <input v-model.number="age" type="number">
    
    <!--自动过滤用户输入的首尾空白字符   -->
    <input v-model.trim="msg">
    
    <!-- 在“change”时而非“input”时更新 -->
    <input v-model.lazy="msg" >
    

二、自定义指令

  • 内置指令不能满足我们特殊的需求
  • Vue允许我们自定义指令
Vue.directive 注册全局指令
<!-- 
  使用自定义的指令,只需在对用的元素中,加上'v-'的前缀形成类似于内部指令'v-if','v-text'的形式。 
-->
<input type="text" v-focus>
<script>
// 注意点: 
//   1、 在自定义指令中  如果以驼峰命名的方式定义 如  Vue.directive('focusA',function(){}) 
//   2、 在HTML中使用的时候 只能通过 v-focus-a 来使用 

// 注册一个全局自定义指令 v-focus
Vue.directive('focus', {
    // 当绑定元素插入到 DOM 中。 其中 el为dom元素
    inserted: function (el) {
        // 聚焦元素
        el.focus();
    }
});
new Vue({
  el:'#app'
});
</script>
Vue.directive 注册全局指令 带参数

binding对象的name属性对应自定义指令的名称,value属性对应自定义指令的值

 <input type="text" v-color='msg'>
 <script type="text/javascript">
    /*
      自定义指令-带参数
      bind - 只调用一次,在指令第一次绑定到元素上时候调用
    */
    Vue.directive('color', {
      // bind声明周期, 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
      // el 为当前自定义指令的DOM元素  
      // binding 为自定义的函数形参   通过自定义属性传递过来的值 存在 binding.value 里面
      bind: function(el, binding){
        // 根据指令的参数设置背景色
        // console.log(binding.value.color)
        el.style.backgroundColor = binding.value.color;
      }
    });
    var vm = new Vue({
      el: '#app',
      data: {
        msg: {
          color: 'blue'
        }
      }
    });
  </script>
自定义指令局部指令
  • 局部指令,需要定义在 directives 的选项 用法和全局用法一样
  • 局部指令只能在当前组件里面使用
  • 当全局指令和局部指令同名时以局部指令为准
<input type="text" v-color='msg'>
 <input type="text" v-focus>
 <script type="text/javascript">
    /*
      自定义指令-局部指令
    */
    var vm = new Vue({
      el: '#app',
      data: {
        msg: {
          color: 'red'
        }
      },
      //局部指令,需要定义在  directives 的选项
      directives: {
        color: {
          bind: function(el, binding){
            el.style.backgroundColor = binding.value.color;
          }
        },
        focus: {
          inserted: function(el) {
            el.focus();
          }
        }
      }
    });
  </script>

三、计算属性 computed

  • 模板中放入太多的逻辑会让模板过重且难以维护,使用计算属性可以让模板更加的简洁
computed: {
    reversedMessage: function () {
        return this.msg.split('').reverse().join('') 
    }
}
  • 计算属性是基于它们的响应式依赖(data)进行缓存的,只要data中的数据(我们使用的值)没有发生变化,就不会再进行一次计算。而用方法实现的化是调用一次就执行一次。因此有时使用计算属性相对耗时的计算可以节省一些性能。
  • computed比较适合对多个变量或者对象进行处理后返回一个结果值,只有当多个变量中的某一个值发生了变化,我们监控的返回值才就会发生变化(才会再次进行计算)
 <div id="app">
     <!--  
        当多次调用 reverseString  的时候 
        只要里面的 num 值不改变 他会把第一次计算的结果直接返回
        直到data 中的num值改变 计算属性才会重新发生计算
     -->
    <div>{{reverseString}}</div>
    <div>{{reverseString}}</div>
     <!-- 调用methods中的方法的时候  他每次会重新调用 -->
    <div>{{reverseMessage()}}</div>
    <div>{{reverseMessage()}}</div>
  </div>
  <script type="text/javascript">
    /*
      计算属性与方法的区别:计算属性是基于依赖进行缓存的,而方法不缓存
    */
    var vm = new Vue({
      el: '#app',
      data: {
        msg: 'Nihao',
        num: 100
      },
      methods: {
        reverseMessage: function(){
          console.log('methods')
          return this.msg.split('').reverse().join('');
        }
      },
      //computed  属性 定义 和 data 已经 methods 平级 
      computed: {
        //  reverseString   这个是我们自己定义的名字 
        reverseString: function(){
          console.log('computed')
          var total = 0;
          //  当data 中的 num 的值改变的时候  reverseString  会自动发生计算  
          for(var i=0;i<=this.num;i++){
            total += i;
          }
          // 这里一定要有return 否则 调用 reverseString 的 时候无法拿到结果    
          return total;
        }
      }
    });
  </script>

四、侦听器 watch

image.png

  • 使用watch来响应数据的变化
  • 数据变化时执行异步或开销较大的操作
  • watch 中的属性 一定是data 中 已经存在的数据
  • 当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听
 <div id="app">
     <div>
         <span>名:</span>
         <span>
             <input type="text" v-model='firstName'>
         </span>
     </div>
     <div>
         <span>姓:</span>
         <span>
             <input type="text" v-model='lastName'>
         </span>
      </div>
      <div>{{fullName}}</div>
  </div>

  <script type="text/javascript">
        /* 侦听器 */
        var vm = new Vue({
            el: '#app',
            data: {
                firstName: 'Jim',
                lastName: 'Green',
                // fullName: 'Jim Green'
            },
             //watch  属性 定义 和 data 已经 methods 平级 
            watch: {
                //   注意:  这里firstName  对应着data 中的 firstName 
                //   当 firstName 值 改变的时候  会自动触发 watch
                firstName: function(val) {
                    this.fullName = val + ' ' + this.lastName;
                },
                //   注意:  这里 lastName 对应着data 中的 lastName 
                lastName: function(val) {
                    this.fullName = this.firstName + ' ' + val;
                }
            }
        });
    </script>

五、过滤器

  • Vue.js允许自定义过滤器,可被用于一些常见的文本格式化。
  • 过滤器可以用在两个地方:双花括号插值和v-bind表达式。
  • 过滤器应该被添加在JavaScript表达式的尾部,由“管道”符号指示
  • 支持级联操作
  • 过滤器不改变真正的data,而只改变渲染的结果,并返回过滤后的版本
  • 全局注册时是filter,没有s的。而局部过滤器是filters,是有s的

5.1 自定义过滤器

Vue.filter(‘过滤器名称’, function(value){ // 过滤器业务逻辑 })

5.2 过滤器的使用

<div>{{msg | upper}}</div>
<div>{{msg | upper | lower}}</div>
<div v-bind:id=“id | formatId"></div>

5.3 局部过滤器

filters:{ capitalize: function(){} }

<div id="app">
    <input type="text" v-model='msg'>
      <!-- upper 被定义为接收单个参数的过滤器函数,表达式  msg  的值将作为参数传入到函数中 -->
    <div>{{msg | upper}}</div>
    <!--  
      支持级联操作
      upper  被定义为接收单个参数的过滤器函数,表达式msg 的值将作为参数传入到函数中。
      然后继续调用同样被定义为接收单个参数的过滤器 lower ,将upper 的结果传递到lower中
    -->
    <div>{{msg | upper | lower}}</div>
    <div :abc='msg | upper'>测试数据</div>
</div>

<script type="text/javascript">
   //  lower  为全局过滤器     
   Vue.filter('lower', function(val) {
      return val.charAt(0).toLowerCase() + val.slice(1);
    });
    var vm = new Vue({
      el: '#app',
      data: {
        msg: ''
      },
       //filters  属性 定义 和 data 已经 methods 平级 
       //  定义filters 中的过滤器为局部过滤器 
      filters: {
        //   upper  自定义的过滤器名字 
        //    upper 被定义为接收单个参数的过滤器函数,表达式  msg  的值将作为参数传入到函数中
        upper: function(val) {
         //  过滤器中一定要有返回值 这样外界使用过滤器的时候才能拿到结果
          return val.charAt(0).toUpperCase() + val.slice(1);
        }
      }
    });
</script>

5.4 过滤器中传递参数

定义

Vue.filter(‘format’, function(value, arg){ 
    // arg就是过滤器传递过来的参数
})

使用
<div>{{date | format(‘yyyy-MM-dd')}}</div>

    <div id="box">
        <!--
            filterA 被定义为接收三个参数的过滤器函数。
            其中 message 的值作为第一个参数,
            普通字符串 'arg1' 作为第二个参数,表达式 arg2 的值作为第三个参数。
        -->
        {{ message | filterA('arg1', 'arg2') }}
    </div>
    <script>
        // 在过滤器中 第一个参数 对应的是  管道符前面的数据   n  此时对应 message
        // 第2个参数  a 对应 实参  arg1 字符串
        // 第3个参数  b 对应 实参  arg2 字符串
        Vue.filter('filterA',function(n,a,b){
            if(n<10){
                return n+a;
            }else{
                return n+b;
            }
        });

        new Vue({
            el:"#box",
            data:{
                message: "哈哈哈"
            }
        })

    </script>

六、生命周期

  • 事物从出生到死亡的过程
  • Vue实例从创建 到销毁的过程 ,这些过程中会伴随着一些函数的自调用。我们称这些函数为钩子函数

6.1. 主要阶段

  • 挂载(初始化相关属性)
    1 beforeCreate
    2 created
    3 beforeMount 4 mounted
  • 更新(元素或组件的变更操作)
    1 beforeUpdate
    2 updated
  • 销毁(销毁相关属性)
    1 beforeDestroy
    2 destroyed
常用的钩子函数
beforeCreate在实例初始化之后,数据观测和事件配置之前被调用。此时data 和 methods 以及页面的DOM结构都没有初始化 什么都做不了
created在实例创建完成后被立即调用此时data 和 methods已经可以使用 但是页面还没有渲染出来
beforeMount在挂载开始之前被调用 此时页面上还看不到真实数据 只是一个模板页面而已
mountedel被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子。 数据已经真实渲染到页面上,在这个钩子函数里面我们可以使用一些第三方的插件
beforeUpdate数据更新时调用,发生在虚拟DOM打补丁之前。页面上数据还是旧的
updated由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。 页面上数据已经替换成最新的
beforeDestroy实例销毁之前调用
destroyed实例销毁后调用
数组变异方法
  • 在 Vue 中,直接修改对象属性的值无法触发响应式。当你直接修改了对象属性的值,你会发现,只有数据改了,但是页面内容并没有改变
  • 变异数组方法即保持数组方法原有功能不变的前提下对其进行功能拓展
push()往数组最后面添加一个元素,成功返回当前数组的长度
pop()删除数组的最后一个元素,成功返回删除元素的值
shift()删除数组的第一个元素,成功返回删除元素的值
unshift()往数组最前面添加一个元素,成功返回当前数组的长度
splice()有三个参数,第一个是想要删除的元素的下标(必选),第二个是想要删除的个数(必选),第三个是删除 后想要在原位置替换的值
sort()sort() 使数组按照字符编码默认从小到大排序,成功返回排序后的数组
reverse()reverse() 将数组倒序,成功返回倒序后的数组
替换数组
  • 不会改变原始数组,但总是返回一个新数组
filterfilter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
concatconcat() 方法用于连接两个或多个数组。该方法不会改变现有的数组
sliceslice() 方法可从已有的数组中返回选定的元素。该方法并不会修改数组,而是返回一个子数组
动态数组响应式数据

触发视图重新更新一遍,让数据动态起来
Vue.set(vm.items, indexOfItem, newValue)
vm.$set(vm.items, indexOfItem, newValue)
1 参数一表示要处理的数组名称
2 参数二表示要处理的数组的索引
3 参数三表示要处理的数组的值

        var vm = new Vue({
            el: '#app',
            data: {
                list: ['apple', 'orange', 'banana'],
                info: {
                    name: 'lisi',
                    age: 12
                }
            },
        });
        // vm.list[1] = 'lemon'; // 无法触发动态修改数据
        // Vue.set(vm.list, 2, 'lemon');
        vm.$set(vm.list, 1, 'lemon');
图书列表案例
  • 静态列表效果
  • 基于数据实现模板效果
  • 处理每行的操作按钮
1.提供的静态数据
  • 数据存放在vue 中 data 属性中
 var vm = new Vue({
      el: '#app',
      data: {
        books: [{
          id: 1,
          name: '三国演义',
          date: ''
        },{
          id: 2,
          name: '水浒传',
          date: ''
        },{
          id: 3,
          name: '红楼梦',
          date: ''
        },{
          id: 4,
          name: '西游记',
          date: ''
        }]
      }
    }); 
2.把提供好的数据渲染到页面上
  • 利用 v-for循环 遍历 books 将每一项数据渲染到对应的数据中
 <tbody>
    <tr :key='item.id' v-for='item in books'>
       <!-- 对应的id 渲染到页面上 -->
       <td>{{item.id}}</td>
        <!-- 对应的name 渲染到页面上 -->
       <td>{{item.name}}</td>
       <td>{{item.date}}</td>
       <td>
         <!-- 阻止 a 标签的默认跳转 -->
         <a href="" @click.prevent>修改</a>
         <span>|</span>
          <a href="" @click.prevent>删除</a>
       </td>
     </tr>
</tbody>
3.添加图书
  • 通过双向绑定获取到输入框中的输入内容
  • 给按钮添加点击事件
  • 把输入框中的数据存储到 data 中的 books 里面
<div>
  <h1>图书管理</h1>
  <div class="book">
       <div>
         <label for="id">
           编号:
         </label>
          <!-- 3.1、通过双向绑定获取到输入框中的输入的 id  -->
         <input type="text" id="id" v-model='id'>
         <label for="name">
           名称:
         </label>
           <!-- 3.2、通过双向绑定获取到输入框中的输入的 name  -->
         <input type="text" id="name" v-model='name'>
            <!-- 3.3、给按钮添加点击事件  -->
         <button @click='handle'>提交</button>
       </div>
  </div>
</div>
  <script type="text/javascript">
    /*
      图书管理-添加图书
    */
    var vm = new Vue({
      el: '#app',
      data: {
        id: '',
        name: '',
        books: [{
          id: 1,
          name: '三国演义',
          date: ''
        },{
          id: 2,
          name: '水浒传',
          date: ''
        },{
          id: 3,
          name: '红楼梦',
          date: ''
        },{
          id: 4,
          name: '西游记',
          date: ''
        }]
      },
      methods: {
        handle: function(){
          // 3.4 定义一个新的对象book 存储 获取到输入框中 书 的id和名字 
          var book = {};
          book.id = this.id;
          book.name = this.name;
          book.date = '';
         // 3.5 把book  通过数组的变异方法 push 放到    books 里面
          this.books.push(book);
          //3.6 清空输入框
          this.id = '';
          this.name = '';
        }
      }
    });
  </script>
4.修改图书-上
  • 点击修改按钮的时候 获取到要修改的书籍名单

    • 4.1 给修改按钮添加点击事件, 需要把当前的图书的id 传递过去 这样才知道需要修改的是哪一本书籍
  • 把需要修改的书籍名单填充到表单里面

    • 4.2 根据传递过来的id 查出books 中 对应书籍的详细信息
    • 4.3 把获取到的信息填充到表单
 <div id="app">
    <div class="grid">
      <div>
        <h1>图书管理</h1>
        <div class="book">
          <div>
            <label for="id">
              编号:
            </label>
            <input type="text" id="id" v-model='id' :disabled="flag">
            <label for="name">
              名称:
            </label>
            <input type="text" id="name" v-model='name'>
            <button @click='handle'>提交</button>
          </div>
        </div>
      </div>
      <table>
        <thead>
          <tr>
            <th>编号</th>
            <th>名称</th>
            <th>时间</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          <tr :key='item.id' v-for='item in books'>
            <td>{{item.id}}</td>
            <td>{{item.name}}</td>
            <td>{{item.date}}</td>
            <td>
              <!--- 
                4.1  给修改按钮添加点击事件,  需要把当前的图书的id 传递过去 
                这样才知道需要修改的是哪一本书籍
                --->  
              <a href="" @click.prevent='toEdit(item.id)'>修改</a>
              <span>|</span>
              <a href="" @click.prevent>删除</a>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
 <script type="text/javascript">
    /*
      图书管理-添加图书
    */
    var vm = new Vue({
      el: '#app',
      data: {
        flag: false,
        id: '',
        name: '',
        books: [{
          id: 1,
          name: '三国演义',
          date: ''
        },{
          id: 2,
          name: '水浒传',
          date: ''
        },{
          id: 3,
          name: '红楼梦',
          date: ''
        },{
          id: 4,
          name: '西游记',
          date: ''
        }]
      },
      methods: {
        handle: function(){
          // 3.4 定义一个新的对象book 存储 获取到输入框中 书 的id和名字 
          var book = {};
          book.id = this.id;
          book.name = this.name;
          book.date = '';
         // 3.5 把book  通过数组的变异方法 push 放到    books 里面
          this.books.push(book);
          //3.6 清空输入框
          this.id = '';
          this.name = '';
        },
        toEdit: function(id){
          console.log(id)
          //4.2  根据传递过来的id 查出books 中 对应书籍的详细信息
          var book = this.books.filter(function(item){
            return item.id == id;
          });
          console.log(book)
          //4.3 把获取到的信息填充到表单
          // this.id   和  this.name 通过双向绑定 绑定到了表单中  一旦数据改变视图自动更新
          this.id = book[0].id;
          this.name = book[0].name;
        }
      }
    });
  </script>
5. 修改图书-下
  • 5.1 定义一个标识符, 主要是控制 编辑状态下当前编辑书籍的id 不能被修改 即 处于编辑状态下 当前控制书籍编号的输入框禁用
  • 5.2 通过属性绑定给书籍编号的 绑定 disabled 的属性 flag 为 true 即为禁用
  • 5.3 flag 默认值为false 处于编辑状态 要把 flag 改为true 即当前表单为禁用
  • 5.4 复用添加方法 用户点击提交的时候依然执行 handle 中的逻辑如果 flag为true 即 表单处于不可输入状态 此时执行的用户编辑数据数据
<div id="app">
    <div class="grid">
      <div>
        <h1>图书管理</h1>
        <div class="book">
          <div>
            <label for="id">
              编号:
            </label>
              <!-- 5.2 通过属性绑定 绑定 disabled 的属性  flag 为 true 即为禁用      -->
            <input type="text" id="id" v-model='id' :disabled="flag">
            <label for="name">
              名称:
            </label>
            <input type="text" id="name" v-model='name'>
            <button @click='handle'>提交</button>
          </div>
        </div>
      </div>
      <table>
        <thead>
          <tr>
            <th>编号</th>
            <th>名称</th>
            <th>时间</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          <tr :key='item.id' v-for='item in books'>
            <td>{{item.id}}</td>
            <td>{{item.name}}</td>
            <td>{{item.date}}</td>
            <td>
              <a href="" @click.prevent='toEdit(item.id)'>修改</a>
              <span>|</span>
              <a href="" @click.prevent>删除</a>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>   
<script type="text/javascript">
        /*图书管理-添加图书*/
    var vm = new Vue({
        el: '#app',
        data: {
            flag: false,
            id: '',
            name: '',
            books: [{
                id: 1,
                name: '三国演义',
                date: ''
            },{
                id: 2,
                name: '水浒传',
                date: ''
            },{
                id: 3,
                name: '红楼梦',
                date: ''
            },{
                id: 4,
                name: '西游记',
                date: ''
            }]
        },
        methods: {
            handle: function() {
                /*
                  5.4  复用添加方法   用户点击提交的时候依然执行 handle 中的逻辑
                      如果 flag为true 即 表单处于不可输入状态 此时执行的用户编辑数据数据
                */
                if (this.flag) {
                    // 编辑图书
                    // 5.5  根据当前的ID去更新数组中对应的数据
                    this.books.some((item) => {
                        if (item.id == this.id) {
                            // 箭头函数中 this 指向父级作用域的this
                            item.name = this.name;
                            // 完成更新操作之后,需要终止循环
                            return true;
                        }
                    });
                    // 5.6 编辑完数据后表单要处以可以输入的状态
                    this.flag = false;
                    //  5.7  如果 flag为false  表单处于输入状态 此时执行的用户添加数据
                } else {
                    var book = {};
                    book.id = this.id;
                    book.name = this.name;
                    book.date = '';
                    this.books.push(book);
                    // 清空表单
                    this.id = '';
                    this.name = '';
                }
                // 清空表单
                this.id = '';
                this.name = '';
            },
            toEdit: function(id) {
                /*
                5.3  flag 默认值为false   处于编辑状态 要把 flag 改为true 即当前表单为禁                      用
                */
                this.flag = true;
                console.log(id)
                var book = this.books.filter(function(item) {
                    return item.id == id;
                });
                console.log(book)
                this.id = book[0].id;
                this.name = book[0].name;
            }
        }
    });
    </script>
6.删除图书
  • 6.1 给删除按钮添加事件 把当前需要删除的书籍id 传递过来
  • 6.2 根据id从数组中查找元素的索引
  • 6.3 根据索引删除数组元素
  <tbody>
          <tr :key='item.id' v-for='item in books'>
            <td>{{item.id}}</td>
            <td>{{item.name}}</td>
            <td>{{item.date}}</td>
            <td>
              <a href="" @click.prevent='toEdit(item.id)'>修改</a>
              <span>|</span>
               <!--  6.1 给删除按钮添加事件 把当前需要删除的书籍id 传递过来  --> 
              <a href="" @click.prevent='deleteBook(item.id)'>删除</a>
            </td>
          </tr>
</tbody>
  <script type="text/javascript">
    /*
      图书管理-添加图书
    */
    var vm = new Vue({
      methods: {
        deleteBook: function(id){
          // 删除图书
          #// 6.2 根据id从数组中查找元素的索引
          // var index = this.books.findIndex(function(item){
          //   return item.id == id;
          // });
          #// 6.3 根据索引删除数组元素
          // this.books.splice(index, 1);
          // -------------------------
         #// 方法二:通过filter方法进行删除

          # 6.4  根据filter 方法 过滤出来id 不是要删除书籍的id 
          # 因为 filter 是替换数组不会修改原始数据 所以需要 把 不是要删除书籍的id  赋值给 books 
          this.books = this.books.filter(function(item){
            return item.id != id;
          });
        }
      }
    });
  </script>

七、常用特性应用场景

7.1 过滤器

  • Vue.filter 定义一个全局过滤器
 <tr :key='item.id' v-for='item in books'>
            <td>{{item.id}}</td>
            <td>{{item.name}}</td>
            <!-- 1.3  调用过滤器 -->
            <td>{{item.date | dateFormat}}</td>
            <td>
              <a href="" @click.prevent='toEdit(item.id)'>修改</a>
              <span>|</span>
              <a href="" @click.prevent='deleteBook(item.id)'>删除</a>
            </td>
</tr>
<script>
        #1.1  Vue.filter  定义一个全局过滤器
   Vue.filter('dateFormat', (dataStr) => {
        console.log(dataStr)
        var time = new Date(dataStr);
        console.log(time)
        function timeAdd0(str) {
            if (str < 10) {
                str = '0' + str;
            }
            return str
        }
        var y = time.getFullYear();
        var m = time.getMonth() + 1;
        var d = time.getDate();
        var h = time.getHours();
        var mm = time.getMinutes();
        var s = time.getSeconds();
        return y + '-' + timeAdd0(m) + '-' + timeAdd0(d) + ' ' + timeAdd0(h) + ':' + timeAdd0(mm) + ':' + timeAdd0(s);
    })
#1.2  提供的数据 包含一个时间戳   为毫秒数
   [{
          id: 1,
          name: '三国演义',
          date: 2525609975000
        },{
          id: 2,
          name: '水浒传',
          date: 2525609975000
        },{
          id: 3,
          name: '红楼梦',
          date: 2525609975000
        },{
          id: 4,
          name: '西游记',
          date: 2525609975000
        }];
</script>

7.2 自定义指令

  • 让表单自动获取焦点
  • 通过Vue.directive 自定义指定
<!-- 2.2  通过v-自定义属性名 调用自定义指令 -->
<input type="text" id="id" v-model='id' :disabled="flag" v-focus>

<script>
    # 2.1   通过Vue.directive 自定义指定
    Vue.directive('focus', {
      inserted: function (el) {
        el.focus();
      }
    });
</script>

7.3 计算属性

  • 通过计算属性计算图书的总数

    • 图书的总数就是计算数组的长度
  <div class="total">
      <span>图书总数:</span>
      <!-- 3.2 在页面上 展示出来 -->
      <span>{{total}}</span>
  </div>

  <script type="text/javascript">
    /*计算属性与方法的区别:计算属性是基于依赖进行缓存的,而方法不缓存*/
    var vm = new Vue({
      data: {
        flag: false,
        submitFlag: false,
        id: '',
        name: '',
        books: []
      },
      computed: {
        total: function(){
          // 3.1  计算图书的总数
          return this.books.length;
        }
      },
    });
  </script>

7.4 侦听器

当添加图书时,输入的书名已存在,则提交按钮禁用


book.date = new Date().getTime();

<button @click='handle' :disabled="submitFlag">提交</button>
<script>
  var vm = new Vue({
      el: '#app',
      data: {   
        submitFlag: false,
        id: '',
        name: '',
        books: []
      },
      watch: {
        name: function(val) {
          // 验证图书名称是否已经存在
          var flag = this.books.some(function(item){
            return item.name == val;
          });
          if(flag) {
            // 图书名称存在
            this.submitFlag = true;
          }else{
            // 图书名称不存在
            this.submitFlag = false;
          }
        }
      },
  });
</script>

7.5 生命周期

mounted生命周期钩子函数被触发的时候,模板已经可以使用
一般此时用于获取后台数据,然后把数据填充到模板

    mounted: function(){
        var data = [{
          id: 1,
          name: '三国演义',
          date: 2525609975000
        },{
          id: 2,
          name: '水浒传',
          date: 2525609975000
        },{
          id: 3,
          name: '红楼梦',
          date: 2525609975000
        },{
          id: 4,
          name: '西游记',
          date: 2525609975000
        }];
        this.books = data;
      }
查看原文

赞 0 收藏 0 评论 0

豆约翰 发布了文章 · 2020-10-23

01.初识vue.js

一、Vue概述

  • Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架
  • vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合
    ⚫易用:熟悉HTML、CSS、JavaScript知识后,可快速上手Vue
    ⚫灵活:在一个库和一套完整框架之间自如伸缩
    ⚫高效:20kB运行大小,超快虚拟 DOM

二、Vue基本使用

使用Vue将helloworld 渲染到页面上

image.png

  1. 实例参数分析
  • el: 元素的挂载位置(值可以是CSS选择器或者DOM元素)
  • data:模型数据(值是一个对象)
  1. 插值表达式用法
  • 将数据填充到HTML标签中
  • 插值表达式支持基本的计算操作

三、Vue模板语法

3.1 指令

  • 本质就是自定义属性
  • Vue中指定都是以 v- 开头
3.1.1 v-cloak
  • 防止页面加载时出现闪烁问题

     <style type="text/css">
      /* 
        1、通过属性选择器 选择到 带有属性 v-cloak的标签  让他隐藏
     */
      [v-cloak]{
        /* 元素隐藏    */
        display: none;
      }
      </style>
    <body>
      <div id="app">
        <!-- 2、 让带有插值 语法的   添加 v-cloak 属性 
             在 数据渲染完场之后,v-cloak 属性会被自动去除,
             v-cloak一旦移除也就是没有这个属性了  属性选择器就选择不到该标签
           也就是对应的标签会变为可见
        -->
        <div  v-cloak>{{msg}}</div>
      </div>
      <script type="text/javascript" data-original="js/vue.js"></script>
      <script type="text/javascript">
        var vm = new Vue({
          //  el   指定元素 id 是 app 的元素  
          el: '#app',
          //  data  里面存储的是数据
          data: {
            msg: 'Hello Vue'
          }
        });
    </script>
    </body>
    </html>
    
3.1.2 数据绑定指令
v-text(填充纯文本,相比插值表达式更加简洁)
  • v-text指令用于将数据填充到标签中,作用于插值表达式类似,但是没有闪动问题
  • 如果数据中有HTML标签会将html标签一并输出
  • 注意:此处为单向绑定,数据对象上的值改变,插值会发生变化;但是当插值发生变化并不会影响数据对象的值
<div id="app">
    <!--  
        注意:在指令中不要写插值语法  直接写对应的变量名称 
        在 v-text 中 赋值的时候不要在写 插值语法
        一般属性中不加 {{}}  直接写 对应 的数据名 
    -->
    <p v-text="msg"></p>
    <p>
        <!-- Vue  中只有在标签的 内容中 才用插值语法 -->
        {{msg}}
    </p>
</div>

<script>
    new Vue({
        el: '#app',
        data: {
            msg: 'Hello Vue.js'
        }
    });

</script>
v-html
  • 用法和v-text 相似 但是他可以将HTML片段填充到标签中
  • 可能有安全问题, 一般只在可信任内容上使用 v-html永不用在用户提交的内容上
  • 本网站内部数据可以使用,来自第三方(跨域)的数据不可以用
  • 它与v-text区别在于v-text输出的是纯文本,浏览器不会对其再进行html解析,但v-html会将其当html标签解析后输出。

    <div id="app">
      <p v-html="html"></p> <!-- 输出:html标签在渲染的时候被解析 -->
    
        <p>{{message}}</p> <!-- 输出:<span>通过双括号绑定</span> -->
    
      <p v-text="text"></p> <!-- 输出:<span>html标签在渲染的时候被源码输出</span> -->
    </div>
    <script>
      let app = new Vue({
      el: "#app",
      data: {
        message: "<span>通过双括号绑定</span>",
        html: "<span>html标签在渲染的时候被解析</span>",
        text: "<span>html标签在渲染的时候被源码输出</span>",
      }
     });
    </script>
    
v-pre
  • 显示原始信息跳过编译过程
  • 跳过这个元素和它的子元素的编译过程。
  • 一些静态的内容不需要编译加这个指令可以加快渲染
    <span v-pre>{{ this will not be compiled }}</span>    
    <!--  显示的是{{ this will not be compiled }}  -->
    <span v-pre>{{msg}}</span>  
     <!--   即使data里面定义了msg这里仍然是显示的{{msg}}  -->
<script>
    new Vue({
        el: '#app',
        data: {
            msg: 'Hello Vue.js'
        }
    });

</script>
3.1.3 数据响应式
v-once
  • 执行一次性的插值【当数据改变时,插值处的内容不会继续更新】
  <!-- 即使data里面定义了msg 后期我们修改了 仍然显示的是第一次data里面存储的数据即 Hello Vue.js  -->
     <span v-once>{{ msg}}</span>    
<script>
    let vm = new Vue({
        el: '#app',
        data: {
            msg: 'Hello Vue.js'
        }
    });
    //在控制台修改vm.msg的值试试
</script>

3.2 双向数据绑定

  • 当数据发生变化的时候,视图也就发生变化
  • 当视图发生变化的时候,数据也会跟着同步变化

v-model

  • v-model是一个指令,限制在 <input>、<select>、<textarea>、components中使用
 <div id="app">
      <div>{{msg}}</div>
      <div>
          当输入框中内容改变的时候,  页面上的msg  会自动更新
        <input type="text" v-model='msg'>
      </div>
  </div>

mvvm

  • MVC 是后端的分层开发概念; MVVM是前端视图层的概念,主要关注于 视图层分离,也就是说:MVVM把前端的视图层,分为了 三部分 Model, View , VM ViewModel
  • m model

    • 数据层 Vue 中 数据层 都放在 data 里面
  • v view 视图

    • Vue 中 view 即 我们的HTML页面
  • vm (view-model) 控制器 将数据和视图层建立联系

    • vm 即 Vue 的实例 就是 vm

image.png

3.3 事件绑定

v-on
  • 用来绑定事件的
  • 形式如:v-on:click 缩写为 @click;

image

v-on事件函数中传入参数

<body>
    <div id="app">
        <div>{{num}}</div>
        <div>
            <!-- 如果事件直接绑定函数名称,那么默认会传递事件对象作为事件函数的第一个参数 -->
            <button v-on:click='handle1'>点击1</button>
            <!-- 2、如果事件绑定函数调用,那么事件对象必须作为最后一个参数显示传递,
                 并且事件对象的名称必须是$event 
            -->
            <button v-on:click='handle2(123, 456, $event)'>点击2</button>
        </div>
    </div>
    <script type="text/javascript" data-original="js/vue.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#app',
            data: {
                num: 0
            },
            methods: {
                handle1: function(event) {
                    console.log(event.target.innerHTML)
                },
                handle2: function(p, p1, event) {
                    console.log(p, p1)
                    console.log(event.target.innerHTML)
                    this.num++;
                }
            }
        });
    </script>
事件修饰符
  • 在事件处理程序中调用 event.preventDefault()event.stopPropagation() 是非常常见的需求。
  • Vue 不推荐我们操作DOM 为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符
  • 修饰符是由点开头的指令后缀来表示的
阻止单击事件继续传播(冒泡) 
<a v-on:click.stop="doThis"></a>

提交事件不再重载页面
<form v-on:submit.prevent="onSubmit"></form>

修饰符可以串联   即阻止冒泡也阻止默认事件
<a v-on:click.stop.prevent="doThat"></a>

只有当在 event.target (点击的)是当前元素自身时才会触发处理函数
即事件不是从内部元素触发冒泡上来的
<div v-on:click.self="doThat">...</div>


按键修饰符
  • 在做项目中有时会用到键盘事件,在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符
<!-- 只有在 `keyCode` 是 13 时调用 `vm.submit()` -->
<input v-on:keyup.13="submit">

<!-- -当点击enter 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">

<!--当点击enter或者space时  时调用 `vm.alertMe()`   -->
<input type="text" v-on:keyup.enter.space="alertMe" >

常用的按键修饰符
.enter =>    enter键
.tab => tab键
.delete (捕获“删除”和“退格”按键) =>  删除键
.esc => 取消键
.space =>  空格键
.up =>  上
.down =>  下
.left =>  左
.right =>  右

<script>
    var vm = new Vue({
        el:"#app",
        methods: {
              submit:function(){},
              alertMe:function(){},
        }
    })

</script>
自定义按键修饰符别名
  • 在Vue中可以通过config.keyCodes自定义按键修饰符别名
<div id="app">
    预先定义了keycode 116(即F5)的别名为f5,因此在文字输入框中按下F5,会触发prompt方法
    其实直接用树值,如写v-on:keydown.116="prompt()",效果是一致的,但是为按键自定义一个名称比较直观和语义化
    <input type="text" v-on:keydown.f5="prompt()">
</div>

<script>

    Vue.config.keyCodes.f5 = 116;

    let app = new Vue({
        el: '#app',
        methods: {
            prompt: function() {
                alert('我是 F5!');
            }
        }
    });
</script>

3.4 属性绑定

v-bind
  • v-bind 指令被用来响应地更新 HTML 属性
  • v-bind:href 可以缩写为 :href;
<!-- 绑定一个属性 -->
<img v-bind:data-original="imageSrc">

<!-- 缩写 -->
<img :data-original="imageSrc">

3.5 样式绑定

  1. class样式处理
  • 对象语法
    <div v-bind:class="{ active: isActive }"></div>
    isActive的值是布尔值,控制类名是否添加到标签上,类似toggleClass方法
  • 数组语法
    <div v-bind:class="[activeClass, errorClass]"></div>
    activeClass和errorClass的值会被分别添加到样式名中
  1. style样式处理
  • 对象语法
    <div v-bind:style="{ color: activeColor, fontSize: fontSize }"></div>
  • 数组语法
    <div v-bind:style="[baseStyles, overridingStyles]"></div>
绑定对象
  • 我们可以给v-bind:class 一个对象,以动态地切换class。
  • 注意:v-bind:class指令可以与普通的class特性共存
1、 v-bind 中支持绑定一个对象 
    如果绑定的是一个对象 则 键为 对应的类名  值 为对应data中的数据 
<!-- 
    HTML最终渲染为 <ul class="box textColor textSize"></ul>
    注意:
        textColor,textSize  对应的渲染到页面上的CSS类名 
        isColor,isSize  对应vue data中的数据  如果为true 则对应的类名 渲染到页面上
        当 isColor 和 isSize 变化时,class列表将相应的更新,
        例如,将isSize改成false,
        class列表将变为 <ul class="box textColor"></ul>
-->
<ul class="box" v-bind:class="{textColor:isColor, textSize:isSize}">
    <li>学习Vue</li>
    <li>学习Node</li>
    <li>学习React</li>
</ul>
<div :class="objClasses">对象语法简化操作</div>
<sript>
var vm= new Vue({
    el:'.box',
    data:{
        isColor:true,
        isSize:true,
        objClasses:{ //绑定该属性的标签实际效果为: class="objColor objSize"
             objColor:true,
             objSize:true
        }
    }
})
</sript>
<style>
    .box{
        border:1px dashed #f0f;
    }
    .textColor{
        color:#f00;
        background-color:#eef;
    }
    .textSize{
        font-size:30px;
        font-weight:bold;
    }
</style>
绑定数组
2、  v-bind 中支持绑定一个数组    数组中classA和 classB 对应为data中的数据

这里的classA  对用data 中的  classA
这里的classB  对用data 中的  classB
<ul class="box" :class="[classA, classB]">
    <li>学习Vue</li>
    <li>学习Node</li>
    <li>学习React</li>
</ul>
<div :class="arrClasses">数组语法绑定简化操作</div>
<script>
var vm= new Vue({
    el:'.box',
    data:{
        classA:‘textColor‘,
        classB:‘textSize‘,
        arrClasses:["textColor","textSize"]
    }
})
</script>
<style>
    .box{
        border:1px dashed #f0f;
    }
    .textColor{
        color:#f00;
        background-color:#eef;
    }
    .textSize{
        font-size:30px;
        font-weight:bold;
    }
</style>
绑定对象和绑定数组 的区别

它们可以结合使用,如:class="[activeClass,errorClass,{test:isTest}]"

  • 绑定对象的时候,对象的属性就是要渲染的类名,对象的属性值对应的是 data中的数据
  • 绑定数组的时候数组里面存的是data中的数据
绑定style
 <div v-bind:style="styleObject">绑定样式对象</div>'

<!-- CSS 属性名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用单引号括起来)    -->
 <div v-bind:style="{ color: activeColor, fontSize: fontSize,background:'red' }">内联样式</div>

<!--组语法可以将多个样式对象应用到同一个元素 -->
<div v-bind:style="[styleObj1, styleObj2]"></div>
<script>
    new Vue({
        el: '#app',
        data: {
            styleObject: {
                color: 'green',
                fontSize: '30px',
                background: 'red'
            },
            styleObj1: {
                color: 'red',
                width: '100px'
            },
            styleObj2: {
                fontSize: '30px'
            },
            activeColor: 'green',
            fontSize: "30px"
        }

    })

</script>

3.6 分支结构

v-if 使用场景
  • 1- 多个元素 通过条件判断展示或者隐藏某个元素。或者多个元素
  • 2- 进行两个视图之间的切换
<div id="app">
        <!--  判断是否加载,如果为真,就加载,否则不加载-->
        <span v-if="flag">
           如果flag为true则显示,false不显示!
        </span>
</div>

<script>
    var vm = new Vue({
        el:"#app",
        data:{
            flag:true
        }
    })
</script>

----------------------------------------------------------

    <div v-if="type === 'A'">
       A
    </div>
  <!-- v-else-if紧跟在v-if或v-else-if之后   表示v-if条件不成立时执行-->
    <div v-else-if="type === 'B'">
       B
    </div>
    <div v-else-if="type === 'C'">
       C
    </div>
  <!-- v-else紧跟在v-if或v-else-if之后-->
    <div v-else>
       Not A/B/C
    </div>

<script>
    new Vue({
      el: '#app',
      data: {
        type: 'C'
      }
    })
</script>
v-show 和 v-if的区别
  • v-show本质就是标签display设置为none,控制隐藏

    • v-show只编译一次,后面其实就是控制css,控制元素是否显示(已经渲染到了页面)。而v-if不停的销毁和创建,故v-show性能更好一点
  • v-if是动态地向DOM树内添加或者删除DOM元素,控制元素是否渲染到页面

    • v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件

3.7 循环结构

v-for
  • v-for遍历数组
    <li v-for='item in list'>{{item}}</li>
    <li v-for='(item,index) in list'>{{item}} + '---' +{{index}}</li>
  • key的作用:给每个节点绑定唯一标识(可以是id或者index),帮助Vue区分不同的元素,从而提高性能。不会加到DOM上,对开发没有影响
    <li :key='item.id' v-for='(item,index) in list'>{{item}} + '---' {{index}}</li>
  • 用于循环的数组里面的值可以是对象,也可以是普通元素
<ul id="example-1">
   <!-- 循环结构-遍历数组  
    item 是我们自己定义的一个名字  代表数组里面的每一项  
    items对应的是 data中的数组-->
  <li v-for="item in items">
    {{ item.message }}
  </li> 
</ul>
<script>
 new Vue({
  el: '#example-1',
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ],

  }
})
</script>
  • key的作用主要是为了高效地更新虚拟DOM
<ul>
  <li v-for="item in items" :key="item.id">...</li>
</ul>
  • v-for遍历对象
    <div v-for='(value, key, index) in object'></div>
  • v-if和v-for结合使用
    <div v-if='value==12' v-for='(value, key, index) in object'></div>
  • v-ifv-for 一起使用时,v-for 具有比 v-if 更高的优先级。即先遍历一遍数组,再判断v-if里面的条件。对于性能并不友好,因此不推荐同时使用 v-ifv-for
<!--  循环结构-遍历对象
        v 代表   对象的value
        k  代表对象的 键 
        i  代表索引 
---> 
<div v-if='v==13' v-for='(v,k,i) in obj'>{{v + '---' + k + '---' + i}}</div>

<script>
 new Vue({
  el: '#example-1',
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ],
    obj: {
        uname: 'zhangsan',
        age: 13,
        gender: 'female'
    }
  }
})
</script>

四、选项卡案例

4.1、 HTML 结构

    <div id="app">
        <div class="tab">
            <!--  tab栏  -->
            <ul>
                <li class="active">apple</li>
                <li class="">orange</li>
                <li class="">lemon</li>
            </ul>
              <!--  对应显示的图片 -->
            <div class="current"><img data-original="img/apple.png"></div>
            <div class=""><img data-original="img/orange.png"></div>
            <div class=""><img data-original="img/lemon.png"></div>
        </div>
    </div>

4.2、 提供的数据

         list: [{
                    id: 1,
                    title: 'apple',
                    path: 'img/apple.png'
                }, {
                    id: 2,
                    title: 'orange',
                    path: 'img/orange.png'
                }, {
                    id: 3,
                    title: 'lemon',
                    path: 'img/lemon.png'
                }]

image.png
image.png
image.png

4.3、 把数据渲染到页面

  • 把tab栏 中的数替换到页面上

    • 把 data 中 title 利用 v-for 循环渲染到页面上
    • 把 data 中 path利用 v-for 循环渲染到页面上
        <div id="app">
            <div class="tab">  
                <ul>
                      <!--  
                        1、绑定key的作用 提高Vue的性能 
                        2、 key 需要是唯一的标识 所以需要使用id, 也可以使用index ,
                          index 也是唯一的 
                        3、 item 是 数组中对应的每一项  
                        4、 index 是 每一项的 索引
                    -->
                       <li :key='item.id' v-for='(item,index) in list'>{{item.title}}</li>
                  </ul>
                  <div  :key='item.id' v-for='(item, index) in list'>
                        <!-- :  是 v-bind 的简写   绑定属性使用 v-bind -->
                        <img :data-original="item.path">
                  </div>
            </div>
        </div>
    <script>
        new  Vue({
            //  指定 操作元素 是 id 为app 的 
            el: '#app',
                data: {
                    list: [{
                        id: 1,
                        title: 'apple',
                        path: 'img/apple.png'
                    }, {
                        id: 2,
                        title: 'orange',
                        path: 'img/orange.png'
                    }, {
                        id: 3,
                        title: 'lemon',
                        path: 'img/lemon.png'
                    }]
                }
        })
    
    </script>
    

4.4、 给每一个tab栏添加事件,并让选中的高亮

  • 1.让默认的第一项tab栏高亮

    • tab栏高亮 通过添加类名active 来实现 (CSS active 的样式已经提前写好)

      • 在data 中定义一个 默认的 索引 currentIndex 为 0
      • 给第一个li 添加 active 的类名

        • 通过动态绑定class 来实现 第一个li 的索引为 0 和 currentIndex 的值刚好相等
        • currentIndex === index 如果相等 则添加类名 active 否则 添加 空类名
  • 2.让默认的第一项tab栏对应的div 显示

    • 实现思路 和 第一个 tab 实现思路一样 只不过 这里控制第一个div 显示的类名是 current
      <ul>
         <!-- 动态绑定class   有 active   类名高亮  无 active   不高亮-->
           <li  :class='currentIndex==index?"active":""'
               :key='item.id' v-for='(item,index) in list'
               >{{item.title}}</li>
      </ul>
      <!-- 动态绑定class   有 current  类名显示  无 current  隐藏-->
      <div :class='currentIndex==index?"current":""' 
    
           :key='item.id' v-for='(item, index) in list'>
            <!-- :  是 v-bind 的简写   绑定属性使用 v-bind -->
            <img :data-original="item.path">
      </div>
    <script>
        new  Vue({
            el: '#app',
                data: {
                    currentIndex: 0, // 选项卡当前的索引  默认为 0  
                    list: [{
                        id: 1,
                        title: 'apple',
                        path: 'img/apple.png'
                    }, {
                        id: 2,
                        title: 'orange',
                        path: 'img/orange.png'
                    }, {
                        id: 3,
                        title: 'lemon',
                        path: 'img/lemon.png'
                    }]
                }
        })
    </script>
    
  • 3.点击每一个tab栏 当前的高亮 其他的取消高亮

    • 给每一个li添加点击事件
    • 让当前的索引 index 和 当前 currentIndex 的 值 进项比较
    • 如果相等 则当前li 添加active 类名 当前的 li 高亮 当前对应索引的 div 添加 current 当前div 显示 其他隐藏

          <div id="app">
              <div class="tab">
                  <ul>
                      <!--  通过v-on 添加点击事件   需要把当前li 的索引传过去 
                      -->
                      <li v-on:click='change(index)'                          
                          :class='currentIndex==index?"active":""'                   
                          :key='item.id' 
                          v-for='(item,index) in list'>{{item.title}}</li>
                  </ul>
                  <div :class='currentIndex==index?"current":""' 
                       :key='item.id' v-for='(item, index) in list'>
                      <img :data-original="item.path">
                  </div>
              </div>
          </div>
      
      <script>
          new  Vue({
              el: '#app',
                  data: {
                      currentIndex: 0, // 选项卡当前的索引  默认为 0  
                      list: [{
                          id: 1,
                          title: 'apple',
                          path: 'img/apple.png'
                      }, {
                          id: 2,
                          title: 'orange',
                          path: 'img/orange.png'
                      }, {
                          id: 3,
                          title: 'lemon',
                          path: 'img/lemon.png'
                      }]
                  },
                  methods: {
                      change: function(index) {
                          // 通过传入过来的索引来让当前的  currentIndex  和点击的index 值 相等 
                          //  从而实现 控制类名    
                          this.currentIndex = index;
                      }
                  }
          })
      </script>
      

css样式

  .tab ul {
            overflow: hidden;
            padding: 0;
            margin: 0;
        }
        .tab ul li {
            box-sizing: border-box;
            padding: 0;
            float: left;
            width: 100px;
            height: 45px;
            line-height: 45px;
            list-style: none;
            text-align: center;
            border-top: 1px solid blue;
            border-right: 1px solid blue;
            cursor
        }
        .tab ul li:first-child {
            border-left: 1px solid blue;
        }
        .tab ul li.active {
            background-color: orange;
        }
        .tab div {
            width: 500px;
            height: 300px;
            display: none;
            text-align: center;
            font-size: 30px;
            line-height: 300px;
            border: 1px solid blue;
            border-top: 0px;
        }
        .tab div.current {
            display: block;
        }
查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 7 次点赞
  • 获得 4 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 4 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-12-09
个人主页被 745 人浏览