​ 这是我写的第一个项目(如果它能被称作是项目的话),前前后后大概花了一个星期。过程中碰到了很多问题,在此把我记得的问题全部记录下来。

整体结构与框架:

​ 该项目实现的操作是:在网页上提交一组数据(两个字符串),然后基于一定的算法实现对这两个字符串的匹配程度打分。

项目包含了以下文件:

  1. find_your_happiness.py:项目最主要的程序。通过flask模块创建HTML文本。
  2. fate.py:项目评分的主要算法。
  3. find_your_happiness.html:项目的主页面,提交数据的页面。
  4. result.html:结果显示页面
  5. base.html:基模板。包含web页面共享的HTML标记。我们可以使用Jinja2的extends指令继承这个模板。
  6. love.css:主页面的样式表
  7. result.css:结果页面的样式表
  8. images文件夹:包含网页中所需的图片文件路径部署问题

文件路径部署:

项目的文件是这样部署的:

注意事项:

  • 必须使用templates文件夹存放html文件
  • 必须将静态文件(包括图像、css)放在名为static的文件夹中。其中css文件直接放在static文件夹的根目录下(尝试过将css文件放在名为css的文件夹中,但是找不到文件)
  • templates、static与py文件必须在同一目录下

评分算法实现:

第一步:通过对两个字符串使用哈希算法得到两个两个32位16进制数的字符串,再将其转换为十进制数。

(以下代码来自find_your_happiness.py)

hash1 = hashlib.md5(name1.encode("utf-8")).hexdigest()
hash2 = hashlib.md5(name2.encode("utf-8")).hexdigest()
score1 = int(hash1[0:31], 16)
score2 = int(hash2[0:31], 16)
return fate.calc(score1, score2)

先说一下这里的几个函数:

hashlib.md5()   调用库:import hashlib

Python的hashlib提供了常见的摘要算法,md5就是其中的一种。摘要算法又称哈希算法、散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串。

encode(“utf-8”)

hashlib.md5(data)函数中,data参数的类型应该是bytes。也就是说我们在进行hash前必须把数据转换成bytes类型。而由于中文字符在python中是以Unicode的形式存在的,所以在此我们统一对字符串进行转码。

.hexdigest()

我们可以通过.digest()查看hashlib.m5()加密后的二进制结果

也可以通过.hexdigest()查看hashlib.m5()加密后的十六进制结果

int(string, 16)

我们可以通过这个函数将一个16进制的字符串转化为int

第二步:使用fate.py对这两个int进行打分

感谢www.harryshaun.wang提供的算法!

下面贴出了部分代码

(以下代码来自fate.py)

medium = 85
high = 10
low = 5
total = medium + high + low

basic0 = 45
basic1 = 75
basic2 = 90
basic3 = 100

esp = 1e15

def classify(x):
    if x <= medium:
        return 1
    elif x <= high + medium:
        return 2
    else:
        return 3

def calcScore(a, b, l, r):
    return (a ^ b) % ((r - l) * esp + 1) / esp + l

def calc(a, b):
    flag = classify((a ^ b) % total + 1)

    if flag == 1:
        return calcMedium(a, b)
    elif flag == 2:
        return calcHigh(a, b)
    else:
        return calcLow(a, b)

算法思路为:我们先通过两数异或的结果再mod100 + 1,得到一个范围为[1, 100]的数字。我么们通过这个数字来决定最终成绩分布在哪个低、中、高哪个分数段内。最后我们通过calcScore函数,由a、b异或得到的某一特定值取mod来得到在规定分数范围内的分数。因此决定分数高低的因素是:第一步得到的两个int的异或值

我们使用low、high、medium三个值来控制三种分数段分布的比例。

我们使用四个basic值来控制低、中、高分数段的分数范围。

在这里即: 45~75分数段占比为5%

​ 75~90分数段占比为85%

​ 90~100分数段占比为10%

综上述两步,评分的算法就已经实现了。

链接py与web:

(以下代码来自find_your_happiness.py)

@app.route('/')
@app.route('/entry')
def entry_page() ->'html':
    return render_template('find_your_happiness.html')

@app.route('/findyourlove', methods=['POST'])
def love() -> 'html':
    name1 = request.form['name1']
    name2 = request.form['name2']
    results = str(scoreTwoNames(name1, name2))
    comment = giveComment(results)
    return render_template('results.html',
                           the_name1=name1,
                           the_name2=name2,
                           the_results=results,
                           the_comment=comment,)

@app.route(URL)

python中提供了很多内置的函数修饰符。route就是其中之一。函数修饰符可以调整一个现有函数的行为,而无序修改函数的代码(就像我们这里做的一样)。

