【三】Vue之后端交互
【一】与后端交互 - ajax
【1】版本1 - 出现了跨域问题
(1)前端:index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue与后端交互 - 出现了跨域问题</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js"></script>
</head>
<body>
<div id="box">
    <button @click="handleClick">加载数据</button>
</div>
</body>
<script>
    let vm = new Vue({
        el: '#box',
        data: {},
        methods: {
            handleClick() {
                $.ajax({
                    url: 'http://127.0.0.1:5000/',    // 发送请求的url,本地的5000端口,是flask的默认端口
                    method: 'get',
                    success: (data) => {
                        console.log(data)
                    }
                })
            }
        }
    })
</script>
</html>
(2)后端:main.py
from flask import Flask    # 这里用轻量级的Flask框架来测试
app = Flask(__name__)
@app.route('/')
def index():
    print('请求来了')
    return 'Hello World'
if __name__ == '__main__':
    app.run()

- 这里可以看出:前端向后端成功发送了请求,后端也成功响应了,但是前端却报错了
- 这是因为:
跨域问题的存在,浏览器检测到前端和后端不是来自同一个域,所以认为这是不安全的,所以就拦截了该资源的传递- 想要解决这个问题,就要实现:CORS,也就是 跨域资源共享
【2】版本2 - 解决了跨域问题
(1)前端:index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue与后端交互 - 解决了跨域问题</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js"></script>
</head>
<body>
<div id="box">
    <button @click="handleClick">加载数据</button>
    <p>加载的数据:{{myText}}</p>
</div>
</body>
<script>
    let vm = new Vue({
        el: '#box',
        data: {
            myText: ''
        },
        methods: {
            handleClick() {
                $.ajax({
                    url: 'http://127.0.0.1:5000/',
                    method: 'get',
                    success: (data) => {
                        console.log(data)
                        this.myText = data
                    }
                })
            }
        }
    })
</script>
</html>
(2)后端:main.py
from flask import Flask, make_response
app = Flask(__name__)
@app.route('/')
def index():
    print('请求来了')
    res = make_response('Hello World')
    res.headers['Access-Control-Allow-Origin'] = '*'    # 访问控制允许的源 设置为全部
    return res
if __name__ == '__main__':
    app.run()

【3】版本3 - 后端读取json文件传到前端
(1)json文件:file.json
{
  "name": "Darker",
  "age": "18",
  "gender": "male"
}
(2)前端:index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue与后端交互 - json</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js"></script>
</head>
<body>
<div id="box">
    <button @click="handleClick">加载数据</button>
    <p>加载的数据:{{myText}}</p>
</div>
</body>
<script>
    let vm = new Vue({
        el: '#box',
        data: {
            myText: ''
        },
        methods: {
            handleClick() {
                $.ajax({
                    url: 'http://127.0.0.1:5000/',
                    method: 'get',
                    success: (data) => {
                        console.log(data)
                        this.myText = data
                    }
                })
            }
        }
    })
</script>
</html>
(3)后端:main.py
import json
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/')
def index():
    print('请求来了')
    with open('file.json', mode='rt', encoding='utf-8') as f:
        dic = json.load(f)
    res = jsonify(dic)
    res.headers['Access-Control-Allow-Origin'] = '*'
    return res
if __name__ == '__main__':
    app.run()

【二】fetch
【1】简介
(1)fetch介绍
- 提供了一个 JavaScript 接口,用于访问和操纵 HTTP 管道的一些具体部分,例如请求和响应
- 它还提供了一个全局
fetch()方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源
- fetch 是一种在 JavaScript 中用于访问和操纵 HTTP 管道的接口。
- 它提供了一个全局的 fetch()方法,该方法可以用来跨网络异步获取资源。
 
- 它提供了一个全局的 
(2)fetch 方法的基本格式如下:
fetch(url [, options])
  .then(function(response) {
    // 对响应进行处理
    return response.json();
  })
  .then(function(data) {
    // 处理从响应中获取的数据
    console.log(data);
  });
