抱歉,你查看的文章已删除

动手尝试实现后台页面

由于业务需要,需要将现有完成的一些算法,且未上线或者已经上线的展示出来,需要制作类似于图形用户界面的展示看板,由于部分算法已经实现服务接口,讨论以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

本文分享自微信公众号 - AI科技时讯(aiblog_research)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-11-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

编辑于

AI科技时讯

93 篇文章9 人订阅

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励