本节主要介绍 Spring Boot 中 JdbcTemplate 的用法,所以前端页面仅给出代码和注释,不再进行详细介绍了。前端只有一个页面,使用 Bootstrap 的样式和插件,通过 jQuery 的 $.ajax 方法访问后端接口,逻辑并不复杂。此处简单展示下浏览商品部分的前端代码,感兴趣的同学可以从 Git仓库 查看完整代码。实例: //浏览商品 function viewGoods() { var row = ""; //先清空表格 $('#GoodsTable').find("tr:gt(0)").remove(); $.ajax({ type: "GET", url: "http://127.0.0.1:8080/goods", dataType: "json", contentType: "application/json; charset=utf-8", success: function (res) { console.log(res); $.each(res, function (i, v) { row = "<tr>"; row += "<td>" + v.id + "</td>"; row += "<td>" + v.name + "</td>"; row += "<td>" + v.price + "</td>"; row += "<td>" + v.pic + "</td>"; row += "<td><a class='btn btn-primary btn-sm' href='javascript:editGoods(" + v.id + ")' >编辑</a>"; row += "<a class='btn btn-danger btn-sm' href='javascript:removeGoods(" + v.id + ")' >删除</a></td>"; row += "</tr>"; console.log(row); $("#GoodsTable").append(row); }); }, error: function (err) { console.log(err); } }); }
使用 CDN 的方式引入Vue更加方便、快捷。以下推荐几个比较稳定的 CDN。https://cdn.staticfile.org/vue/2.2.2/vue.min.jshttps://unpkg.com/vue/dist/vue.jshttps://cdnjs.cloudflare.com/ajax/libs/vue/2.1.8/vue.min.jshttps://cdn.jsdelivr.net/npm/vue/dist/vue.js528
function updateUser(button, userId){ var children = $(button).parent().children(); var name = children.eq(0).val(); var phone = children.eq(1).val(); var data = JSON.stringify({'name': name, 'phone': phone}); $.ajax({ 'url': '/users/' + userId, 'type': 'PUT', 'contentType': 'application/json', 'data': data, 'dataType': 'json', 'error': ajaxError, 'success': ajaxSuccess });}点击 “更新” 按钮后,执行函数 updateUser(button, userId),button 指向的是 “新增” 按钮,userId 是需要更新的联系人 id。在第 3 行到第 5 行,获取需要联系人的姓名和电话,使用了和 6.2 小节相同的方法,请参考 6.2 小节。在第 8 行,通过 jquery 的 ajax 函数调用后端服务,设置 url 为 ‘/users/userId’、type 为 ‘PUT’ ,表示 RESTful 架构下的更新联系人。
今天我们来基于 Scrapy 框架完成一个新闻数据抓取爬虫,本小节中我们将进一步学习 Scrapy 框架的,来抓取异步 ajax 请求的数据,同时学习 Scrapy 的日志配置、邮件发送等功能。
本篇我们通过商品浏览项目实例,展现前后端分离项目的开发、测试全流程。技术选型方面,后端毫无疑问选择 Spring Boot ,接口风格采用 RESTful 标准。前端则使用简单的 HTML + Bootstrap + jQuery ,并通过 jQuery 的 $.ajax 方法访问后端接口。
如图所示,请求正确接口的 Ajax 请求都得到了正确的返回。而访问服务端暂时没有的接口则返回了 404 错误。同时,GET 请求中没有显式提供 method,默认配置也能够及时生效,默认为 GET。
load(url, [data], [callback])load 是 jQuery 一个非常便捷的 Ajax 交互方法,从接口文档上看,load 方法接受三个参数,分别是 url,发送的参数以及响应结果的回调函数。实际上,后两个参数我们也常常可以忽略。我们先来看一个简单的例子:假设我们要在页面中插入一个从后端请求回来的数据,那么我们会怎么做?有同学非常聪明,可能会说:我先启用 ajax ,发送 GET 请求,在成功的时候回调函数里面再操作页面的元素对象,为 innerHTML 赋值结果。是的,基本流程就是查询之后再操作元素添加数据即可。但是,jQuery 的 load 可能更加简单哦。不信请看:
Web Worker 能解决传统的 JavaScript 单线程出现的执行阻塞问题,因而适合以下几种业务场景:并行计算;ajax 轮询;耗时的函数执行;数据预处理/加载。
话不多说,上代码:xhr.open("POST", "http://localhost:8080/simple/post");xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");xhr.send("mk=慕课网&class=ajax");查看效果图:基本和上面 GET 请求类似,这里我们构造了一个 POST 请求,请求的 url 为 http://localhost:8080/simple/post,发送请求的参数有两个,分别为 mk=慕课网 和 class=ajax。从浏览器的控制台面板上可以看到,在 Headers 上,Form Data 部分正是我们要发送的数据,数据发送正常。这里两个地方需要注意:send 方法接受可选参数作为请求主体,即发送到服务器的内容。Content-type 需要设置为请求主体类型, 这是因为如果不设置的话会采取默认值,在很多时候服务端可能无法解析参数。XMLHttpRequest.setRequestHeader() 是请求HTTP 请求头部的方法,因此设置 Content-type 自然也是通过调用这个方法来实现。该方法需要在 open() 和 send() 之间使用。
编写web api,在写 post 请求接口时,通常将接口参数以 json 格式发送给服务端,request.json 保存了请求中的 json 数据,下面编写一个例子 request-json.py 解析 json 数据:from flask import Flask, requestapp = Flask(__name__)@app.route('/')def root(): file = open('api.html', encoding = 'utf-8') return file.read()@app.route('/api/addUser', methods = ['POST'])def addUser(): json = request.json print('JSON', json) print('name = %s' % json['name']) print('age = %s' % json['age']) return 'addUser OK'if __name__ == '__main__': app.run(debug = True)在第 4 行,编写路径 / 的处理函数 root(),它读取文件 api.html,将内容返回给浏览器。在第 9 行,编写路径 /api/addUser 的处理函数 addUser(),打印 request.json 中的参数 name 和 age,返回给浏览器 ‘addUser OK’。客户端使用 POST 方法提交请求 /api/addUser,在 Flask 中,需要指明 methods 为 ‘POST’。路径 / 返回 api.html,api.html 通过 ajax 调用服务端的 /api/addUser,内容如下:<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.0/jquery.js"></script><h1 id='result'>result</h1><script>var data = JSON.stringify({'name':'zhangsan', 'age':'20'});$.ajax({ url: '/api/addUser', type: 'post', contentType:'application/json', data: data, success: function(data) { $("#result").html(data); }, error: function(e) { alert('ERROR') }});</script>在第 2 行,定义 id 为 result 的标签,用于显示调用结果;在第 4 行,设定 /api/addUser 的接口参数: name 和 age;在第 5 行,通过 jquery.ajax 调用服务端的 /api/addUser。请求调用成功时,回调 success 函数,将结果显示在 id 为 result 的标签中,如下所示:
好了,前后端都有相应的部署后,我们可以来看看效果了:我们可以看到,刷新页面的时候,前端会通过 Ajax 发出 get 请求,服务端返回数据后,前端便在页面上绘制出表格。
数据库使用 MySQL ,商品信息存储到商品表内即可。后端项目使用 Spring Boot ,通过控制器暴露 RESTful 风格的接口供前端调用,通过 JdbcTemplate 实现对数据库的操作。前端项目使用 Bootstrap 开发,通过 jQuery 提供的 $.ajax 方法访问后端接口。
本章节会给出前后端简单代码,弱化容错性等增强性需求,重点描述前后端交互的过程和效果。本章节前端使用前面章节封装的 Ajax, 因此本章节代码不涉及封装 Ajax 的相关代码,需要了解的同学可以翻看前面章节。本章节会给出业务相关的前端代码, 前端使用 HTML、Css、和JavaScript,使用 moment.js 库进行时间的格式化。本章节提供 node 后端代码,使用框架为 Express。node 端会使用 MySQL包。本章节也提供相应的 Java 后端代码。本章节 Java 服务端使用 servlet 提供服务。引入了 fastjson 包进行 JSON 的一些列序列化和反序列化的操作;使用 mysql-connector-java 来进行 java 端数据库的连接和操作。
<html><head><meta charset='utf-8'><script src="https://lib.baomitu.com/jquery/2.2.4/jquery.min.js"></script><link href="{{url_for('static', filename='style.css')}}" rel="stylesheet"><script src="{{url_for('static', filename='script.js')}}"></script></head>在第 4 行,引入库 jquery,在前端需要使用 jquery 的 ajax 功能调用后端服务。在第 5 行,引入静态文件 style.css,函数 url_for(‘static’, filename=‘style.css’) 的输出为 ‘/static/style.css’。在第 6 行,引入静态文件 script.js,函数 url_for(‘static’, filename=‘script.js’) 的输出为 ‘/static/script.js’。
接下来讲到的一种是服务端代理的方式。要问为什么采取服务端代理的方式呢?很简单,因为浏览器端 Ajax 请求有跨域的限制,那我们就把请求不同域的操作放在服务端好了,毕竟服务端是没有跨域限制这一说的。3.2.1 服务端代理原理浏览器端发送请求到同域的服务端;服务端接收到请求之后,进行转发,请求不同域的另外一个服务端;服务端间进行交互数据后,同域服务端返回数据给浏览器端。3.2.2 具体例子举一个服务端代理的例子,这里我使用了一个 Express 的中间件,叫做 express-http-proxy 。当然同学们也可以在同域服务端接收到请求的时候,发起 http 请求访问不同域的服务端来模拟这一代理行为。前端方面我使用了 jQuery 的 Ajax 方法。3.2.2.1 javaScript 关键代码$.ajax({ url: '/proxy/proxy_get', method: 'GET', data: { a: '123', b: '234' }}).done(data => { console.log(data)})很简单,我们就是向同域的服务器发送了一个请求。3.2.2.2 同域服务器关键代码const proxy = require('express-http-proxy'); // 引入代理中间件// ... 一些代码app.use('/proxy', proxy('http://localhost:8082/')); // 注册,之后 /proxy 都会代理到 http://localhost:8082/ 上3.2.2.3 不同域的服务器关键代码router.get("/proxy_get", function(req, res) { const {a, b} = req.query res.send(`参数是:${a} 和 ${b}`)});这是目标服务器的响应方法,返回一个 处理后的字符串。3.2.2.4 效果3.2.3 服务端代理小结服务端代理通过服务端和服务端之间的交互来避免浏览器和不同域的服务端之间直接进行交互,从而避免了跨域的问题。当然这种方法要求我们有一个中间服务器的存在。
在开发过程中会遇到一个很常见的需求,我们想获取一个值,但不能直接拿到,我们只能先请求一个接口如:api_1,获取这个值的接口地址如:api_2。然后,请求 api_2 接口才能获取这个值。这是一个典型的需要异步回调才能完成的功能。在学习 Promise 的时候我们也针对这样的情况,我们可以使用 Promise 来完成这样的功能:var promise = function (url) { return new Promise((resolve, reject) => { ajax(url, (data) => { resolve(data) // 成功 }, (error) => { reject(error) // 失败 }) })} promise('api_1').then(res1 => { promise(res1).then(res2 => { console.log(res2) })})从上面的代码中可以看出,在这种情况下,使用 Promise 好像并没有解决回调地狱的问题。那如何解决这种问题呢?我们想到了 Generator 函数具有暂停功能,那是不是我们可以让请求 api_2 接口时暂停,等到 api_1 请求成功获取到地址后再去请求呢?按照这个思路我们可以有下面的代码:const ajax = function(api) { return new Promise((resolve, reject) => { setTimeout(() => { if (api === 'api_1') { resolve('api_2'); } if (api === 'api_2') { resolve(100); } }, 0) })}function * getValue() { const api = yield ajax('api_1'); const value = yield ajax(api); return value;}console.log(getValue()); // Object [Generator] {}上面的代码是我们模拟 ajax 请求,通过使用生成器函数写出的代码让我们感觉有了同步的感觉,但是这样去执行 getValue 函数是不会得到结果的。那么我们要怎样去获得结果呢?根据生成器函数的特点,可以这样写:let it = getValue();let { value } = it.next();value.then((data) => { let { value } = it.next(data); value.then((data) => { Console.log(data); });});从上面的代码中看出还是有嵌套,好像并没有解决问题。但如果你细心,你会发现每个回调的逻辑基本都是一样的。那么我们能不能对这样的嵌套函数进行封装呢?答案当然是可以的,有个库就专门解决了这个痛点 —— co 库,有兴趣的可以去研究一下这个库,代码很少,下面我们就来封装一个这样的库。先让我们看看 co 库是怎么使用的:co(getValue()).then(res => { console.log(res);})从上面的代码中可以看出,把函数传入进去,并让函数执行,然后在 then 的成功回调中可以获取 getValue 函数返回的最终结果。这样非常清晰地解决了上面我们需要手动执行的方法,下面我来分析一下具体的实现步骤:从上面的用法可以看出,co 返回的是一个 Promise 实例,所以我们需要返回一个 new Promise() 实例;传入的生成器函数执行后,我们可以调用 next () 函数拿到返回的值和是否执行完的状态,判断 done 如果是 true 时说明执行完了,可以执行 resolve;当生成器函数没有执行完时,这时我们就需要递归地去调用这个 next () 来执行下一步,因为传入的值是一个 Promise 实例,要想获取它的结果就需要链式调用 then 方法,然后拿到结果进行递归执行。有了上面的步骤分析,不难得到下面的代码:function co(it) { return new Promise((resolve, reject) => { function next(data) { let { value, done } = it.next(data); if (done) { resolve(value); } else { Promise.resolve(value).then(data => { next(data); }, reject) } } next(undefined); })}上面的代码中需要注意的是,如果 yield 返回的不是一个 Promise 对象时,我们对 value 使用了 Promise.resolve() 进行了包装,这样就可以处理返回一个普通值时没有 then 方法的问题。
Lodash 提供了许多原生同名方法,如数组 forEach、map、includes 等。Lodash 对这些方法增加了容错,如果是原生方法,碰到值为 null 或者 undefined 会报错,在 Lodash 中会处理掉这份错误。Lodash 在引入后,入口为全局下的 _。<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script><script> var arr = null; // 不知道出于什么原因 本来应该是个数组 但是变成了null _.forEach(arr, function() { }); arr.forEach(function() { }); // 异常:Cannot read property 'forEach' of null</script>同时 Lodash 对一些方法做了优化处理,如:假使在 forEach 的回调中返回了 false ,则不会再继续遍历,达到与 break 类似的效果。<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script><script> var arr = [1, 2, 3, 4]; var fn = function(item, index) { if (index === 2) { return false; } console.log(item); }; console.log('lodash: '); _.forEach(arr, fn); console.log('native: ') arr.forEach(fn);</script>对项目有强健壮性和稳定性的项目,可以考虑使用 Lodash 这样的库替代原生方法进行使用,让第三方做好兼容处理。
向服务端添加数据,我们会采用 POST 请求。思路也非常简单,大致过程如下:在页面的表单上填写数据;填写完毕,点击提交按钮。这个时候发送 Ajax 请求;服务端接收请求,执行 sql 语句进行数据插入;插入成功,返回成功响应;前端接收到服务端响应,重新更新页面上的表格。咱一步一步来,首先我们需要有些函数和事件,来处理提交按钮的点击事件,并发送请求。
function addUser(button){ var children = $(button).parent().children(); var name = children.eq(0).val(); var phone = children.eq(1).val(); var data = JSON.stringify({'name': name, 'phone': phone}); $.ajax({ 'url': '/users', 'type': 'POST', 'contentType': 'application/json', 'data': data, 'dataType': 'json', 'error': ajaxError, 'success': ajaxSuccess });}点击 “新增” 按钮后,执行函数 addUser(button),button 指向的是 “新增” 按钮。在 templates/index.html 中,按钮、联系人姓名、联系人电话于相同的 DIV 中,如下所示:<div class="row"> <input type="text" placeholder='姓名'> <input type="text" placeholder='电话'> <span class="button" onclick="addUser(this);">增加</span></div>在第 3 行到第 5 行,表达式的含义如下所示:表达式含义$(button).parent()指向按钮的父节点$(button).parent().children()表示 div 的 3 个子节点children.eq(0)指向联系人姓名children.eq(1)指向联系人电话children.eq(2)指向新增按钮在第 4 行,根据联系人的姓名和电话创建一个新联系人,将它作为参数、调用后端新增联系人的服务。在第 8 行,通过 jquery 的 ajax 函数调用后端服务,设置 url 为 ‘/users/’、type 为 ‘POST’ ,表示 RESTful 架构下的新增联系人。
前后端分离这种概念和技术,早就流行多年了。具体点说,前端编写 HTML 页面,然后通过 Ajax 请求后端接口;后端把接口封装成 API ,返回 JSON 格式的数据;前端接收到 JSON 返回数据后渲染到页面。前端工程师根本不需要懂后端,调用后端接口就行。后端使用 Spring Boot 控制器返回 JSON 十分简单,给方法添加个注解,就能将返回值序列化为 JSON 。前端干前端的活,后端干后端的活,职责分明,界限明确。这就是前后端分离的好处啊!
现代网页的构成几乎离不开 AJAX 技术,如果 JavaScript 无法发起 HTTP 请求,几乎所有的现代网站都会瘫痪,变得难用。除了 XHR 对象之外,还有 fetch API 这个后起之秀,也可以承担 XHR 对象的工作。但 fetch 还有部分缺陷,如无法监控进度,对状态码的处理逻辑不够符合业务,所以用户面较广的产品使用一般不会选择 fetch。
moment.js 在全局下以 moment 作为入口,提供了一系列时间相关的方法。<script src="https://cdn.bootcdn.net/ajax/libs/moment.js/2.27.0/moment.min.js"></script><script> var now = moment().calendar(); console.log(now); // 输出当前日历时间</script>现在的相对时间差需求非常常见,如下单时间,是 多少分钟前,moment.js 提供了相对时间计算:moment().startOf('hour').fromNow(); // 相对这个小时过去了多少分钟var timestamp = 1593933593236; // 2020年7曰5日下午15点20分38秒moment(timestamp).fromNow(); // 相对时间戳多久前
首先我们先不去管如何向后端添加一条数据,我们来做一个简单的数据查询。那么, 前后端分别要做什么?简单来说,前后端按顺序应该是这样的:前端通过 Ajax 发送一个查询请求;后端接收到请求,处理请求,包括 MySQL 查询等,最后返回结果;前端收到请求,进行界面上的 table 更新。Talk is cheap,接下来我们来实现一下。
工欲善其事,必先利其器,在我们进入本节的学习前,我们需要先搭建我们的开发环境,在实际的项目中也是必须的。本节使用的是 Vue 脚手架生成的项目,不了解 Vue 的同学可以先去学习一下。在 vue.config.js 配置中,对 ajax 请求进行了 mock 操作,mock 的逻辑在 mock.config.js 文件中,mock 的结果在 mock 文件夹下对应的 json。这样的配置在本节中就可以基本模拟真实的数据请求过程了,本节的源码在 GitHub 上。
并非所有场景都适合使用 sse 处理,在消息推送接收不频繁的情况下选用 ajax 轮询或者 sse 或者 websocket 其实差别不太大。sse 应该适用于服务端向客户端发送消息频繁而客户端几乎无需向服务端发送数据的场景下,例如:新邮件通知;订阅新闻通知;天气变化;服务器异常通知;网站公告;等等。sse 的优缺点:SSE 使用 HTTP 协议,除 IE 外的大部分浏览器都支持;SSE 属于轻量级,使用简单;SSE 默认支持断线重连;SSE 一般只用来传送文本,二进制数据需要编码后传送;SSE 支持自定义发送的消息类型。
首先需要明确 Promise 是一个类,我们在 VSCode 中输入 new Promise() 会给我们如下的提示:在 new Promise() 时需要默认需要传入一个回调函数,这个回调函数是 executor(执行器),默认会立即执行。执行器会提供两个方法(resolve 和 reject)用于改变 promise 的状态。resolve 会触发成功状态,reject 会触发失败状态,无论成功或失败都会传入一个返回值,这个返回值会在实例调用 then 方法后作为响应值获取。var promise = new Promise((resolve, reject) => { ajax(url, (data) => { resolve(data) // 成功 }, (error) => { reject(error) // 失败 })})上面的代码中实例化一个 ajax 请求的 Promise, 当接口请求成功就会调用 resolve () 方法把请求的值传入进去,如果失败了就调用 reject () 方法把错误信息传入进去。在后续的链式调用中获取相应的结果。我们需要知道的是,Promise 有三个状态:等待(pending)、成功(fulfilled),失败(rejected)。在初始化时,这个状态是等待态,在等待状态时可以转化为成功态或失败态。当状态是成功态或是失败态后不能再被改变了。上面的代码中可以改变 Promise 状态的是执行器提供的 resolve 和 reject,resolve 会将等待态变为成功态,reject 则会将等待态变为失败态,在状态变为成功或失败的状态时就不能被更改了。
服务器响应完成之后,我们通常会使用 XMLHttpRequest.status 来查看当前 XMLHttpRequest 响应中的数字状态码。这个数字状态码是一个无符号短整型状态码,代表着我们的 Ajax 请求的状态成功与否。在 XMLHttpRequest 中, status 码对应着标准的 HTTP 状态码。并且在请求完成前,该值为 0。HTTP 状态码很多,这里就不做过多的铺开,具体可以到 HTTP 响应代码 进行学习和查阅。接下来我们来讲几个常见的状态码。是的,这也是很常见的两个状态码。1.2.1 200 和 304 状态码在 HTTP 状态码中,200 代表着 HTTP 请求成功,而 304 代表着由于浏览器缓存原因,GET 请求命中并返回了缓存中的数据。结合 上面 XMLHttpRequest.readyState , 假设请求成功,我们的响应模块应该如下:xhr.open("GET", "http://localhost:8080/simple/get?mk=慕课网");xhr.send();xhr.onreadystatechange = function() { // 当前 this 为 xhr if (this.readyState == 4) { if (this.status === 200 || this.status === 304) { // code ... } }};在后端设置了协商缓存的情况下,我们来看看效果:第一次请求资源:刷新页面,进行第二次请求同样的资源:由于浏览器的缓存机制,GET请求有可能会缓存我们的请求内容。上面前后两次请求中,第一次请求的时候获取新的内容,返回的是 200 的状态码;而第二次再进行获取,我们就有可能获取第二图的结果,使用的是本地缓存。因此,在对 Ajax 成功的判断中,我们不应该遗漏 304 状态码的判断。1.2.2 404 和 500 状态码有正确的返回,那当然也会有错误的返回。打个比方,让我们来假设这样的场景:客户端发送一个请求,刚好请求的接口找不到,因为服务端并没有提供。客户端发送一个请求,服务端内部发生错误了。如果遇到这样的情况,Ajax 当然不能坐以待毙——我们总不该不把任何响应告诉用户吧!真实的情况是,Ajax 会返回我们相应的 status ,客户端根据该 status 进行必要的操作。首先,我们来请求一个捏造的接口,即服务端并没有支持的接口。html 关键容器:<div id="container"></div>JavaScript 脚本关键代码:var container = document.getElementById('container')xhr.onreadystatechange = function() { // 当前 this 为 xhr if (this.readyState == 4) { if (this.status === 200 || this.status === 304) { container.innerHTML = "当前状态码为: " + this.status; } else { container.innerHTML = "当前错误状态码为: " + this.status; // 主要看这里,出现非 200 和 304 状态会在这边进行显示 } }};看看运行后的效果图:404 Not Found,显而易见,当我们在查询的时候,服务端找不到对应的资源的时候就会返回该状态码,表示你要找的东西没有,不存在。在我们的实际工作中,我们经常会遇到这样的错误,往往这个时候你就应该警惕:是不是你的请求 url 写错了?是不是前后端线上资源不同步?比方说后端还没上线对应接口而你已经在开始在代码中请求了。讲完 404 状态码,我们接下来继续来看看一个很常见的场景,服务器内部发生错误了!!!代码沿用上一个示例,接口改为服务端提供的接口,这次我们会在服务端假设发生错误,并返回 500 错误。来看看请求的结果:事实上,500 错误码也是非常常见的,500 Internal Server Error 代表着服务端错误,如果我们在开发过程中遇到这样的错误,那么,就需要后端的同学来查找原因了。除此之外,HTTP 状态码还有很多,每个都有不同的含义,这里也不会做过多的展开,有兴趣的同学可以做一个额外的学习查阅。HTTP 协议中,状态码可以让我们在请求之后,获知请求的状态。客户端也能够以此做出相应的响应。
<!DOCTYPE html><html lang="en"> <head> <meta charset="utf-8" /> <title>ajax example</title> </head> <style> table { border-collapse: collapse; text-align: center; width: 800px; } table td, table th { border: 1px solid #b8cfe9; color: #666; height: 30px; } table tr:nth-child(odd) { background: #fff; } table tr:nth-child(even) { background: rgb(246, 255, 255); } input { outline-style: none; border: 1px solid #ccc; border-radius: 3px; padding: 5px 10px; width: 200px; font-size: 14px; } button { border: 0; background-color: rgb(87, 177, 236); color: #fff; padding: 10px; border-radius: 5px; margin-top: 20px; } </style> <body> <div id="container"> <!--------列表查询模块-------------> <div class="query"> <h3>课程列表</h3> <table id="courseTable"></table> </div> <!--------列表查询模块 结束-------------> <!--------课程录入模块-------------> <div class="create"> <h3>添加课程</h3> <div> <label for="name">课程名称:</label><br /> <input type="text" id="name" name="name" /><br /> <label for="teacher">老师:</label><br /> <input type="text" id="teacher" name="teacher" /><br /> <label for="startTime">开始时间:</label><br /> <input type="date" id="startTime" name="startTime" /><br /> <label for="endTime">结束时间:</label><br /> <input type="date" id="endTime" name="endTime" /><br /> <button id="submitBtn">点击提交</button> </div> </div> <!--------课程录入模块 结束-------------> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script> <script src="/__build__/example.js"></script> </body></html>如上所示,我们首先定义好页面的结构和样式。可以清晰看出。页面主要分为两块,上面一块展示的是所有课程的结果,并且是表格呈现的,这里的 table 标签之所以没有嵌套,是因为我们会在后面 JavaScript 部分进行插入。下面一块则是录入课程的模块,分别有 课程名称、老师、开始时间和结束时间 4 个 input 标签。
可以通过 CDN 引入 ECharts 文件:<!-- bootstrap 服务 --><!-- bootstrap 提供的免费CDN服务,亲测非常稳定 --><script src="//cdn.bootcss.com/echarts/4.5.0/echarts.common.js"></script><!-- 七牛云存储服务 --><!-- 国内速度稳定,开放性强 --><script src="//cdn.staticfile.org/echarts/4.5.0/echarts.common.js"></script><!-- jsdeliver 服务 --><!-- 微软的CDN服务,虽然国内访问速度比不上国内CDN,但速度不至于太慢,有国际化需求的可以试试 --><script src="//cdn.jsdelivr.net/npm/echarts@4.5.0/echarts.common.js"></script><!-- cdnjs 服务 --><!-- 一个非常全的CDN服务,存储了大多数主流的js、css、图片库 --><script src="//cdnjs.cloudflare.com/ajax/libs/echarts/4.5.0/echarts.common.js"></script>