- url:表示要获取资源的路径或 URL。可以是绝对路径或相对路径。
- options(可选):表示请求的配置项,例如请求方法(GET、POST等)、请求头等。
(3)fetch 方法返回一个 Promise 对象
- 
fetch 方法返回一个 Promise 对象,通过使用 .then()方法来处理响应。- 第一个 .then()函数接收一个response参数,该参数代表从服务器端返回的响应。
- 可以在该函数中对响应进行处理,比如解析 JSON 数据、读取响应头信息等。
- 通常,我们使用 response.json()方法来将响应转换为 JSON 格式的数据,并将其传递给下一个.then()函数。
- 在第二个 .then()函数中,我们可以处理从响应中获取的数据。
 
- 第一个 
- 
值得注意的是,由于 fetch 是异步的,因此可以链式调用多个 .then()方法来处理多个操作。
- 
除了 .then()方法之外,还可以使用.catch()方法来捕获发生的错误,以及.finally()方法来执行无论成功或失败都要执行的操作。
(4)常见的可配置项
- 
fetch 在发送请求时还可以通过配置 options 参数来设置请求的详细信息,例如请求方法、请求头等。常见的可配置项包括: - 
method:请求的方法,如 GET、POST 等,默认为 GET。 
- 
headers:请求头信息,可以设置为一个 Headers 对象或普通对象。 
- 
body:请求体,即要发送给服务器的数据,可以是 FormData、URLSearchParams、Blob、BufferSource、或者其他支持的类型。 
- 
mode:确定是否允许跨域请求,可以设置为 same-origin(仅在同源下请求)、cors(跨域请求)或 no-cors(不允许跨域请求)。 
- 
credentials:确定是否发送凭证(如 Cookie 或 HTTP 身份验证),可以设置为 include(发送凭证)、same-origin(仅发送同源凭证)或 omit(不发送凭证)。 
- 
cache:指定缓存模式,可以设置为 default(使用浏览器缓存)、no-store(禁止使用缓存)等。 
- 
redirect:指定重定向模式,可以设置为 follow(自动处理重定向)、error(遇到重定向时抛出错误)等。 
 
- 
(5)小结
- fetch 是现代 JavaScript 中一种常用的发起网络请求的方式,它提供了简单、灵活的API,可以满足对获取和处理资源的需求,并且可以通过配置参数来满足各种不同的请求场景。
【2】实例
(1)json文件:file.json
{
  "name": "Darker",
  "age": "18",
  "gender": "male"
}
(2)后端:main.py
import json
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/')
def index():
    print('请求来了')
    with open('file.json', mode='rt', encoding='utf-8') as f:
        dic = json.load(f)
    res = jsonify(dic)
    res.headers['Access-Control-Allow-Origin'] = '*'
    return res
if __name__ == '__main__':
    app.run()
(3)前端:index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue与后端交互 - fetch</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js"></script>
</head>
<body>
<div id="box">
    <button @click="handleClick">加载数据</button>
    <p>加载的数据:</p>
    <ul>
        <li >姓名:{{name}}</li>
        <li >年龄:{{age}}</li>
        <li >性别:{{gender}}</li>
    </ul>
</div>
</body>
<script>
    let vm = new Vue({
        el: '#box',
        data: {
            name:'',
            age: '',
            gender: ''
        },
        methods: {
            handleClick() {
                fetch('http://127.0.0.1:5000/').then(response => {
                    return response.json()
                }).then(json => {
                    console.log('从后端获取的json数据', json)   // success 获取的数据
                    this.name = json.name
                    this.age = json.age
                    this.gender = json.gender
                }).catch(ex => {
                    console.log('出现了异常', ex)    // 抛出异常
                })
            }
        }
    })
</script>
</html>

