为了账号安全,请及时绑定邮箱和手机立即绑定
2.3 tr 标签

tr 用于定义表格中的行,行内可以包含表头单元格 th 或者数据单元格 td, tr 标签表示一行,当中的内容可以是 th 或者 td,tr 标签支持所有的浏览器。

2.4 td 标签

表格中有两种类型的单元格,一种是上边介绍的表头单元格 th,一种是内容单元格 td。和 th 不同的是 td 的内容默认显示正常字体,使用左对齐方式。td 支持的属性基本上和 th 一致。td 必须包含在 tr 标签中才能生效。

2.2 设定表格列内容的对齐方式

在分割线上使用 「冒号 :」可以定义列内容的对齐方式。实例 2:#### 表格内容的对齐|左对齐|居中对齐|右对齐||:--|:--:|--:||1|张三|17岁||2|李四|18岁||3|王五|19岁|其渲染结果如下:其转换后的 html 的内容如下:<table><thead><tr><th align="left">左对齐</th><th align="center">居中对齐</th><th align="right">右对齐</th></tr></thead><tbody><tr><td align="left">1</td><td align="center">张三</td><td align="right">17岁</td></tr><tr><td align="left">2</td><td align="center">李四</td><td align="right">18岁</td></tr><tr><td align="left">3</td><td align="center">王五</td><td align="right">19岁</td></tr></tbody></table>

4.2 模板渲染

用 Python 生成 HTML 的过程十分繁琐,需要进行字符串拼接,代码的可读性差。假设,在一个学生管理系统中,有一个页面是展示所有学生的姓名、学号等信息,Python 后端程序通过读取数据库生成一段 HTML 文本,代码如下:def generateHtml(): html = '' html += '<table>' sql = 'SELECT * FROM students' cursor.execute(sql) students = cursor.fetchall() for student in students: html += '<tr>' html += '<td>%s</td>' % student[0] html += '<td>%s</td>' % student[1] html += '</tr>' html += '<table>' 函数 generateHtml() 通过 SQL 语句读取数据,使用字符串拼接成一段 HTML 的 table,返回给浏览器。这样的方式的缺点在于:HTML 代码和 Python 代码混合在一起,程序的可读性很差。针对以上问题,提出了模板的解决方案,实现数据与 HTML 代码分离。以下是使用 django 的模板实现展示所有的学生<table> {% for student in students %} <tr> <td>{{ student[0] }}</td> <td>{{ student[1] }}</td> </tr> {% endfor %}</table> 与拼接字符串相比,使用模板生成 HTML,显著的提高了程序的可读性,Web 框架都配备了各种模板引擎,Web 开发者需要学习模板的语法。

2.1 定义表格

Markdown 表格由 「竖线 |」、「减号 -」、「冒号 :」三种符号组成。竖线 用来定义列,每两个竖线之间为一个单元格元素;减号 用来定义分割线,也就是分割表头和数据体;冒号 配合减号使用,用于定义列数据的对齐属性。我们先假定一组表格数据:学号 姓名1 张三2 李四3 王五这其中,第一行 “学号” 和 “姓名” 就是表格的表头,“1”、“2”、“3”,“张三”、“李四”,这些都属于数据体,我们可以用以下方式展示表格内容。实例 1:#### 定义表格|学号|姓名||----|----||1|张三||2|李四||3|王五|其渲染结果如下:其转换后的 html 的内容如下:<table><thead><tr><th>学号</th><th>姓名</th></tr></thead><tbody><tr><td>1</td><td>张三</td></tr><tr><td>2</td><td>李四</td></tr><tr><td>3</td><td>王五</td></tr></tbody></table>

5. 小结

表格的结果为表头和表身,可以为表格添加标题。thead 代表表头,tbody 代表表身, thead 嵌套 tr 和 th, tbody 里嵌套 tr 和 td。tr 里只能嵌套 th 和 td, th 一般用来表示表头,有加粗的样式。 td 代表表头对应的具体数据。原生的表格样式比较丑,我们可以通过 CSS 为表格设置样式。

浮点数精度问题

有关浮点数的精度是一个老生常谈的问题了。面试题中这个知识点出现的频率非常高:0.1 + 0.2 === 0.3 // false其中可以参阅 数字 章节。这个问题很少会有面试官进一步的进行考察,如 “怎样让计算结果正确”,最常见的解决方案有两个:计算过程中将数字转成整数计算使用第三方库第一个方案很好理解,将 0.1 与 0.2 扩大 10 倍,相加后再相除就得到了正确的结果:((0.1 * 10) + (0.2 * 10)) / 10 === 0.3 // true如果存在两位小数,则同时放大 100倍,计算后再缩小 100倍 就可以了。第二个方案可以有很多选择,这里列出了几个常用的第三方库:bignumber.jsmath.jsbig.js

1.5 编辑告警邮件模板 alert.html

{{ define "alert.html" }}<table border="1"> <tr> <td>报警项</td> <td>实例</td> <td>报警内容</td> <td>开始时间</td> </tr> {{ range $i, $alert := .Alerts }} <tr> <td>{{ index $alert.Labels "alertname" }}</td> <td>{{ index $alert.Labels "instance" }}</td> <td>{{ index $alert.Annotations "description" }}</td> <td>{{ $alert.StartsAt }}</td> </tr> {{ end }}</table>{{ end }}

3. 利用表格分割页面

众所周知,初期的网页设计都是用表格来分割页面的内容和结构。那么作为一节学习表格的知识点,我们势必也要掌握一下,如何用表格来规划网页的结构,由于已经不怎么用了,所以在这里简单给大家说一下原理就可以。它的原理就是把表格单元格作为单独的区域,甚至表格与表格之间的嵌套。我们都知道,在表格中 tr 标签代表了行。td 标签代表了列。th 标签代表了标题 ( table head )。而 tr td th 又都能够设置大小,也就是长度和宽度。那么我们假设整个网页就是一个大表格,里面的形形色色的网页元素,比如图片,音频,视频,文字都分别位于这些不同尺寸的单元格里,这样就是利用表格这种 HTML 元素的特性来实现了网页内容的结构化。

3.1 文本宽度 TextMetrics.width

TextMetrics.width 说明TextMetrics.width 是当前要绘制文本在画布上将要占用的宽度。语法:ctx.measureText(value);变量说明:属性名类型说明valueString将要绘制的文本字符串。

2. 计算文本宽度

