动手尝试实现后台页面

  • 2019 年 11 月 12 日
  • 筆記

由于业务需要,需要将现有完成的一些算法,且未上线或者已经上线的展示出来,需要制作类似于图形用户界面的展示看板,由于部分算法已经实现服务接口,讨论以UI页面的形式展示,而没有使用pyQt编写图形用户界面GUI,其实两种方式都可以。咨询公司前端小组的同学,给的建议是使用vue+element来实现。下面详细如何实现一个后台页面。

Vue

什么是Vue呢?Vue是一个构建用户界面渐进式框架,相当于底层框架。官方介绍如下:

Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。

这里不做过多的介绍,类似于汽车的底盘跟发动机一样,页面就是车身之类的,为其提供支持。详细介绍可以参考官方资料。

element

什么是element呢?element是一套为开发者、设计师和产品经理准备的基于Vue 2.0 的桌面端组件库。类似于word文档的画图功能,作者提供了很多控件供设计者使用。官方介绍如下:

定位于网站快速成型工具,基于Vue 2.0 的桌面端组件库。

这里不做过多的介绍,Vue提供汽车的底盘,这里使用element来设计车身,以及如何与发动机进行互动,从而驱使汽车能够正常运作。详细介绍可以参考对应的官方资料。

页面制作

第一阶段:只造壳,车动不了

由于第一次接触这方面的内容,比较不容易上手,但是借助于Vue以及element很容易去造壳,画出相应的界面,但是如何与服务对接起来,如何让车动起来还是不会。这里详细记录下如何去造壳。

参考学习视频:Element UI 教程,能够快速上手element构建一个页面,然后使用npm启动服务。

npm run dev

这个阶段只是了一个壳子,壳子如下所示:

主页面是一个Vue的logo加几个链接,这里主要是讲述如何实现主页面,子页面,以及如何链接到一起。

整个项目目录如下所示:

主要是编辑src文件夹中的内容:

其中App.vue是是我们的主组件,页面入口文件 ,所有页面都是在App.vue下进行切换的。也是整个项目的关键,app.vue负责构建定义及页面组件归集。

<template>    <div id="app">      <img src="./assets/logo.png">      <router-view/>    </div></template><script>export default {    name: 'App'}</script><style>#app {  font-family: 'Avenir', Helvetica, Arial, sans-serif;  -webkit-font-smoothing: antialiased;  -moz-osx-font-smoothing: grayscale;  text-align: center;  color: #2c3e50;  margin-top: 60px;  }</style>

main.js是程序入口文件,是初始化vue实例并使用需要的插件,加载各种公共组件:

// The Vue build version to load with the `import` command  // (runtime-only or standalone) has been set in webpack.base.conf with an alias.import ElementUI from 'element-ui'import 'element-ui/lib/theme-chalk/index.css'import Vue from 'vue'import App from './App'import router from './router'Vue.use(ElementUI)  Vue.config.productionTip = false/* eslint-disable no-new */new Vue({    el: '#app',    router,    components: { App },  template: '<App/>'

router文件夹中是index.js,它的作用是集那个准备好的路由组件注册到路由里,即上面的三个链接子页面:

import Vue from 'vue'import Router from 'vue-router'import NewContact from '@/components/NewContact'import Collocation from '@/components/Collocation'import Swapface from '@/components/Swapface'Vue.use(Router)export default new Router({    routes: [      {        path: '/newcontact',        name: 'NewContact',        component: NewContact      },      {        path: '/Collocation',        name: 'Collocation',        component: Collocation      },      {        path: '/Swapface',        name: 'Swapface',        component: Swapface      }    ]  })

每个子页面也是用vue和element编辑的,这里不做过多的展示。这里介绍其它的文件:

  • router/index.html文件入口
  • src放置组件和入口文件
  • node_modules为依赖的模块
  • config中配置了路径端口值等
  • build中配置了webpack的基本配置、开发环境配置、生产环境配置等
  • assets中存放一些素材
  • components中存放一些子页面vue

这种方式我目前只学会了静态页面,无法实现交互的那种,即制造好了壳子,车身无法与底盘发动机进行结合,车开不动,待学习中,后续补充相关内容

第二阶段:既造壳,车也能动

在第二阶段,由于参考数据组同学的做法及另一个小伙伴的加入,如何既造壳,又让车动起来了变得简单了。采用的方案是没有使用Vue进行编写,而是使用js编写数据相关部分,element编写html界面,flask起服务。整个项目目录如下:

其中,各个文件中内容如下:

  • api:存放的是python代码,主要是起服务,以及服务所需要的数据驱动等
  • conf:数据库配置文件
  • static:主要包含两部分内容

js: 各个页面所需要的js文件,这个js文件包含

style:一些css配置风格

  • templates: 网页模版,主要是包含各个网页的html文件

其中,需要重度开发的内容主要是js和api这两个文件夹中的内容。

首先看下完成后的节面:

可以看到整个页面布局不在像上面的那种,点击一个就跳转一个页面,而是类似于后台管理系统,左边是一些目录选项,右边是展示框,不同的目录下又有几个小的功能,分别对应不同业务。

整个服务是通过api中的run.py函数启动服务

python run.py# 运行成功后* Debug mode: on  * Running style="box-sizing: border-box; color: rgb(174, 129, 255);">127.0.0.1:5000/ (Press CTRL+C to quit)  * Restarting with stat  * Debugger is active!  * Debugger PIN: 154-962-794127.0.0.1 - - [01/Nov/2019 18:16:28] "GET / HTTP/1.1" 200

其中run.py如下所示:

# coding=utf-8import os  BASE_DIR = os.path.dirname(os.path.realpath(__file__)) + '/'from flask import Flask, Response, requestfrom flask import render_templatefrom flask_cors import CORSfrom conn_tool import get_mysql_poolfrom tag_comment import CommentsListView    app = Flask(__name__,          static_folder=BASE_DIR + "../static/",          template_folder=BASE_DIR + "../templates/")  CORS(app, supports_credentials=True)  app.jinja_env.variable_start_string = '[['  # 服务器模板(Jinja)和JS模板(ArgulaJS)分隔符冲突的解决方法app.jinja_env.variable_end_string = ']]'class Service():      def __init__(self):          pass        def check_alive(self):          return Response("hello, world", mimetype='text/html')    def index(self):          return render_template('tag-comment.html')      # init main classui_service = Service()# add url rulesapp.add_url_rule('/', view_func=algo_ui_service.check_alive methods=['GET'])  app.add_url_rule('/comment_tag', view_func=algo_ui_service.index, methods=['GET'])# data interfaceapp.add_url_rule('/get_comment_list', view_func=CommentsListView.as_view('get_comment_list'), methods=['GET'])if __name__ == "__main__":      app.run(port=5000, debug=True)

这里只以一个评论业务为例子讲解,使用flask导入相应的包,以支持服务。其中render_template用来渲染模板及jinja2,flask_cors用于支持跨域请求。这里将页面与数据接口进行了分开,如果输入网站为

 http://127.0.0.1:5000/product_tag

就跳转到对应的商品评价业务,该业务的数据接口是使用/get_comment_list实现,这是由tag_comment实现

tag_comment.py代码如下:

import jsonfrom flask import request, viewsfrom conn_tool import get_mysql_pool    mysql_pool = get_mysql_pool('reco')class TagListView(views.View):      def dispatch_request(self):          if 'pid' not in request.args:            return ''          pid = request.args['pid']          sel_tags_sql = '''              select tag_id from review where pid = %s;              '''          with mysql_pool.connection().cursor() as cur:              cur.execute(sel_tags_sql, int(pid))              tag_list = cur.fetchall()        return json.dumps(tag_list)

这里使用views来实现从数据库中载入一个对象列表并渲染到视图的函数。其主要目的是让你可以对已实现的部分进行替换,并且这个方式可以定制即插视图。

回到上面的run.py函数,输入对应的网址后,跳转到tag-comment.html这个html文件,该文件存放于templates文件夹中

{% extends "layout.html" %}    {% block main %}<div id="tag-comment-app"></div>{% endblock %}    {% block script %}<script src="[[ url_for('static', filename='js/tag-comment.js')]]"></script>{% endblock %}

可以看到,首选展开layout.html文件,即最基础的版面:

<!DOCTYPE html><html>      <head>          <meta charset="UTF-8">            <link rel="stylesheet" href="[[ url_for('static', filename='style/theme/index.css') ]]">            <script src="[[ url_for('static', filename='js/vue.js') ]]"></script>            <script src="[[ url_for('static', filename='js/index.js') ]]"></script>          <script src="[[ url_for('static', filename='js/axios.min.js') ]]"></script>      </head>        <body>          <div id="app" v-cloak>              <el-container style="height: 1100px; border: 1px solid #eee">                  <el-aside width="initial" style="background-color: rgb(238, 241, 246)">                      <el-menu :default-openeds="['1']">                          <el-submenu index="1">                              <template slot="title"><i class="el-icon-menu"></i>数据展示</template>                              <el-menu-item index="1-1">                                  <el-link href="/comment_tag">评论标签</el-link>                              </el-menu-item>                          </el-submenu>            </el-menuel>          </el-aside>        </el-container>          </div>      </body>        <script src="[[ url_for('static', filename='js/main.js') ]]"></script>      {% block script %} {% endblock %}    <style>          .el-header {            background-color: #B3C0D1;            color: #333;            line-height: 60px;          }        .el-aside {            color: #333;          }    </style></html>

即提供上面展示图中左侧栏,然后调用了tag-comment.js中的内容去替换layout.html中的body部分以及scripy脚步部分。tag-comment.js代码如下:

var tag_comment_app = new Vue({      el: '#tag-comment-app',      data: function() {          return {              input_pid: '',              alert_info: '输入商品pid,  点击查询查询商品标签,  点击标签查询详细评价!',              tag_comments: [],          }      },      methods: {          getTagList: function() {              // 获取标签集合              this.tag_comments = []              var re = /^[1-9]+[0-9]*]*$/              if (this.input_pid == null || this.input_pid.trim() == "" ||                  !re.test(this.input_pid)) {                  this.input_pid = 'xxxx'                  this.alert_info = '输入的PID为空或不合法! 默认查询商品pid为xxxx '              } else {                  this.alert_info = '输入商品pid,  点击查询查询商品标签,  点击标签查询详细评价!'              }              axios                  .get('/get_tag_list', {                      params: {                          "product_id": this.input_pid,                          "busi_type": this.busi_type                      }                  })                  .then(response => (this.product_tag_list = response.data))          },          }      },      template: `    <div>          <el-alert              style="font-size: 20spx;"              :title="alert_info"              type="warning"              show-icon>          </el-alert>            <el-row>              <el-col :span="16">                  <div style="margin-left: 0px;margin-top: 15px;">                      <div style="width: 120px; margin-top: 15px; margin-right: 5px; float: left;">                          <el-select v-model="busi_type" placeholder="请选择查询类型">                              <el-option label="晒图评价" value="1"></el-option>                              <el-option label="订单评价" value="2"></el-option>                          </el-select>                      </div>                      <div style="width: 480px; margin-top: 15px; float: left;">                          <el-input placeholder="请输入内容, 点击搜索按钮,  默认展示商品33911" v-model="input_pid" class="input-with-select">                              <template slot="prepend">商品pid</template>                              <el-button @click="getTagList()" slot="append" icon="el-icon-search"></el-button>                          </el-input>                      </div>                  </div>              </el-col>          </el-row>          <br />            <div class="tag-group">              <el-card class="box-card">                 <span> 标签结果 </span>                 <el-tag v-for="(tag,index) in product_tag_list"                         :id="tag.tag_id"                         :key="tag.label"                         :type="tag.type"                         style="margin: 10px 10px; cursor: pointer;"                         @click="getCommentList($event)">                     {{ tag.label }}({{ tag.tag_cnt }})               </el-tag>              </el-card>          </div>          <br />            <el-table :data="tag_comments" stripe style="width: 100%">              <el-table-column prop="feedback" label="评论内容">                  <template slot-scope="scope">                      <i class="el-icon-user-solid"> {{ scope.row.feedback }}</i>                      <span style="margin-left: 10px"></span>                  </template>              </el-table-column>              <el-table-column prop="comment_id" label="评论编号">              </el-table-column>          </el-table>          <br />      </div>      `  })

可以看到上述文件包含三部分内容,分别为datamethods以及template。其中data是整个页面需要的数据,算是初始化,methods是方法,调用run.py中的接口来获得返回数据,template用来画页面结构,并展示相应的数据。这样就可以将壳子与后台服务接口串联起来,让车开动起来,进行交互。

总结

整个过程如下:

  • run.py启动服务
  • 输入相应的网址展示对应的模版
  • 点击相应的业务,进行模版渲染
  • 在该模版上进行数据交互,调用相应的服务,完成数据传递与展示

通过这个项目,学会了将实现的算法以及得到的数据以页面的形式展示,便于可视化。数据不在是冰冷的了,反而多姿多彩。

参考

  • Vue官方教程
  • element官方教程
  • Element UI 教程
  • djangogirls
  • 即插视图flask view
  • vue 系列博客
  • render_template渲染模板及jinja2

阅读原文 https://blog.csdn.net/uncle_ll/article/details/102863391