【二】Axios
【1】简介
- Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中
- axios官网:http://www.axios-js.com/
- 
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 Node.js 环境中进行数据请求和传输。 - 它提供了一种简洁、直观的 API
 
- 
并且具备以下特性: - 
支持浏览器和 Node.js: - Axios 可以在浏览器和 Node.js 环境中同时使用,使得前后端代码可以共用。
 
- 
Promise 库: - Axios 使用 ES6 的 Promise 来处理异步操作,方便进行链式调用、错误处理等。
 
- 
支持拦截器: - 
通过拦截器,可以在发送请求之前和接收响应之前对请求进行全局的配置或者转换。 
- 
这样可以对请求或响应做统一的处理,例如添加请求头、请求参数校验、响应数据处理等。 
 
- 
- 
提供并发请求的接口: - Axios 提供了可以同时发送多个请求的接口,可以方便地管理多个请求,等待所有请求都完成后再进行处理。
 
- 
可以取消请求: - Axios 允许通过取消请求的方式来停止正在进行的请求,减少不必要的流量和资源消耗。
 
- 
支持cookie的自动保存和发送: - 在浏览器环境下,Axios 可以自动保存从服务器接收的 cookie,并在后续的请求中自动发送,方便进行会话状态的维护。
 
- 
支持跨域请求: - Axios 提供了简便的方法来处理跨域请求,可以通过配置跨域策略、发送跨域请求。
 
- 
提供丰富的错误处理机制: - Axios 捕获请求过程中的错误,并提供了一些常见的错误处理方法,方便进行错误处理和异常信息的展示。
 
 
- 
- 
Axios 的官方网站是 http://www.axios-js.com/ - 在该网站上可以找到 Axios 的详细文档、示例以及相关资源。
- 通过 Axios,开发人员可以快速简单地进行网络请求,并且具备灵活的配置和丰富的功能,使得与后端接口的交互更加高效和方便。
 
【2】实例
(1)json文件:film.json(这里只是一部分,原代码太多了...)
https://m.maizuo.com/v5/?co=mzmovie#/films/nowPlaying
(2)后端:main.py
import json
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/film')
def index():
    print('请求来了')
    with open('film.json', mode='rt', encoding='utf-8') as f:
        dic = json.load(f)
    res = jsonify(dic)
    res.headers['Access-Control-Allow-Origin'] = '*'
    return res
if __name__ == '__main__':
    app.run()
(3)前端:index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Vue与后端交互</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.0/axios.min.js"></script>
</head>
<body>
<div id="box">
    <button @click="handleClick">加载电影数据</button>
    <p>加载的数据:</p>
    <ul>
        <li v-for="item in dataList">
            <p>电影:{{item.name}}</p>
            <p>导演:{{item.director}}</p>
            <img :src="item.poster" alt="">
        </li>
    </ul>
</div>
</body>
<script>
    let vm = new Vue({
        el: '#box',
        data: {
            dataList: []
        },
        methods: {
            handleClick() {
                axios.get("http://127.0.0.1:5000/film/").then(res => {
                    console.log(res.data.data.films) // axios 自动包装data属性 res.data
                    this.dataList = res.data.data.films
                }).catch(err => {
                    console.log(err);
                })
            }
        }
    })
</script>
</html>

【三】计算属性
计算属性是基于它们的依赖进行缓存的
计算属性只有在它的相关依赖发生改变时才会重新求值
计算属性就像
Python中的property,可以把方法/函数伪装成属性
- 计算属性(Computed Properties)是一种在编程中常见的概念,它允许我们根据依赖的数据进行动态计算,并将计算结果作为属性进行缓存。
- 计算属性只有在其相关依赖发生改变时才会重新求值,这也是它们与普通方法或函数的主要区别。
 
- 计算属性类似于Python中的property,通过将一个方法或函数伪装成属性来使用。
- 与普通的属性不同,计算属性的值是根据其他数据动态计算得出的,而不是直接存储的。
 