在我们的web应用代码中,可以通过app变量使用Flask的route修饰符。route修饰符允许你将一个URL Web路径与一个已有的Python函数关联。如代码第一行:当一个指向“/”URL的请求到达服务器时,route修饰符会安排Flask Web服务器调用下面的entry_page()函数。然后route修饰符会等待所修饰的函数生成的输出,再将输出返回给服务器,服务器再将输出返回给正在等待的web浏览器。

简单来说即:Flask提供了一种机制,我们利用这种机制可以执行任何已有的Python函数,并在一个web浏览器中显示它的输出

render_template()

Flask提供了一个名叫render_template()的函数,如果指定一个模板名和所需要的参数,调用这个函数时将会返回一个HTML串。

在上述代码中我们可以看到,当我们进入“/”或”/entry”URL时进入我们的主页面find_your_happiness.html;当我们URL为“/findyourlove”时,我们会进入我们的结果页面result.html

(以下代码来自find_your_happiness.py)

return render_template('results.html',
                           the_name1=name1,
                           the_name2=name2,
                           the_results=results,
                           the_comment=comment,)
name1 = request.form['name1']
name2 = request.form['name2']
results = str(scoreTwoNames(name1, name2))
comment = giveComment(results)

(以下代码来自result.html)

<div id="main">
        <h1>Here are your results:</h1>
        <p>You submitted the following data:</p>
        <table>
          <tr><td>Your Name:</td><td>{{ the_name1 }}</td></tr>
          <tr><td>His/Her Name:</td><td>{{ the_name2 }}</td></tr>
        </table>
        <p>When judging how much you and "{{ the_name2 }}" match, the result are as follows:</p>
        <h3>{{ the_results }}</h3>
        <p>{{ the_comment }}</p>
    </div>

我们看到,在html文件中,出现在{{ 和 }}之间的标记是针对Jinja2模板引擎的,我们可以通过在py文件中指定来提供它们的值:用name1、name2、results、comment来指定the_name1、the_name2、the_results、the_comment的值。

method=[‘POST’]

我们的浏览器使用HTTP方法来向服务器发送请求。在这里我们需要了解两种方法:GET和POST

GET方法

浏览器通常使用GET方法向Web服务器请求一个资源。这是Flask默认的HTTP方法。也就是说,当我们没有指定methods的时候,浏览器会默认我们要从Web服务器请求资源。

POST方法

POST方法允许浏览器向Web服务器通过HTTP发送数据 ,这样可以让我们的Web应用从浏览器端接收数据 。因为POST不是默认方法,因此我们要在@app.route行上提供一个额外的参数,即:

@app.route('/findyourlove', methods=['POST'])

所以这一步操作的意思是:

@app.route('/findyourlove', methods=['POST'])
def love() -> 'html':
    name1 = request.form['name1']
    name2 = request.form['name2']
    results = str(scoreTwoNames(name1, name2))
    comment = giveComment(results)
    return render_template('results.html',
                           the_name1=name1,
                           the_name2=name2,
                           the_results=results,
                           the_comment=comment,)

浏览器刚刚在主页面上接受了用户提交的一个表单,它得到了一些表单数据。现在它要将这些数据通过POST方法发给Web应用,以便完成一些操作。操作完成后,会返回给我们的/findyourlove(即result.com)页面。

request.form()

在我们的主页面find_your_happiness.html上有这样一个表哥:

Your name:
His/Her name:

(以下代码来自find_your_happiness.html)

<table>
    <tr><td>Your name:</td><td><input name='name1' type='TEXT' width='60'></td></tr>
    <tr><td>His/Her name:</td><td><input name='name2' type='TEXT' width='60'></td></tr>
</table>

我们会将两个要进行评分的字符串(即这里的 ‘name1’ 和 ’name2‘ 输入到浏览器中。那么我们的应用怎样获得这两个数据的值呢?

Flask提供了一个内置对象,叫做request。利用这个对象我们可以很容易地访问HTML表单(form)提交的数据。因此很容易理解,在request对象中包含一个名为form的字典属性,可以让我们访问从浏览器提交的HTML表单数据。我们在上面已经见过这两行代码了:

(以下代码来自find_your_happiness.py)

name1 = request.form['name1']
name2 = request.form['name2']

这一步操作即帮助我们将HTML表单的数据赋给了py文件中新创建的变量。因此后面我们才能在py中对这两个变量进行操作并将结果使用POST方法传给服务器。

至此,将py和web链接起来所用到的函数已全部讲述完毕。在这里我来总结一下新学到的内容以及整个的思路:

运行代码 ->

一个指向“/”的请求发送给服务器 ->

route修饰符安排Flask Web服务器调用entry_page()函数 ->

我们通过render_template()函数进入find_your_happiness.html主页面 ->

我们在浏览器的主页面上提供两个字符串数据 ->

web应用通过Flask中的request.form[ ]访问我们提交到的数据 ->

我们将这个两个数据赋给py文件中的变量 ->

在web应用中对这两个变量进行运算后我们将得到的结果使用render_template()函数返回一个result页面,并将得到的结果返回到这个页面上。

HTML表单:

上面我们提到了HTML表单:在HTML中,

之间的内容就是表单。

表单实际上是一个包含输入域的Web页面,允许我们输入信息。当我们提交表单时,这些信息会打包并发送到一个服务器,有一个服务器脚本处理;处理结束后,我们会得到另一个Web页面作为响应。

HTML布局问题、CSS的使用:

在编写页面的过程中,我碰到了很多问题。其中大部分问题是在布局与定位中碰到的。在这里我把我还记得的部分问题记录下来,方便今后查询。

主页面的页眉(header)实现左右两部分在缩放时始终在可视范围内

我找到的目前最佳实现的方案为:

将页眉分为左右两个部分,两部分都规定高度 ,对右半部分使用float;

一般来说,我们float一个元素时必须指明它的宽度, 但是image元素默认有一个指定宽度,即图像本身的宽度。在这里我们不用指明,但是我们浮动别的元素时一定不能忘记这一点。

#header_right{
    float:              right;
}