canvas 为我们提供了一个计算文本宽度的方法:measureText 方法,方法返回一个 TextMetrics 对象,包含关于文本尺寸的信息,里面就有文本宽度。我们看一个案例:1499运行结果:上面案例中,我们封装了一个函数,可以绘制自适应大小的按钮,让我们拆分讲解一下主要代码。先看绘制按钮的封装函数 drawBtn,函数传入了三个参数,分别是绘制文本以及绘制文本的起点坐标 (x, y)。function drawBtn(str, x, y ){ ...}(1)获取到绘制文本的宽度和高度,其实文本的高度就是设置的文本字体的大小值,这里我们用了一个小技巧拿到了设置的文本大小。 var w = ctx.measureText(str).width; // 获取到绘制文本的宽度w var h = parseInt(ctx.font) // 通过小技巧获取到了文本高度h(2)设置按钮背景为 #456795 这个颜色,并且绘制了一个矩形,这里矩形坐标我们向左上方移动了10个像素,目的是给按钮添加一个内边距,美观一些,因为左侧有10个像素边距,右侧也有10个像素边距,所以我们给背景的矩形框的长度增加了20个像素,高度同理,也增加了20个像素。 ctx.fillStyle="#456795"; ctx.fillRect(x-10,y-10,w+20,h+20)(3)设置文字的颜色为白色,把文字的基线设置为 hanging,这样做的目的是将文本左上角和基线对齐,方便计算,我们也可以设置为其他值,不过计算起来会比较麻烦。 ctx.fillStyle="#fff"; ctx.textBaseline="hanging"(4)绘制文本。 ctx.fillText(str,x,y)设置字体大小为16像素,调用封装函数绘制文字。 ctx.font="16px 微软雅黑"; drawBtn("慕课Wiki",40,40)设置字体大小为18像素,调用封装函数绘制文字。 ctx.font="18px 微软雅黑"; drawBtn("Imooc教程 Hello World", 40, 90)设置字体大小为20像素,调用封装函数绘制文字。 ctx.font="20px 微软雅黑"; drawBtn("确认", 40, 140)

1.3 HTML 标签

1.3.1 标签属性每一个 html 标签都可以定义很多属性,用来标识这个 标签,也方便后面我们使用属性定位方式找到这个标签。属性是卸载标签内部的,比如下面的 <a> 标签中定义了一个 href 属性:<a href="www.baidu.com">百度一下,你就知道</a>这个是链接属性,当点击这个 百度一下 文本时会跳转到百度的主页。当然标签中比较重要和常用的属性有:id:用于表示元素的唯一性,方便 css 选择器或者 js 找到该元素;class:给元素添加一个或者多个样式,多个样式使用空格隔开,比如下面定义了两个 class 样式,div 元素中包含这两个样式:...<style type="text/css">.margin-lass { margin-left:10px text-align: center}.text-class { font-size: 12px}</style>...<div class="margin-class text-class" ></div>...style:规定元素的行内样式:<div style="color:red; float:left"></div>title:当鼠标移到该元素上时,显示的提示信息<div title="这是提示信息">包含提示的文本</div>1.3.2 链接元素HTML 中的链接元素为 <a>,它能让我们从一个网页链接到另一个网页或者锚位置,只需要设置 href 属性即可。在互联网中各种网站上的链接的地址都是用的 <a> 标签。如淘宝网站上的各种商品链接:淘宝首页商品链接<a> 元素的两个重要属性分别为:href:点击该元素时,链接的地址;target:值有 _blank|_parent|_self|_top,它规定了跳转到新页面的位置;1.3.3 容器元素HTML 中的容器元素为 <div>,它是一个块级元素,会自动开启一个新行,主要是用来固定一块区域。示例:<div id="node" style="color:#FF0000" class="test">这是一个div容器</div>1.3.4 标题元素HTML 中的标题元素有 <h1>-<h6>,它们规定了网页主体或者段落的标题,而且是从大到小的顺序。示例:<h1>这是h1标题</h1><h2>这是h2标题</h2><h3>这是h3标题</h3><h4>这是h4标题</h4><h5>这是h5标题</h5><h6>这是h6标题</h6>效果图:实例效果图1.3.5 表格元素HTML 中 <table> 标签用来在网页上绘制表格。简单的 HTML 表格由 table 元素以及一个或多个 tr、th 或 td 元素组成。tr 元素定义表格行,th 元素定义表头,td 元素定义表格单元。示例:<table border="1" width="400px" style="text-align: center;"> <thead> <th>列1</th> <th>列2</th> <th>列3</th> <th>列4</th> </thead> <tbody> <tr style="background-color: red"> <td>行1值1</td> <td>行1值2</td> <td>行1值3</td> <td>行1值4</td> </tr> <tr> <td>行2值1</td> <td>行2值2</td> <td>行2值3</td> <td>行2值4</td> </tr> </tbody></table>我们使用 <thead> 定义表头部,用 <tbody> 定义表格的内容。在 <table> 标签中定义了整个表格的属性,包括表格宽度为 400px,单元格内文字居中显示,以及单元之间的间距为 1px。此外在第一行的表格中设置背景颜色为空色,最终得到的结果图如下:表格效果图1.3.6 列表元素HTML 中的列表标签有 <ul> 和 <ol>,分别表示无序和有序列表,列表元素的标签有 li。示例:<ul><li>老虎</li><li>狮子</li><li>蛇</li></ul><ol start="20"><li>老虎</li><li>狮子</li><li>蛇</li></ol>效果图:列表效果图1.3.7 表单元素表单元素是网页布局中非常重要的一个元素。比如我们可以在各种论坛、填写个人信息、登录等网页上看到 <form> 元素。比如慕课网的登录页面:慕课网登录页面示例在 <form> 表单中我们会包含许多元素,比如输入框元素 <input>、按钮元素 <button> 等等。现在我们来简单实现一个登录表单,示例代码如下:<form><div><span>账号:</span><input type="text" style="margin-bottom: 10px" placeholder="请输入登录手机号/邮箱" /></div><div><span>密码:</span><input type="password" style="margin-bottom: 10px" placeholder="请输入密码" /></div><div><label style="float: left;font-size: 10px; color: grey"><input type="checkbox" checked="checked"/>7天自动登录</label></div></form>效果图:登录注册效果图除了上面这些常用元素之外,还有许多其他的 HTML 元素以及元素对应的属性值,需要在使用的时候去搜索相关资料。有了这些知识,对网页会有一个初步的认识,接下来学习如何让静态的网页变得丰富多彩。

3.3 宽度自适应