- 使用计算属性可以简化代码,并提高性能。
- 当我们需要基于现有数据进行复杂的计算或处理时,可以使用计算属性来动态获取计算结果,而不必每次都手动调用方法或函数进行计算。
 
- 在很多框架和库中,如Vue.js等,都提供了对计算属性的支持。
- 在Vue.js中,我们可以定义计算属性并在模板中进行引用,当计算属性的相关依赖发生改变时,模板会自动更新显示计算属性的最新值。
 
- 总结来说,计算属性是一种根据依赖动态计算结果并缓存的属性,可以简化代码、提高性能,并且给使用者提供了更加便捷的操作方式。
【1】通过计算属性实现名字首字母大写
(1)案例一
- 在Vue中,我们可以通过计算属性(Computed Property)来实现将名字的首字母大写。下面是一个示例代码:
<template>
  <div>
    <p>原始名字:{{ firstName }}</p>
    <p>首字母大写名字:{{ capitalizedFirstName }}</p>
  </div>
</template>
<script>
export default {
  data() {
    return {
      firstName: 'john',
      lastName: 'doe'
    };
  },
  computed: {
    fullName() {
      return this.firstName + ' ' + this.lastName;
    },
    capitalizedFirstName() {
      return this.firstName.charAt(0).toUpperCase() + this.firstName.slice(1);
    }
  }
};
</script>
- 
在上面的代码中,我们定义了一个Vue组件,并在 computed中定义了两个计算属性,分别是fullName和capitalizedFirstName。- 
fullNamefirstName和lastName拼接而得到的。
- 
capitalizedFirstName计算属性是通过使用字符串的charAt(0)方法获取firstName的首字母,并调用toUpperCase()方法将首字母转换为大写,然后再使用slice(1)方法获取剩余部分组合而成。
 
- 
- 
在模板中,我们使用了插值表达式 {{ ... }}来显示计算属性的值。
- 
运行以上示例代码,你会看到通过计算属性实现名字首字母大写的效果。 
- 
这种方式可以让我们在模板中直接使用计算属性的值,使代码更清晰易读。 
(2)案例二
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首字母大写</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js"></script>
</head>
<body>
<div id="box">
    <!--大段的代码写在这里不好,使用计算属性-->
    模板插值:
    {{myText.substring(0,1).toUpperCase()+myText.substring(1)}}
    <p>普通方法:{{getNameMethod()}}</p>
    <!--区别是在同一个页面中使用多次计算属性,不会多次执行-->
    <p>计算属性:{{getName}}</p>
    <!--普通方法要加括号-->
</div>
</body>
<script>
    var vm = new Vue({
        el: '#box',
        data: {
            myText: 'darker',
        },
        computed: {
            getName() { // 依赖的状态改变了,会重新计算
                return this.myText.substring(0, 1).toUpperCase() + this.myText.substring(1)
            }
        },
        methods: {
            getNameMethod() {
                return this.myText.substring(0, 1).toUpperCase() + this.myText.substring(1)
            }
        }
    })
</script>
</html>

【2】通过计算属性重写过滤案例
(1)案例一
- 当我们需要对数据进行筛选或过滤时,Vue的计算属性(Computed Property)是一种非常方便且优雅的方式。
- 下面是一个示例,展示了如何使用计算属性来重写一个过滤案例。
- 假设我们有一个包含学生信息的数组,我们想要筛选出年龄大于等于18岁的学生。
- 我们可以使用计算属性来实现这个功能。
<template>
  <div>
    <h2>学生列表:</h2>
    <ul>
      <li v-for="student in filteredStudents" :key="student.id">
        {{ student.name }} - {{ student.age }}岁
      </li>
    </ul>
  </div>