#header{
    margin:             10px 0px 0px 0px;
    height:             210px;
}

主页面中main和边栏的布局问题

在这里我采用了非常方便、经典的表格布局模式。这样我们可以轻易实现两栏的布局:

<div id="tableContainer">
    <div id="tableRow">
        <div id="main">
        ......
        </div>

        <div id="sidebar">
        ......
        </div>
    </div>
</div>

因为在这里我们只用显示两列,因此HTML中仅仅制定了tableRow。相应的,我们在CSS中也要指定各个表格项目的display:包括table-cell、table-row、table等

#main{
    display:            table-cell;
    ......
}

#sidebar{
    display:            table-cell;
    ......
}

#tableContainer{
    display:            table;
    border-spacing:     70px;
    margin-left:        auto;
    margin-right:       auto;
}

#tableRow{
    display:            table-row;
}

另外,为了使两栏居中、并且缩放时能做到一直居中的效果,如上所示,我采用了在tableContainer中指定margin-left、margin-right均为auto;这样包含在tableContainer中的内容便会一直居中了。

主页面main栏中表格无法居中?

我曾经尝试对在main中指定text-align:center来实现这一栏中所有内容居中的操作;但是这样并不能使表格也居中(原因尚且不清楚)。于是我尝试在table的CSS中指定margin-left及margin-right均为auto;至此实现了这个操作。

table{
    ......
    margin-left:        auto;
    margin-right:       auto;
}

主页面边栏中的小诗外边距比边栏中的其他元素更大:span类元素怎样指定外边距

之前想实现这个操作的时候遇到了困难:(因为我莫名其妙把那一段诗指定为了span元素,而span元素行级元素,指定内外边距时有些玄学问题。我也不知道为什么这样做了,其实完全可以指定为div,这样就不会遇到后面的问题)但由于遇到了这个问题,我找到了实现span元素指定内外边距的方法:将行类元素块级化

即:

#poem{
    display:            block;
    line-height:        3;
    margin:             0px 70px 50px 70px ;
}

如上述代码,我们将display的类型指定为block,这样便可以轻松的指定各边的外边距了。

主页面图片随网页缩放的显示问题

在主页面中我们可以看到2张图片;最开始如何摆放这两张图片让我非常苦恼:有一个版本导致缩小时图片可能会超出div的边界;另一个版本放大到一定程度时不能再放大后会留出空白很丑。

因此我对main即sidebar两块的img元素做出了如下操作:

#main img{
    width:              100%;
    height:             auto;
}
#sidebar img{
    width:              100%;
    height:             auto;   /*宽度根据父元素决定 高度自动 达到缩放目的*/
}

正如注释所写,当我们指定图片的width为100%(或者cover)、height为auto时,我们可以做到让图片的宽度根据它所在的块级元素的缩放而缩放;图片的比例同时保持不变。这样的效果确实非常好。

如果我们要实现高度根据块级元素的缩放而缩放,只要指定height:100%;width:auto即可;

结果页面图片摆放问题

在result页面中我一直想摆一张大图。由于这张大图非常有感觉,我想实现如图所示的效果:

result

在这里我没有选择将这张大图放在HTML中,而是放在了CSS的background里。CSS如下:

#header{
    background-image:   url(images/result.jpg);
    background-repeat:  no-repeat;
    background-size:    100%;
    height:             700px;
    margin:             20px;
}

之前我仅仅制定了background-size为cover(或100%)时,图片无法显示出来(不知道为什么)。当我指定了高度后,非常轻松的便达到了如图所示的效果!

