摘要:本文将为大家带来基于Flask框架和JQuery实现管理平台网站的开发功能。

【写在前面】

你要开发网站? 嗯。。

会Flask吗? 什么东西,没听过。。。

会JQuery吗? 是python的库吗 ?

那你会什么? 我会F12打开网站

好吧,那我们来写个简单的表格管理平台。

基于Flask框架和JQuery实现管理平台网站的开发功能,我代码编写用了2天的时间 ,从零开始写;又对具体实现流程,自己断断续续地整理总结了近半个月。从自我感觉来说,整个过程和结果的实现都让我很满意。

【效果如下】

【第一步】了解Flask框架

1、了解python 主流的web框架

(1)Django:简单来说就是重武器,是最全能的开发框架,你想要的功能它都有;但是比较繁重,适合企业级的web开发;

(2)Flask:属于web开发微框架,小巧灵活,相关的第三方库丰富,适合开发小型web;

(3)Tornado:天生异步,性能强悍,但是框架提供的功能少,需要开发者自己实现;

因此,本文代码实现主要是基于Flask实现的。

2、了解Flask框架

(1)HelloWorld实现:

几乎所有的编程都是基于“hello world”实现的,因此也大致讲下helloworld涉及的内容。

from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return "Hello world!"
if __name__ == '__main__':
app.run()

先声明一个Flask框架的对象,然后定义路由'/',即URL地址为 http://127.0.0.1:5000/;如果定义路径为‘/new’,那对应的访问地址需要改为http://127.0.0.1:5000/new。另外,@app.route是个装饰器。

(2)如何实现具体IP地址或者端口的访问呢?

app.run(debug=True,host="x.x.x.x",port="1234")

通过对app.run()方法的参数定义,分别实现了调试功能,访问URL变更为 http://x.x.x.x:1234

这里调试功能还是很好用的,不但可以帮助开发者重新加载网页,而且会打印详细的错误信息,协助定位。

(3)如何在web上显示自己特定的html模板

from flask import Flask,render_template
app = Flask(__name__)
@app.route('/')
def hello_world():
return render_template('test.html')
if __name__ == '__main__':
app.run()

只需要导入render_template库,并且在函数返回时改成对应的方法即可。

不过 这 里要 注意,test.html必须保存在工程目录与下template文件下,否则会报错。(这是因为render_template方法定义时默认写了template路径 )

【第二步】了解Sqlite3数据库

web数据交互离不开后台数据库的管理,本章节重点解释python自带的sqlite3数据库。相比较于其他“正规”的数据库,如mongo、solr、MySQL等,sqlite3相当简单,属于轻量级的数据库。

1、sqlite3数据库的安装和创建

用pip命令可以下载安装sqlite3数据库

创建数据库:

con = sqlite3.connect('material.db')

如果有数据库material.db,则进行连接数据库的操作;如果没有此数据库,则先创建数据库再进行连接;

2、创建数据表

label = ['ID','网络IP','地址','责任人','联系方式']
content = ['1','10.10.10.10','杭州滨江','鹏哥','123456']
def create():
sql = 'create table {0} ({1},{2},{3},{4},{5})'.format(tablename,label[0],label[1],label[2],label[3],label[4])
result = cur.execute(sql)
con.commit()
return True if result else False

简单描述为:create table 表名 (各字段名1,各字段名2……)

当前对数据表的字段未进行输入类型及长度的限制,比如需要规则ID为必填项,并且为整形,长度在10个字节内,则需要修改为

sql = 'create table matrial_table ("ID" int[10] primary key not null )'

同理,其他字段也可以相同的方式进行类型、长度的限制。

注意:在执行cur.execute()后,要记得con.commit()进行数据库提交,否则数据并不会真正写入数据库中。

3、插入数据

def insert():
sql = 'insert into {0} ({1},{2},{3},{4},{5}) values({6},"{7}","{8}","{9}","{10}")'.format(tablename,label[0],label[1],
label[2],label[3],label[4],content[0],content[1],content[2],content[3],content[4])
result = cur.execute(sql)
con.commit()
return True if result else False

简单描述为:insert into 表名 (各字段名1,各字段名2……) values(数值1,数值2……)

这 里要注意,”{7}“ 是有加双引号的,为什么呢?因为”{7}“对应的是网络IP,是个字符串,因此需要加 双引号,否则会报错。

4、查询数据

def query():
sql = 'select * from {0}'.format(tablename)
result = cur.execute(sql)
return list(result)