ECharts 图表不具备响应式特性,初次渲染后不会因为容器尺寸的变化做自适应调节,需要用户自行监听屏幕尺寸的变化,并随之调用 resize 函数,函数签名:(opts?: { width?: number | string, height?: number | string, silent?: boolean }) => ECharts;参数:width: 显式指定实例宽度,单位为像素。如果传入值为 null/undefined/'auto',则表示自动取 dom(实例容器)的宽度;height: 显式指定实例高度,单位为像素。如果传入值为 null/undefined/'auto',则表示自动取 dom(实例容器)的高度;silent: 是否禁止抛出事件。为了实现图表元素响应屏幕尺寸的变化,通常可以加入如下代码片段:window.addEventListener('resize', myChart.resize);增加上述代码片段后,在 SPA 场景下,当图表随页面跳转而析构后务必移除对应的事件监听,否则 ECharts 实例对象会一直被事件系统保留引用,导致内存泄漏!但是 ECharts 并没有暴露示例的析构事件,处理时机只能由开发者自行把握,以 vue 为例,推荐的用法:Vue.component('HelloWorld', { mounted() { this._ec = echarts.init(xxx); window.addEventListener('resize', this._ec.resize); }, beforeDestroy() { window.removeEventListener('resize', this._ec.resize); },});

2. 表格的使用

想要编写表格,需要用到表格的一组标签。table 标签表示表格整体,类似 ul 和 ol 表示列表整体一样。在 table 标签里, thead标签表示表头, tbody 标签表示表示。 在 table 表头中, tr 标签代表行, th 标签代表表头的每一项。在 tbody 标签中, tr 标签代表行, td 标签代表每一个表头对应的具体数据。代码如下: <table> <!-- thead 代表表头 --> <thead> <!-- tr代表表头这一行 --> <tr> <!-- th代表表头的每一项 会有加粗的效果 --> <th>姓名</th> <th>年龄</th> <th>性别</th> </tr> </thead> <!-- tbody 代表表身 表格的具体内容 --> <tbody> <!-- tr代表表身的每一行 --> <tr> <!-- td代表对应表头的具体数据 --> <td>小明</td> <td>20</td> <td>男</td> </tr> <tr> <td>小红</td> <td>18</td> <td>女</td> </tr> </tbody> </table>效果如下:我们可以给表格添加 border属性给表格添加边框,border属性的值为正整数,默认为 0,则无边框,我们把border 设置为 1,代码如下:<table border='1'> <!-- 代码和上面演示代码一致 --> ...</table>则会呈现以下效果:我们还可以给 table 设置cellpadding来使用单元格填充来创建单元格内容与其边框之间的空白,cellpadding值也是正整数,我们把表格的 cellpadding设置为 10,代码如下:<table border='1' cellpadding='10'> <!-- 代码和上面演示代码一致 --> ...</table>则效果如下:我们还可以给 table 设置cellspacing来设置单元格与单元格直接的距离,cellspacing值也是正整数,我们把表格的 cellspacing设置为 10,代码如下:<table border='1' cellspacing='10'> <!-- 代码和上面演示代码一致 --> ...</table>效果如下:我们也可以为表格添加标题,那么我们就需要在 thead 标签前加上 caption 标签,而 caption 标签的内容则是表格的标题,代码如下:<table> <caption>学生表</caption> <!-- 代码和上面演示代码一致 --> ...</table>效果如下:

1. HTML 基础

HTML(HyperText Markup Language)是一种超文本标记语言;CSS(Cascading Style Sheets)简称为层叠样式表。大家如果只是看这两个名字的定义,估计仍然是一头雾水。这俩东西到底是干啥的?我给大家举一个例子就明白了,大家应该都画过画吧,即使没有亲自画过应该也见过。毕竟吃猪肉和见猪跑你总得占一样吧。在画画的时候,我们首先会拿画笔勾勒出图形的样式骨架,之后会涂上一些好看的颜色让画变的更美观。我们写网页的时候也是一样的道理,HTML 就相当于画笔,用于勾勒页面的骨架。CSS 则是好看的颜色,让页面的样式更加美观。下面我们来认识一下页面的骨架 HTML:Tips: 可以新建一个 txt 文件,将下面的代码复制进去,然后将文件的后缀名改为 .html ,然后使用浏览器打开就可以看到效果:HTML 的标题<!DOCTYPE html> <html> ​ <head> <title>欢迎访问慕课网</title> <head> ​ <body> ​ <h>这是一个标题</h> <h3> <bold>这是一个加粗的标题</bold> </h3> ​ <!-- 段落 --> <P>这是一个段落</P> <button>这是一个按钮</button> </body> ​ </html>效果如下图所示:HTML 中添加图片<!DOCTYPE html><html><head> <title>欢迎访问慕课网</title> <head> <body> <h>这是一个标题</h> <h3> <bold>这是一个加粗的标题</bold> </h3> <!-- 段落 --> <P>这是一个段落</P> <button>这是一个按钮</button> <!-- 下面是慕课网图片 --> <img src="https://www.imooc.com/static/img/index/logo.png" alt="图片无法显示!"> <!-- 超链接 --> <a href="https://www.imooc.com/">慕课网</a> </body></html>效果如下图所示:有序列表和无序列表<!DOCTYPE html><html><head> <title>欢迎访问慕课网</title> <head> <body> <h>这是一个标题</h> <h3> <bold>这是一个加粗的标题</bold> </h3> <!-- 段落 --> <P>这是一个段落</P> <br> <br> <br> <button>这是一个按钮</button> <!-- 下面是慕课网图片 --> <img src="https://www.imooc.com/static/img/index/logo.png" alt="图片无法显示!"> <img src="h" alt="图片无法显示!"> <!-- 属性 href --> <a href="https://www.imooc.com/">慕课网</a> <!-- 列表 --> <ul> <li>无序列表</li> <li>无序列表</li> <li>无序列表</li> <li>无序列表</li> <li>无序列表</li> </ul> <ol> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> </ol> </body></html>效果如下图所示:表格<!DOCTYPE html><html><head> <title>欢迎访问慕课网</title> <head> <body> <h>这是一个标题</h> <h3> <bold>这是一个加粗的标题</bold> </h3> <!-- 段落 --> <P>这是一个段落</P> <br> <br> <br> <button>这是一个按钮</button> <!-- 下面是慕课网图片 --> <img src="https://www.imooc.com/static/img/index/logo.png" alt="图片无法显示!"> <img src="h" alt="图片无法显示!"> <!-- 属性 href --> <a href="https://www.imooc.com/">慕课网</a> <!-- 列表 --> <ul> <li>无序列表</li> <li>无序列表</li> <li>无序列表</li> <li>无序列表</li> <li>无序列表</li> </ul> <ol> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> </ol> <!-- 表格 --> <table> <thead> <tr> <td>a</td> <td>b</td> <td>c</td> </tr> </thead> <tbody> <tr> <td>001</td> <td>002</td> <td>003</td> </tr> </tbody> </table> </body></html>效果如下图所示:表单<!DOCTYPE html><html><head> <title>欢迎访问慕课网</title> <head> <body> <h>这是一个标题</h> <h3> <bold>这是一个加粗的标题</bold> </h3> <!-- 段落 --> <P>这是一个段落</P> <br> <br> <br> <button>这是一个按钮</button> <!-- 下面是慕课网图片 --> <img src="https://www.imooc.com/static/img/index/logo.png" alt="图片无法显示!"> <img src="h" alt="图片无法显示!"> <!-- 属性 href --> <a href="https://www.imooc.com/">慕课网</a> <!-- 列表 --> <ul> <li>无序列表</li> <li>无序列表</li> <li>无序列表</li> <li>无序列表</li> <li>无序列表</li> </ul> <ol> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> <li>有序列表</li> </ol> <!-- 表格 --> <table> <thead> <tr> <td>a</td> <td>b</td> <td>c</td> </tr> </thead> <tbody> <tr> <td>001</td> <td>002</td> <td>003</td> </tr> </tbody> </table> <!-- 表单 --> <form> <div> <label>aaaa</label> <input type="text" name="ssss" placeholder="ssssss"> </div> <input type="submit" name="submit" value="提交"> </form> </body></html>网页显示结果如下:

5. 前端页面开发

本节主要介绍 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); } }); }

4. 真实案例分享

北京大学官网<table> <thead> <tr> <th>课号</th> <th>课程名称</th> <th>开课单位</th> <th>学分</th> <th>总周数<br>(起止周)</th> <th>课程类型</th> <th>上课时间</th> <th>班号</th> <th>上课教师</th> <th>备注</th> </tr> </thead> <tbody> <tr> <td>01132632</td> <td><p style="text-align:center;padding:5px;"><a href="http://elective.pku.edu.cn/elective2008/edu/pku/stu/elective/controller/courseDetail/getCourseDetail.do?kclx=BK&course_seq_no=BZ1920301132632_15745" target="_blank">生物化学讨论课<br>Current topics on Biochemistry</a></p></td> <td>生命科学学院</td> <td>2</td> <td>2(1-2)</td> <td>A</td> <td><p>星期一(第10节-第12节)</br>星期二(第10节-第12节)</br>星期三(第10节-第12节)</br>星期四(第10节-第12节)</br>星期五(第10节-第12节)</br>星期六(第10节-第12节)</br></p></td> <td>1</td> <td>钟上威</td> <td><p>6月29-7月7日,10-12节,选修同学需先修生物化学理论课</p></td> </tr> <tr> <td>01132022</td> <td><p><a href="http://elective.pku.edu.cn/elective2008/edu/pku/stu/elective/controller/courseDetail/getCourseDetail.do?kclx=BK&course_seq_no=BZ1920301132022_18636" target="_blank">遗传学讨论<br>Current topics on Genetics</a></p></td> <td>生命科学学院</td> <td>2</td> <td>2(3-4)</td> <td>A</td> <td><p>星期一(第10节-第12节)</br>星期二(第10节-第12节)</br>星期三(第10节-第12节)</br>星期四(第10节-第12节)</br>星期五(第10节-第12节)</br></p></td> <td>1</td> <td>范六民</td> <td><p>上课时间:7月13日-7月24日,10-12节。选修同学需先修遗传学理论课</p></td> </tr> </tbody></table>

3. 注意事项

tr 标签只能嵌套 th 和 td 标签,不能嵌套其他标签;tr 代表表格的每一行;td 标签的内容必须和表头的信息对应。

4. 查询结果模板

查询结果页面返回 Redis 数据库中全部的键值对,页面模板文件 templates/query.html 的内容如下:<html><head><meta charset="UTF-8"></head><body><h1>全部的键值对</h1><table border=1 cellpadding=0> <tr> <td>键</td> <td>值</td> </tr> {% for key in dict %} <tr> <td>{{ key }}</td> <td>{{ dict[key] }}</td> </tr> {% endfor %}</table><br><a href="/">返回主页</a></body></html>在第 8 行,定义了一个 table,使用 table 显示全部的键值对,table 包含 2 列,第 1 列显示键,第 2 列显示值;在第 13 行,Flask 程序传递给页面模板一个参数 dict,参数 dict 包含有 Redis 数据库的键值对,使用 for 循环显示 dict 中的键值对。假设 Redis 数据库中包括 3 个键值对:‘www’:‘WWW’;‘imooc’:‘IMOOC’;‘com’:‘COM’。则 /query 界面如下图所示:

5. 示例