因此要实现让背景图片横向平铺 纵向随页面缩放 只需要两步:

  1. 指定background-size为100%/cover
  2. 指定一定的高度

箭头动画的实现

看到上图中的箭头动画了吗!那天看到www.harryshaun.wang 博客中的这个动画我非常羡慕;并且可以通过点击箭头移动到我们指定的版块。为此,我去了解了一下CSS动画(仅仅是看了个皮毛,有很多内容还是不太明白)

首先箭头是哪里来的呢?通过研究上述博客的网页源代码我发现,这个箭头是由一个名为font awesome的图标字体库提供的。这里有非常多的矢量图标:

<div id="header">
        <div id="page-scroll">
            <a href="#main">
                <i class="fa fa-angle-down" style="font-size:180px; color:#ffffff"></i>
            </a>
        </div>
    </div>

在这里我选择将这个箭头图标放在了一个名为page-scroll的div里(方便我进行布局);div中包含了一个到main版块的链接;链接的内容为font awesome中的箭头图标。其中”fa-angle-down”指定了图标字体库中的向下箭头。图标的语法在font awesome官网中有详细的示例,这里不详述。

需要说明的一点是,我们如果需要用到这个字体库中的图标,必须在HTML开头加上:

<link rel="stylesheet" href="https://cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.css">

且联网状态下才能获得图案。

那么,怎样让这个图标动起来呢?

#page-scroll{
    position: absolute;
    top: 58%;
    width: 100%;
    text-align: center;
    animation: dropdown 2s linear infinite;
}

@keyframes dropdown {
    0% { margin-top: 0px;}
    70% { margin-top:50px;}
    100% { margin-top:50px;}
  }

在包含箭头的page-scroll div 中,我使用了animation属性。

animation 属性是一个简写属性,用于设置六个动画属性:

描述
animation-name规定需要绑定到选择器的 keyframe 名称
animation-duration规定完成动画所花费的时间,以秒或毫秒计。
animation-timing-function规定动画的速度曲线。
animation-delay规定在动画开始之前的延迟。
animation-iteration-count规定动画应该播放的次数。
animation-direction规定是否应该轮流反向播放动画。

animation-name:指定了一个名称@keyframes animation

  • keyframename:指定要绑定到选择器的关键帧的名称
  • none:指定没有动画,该属性可以用于覆盖任何动画

animation-duration:定义动画完成一个周期需要多少秒或毫秒

  • time:指定动画播放完成花费的时间。默认值为0,以为这没有动画效果

animation-timing-function:定义过渡动画的效果

  • linear:以相同速度开始至结束的过渡效果
  • ease:慢速开始,然后变快,然后慢速结束的过渡效果
  • ease-in:慢速开始的过度效果
  • ease-out:慢速结束的过渡效果
  • ease-in-out:慢速开始和结束的过渡效果
  • cubic-bezier:在cubic-bezier函数中定义自己的值。可能的值是0-1之间
  • step-start:马上跳转到动画结束状态
  • step-end:保持动画开始状态,直到动画执行时间结束,马上跳转到动画结束

animation-delay:定义动画什么时候开始,单位可以是秒(s)或毫秒(ms)

  • time:可选,定义动画开始前等待的时间,默认值为0

animation-iteration-count:定义动画应该播放多少次

  • n:一个数字,定义应该播放多少次动画
  • infinite:指定动画应该播放无限次(永远)

animation-directions:定义是否循环交替方向播放动画

  • normal:默认值。动画按正常播放。
  • reverse:动画方向播放
  • alternate:动画在奇数次(1.3.5)正向播放,在偶数次(2.4.6)反反播放
  • alternate-reverse:动画在奇数次反向播放,在偶数次正向播放
  • initial:设置该属性为他的默认值
animation: dropdown 2s linear infinite;

在这里我指定了其中的4个属性:

  • 动画名称为dropdown;
  • 一个动画周期为2s;
  • 以相同速度开始直至结束的过渡
  • 无限次播放动画
@keyframes dropdown {
    0% { margin-top: 0px;}
    70% { margin-top:50px;}
    100% { margin-top:50px;}
  }

在下面的关键帧指定中,我指定了动画的0%~70%部分从上往下移动;

​ 70%~100%部分悬停。

至此,便达到了箭头的动画效果。

为了使这个动画更有特色,我使用伪类选择器指定当鼠标悬停在箭头上时,出现黑色阴影的效果。

#page-scroll:hover{
    text-shadow:        4px -9px 11px #2a2a2a;
}

至此,我网页上的所有动画全部完成。但是还有一点小遗憾是博客单击箭头后只能跳动至相应模块,而不是滑动;所以显得有些突兀。

问题总结到这里。后续如果想到其他问题,我会进行补充。

分类: web_app

发表评论

电子邮件地址不会被公开。 必填项已用*标注