简单描述为:select XX,XX from 表名 where 字段名1="数值1"

5、更新数据:update 表名 set 字段名1=”数值1“ where 字段名2="数值2"

6、删除某条数据:delete from 表名 where 字段名1="数值1"

【补充】

如果生成了db数据库,如何查看呢?可以下载一个SQLite Expert,打开后就可以很直观地进行数据库查看,并且可以通过图形化按钮进行 数据表的增删改查。

【第三步】了解html

1、Flask框架方法编写

前面已经讲过 Flask如何调用html模板,因此我们直接展示上图对应的flask框架方法的代码

# coding=utf-8
# @Auther : "鹏哥贼优秀"
# @Date : 2019/9/23
# @Software : PyCharm
from flask import Flask,render_template
import sqlite3
app = Flask(__name__)
con = sqlite3.connect('material.db',check_same_thread=False)
@app.route('/')
def Material_Mangement():
# 获取数据库material_table表的内容
cur = con.cursor()
sql = 'select * from {0}'.format("material_table")
cur.execute(sql)
content = cur.fetchall()
# 获取数据库表的表头
labels = [tuple[0] for tuple in cur.description]
return render_template('test.html',content=content,labels=labels)
if __name__ == '__main__':
app.run(debug=True)

动态路由、sqlite3数据库操作、调试模式的设置,这些知识请参考之前的博客。但有2个知识点,我想再提下:

(1)如果在数据库连接时,不添加check_same_thread=False参数,则

数据库连接会报错:sqlite3.ProgrammingError: SQLite objects created in a thread can only be used in that same thread. The object was created in thread id XX。

这是因为sqlite3数据库被多线程访问导致冲突,因此这里要注意下。

(2)获取数据库表头:labels = [tuple[0] for tuple in cur.description]

2、Html文件(仅展示表格内容)

