動手嘗試實現後台頁面

  • 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