</template>
<script>
export default {
  data() {
    return {
      students: [
        { id: 1, name: '张三', age: 17 },
        { id: 2, name: '李四', age: 19 },
        { id: 3, name: '王五', age: 20 },
        { id: 4, name: '赵六', age: 16 },
        { id: 5, name: '刘七', age: 18 }
      ]
    };
  },
  computed: {
    filteredStudents() {
      return this.students.filter(student => student.age >= 18);
    }
  }
};
</script>
- 在上面的示例代码中,我们定义了一个含有学生信息的数组students。- 通过计算属性filteredStudents,我们对students数组进行筛选,只返回年龄大于等于18岁的学生。
- 然后,在模板中使用v-for指令遍历filteredStudents数组,并显示学生的姓名和年龄。
 
- 通过计算属性
- 这样,我们就实现了一个简单的学生过滤案例。
- 当students数组中的数据发生变化时,计算属性会自动更新过滤结果,保持视图的同步。
 
- 当
- 通过使用计算属性来重写过滤案例,我们可以更加清晰地表达出我们的意图,并且能够轻松应对数据的变化。
(2)案例二
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>通过计算属性重写过滤案例</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js"></script>
</head>
<body>
<div id="box">
    <p><input type="text" v-model="myText" placeholder="请输入要筛选的内容:"></p>
    <ul>
        <li v-for="data in newList">{{data}}</li>
    </ul>
</div>
</body>
<script>
    var vm = new Vue({
        el: '#box',
        data: {
            myText: '',
            dataList: ['a', 'at', 'atom', 'be', 'beyond', 'cs', 'csrf'],
        },
        computed: {
            newList() {
                return this.dataList.filter(item => {
                    return item.indexOf(this.myText) > -1   // 返回索引大于1的元素:>-1 就表示包含在其中
                })
            }
        }
    })
</script>
</html>

【四】虚拟DOM 与diff算法 key的作用
【1】Vue2.0 v-for 中 :key 有什么用呢?
其实呢不只是vue,react中在执行列表渲染时也会要求给每个组件添加key这个属性
key简单点来说就是唯一标识,就像ID一样唯一性
要知道,vue和react都实现了一套虚拟DOM,使我们可以
不直接操作DOM元素,只操作数据便可以重新渲染页面
而隐藏在背后的原理便是其高效的Diff算法
分层级比较:只做同层级的对比
通过key值比较:出现新的key就插入同组(循环中,尽量加
key,让key值唯一)
通过组件/标签进行比较:然后进行替换
- 
当在 Vue 2.0 中使用 v-for进行列表渲染时,为每个被渲染的元素添加:key属性是很重要的。
- 
这是因为 key属性在虚拟 DOM 中具有以下作用:- 
唯一标识: - 
key属性用于给每个被渲染的元素分配一个唯一的标识符。
- 
这样 Vue 可以根据 key来区分每个元素,并在重新渲染时确定哪些元素是新增的、哪些元素是删除的或移动的。
 
- 
- 
性能优化: - 
通过提供唯一的 key值,Vue 可以进行更高效的列表更新。
- 
当数据发生改变时,Vue 使用 key来对比前后两个渲染状态,只对需要更新的部分进行重新渲染,而不是整个列表。这样可以节省大量的计算资源和时间,提高应用的性能。
 
- 
- 
确保组件状态的正确性: - 
在列表中使用组件时, key属性对于确保组件状态的正确性非常重要。
- 
当列表发生改变时,Vue 会根据 key来判断是复用已有组件还是创建新的组件。
- 
如果没有提供 key或者key值不唯一,可能会导致组件的状态出现混乱或错误的复用现象。
 
- 
- 
过渡与动画: - 当在列表中使用过渡或动画效果时,key属性也起到关键作用。
- Vue 使用 key来识别新增、删除和移动的元素,并为它们添加合适的过渡效果或动画。
- 这样可以提供更好的用户体验和视觉效果。
 
- 当在列表中使用过渡或动画效果时,
 