给 demo 增加右上和左下的圆角<div class="demo"></div>可以这样.demo{ border-radius:0 8px 0 8px; }推荐第一种写法,但是也可以这样.demo{ border-top-right-radius:8px; border-bottom-left-radius:8px;}效果图demo 增加右上和左下的圆角效果图制作一个带有圆角的卡片<div class="card"> <div class="text"> demo1 </div></div>.card{ background: red; width: 100px; height: 200px; line-height: 200px; text-align: center; border-radius: 6px;}.text{ display: inline-block; width: 50px; height: 50px; line-height: 50px; background: #fff; border-radius: 50%;}效果图带有圆角的卡片效果图给一个 table 增加圆角左上和右上是 8px 右下和左下是直角<table> <tr> <td>姓名</td><td>年龄</td> </tr> <tr> <td>demo</td><td>19</td> </tr></table>table{ background: red; border-radius: 8px 8px 0 0; font-size: 18px; color: #fff; border-collapse: collapse; overflow: hidden;}`table` 增加圆角左上和右上是 8px 右下和左下是直角效果图

2.4 表单输出

Form 对象的另一个作用是将自身转为HTML。为此,我们只需简单地使用 print() 方法就可以看到 From 对象的 HTML 输出。(django-manual) [root@server first_django_app]# python manage.py shellPython 3.8.1 (default, Dec 24 2019, 17:04:00) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linuxType "help", "copyright", "credits" or "license" for more information.(InteractiveConsole)>>> from hello_app.views import LoginForm>>> from django import forms>>> f = LoginForm()>>> print(f)<tr><th><label for="id_name">账号:</label></th><td><input type="text" name="name" class="input-text" placeholder="请输入登录账号" minlength="4" required id="id_name"></td></tr><tr><th><label for="id_password">密码:</label></th><td><input type="password" name="password" class="input-text" placeholder="请输入密码" maxlength="20" minlength="6" required id="id_password"></td></tr><tr><th><label for="id_save_login">7天自动登录:</label></th><td><input type="checkbox" name="save_login" value="checked" class="checkbox" id="id_save_login" checked></td></tr>如果表单绑定了数据,则 HTML 输出包含数据的 HTML 文本,我们接着上面的 shell 继续执行:>>> login = LoginForm({'name': 'te', 'password': 'spyinx1111', 'save_login': False})>>> print(login)<tr><th><label for="id_name">账号:</label></th><td><ul class="errorlist"><li>账号名最短4位</li></ul><input type="text" name="name" value="te" class="input-text" placeholder="请输入登录账号" minlength="4" required id="id_name"></td></tr><tr><th><label for="id_password">密码:</label></th><td><ul class="errorlist"><li>密码需要包含大写、小写和数字</li></ul><input type="password" name="password" value="spyinx1111" class="input-text" placeholder="请输入密码" maxlength="20" minlength="6" required id="id_password"></td></tr><tr><th><label for="id_save_login">7天自动登录:</label></th><td><input type="checkbox" name="save_login" class="checkbox" id="id_save_login"></td></tr> >>> login = LoginForm({'name': 'texxxxxxxx', 'password': 'SPYinx123456', 'save_login': False})>>> print(login)<tr><th><label for="id_name">账号:</label></th><td><input type="text" name="name" value="texxxxxxxx" class="input-text" placeholder="请输入登录账号" minlength="4" required id="id_name"></td></tr><tr><th><label for="id_password">密码:</label></th><td><input type="password" name="password" value="SPYinx123456" class="input-text" placeholder="请输入密码" maxlength="20" minlength="6" required id="id_password"></td></tr><tr><th><label for="id_save_login">7天自动登录:</label></th><td><input type="checkbox" name="save_login" class="checkbox" id="id_save_login"></td></tr>上面我们测试了两种情况,一种错误数据,一种是正常情况显示的 HTML。此默认输出的每个字段都有一个<tr>。需要注意以下几点:输出不会包括 <table> 和 </table> 以及 <form> 和 </form>,这些需要我们自己写到模板文件中去;每一个 Field 类型都会被翻译成一个固定的 HTML 语句,比如 CharField 表示的是 <input type="text">,EmailField 对应着 <input type="email"> , BooleanField 对应着 <input type="checkbox">;上面 HTML 代码里的 input 元素中的 name 属性值会从对应 Field 的属性值直接获取;每个字段的文本都会有 <label> 元素,同时会有默认的标签值 (可以在 Field 中用 label 参数覆盖默认值);另外,Form 类还提供了以下几个方法帮我们调整下 Form 的输出 HTML:Form.as_p():将表单翻译为一系列 <p> 标签;Form.as_ul():将表单翻译成一系列的 <li> 标签;Form.as_table():这个和前面 print() 的结果一样。实际上,当你调用 print() 时,内部实际上时调用 as_table() 方法。对上面的三个方法我们先进行实操演练,然后在看其源码,这样能更好的帮助我们理解这三个方法调用背后的逻辑。其操作过程和源码如下:>>> from hello_app.views import LoginForm>>> from django import forms>>> login = LoginForm({'name': 'texxxxxxxx', 'password': 'SPYinx123456', 'save_login': False})>>> login.as_p()'<p><label for="id_name">账号:</label> <input type="text" name="name" value="texxxxxxxx" class="input-text" placeholder="请输入登录账号" minlength="4" required id="id_name"></p>\n<p><label for="id_password">密码:</label> <input type="password" name="password" value="SPYinx123456" class="input-text" placeholder="请输入密码" maxlength="20" minlength="6" required id="id_password"></p>\n<p><label for="id_save_login">7天自动登录:</label> <input type="checkbox" name="save_login" class="checkbox" id="id_save_login"></p>'>>> login.as_ul()'<li><label for="id_name">账号:</label> <input type="text" name="name" value="texxxxxxxx" class="input-text" placeholder="请输入登录账号" minlength="4" required id="id_name"></li>\n<li><label for="id_password">密码:</label> <input type="password" name="password" value="SPYinx123456" class="input-text" placeholder="请输入密码" maxlength="20" minlength="6" required id="id_password"></li>\n<li><label for="id_save_login">7天自动登录:</label> <input type="checkbox" name="save_login" class="checkbox" id="id_save_login"></li>'>>> login.as_table()'<tr><th><label for="id_name">账号:</label></th><td><input type="text" name="name" value="texxxxxxxx" class="input-text" placeholder="请输入登录账号" minlength="4" required id="id_name"></td></tr>\n<tr><th><label for="id_password">密码:</label></th><td><input type="password" name="password" value="SPYinx123456" class="input-text" placeholder="请输入密码" maxlength="20" minlength="6" required id="id_password"></td></tr>\n<tr><th><label for="id_save_login">7天自动登录:</label></th><td><input type="checkbox" name="save_login" class="checkbox" id="id_save_login"></td></tr>'# 源码位置:django/forms/forms.py# ...@html_safeclass BaseForm: # ... def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row): "Output HTML. Used by as_table(), as_ul(), as_p()." top_errors = self.non_field_errors() # Errors that should be displayed above all fields. output, hidden_fields = [], [] for name, field in self.fields.items(): html_class_attr = '' bf = self[name] bf_errors = self.error_class(bf.errors) if bf.is_hidden: if bf_errors: top_errors.extend( [_('(Hidden field %(name)s) %(error)s') % {'name': name, 'error': str(e)} for e in bf_errors]) hidden_fields.append(str(bf)) else: # Create a 'class="..."' attribute if the row should have any # CSS classes applied. css_classes = bf.css_classes() if css_classes: html_class_attr = ' class="%s"' % css_classes if errors_on_separate_row and bf_errors: output.append(error_row % str(bf_errors)) if bf.label: label = conditional_escape(bf.label) label = bf.label_tag(label) or '' else: label = '' if field.help_text: help_text = help_text_html % field.help_text else: help_text = '' output.append(normal_row % { 'errors': bf_errors, 'label': label, 'field': bf, 'help_text': help_text, 'html_class_attr': html_class_attr, 'css_classes': css_classes, 'field_name': bf.html_name, }) if top_errors: output.insert(0, error_row % top_errors) if hidden_fields: # Insert any hidden fields in the last row. str_hidden = ''.join(hidden_fields) if output: last_row = output[-1] # Chop off the trailing row_ender (e.g. '</td></tr>') and # insert the hidden fields. if not last_row.endswith(row_ender): # This can happen in the as_p() case (and possibly others # that users write): if there are only top errors, we may # not be able to conscript the last row for our purposes, # so insert a new, empty row. last_row = (normal_row % { 'errors': '', 'label': '', 'field': '', 'help_text': '', 'html_class_attr': html_class_attr, 'css_classes': '', 'field_name': '', }) output.append(last_row) output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender else: # If there aren't any rows in the output, just append the # hidden fields. output.append(str_hidden) return mark_safe('\n'.join(output)) def as_table(self): "Return this form rendered as HTML <tr>s -- excluding the <table></table>." return self._html_output( normal_row='<tr%(html_class_attr)s><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>', error_row='<tr><td colspan="2">%s</td></tr>', row_ender='</td></tr>', help_text_html='<br><span class="helptext">%s</span>', errors_on_separate_row=False, ) def as_ul(self): "Return this form rendered as HTML <li>s -- excluding the <ul></ul>." return self._html_output( normal_row='<li%(html_class_attr)s>%(errors)s%(label)s %(field)s%(help_text)s</li>', error_row='<li>%s</li>', row_ender='</li>', help_text_html=' <span class="helptext">%s</span>', errors_on_separate_row=False, ) def as_p(self): "Return this form rendered as HTML <p>s." return self._html_output( normal_row='<p%(html_class_attr)s>%(label)s %(field)s%(help_text)s</p>', error_row='%s', row_ender='</p>', help_text_html=' <span class="helptext">%s</span>', errors_on_separate_row=True, ) 可以看到,转换输出的三个方法都是调用 _html_output() 方法。这个方法总体上来看代码量不大,涉及的调用也稍微有点多,但是代码的逻辑并不复杂,是可以认真看下去的。给大家留个思考题:上面的 _html_output() 方法在哪一步将 field 转成对应的 html 元素的,比如我们前面提到的 CharField 将会被翻译成 <input type="text">这样的 HTML 标签。这个答案我将会在下一小节中给大家解答。

4.2 表格更新

const updateTable = (data = []) => { const table = document.querySelector('#courseTable'); // 获取页面的table // table 的项, 这里初始化会添加表格头 let tableItems = [ `<tr> <th>课程名称</th> <th>老师</th> <th>开始时间</th> <th>结束时间</th> </tr>`]; // 对数据做一个遍历, 处理为 HTML 字符串并添加到对应的 tableItems 的项中。 data.forEach(item => { const {name, teacher, startTime, endTime} = item tableItems.push( `<tr> <td>${name}</td> <td>${teacher}</td> <td>${moment(startTime).format('YYYY-MM-DD')}</td> <td>${moment(endTime).format('YYYY-MM-DD')}</td> </tr>` ); table.innerHTML = tableItems.join(''); // 数组转为字符串, 添加到 table 中 })}表格更新函数接收一个数组数据,并且会遍历这个数组,分别把每一项处理为 HTML 字符串并添加到 tableItems 中。最后会把 tableItems 这个数组 通过 .join('') 的方式转化及拼接为一个 HTML 字符串,并添加到 table 中。

2.1 HTML 基础

HTML称为超文本标记语言,是一种标识性的语言。它包括一系列标签.通过这些标签可以将网络上的文档格式统一,使分散的Internet资源连接为一个逻辑整体。HTML文本是由HTML命令组成的描述性文本,HTML命令可以说明文字,图形、动画、声音、表格、链接等HTML 不是一门编程语言,而是一种用于定义内容结构的标记语言。HTML 由一系列的元素(elements)组成,这些元素可以用来包围不同部分的内容,使其以某种方式呈现或者工作。 一对标签( tags)可以为一段文字或者一张图片添加超链接,将文字设置为斜体,改变字号,等等。学习 HTML 需要学习各种标签,例如使用 table 标签描述了一个 2 行 2 列的表格:<table> <tr> <td>第 1 行 第 1 列</td> <td>第 1 行 第 2 列</td> </tr> <tr> <td>第 2 行 第 1 列</td> <td>第 2 行 第 2 列</td> </tr></table>table 标签描述了一个表格tr 标签描述表格中的一行td 标签描述表格中的一个单元 第 1 行 第 1 列 第 1 行 第 2 列 第 2 行 第 1 列 第 2 行 第 2 列

1. ListView 类视图介绍和使用

ListView 类从名字上看应该是处理和列表相关的视图,事实也是如此。我们同样基于前面 TemplateView 中实现的例子,使用 ListView 来减少代码,体验下 ListView 视图类给我们带来的便捷。实验1:重现 TemplateView 功能;首先我们完成前面 TemplateView 的简单功能,然后在提出几个报错的问题,这些问题比较简单,只要看下报错位置和源码信息就非常清楚了。首先我先给出一个基本的知识:ListView 具备 TemplateView 所有的功能与属性,并做了许多扩展。那么前面由TemplateView 实现的所有示例直接将 TemplateView 替换成 ListView 也是可以运行的?我们以最简单的一个模板例子进行演示:在 hello_app/views.py 中新增一个视图类 TestListView1:(django-manual) [root@server first_django_app]# cat templates/test1.html <p>{{ content }}</p><div>{{ spyinx.age }}</div>class TestListView1(ListView): template_name = 'test1.html'在 hello_app/urls.py 中新增一个 URLConf 配置:urlpatterns = [ # ... path('test_list_view1/', views.TestListView1.as_view(extra_context=context_data), name='test_list_view1')]使用 runserver 命令启动后,请求对应的 URL 地址,发现异常,错误原因也很明显,缺少queryset。上面的出错是在父类的 get() 方法中,那么修改 hello_app/views.py 位置的视图类 TestListView1,重新定义自己的 get() 方法,如下:class TestListView1(ListView): template_name = 'test1.html' def get(self, request, *args, **kwargs): return self.render_to_response(context={'content': '正文1', 'spyinx': {'age': 29}})启动服务后同样报错,不过这次错误不一样了,如下:同样显示的是没有对象列表。我们通过查看源码也能轻易解决这个问题。这个问题留到后面分析原源码的时候去解决。现在直接给出两个报错的解决方案,如下:# 解决第一个没有自定义get()函数报错class TestListView1(ListView): template_name = 'test1.html' queryset = Member.objects.all() # 另一种写法也是可以的 # model = Member # 解决第二个自定义get()函数报错class TestListView1(ListView): template_name = 'test1.html' object_list = Member.objects.all() def get(self, request, *args, **kwargs): return self.render_to_response(context={'content': '正文1', 'spyinx': {'age': 29}})最后正确的结果如下,这里直接用 curl 命令请求结果显示即可。[root@server ~]# curl http://127.0.0.01:8888/hello/test_list_view1/<p>正文1</p><div>29</div>实验2:简化分页代码。同样前面 TemplateView 做的那个显示会员列表的基础上,简化原来的代码。准备原来的模板文件,修改分页那块代码:(django-manual) [root@server first_django_app]# cat templates/test.html<html><head><style type="text/css"> .page{ margin-top: 10px; font-size: 14px; } .member-table { width: 50%; text-align: center; }</style></head><body><p>会员信息-第{{ page_obj.number }}页/共{{ paginator.num_pages }}页, 每页{{ paginator.per_page }}条, 总共{{ paginator.count }}条</p><div><table border="1" class="member-table"> <thead> <tr> <th>姓名</th> <th>年龄</th> <th>性别</th> <th>职业</th> <th>所在城市</th> </tr> </thead> <tbody> {% for member in members %} <tr> <td>{{ member.name }}</td> <td>{{ member.age }}</td> {% if member.sex == 0 %} <td>男</td> {% else %} <td>女</td> {% endif %} <td>{{ member.occupation }}</td> <td>{{ member.city }}</td> </tr> {% endfor %} </tbody></table><div ><div class="page"></div></div></div></body></html>添加一个新的 ListView 视图类,如下:class TestListView2(ListView): template_name = 'test.html' model = Member queryset=Member.objects.all() paginate_by = 10 ordering = ["-age"] context_object_name = "members"注意:ordering 是设置显示列表的排序字段,字符串前面的 “-” 号表示的是按照这个字段倒序排列,可以设置多个排序字段。context_object_name 一定要设置,对应模板文件中的列表数据名。添加 URLConf 配置:urlpatterns = [ # ... path('test_list_view2/', views.TestListView2.as_view(), name='test_list_view2')]启动 first_django_app 工程,从浏览器上直接访问这个 url,就能看到和前面差不多的结果了。可以传入 page 参数控制第几页,但是页大小在视图中已经固定,无法改变。从这个简单的例子,我们可以看到,相比前面用 TemplateView 手工对数据进行分页,这里的 ListView 内部已经给我们实现了这样的功能。我们只需要简单的配置下,设置好相关属性,就能够实现对表的分页查询,这样能节省重复的代码操作,让项目看起来简洁优雅。但是我们一定要了解背后实现的逻辑,能看得懂源码,这样每一步的报错,我们都能在源码中找到原因,并迅速解决问题。接下来就是对 ListView 视图类源码的学习与分析。

3.1 适用问题

首先,在利用动态规划算法之前,我们需要清楚哪些问题适合用动态规划算法求解。一般而言,能够利用动态规划算法求解的问题都会具备以下两点性质:最优子结构: 利用动态规划算法求解问题的第一步就是需要刻画问题最优解的结构,并且如果一个问题的最优解包含其子问题的最优解,则此问题具备最优子结构的性质。因此,判断某个问题是否适合用动态规划算法,需要判断该问题是否具有最优子结构。Tips: 最优子结构的定义主要是在于当前问题的最优解可以从子问题的最优解得出,当子问题满足最优解之后,才可以通过子问题的最优解获得原问题的最优解。重叠子问题: 适合用动态规划算法去求解的最优化问题应该具备的第二个性质是问题的子问题空间必须足够” 小 “,也就是说原问题递归求解时会重复相同的子问题,而不是一直生成新的子问题。如果原问题的递归算法反复求解相同的子问题,我们就称该最优化问题具有重叠子问题。Tips: 在这里,我们需要注意是,与适用动态规划算法去求解的问题具备重叠子问题性质相反,前面我们介绍的分治算法递归解决问题时,问题的子问题都是互不影响,相互独立的,这个也是我们在选用动态规划算法还是分治法解决问题时的一个判断条件。

1. 创建一个简单的表格

网页中定义表格使用 table 标签,它是一个闭合标签,所有内容都放在 table 的起始标签和结束标签内。在表格中定义一行数据使用 tr 标签,表格的单元格内容放在 tr 标签内,单元格又分为表头 th 和表格单元格 td。基本表格结构如下。976代码解释:以上是 thinkPHP 手册中的关于助手函数的表格示例,其中包含了表头 thead 、内容 tbody、行元素 tr、表头单元格 th、表格单元格 td。

2.4 前端应用部署

前端应用的部署更加简单,我们直接在云服务器上下载 nginx 然后解压。打开网址 http://nginx.org/en/download.html ,点击下图中的链接下载即可。nginx 下载链接下载解压后,将前端页面直接放到 nginx/html 目录下即可。当然如果有很多网页,可以先在该目录下建立子目录便于归类网页。我们建立 shop-front 目录(表示商城系统的前端项目),然后将网页放入其中,效果如下:商城系统前端项目目录内容注意还需要修改 goods.html 中访问的后端 URL 地址,假设云服务器的公网 IP 为 x.x.x.x ,则修改为:实例:$.ajax({ type: "GET", url: "http://x.x.x.x:8080/goods", //后端接口地址 dataType: "json", contentType: "application/json; charset=utf-8", success: function (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 += "</tr>"; $("#goodsTable").append(row); }); }, error: function (err) { console.log(err); } });此处解释下后端地址 http://x.x.x.x:8080/goods , HTTP 代表协议, x.x.x.x 代表云服务器公网地址, 8080 是我们后端项目的启动端口,由于我们没有在配置文件中设置,所以默认就是 8080 ,最后 goods 是控制器中设定的后端接口路径。双击 nginx.exe 启动 nginx ,由于 nginx 默认启动端口是 80 ,所以此时访问 http://x.x.x.x ,效果如下,说明 nginx 启动成功!nginx 已启动成功

2. 案例

有了上面的基础之后,我们来完成一个简单的权限管理案例。首先添加3条告警记录,用于作为后面测试数据:(django-manual) [root@server first_django_app]# python manage.py shellPython 3.8.1 (default, Dec 24 2019, 17:04:00) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linuxType "help", "copyright", "credits" or "license" for more information.(InteractiveConsole)>>> from hello_app.models import AlarmMessage>>> m1 = AlarmMessage(alarm_title='服务异常告警', alarm_content='后台认证管理服务不存在,请检查', level=0)>>> m1.save()>>> m2 = AlarmMessage(alarm_title='内存告警', alarm_content='主机host-001内存使用率100%,请检查', level=1)>>> m2.save()>>> m3 = AlarmMessage(alarm_title='主机掉线', alarm_content='主机host-001已掉线,请检查', level=2)>>> m3.save()我们完成一个登录页面,同样是利用 session 保存登录状态。针对三个不同用户登录,我们会显示不同的结果:普通用户直接提示没有权限登录、平台员工登录后显示告警列表、平台负责人登录后除了显示告警列表外,每个记录对应有个删除按钮。首先来看模板页面,主要有两个:登录页面 (test_form2.html) 和告警信息 (test_permission.html) 列表页面。{# 代码位置: template/test_form2.html #}{% load staticfiles %}<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}" />{% if not success %}<form action="/hello/test_form_view2/" method="POST">{% csrf_token %}<div><span>{{ form.name.label }}:</span>{{ form.name }}<div><span>{{ form.password.label }}:</span>{{ form.password }}<div>{{ form.save_login }}{{ form.save_login.label }}</div><div><input class="input-text input-red" type="submit" value="登录" style="width: 214px"/></div>{% if err_msg %}<div><label class="color-red">{{ err_msg }}</label</div>{% endif %}</form>{% else %} <p>登录成功</p>{% endif %}{# 代码位置: template/test_permission.html #}{% load staticfiles %}<p>登录用户:{{ username }}<a href="/hello/logout/" style="margin-left:30px">登出</a></p><div><table border="1" class="member-table"> <thead> <tr> <th></th> <th>告警编号</th> <th>告警标题</th> <th>告警级别</th> <th>告警内容</th> <th>产生时间</th> {% if perms.hello_app.delete_alarm_message %} <th>操作</th> {% endif %} </tr> </thead> <tbody> {% for message in messages %} <tr> <td></td> <td>{{ message.id }}</td> <td>{{ message.alarm_title }}</td> <td>{{ message.level }}</td> <td>{{ message.alarm_content }}</td> <td>{{ message.created_at|date:"Y-m-d H:m:s" }}</td> {% if perms.hello_app.delete_alarm_message %} <td><a href="/hello/alarm_delete/{{ message.id }}">删除</a></td> {% endif %} </tr> {% endfor %} </tbody></table><div ><div class="page"></div></div></div>我们准备视图函数,这个视图函数是在前面的基础上进行了改造,代码如下:# 代码位置:hello_app/views.py# ...class TestFormView2(TemplateView): template_name = 'test_form2.html' def get(self, request, *args, **kwargs): # print('进入get()请求') success = False err_msg = '' form = LoginForm() if request.session.get('has_login', False): logined_user = User.objects.all().get(id=int(request.session['user_id'])) # 判断登录的用户是否有权查看告警页面 if logined_user.has_perm('hello_app.view_alarm_message'): # 这一步不能掉,request中添加用户信息 request.user = user messages = AlarmMessage.objects.all() return render(request, "test_permission.html", {"username":logined_user.username, "messages": messages}) else: success = False err_msg = "用户无权访问" return self.render_to_response(context={'success': success, 'err_msg': err_msg, 'form': form}) def post(self, request, *args, **kwargs): # print('进入post请求') form = LoginForm(request.POST) success = True err_msg = "" if form.is_valid(): login_data = form.clean() name = login_data['name'] password = login_data['password'] # 登录认证 user = authenticate(username=name, password=password) if not user: success = False err_msg = "用户名密码不正确" # 判断登录用户是否有权限 elif user.has_perm('hello_app.view_alarm_message'): request.user = user request.session['has_login'] = True request.session['user_id'] = user.id # 设置100s后过期 request.session.set_expiry(100) messages = AlarmMessage.objects.all() return render(request, "test_permission.html", {"username": user.username, "messages": messages}) else: success = False err_msg = "用户无权访问" else: success = False err_msg = form.errors['password'][0] return self.render_to_response(context={'success': success, 'err_msg': err_msg, 'form': form})def logout(request, *args, **kwargs): if 'has_login' in request.session: del request.session["has_login"] if 'user_id' in request.session: del request.session["user_id"] request.session.flush() return redirect('/hello/test_form_view2/')我们这里除了改造原来的登录请求外,还多加了一个 logout() 方法用于用户登出操作。接下来准备好 URLConf 配置如下:# 代码位置:hello_app/urls.py# ...urlpatterns = [ # ... path('test_form_view2/', views.TestFormView2.as_view(), name='test_form_view2'), path('logout/', views.logout, name='logout'),]最后,我们启动我们的工程,来看看实际的效果,具体演示演示如下:24

6. ABA 问题

ABA 问题描述:假设有两个线程,线程 1 和线程 2,线程 1 工作时间需要 10 秒,线程 2 工作需要 2 秒;主内存值为 A,第一轮线程 1 和线程 2 都把 A 拿到自己的工作内存;第 2 秒,线程 2 开始执行,线程 2 工作完成把 A 改成了 B ;第 4 秒,线程 2 把 B 又改成了 A,然后就线程 2 进入休眠状态;第 10 秒,线程 1 工作完成,看到期望为 A 真实值也是 A 认为没有人动过,其实 A 已经经过了修改,只不过又改了回去,然后线程 1 进行 CAS 操作。ABA 问题解决:为了解决这个问题,在每次进行操作的时候加上一个版本号或者是时间戳即可。

4. 前端开发流程

前后端分离开发,实际上前端工作就简化了。我们直接新建项目文件夹 shop-front (商城前端项目文件夹),然后将前端页面放到该文件夹即可。注意该页面不需要放到 Spring Boot 项目目录下,随便找个目录放置即可。实际开发过程中,后端和前端的项目可能都不在一台计算机上。前端核心业务代码如下,由于前端技术不是本节介绍的重点,所以不再详细解释,感兴趣的同学可以从 Git仓库 查看完整代码 。实例: //初始化方法 $(function () { var row = ""; $.ajax({ type: "GET", url: "http://127.0.0.1:8080/goods", //后端接口地址 dataType: "json", contentType: "application/json; charset=utf-8", success: function (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 += "</tr>"; $("#goodsTable").append(row); }); }, error: function (err) { console.log(err); } }); });开发完该页面后,直接使用浏览器双击打开,查看控制台发现有错误信息提示。浏览器控制台返回错误信息考验英文水平的时候到了!关键是 has been blocked by CORS policy ,意味着被 CORS 策略阻塞了。我们的前端页面请求被 CORS 阻塞了,所以没成功获取到后端接口返回的数据。

首页上一页1234567下一页尾页
直播
查看课程详情
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号