<!doctype html>
<html>
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous"> <title>物料管理平台</title>
</head>
<body>
<div>
<div class="col-md-6 col-sm-12 col-xs-12">
<div class="panel panel-default">
<div>
<h3>表格管理平台</h3>
</div>
<div>
<div>
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
{% for i in labels %}
<td>{{ i }}</td>
{% endfor %}
</tr>
</thead>
<tbody>
{% for i in content %}
<tr>
{% for j in i %}
<td>{{ j }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div> </div> </div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"
integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"
integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy"
crossorigin="anonymous"></script>
</body>
</html>

对应的效果是这样的:

因为一开始接触flask时,我只调试过html,但是根本没具体接触过html怎么写。因此上述这段代码是参考于大神的代码(https://blog.csdn.net/a19990412/article/details/84955802)。

熟悉这段代码后,我觉得有几块内容是和我要实现的代码有关的。

(1)title 标题修改

(2)表格的长宽大小:<div class="col-md-6 col-sm-12 col-xs-12"> 。 col-xs-*和col-sm-* 和col-md-*(col-xs表示超小屏幕,col-md-中等屏幕,col-sm-小屏幕)主要是用来适应不同屏幕的表格展示。因此需要自适应调整对应的数值。

(3)设置表格的ID:<table class="table table-striped table-bordered table-hover",id="test">。这里其实不设置id也是可以的,但是后续我要对表格进行编辑时,加上id会方便我定位表格,方法是:tab = document.getElementById("test")

3、Html文件(添加编辑、提交按钮)

根据上述要修改的点,我重新修改了html内容,新的html代码如下:

<!doctype html>
<html>
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous"> <title>表格管理平台</title>
</head>
<body>
<div>
<div class="col-md-12 col-sm-12 col-xs-12">
<div class="panel panel-default">
<div>
<h3>表格管理平台</h3>
</div>
<div>
<div>
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
{% for i in labels %}
<td>{{ i }}</td>
{% endfor %}
</tr>
</thead>
<tbody>
{% for i in content %}
<tr>
{% for j in i %}
<td>{{ j }}</td>
{% endfor %}
<td><input type="button" value="编辑"></td>
<td><input type="submit" value="提交"></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div> </div> </div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"
integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"
integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy"
crossorigin="anonymous"></script>
<script>
(function(){
$('input[type="button"]').on('click', function(){
var $this = $(this),
edit_status = $this.attr('edit_status'),
status_value = edit_status && 1 == edit_status ? 0 : 1,
$td_arr = $this.parent().prevAll('td');
$this.val(1 == status_value ? '完成' : '编辑').attr('edit_status', status_value);
$.each($td_arr, function(){
var $td = $(this);
if(1 == status_value) {
$td.html('<input type="text" value="'+$td.html()+'">');
} else if(0 == status_value){
$td.html($td.find('input[type=text]').val());
}
});
});
})();
</script>
</body>
</html>

相比于第 2步时的html文件,这次我主要添加了2块内容:

(1)添加编辑、提交按钮:

<td><input type="button" value="编辑"></td>   <td><input type="submit" value="提交"></td>

将这两行代码放在表格每行最后,就会生成相应的按钮

(2)编辑功能的实现:

编辑按钮对应的function是基于JQuery写的,

当然这 段代码我也是参考另一位大神的(https://blog.csdn.net/luo201227/article/details/50559988),因为当前我对JQuery完全一无所知。

但是在熟悉代码后,我根据自己对代码的理解进行了注释。

<script>
(function(){
<!--定义属于是 button的按钮在点击后,产生下面的function功能-->
$('input[type="button"]').on('click', function(){
<!--获取当前事件,并进行当前按钮的状态,如果是编辑状态,就进行将每个单元格设置成可输入状态-->
var $this = $(this),
edit_status = $this.attr('edit_status'),
status_value = edit_status && 1 == edit_status ? 0 : 1,
$td_arr = $this.parent().prevAll('td');
$this.val(1 == status_value ? '完成' : '编辑').attr('edit_status', status_value);
<!--如果单元格是可编辑状态,获取每列元素的值,并在最后html表格上进行展示-->
$.each($td_arr, function(){
var $td = $(this);
if(1 == status_value) {
$td.html('<input type="text" value="'+$td.html()+'">');
<!--如果按钮状态是完成状态,直接展示单元内的值-->
} else if(0 == status_value){
$td.html($td.find('input[type=text]').val());
}
});
});
})();
</script>

细心的同学会发现,我在点击”提交“按钮时,什么都没 发生。是的,”提交“功能,我将在下一个章节中进行介绍。

【第四步】了解JQuery

1、提交功能的实现,对我来说,最难的是html对前台数据的传输。因此,我就参考着编辑功能,自己一 点点写。以下是示例代码:

(1)HTML文件编写前台界面提交功能

<script>
(function(){
<!--定义属性是submit的按钮在点击后,产生下面的function功能-->
$('input[type="submit"]').on('click', function(){
<!--获取当前行的id -->
var td = event.srcElement.parentElement;
var rownum = td.parentElement.rowIndex;
<!--获取html表格元素 -->
var tab = document.getElementById("test");
<!--将每个单元格元素进行取值,并以字典形式传给后台 -->
var data = {
"ID":tab.rows[rownum].cells[0].innerHTML,
"网络IP":tab.rows[rownum].cells[1].innerHTML,
"地址":tab.rows[rownum].cells[2].innerHTML,
"责任人":tab.rows[rownum].cells[3].innerHTML,
"联系方式":tab.rows[rownum].cells[4].innerHTML,
};
alert("提交成功!")
$.ajax({
type: "get",
url: "/edit",
data: data,
dataType: "json"
});
});
})();

(2)后台对提交后的数据进行读取,并写数据库

@app.route('/edit', methods=['get'])
def edit():
label = ['ID', '网络IP', '地址', '责任人', '联系方式']
content = [request.args.get(i) for i in label]
print(content)
sql = 'update {0} set {1}="{6}",{2}="{7}",{3}="{8}",{4}="{9}" where {5}={10}'.format('material_table',
label[1], label[2], label[3],label[4],label[0],content[1],content[2],content[3],content[4],content[0])
cur = con.cursor()
cur.execute(sql)
con.commit()
return "数据写入成功!"

在实现“提交”功能时,我主要遇到了以下几个 坑:

(1)html代码写完后,发现程序报错,提示$.ajax is not a function。我去,我看其他大神的数据交互也是这么写的呀,为什么我这不行?

查看网上的帖子,都是说未定义或者和其他库有冲突,最后我自己发现,是因为我没有声明是JQuery。需要在script前面加上一行代码:

<script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>

我理解这行代码的意思是声明下面要JQuery库,而不是其他JS库。(可能理解有误)

最后加上这行代码后,问题成功解决!

(2)获取当前行号

一开始在获取行号,怎么获取不到,而且不知道要怎么调试,在同事的指导下,才学会通过alert(td.innerHTML)来查看获取的当前行内容是什么。

最初从网上 查到获取当前行的写法是:

var td = event.parentElement;
var rownum = td.parentElement.rowIndex;

通过alert调试,发现点击提交根本没有反应;又改成

var td = event.srcElement;
var rownum = td.parentElement.rowIndex;

结果是获取不到任何内容

就这么个小问题就花费了我近2个小时的时间去不停地查资料,尝试编写。最后想说的是,如果对html不熟悉,还是应该找个大神带带自己的。

最后在别人的协助下,实现了获取当前行的功能:

(3)获取每个表格里的 内容

tab.rows[0].cells[0].innerHTML

在获取单元格内数据时,最验证的是我获取不到tab,即表格元素。因为我原先没有在表格元素里加id,即

网上找了很多方法都没法实现,最后老老实实地加上 id="test"

讲道理,html里的这 20行代码是我搞这个表格管理平台时,花费最大精力的。至此,表格管理平台的功能基本成行 !

【第五步】添加新增功能

以上功能只实现了对现有数据的编辑保存功能,但是如果用户想要新增数据,怎么办?我当时第一个想法是让用户自己去改数据库,哈哈。还要我去写新增功能,要累死了,不想写。

下面是关于新增功能的介绍和示例代码。

1、如何添加“新增”按钮

<td><input type="button" value="新增" id="create"></td><br>

如果前面 的html能看懂了,这行是不难理解的。

2、点击新增按钮后,如何动态增加表格行、列

<script>
(function () {
$('input[id="create"]').on('click', function(){ var editTable=document.getElementById("tbody");
var tr=document.createElement("tr");
var td1=document.createElement("td");
td1.innerHTML=""; tr.appendChild(td1);
editTable.appendChild(tr);

首先定义tr元素,然后在tr元素中再追加td元素。如果表格里有多列数据,那只需要重复td1的写法,进行复制粘贴就可以了。

另外,当前td.innerTHML是设置为空,如果要将该单元格直接设置为编辑状态,则修改成 :

td1.innerHTML="input[type=text] /";

3、如何动态添加“编辑”、“提交”按钮,下面以“编辑”为例

var td9 = document.createElement("td")
var myedit =document.createElement("input");
myedit.type = "button";
myedit.value = "编辑"
myedit.id = "edit"
td9.appendChild(myedit)

添加方式和添加文本框方式是一样的,只是需要注意元素类型是Input,并且要补充下元素的type/value/id。

4、如何对动态添加的按钮进行事件绑定,下面以提交功能为例

$('input[id="submit"]').on('click', function(){
alert("test")
}

关于动态添加的按钮进行事件绑定有很多帖子,有用Live方法的,有用$(document).on('click','.edit',function()方法的,其实不用这么麻烦,和正常的“提交”写法是完全一样的。

5、获取当前新增行内的数据,进行提交。

var tab = document.getElementById("test");
var rownum = td1.parentElement.rowIndex;
$('input[id="submit"]').on('click', function(){
var data = {
"ID":tab.rows[rownum].cells[0].innerHTML,
};
alert("新增成功!")
$.ajax({
type: "get",
url: "/create",
data: data,
dataType: "json"
});
});

这段代码和提交功能的实现是一样的,大同小异。

本文分享自华为云社区《【Python成长之路】从 零做网站开发 -- 基于Flask和JQuery,实现表格管理平台》,原文作者: 鹏哥贼优秀 。

点击关注,第一时间了解华为云新鲜技术~

从零做网站开发:基于Flask和JQuery,实现表格管理平台的更多相关文章

  1. 【Python成长之路】从 零做网站开发 -- 基于Flask和JQuery,实现表格管理平台

    [写在前面] 你要开发网站?    嗯.. 会Flask吗?    什么东西,没听过... 会JQuery吗?    是python的库吗 ? 那你会什么?    我会F12打开网站 好吧,那我们来写 ...

  2. 基于Flask实现博客开发--准备工作

    背景说明 本项目是基于<深入理解flask>一书,主要是用来记录学习历程和交流心得,所以写得不好请大神勿喷. 准备工作 virtualenv介绍 也许 Virtualenv 是你在开发中最 ...

  3. 基于flask的轻量级webapi开发入门-从搭建到部署

    基于flask的轻量级webapi开发入门-从搭建到部署 注:本文的代码开发工作均是在python3.7环境下完成的. 关键词:python flask tornado webapi 在python虚 ...

  4. Spring 基于注解零配置开发

    本文是转载文章,感觉比较好,如有侵权,请联系本人,我将及时删除. 原文网址:< Spring 基于注解零配置开发 > 一:搜索Bean 再也不用在XML文件里写什么配置信息了. Sprin ...

  5. 《Flask Web开发——基于Python的Web应用开发实践》一字一句上机实践(上)

    目录 前言 第1章 安装 第2章 程序的基本结构 第3章 模板 第4章 Web表单 第5章 数据库 第6章 电子邮件 第7章 大型程序的结构   前言 学习Python也有一个半月时间了,学到现在感觉 ...

  6. 基于Flask的Web应用程序插件式结构开发

    事实上,很多应用程序基于插件式结构开发,可以很方便了扩展软件的功能,并且这些功能完全可以依托于第三方开发者,只要提供好接口和完备文档,比如wordpress.谷歌火狐浏览器等. Python这样的动态 ...

  7. 基于Flask框架的Python web程序的开发实战 <一> 环境搭建

    最近在看<Flask Web开发基于Python的Web应用开发实战>Miguel Grinberg著.安道译 这本书,一步步跟着学习Flask框架的应用,这里做一下笔记 电脑只安装一个P ...

  8. 基于 Intraweb 和 JQuery 的开发套件

    基于 Intraweb 和 JQuery 的开发套件 http://www.cgdevtools.com/ 开发速度无敌,界面也非常美. 我的web短板终于解决了.....!!!!..!!! 做一个小 ...

  9. Python flask 基于 Flask 提供 RESTful Web 服务

    转载自 http://python.jobbole.com/87118/ 什么是 REST REST 全称是 Representational State Transfer,翻译成中文是『表现层状态转 ...

随机推荐

  1. 面试时说Redis是单线程的,被喷惨了!

    Redis是单线程的,这话搁以前,是横着走的,谁都知道的真理.现在不一样,Redis 变了.再说这句话,多少得有质疑的语气来跟你辩驳一番.意志不坚定的,可能就缴械投降,顺着别人走了. 到底是什么样的, ...

  2. mq_open失败,Invalid argument

    经四处查找,终于发现原因: (1)attr.mq_maxmsg 不能超过文件 /proc/sys/fs/mqueue/msg_max 中的数值,我的机器上面是10. (2)attr.mq_msgsiz ...

  3. Linux 系统编程 学习:008-基于socket的网络编程3:基于 TCP 的通信

    背景 上一讲我们介绍了 基于UDP 的通信 这一讲我们来看 TCP 通信. 知识 TCP(Transmission Control Protoco 传输控制协议). TCP是一种面向广域网的通信协议, ...

  4. ubuntu裸机启动python博客项目

    关注公众号"轻松学编程"了解更多. 在linux的ubuntu(18.04)中正确安装python的命令: sudo apt clean sudo apt update sudo ...

  5. 《Clojure编程》笔记 第1章 进入Clojure仙境

    目录 背景简述 第1章 进入Clojure仙境 1.1 基础概念 1.2 常用的一些符号 背景简述 本人是一个自学一年Java的小菜鸡,理论上跟大多数新手的水平差不多,但我入职的新公司是要求转Cloj ...

  6. Stimulsoft Reports和Dashboards发布新版本2020.5具有多项改进

    Stimulsoft仪表工具实现所需的数据可视化和自己的信息图表.该产品能够应用必要的过滤器和排序,汇总数据,执行任何复杂度的计算.该产品的优势在于其多功能性-能够为您的业务,财务,销售,行业等任何领 ...

  7. ZOJ 1005 Jugs(BFS)

    Jugs In the movie "Die Hard 3", Bruce Willis and Samuel L. Jackson were confronted with th ...

  8. 【Kata Daily 190924】Difference of Volumes of Cuboids(长方体的体积差)

    题目: In this simple exercise, you will create a program that will take two lists of integers, a and b ...

  9. Dreamweaver是怎么把图片转换成代码 简单五步骤即可解决

    Dreamweaver图片转换代码图文介绍 1.打开需要转换的Photoshop作品: 2.保存为web格式,得到一个文件夹和一个html格式文件: 3.在html格式文件上单击右键,选择打开方式为D ...

  10. nat+端口转发,使得宿主机secureCRT可以访问vbox里linux虚拟机

    环境:vbox或者叫vitrualbox连接虚拟机,由于公司内网不能分配IP(不知道是不是这个原因),虚拟机用桥接得不到IP,没法实现虚拟机和宿主互相访问,于是用NAT. 遗憾:NAT是能连接网络,也 ...