- 
- 
总之 - 通过在 v-for中为每个元素添加唯一的key属性,可以提高渲染性能、保证组件状态的正确性,以及支持过渡与动画效果。
- 因此,在使用 v-for进行列表渲染时,请务必为每个元素提供一个具有唯一性的key值。
- 通常情况下,可以使用列表项的唯一标识符,如数据库中的 ID,作为 key的值。
 
- 通过在 
【2】虚拟DOM的diff算法
- 虚拟 DOM 的 Diff 算法是 Vue 和 React 中用于高效更新 DOM 树的核心算法之一。
(1)分层级比较:
- 
Diff 算法首先会进行分层级的比较。它会通过调用每个节点的 render方法来构建两个虚拟 DOM 树,分别是当前状态下的旧树和改变后的新树。
- 
然后,Diff 算法只会在同层级进行比较,不会跨层级比较。 
(2)通过 key 值比较:
- 
在同一层级内,Diff 算法会通过比较每个节点的 key值来确定哪些节点是新增的、哪些节点是删除的或者移动的。
- 
如果遇到新的节点没有 key值或者key值不唯一的情况,Diff 算法会按照一定的规则处理。
(3)通过组件/标签进行比较:
- 
当比较两个节点时,Diff 算法会根据节点类型(组件或者标签)来进行不同的处理。 
- 
对于标签节点,Diff 算法会比较节点的标签名、属性和子节点,并更新或替换需要改变的部分。 
- 
对于组件节点,Diff 算法会比较组件类型、 props和子节点,并更新或替换需要改变的组件。
(4)递归比较子节点:
- 
如果两个节点的类型相同,Diff 算法会继续递归比较子节点。 
- 
这样可以确保整个虚拟 DOM 树的所有子节点都被遍历和比较。 
(5)列表优化:
- 在处理列表渲染时,Diff 算法会通过添加唯一的 key值来进行优化。
- 当列表发生变化时,Diff 算法会尽量复用已有的节点,而不是重新创建节点。这样可以减少 DOM 操作和提高性能。
(6)总体来说:
- 虚拟 DOM 的 Diff 算法通过分层级比较、通过 key 值比较、通过组件/标签比较以及递归比较子节点等策略,只对需要改变的部分进行更新,大大减少了 DOM 操作的开销,提高了应用的性能。
- 通过这种方式,Vue 和 React 能够实现快速、高效的页面渲染和更新。

- 虚拟DOM数据渲染图示

【3】具体实现
(1)把树按照层级分解
- 
Diff 算法首先会进行分层级的比较。它会通过调用每个节点的 render方法来构建两个虚拟 DOM 树,分别是当前状态下的旧树和改变后的新树。
- 
然后,Diff 算法只会在同层级进行比较,不会跨层级比较。 

(2)按照key值比较
- 
在同一层级内,Diff 算法会通过比较每个节点的 key值来确定哪些节点是新增的、哪些节点是删除的或者移动的。
- 
如果遇到新的节点没有 key值或者key值不唯一的情况,Diff 算法会按照一定的规则处理。

(3)通过组件进行比较
- 
当比较两个节点时,Diff 算法会根据节点类型(组件或者标签)来进行不同的处理。 
- 
对于标签节点,Diff 算法会比较节点的标签名、属性和子节点,并更新或替换需要改变的部分。 
- 
对于组件节点,Diff 算法会比较组件类型、 props和子节点,并更新或替换需要改变的组件。

<div id="box">
    <div v-if="isShow">111</div>
    <p v-else>222</p>
    <!--    
    {tag:div,value:111}
    {tag:p,value:222}
    直接不比较,直接删除div,新增p
    -->
    <div v-if="isShow">111</div>
    <div v-else>222</div>
    <!--    
    {tag:div,value:111}
    {tag:div,value:222}
    比较都是div,只替换文本内容
    -->
</div>
思考:什么是跨域问题?如何解决?
本文来自博客园,作者:Chimengmeng,转载请注明原文链接:https://www.cnblogs.com/dream-ze/p/17610198.html

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号