Flask-Vue 前后端分離基礎


使用 Flask + Vue 實現一個應用,簡單來說主要是以下幾個步驟:
  • Vue.js 實現前端頁面 & 通過 axios 庫請求后台接口獲取數據后重新渲染頁面;
  • Flask & Flask-CORS 提供接口 & 實現跨域服務。
  • 打包 Vue.js 文件 & 部署到服務器,通過 index 頁面進行訪問。
如果需要最終可以在公網訪問最終打包好的 Vue 前端界面,則執行以下操作:
  • 在服務器實現文件 get_msg.py 文件中配置 app.run(host=’your_ip_address’);
  • 配置 Vue axios請求接口的 base_url 為 your_ip_address;
  • 通過 npm run build 打包得到最終 dist 文件並部署到服務器中(部署可通過 python -m http.server 進行簡單部署,然后通過 http://your_ip_address:8000 端口訪問最終的頁面)。
Vue 前端實現簡單頁面
<template>
    <div>
      <span>{{ serverResponse }} </span>
      <button @click="getData">GET DATA</button>
    </div>
</template>

<script>
    import axios from 'axios';

    export default {
      name: "my-first-vue",
      data: function() {
          return {
            serverResponse: 'resp'
          }
      },
      methods: {
        getData() {
          var that = this;
          // 對應 Python 提供的接口,這里的地址填寫下面服務器運行的地址,本地則為127.0.0.1,外網則為 your_ip_address
          const path = 'http://127.0.0.1:5000/getMsg';
          axios.get(path).then(function (response) {
            // 這里服務器返回的 response 為一個 json object,可通過如下方法需要轉成 json 字符串
            // 可以直接通過 response.data 取key-value
            // 坑一:這里不能直接使用 this 指針,不然找不到對象
            var msg = response.data.msg;
            // 坑二:這里直接按類型解析,若再通過 JSON.stringify(msg) 轉,會得到帶雙引號的字串
            that.serverResponse = msg;

            alert('Success ' + response.status + ', ' + response.data + ', ' + msg);
          }).catch(function (error) {
            alert('Error ' + error);
          })
        }
        }
      }
    }
</script>
項目簡記
項目目錄結構

  • back_flask/app.py 代碼
from flask import Flask, jsonify, request
from flask_cors import CORS


app = Flask(__name__,
            template_folder="../front_vue/dist",
            static_folder="../front_vue/dist/static")
app.config.from_object(__name__)
CORS(app, resources={r'/*': {'origins': '*'}})

RESOURCES = [
    {
        'sn': 'SSNI-1003',
        'teacher': '楚留香',
        'learnt': True
    },
    {
        'sn': 'SSNI-100045',
        'teacher': '令狐沖',
        'learnt': False
    },
{
        'sn': 'HHNI-10037',
        'teacher': '王語嫣',
        'learnt': True
    },
    {
        'sn': 'TUNF-4565',
        'teacher': '霍青桐',
        'learnt': False
    }
]


@app.route('/resources', methods=['GET', 'POST'])
def all_res():
    response_object = {'status': 'success'}
    if request.method == 'POST':
        post_data = request.get_json()
        sn = post_data.get('sn')
        for r in RESOURCES:
            if r['sn'] == sn:
                response_object = {'status': 'failed'}
                response_object['message'] = '資源編號已存在, 添加失敗!'
                break
        else:
            RESOURCES.append({
                'sn': sn,
                'teacher': post_data.get('teacher'),
                'learnt': post_data.get('learnt')
            })
            response_object['message'] = '資源添加成功!'
    else:
        response_object['resources'] = RESOURCES
    return jsonify(response_object)


@app.route('/resources/<sn>', methods=['PUT', 'DELETE'])
def single_res(sn):
    response_object = {'status': 'success'}
    if request.method == 'PUT':
        post_data = request.get_json()
        if remove_res(sn):
            response_object['message'] = '資源更新成功!'
        else:
            response_object['message'] = '新加資源成功!'
        RESOURCES.append({
            'sn': sn,
            'teacher': post_data.get('teacher'),
            'learnt': post_data.get('learnt')
        })
    elif request.method == 'DELETE':
        if remove_res(sn):
            response_object['message'] = '刪除資源成功!'
            response_object['status'] = 'success'
        else:
            response_object['message'] = '該資源不存在!'
            response_object['status'] = 'failed'
    return jsonify(response_object)


def remove_res(sn):
    for r in RESOURCES:
        if r['sn'] == sn:
            RESOURCES.remove(r)
            return True
    return False


@app.route('/open', methods=['GET'])
def open_door():
    return jsonify(u'芝麻開門!')


@app.route('/ping-tong', methods=['GET'])
def ping_pong():
    return jsonify(u'Hello World Flask_Vue 前后端分離!')                  # (jsonify返回一個json格式的數據)


if __name__ == '__main__':
    app.config['JSON_AS_ASCII'] = False
    app.run(debug=True)

  • front_vue/src/components/Alert.vue
<template>
  <div>
    <b-alert :variant="variant" show>{{ message }}</b-alert>
    <br>
  </div>
</template>

<script>
export default {
  props: ['message', 'variant']
}
</script>
  • front_vue/src/components/Door.vue
<template>
  <div class="container">
    <button type="button" class="btn btn-primary">{{ msg }}</button>
  </div>
</template>

<script>
import axios from 'axios'

export default {
  name: 'Door',
  data () {
    return {
      msg: ''
    }
  },
  methods: {
    getMessage () {
      const path = `http://localhost:5000/open`
      axios.get(path)
        .then((res) => {
          this.msg = res.data
        })
        .catch((error) => {
          // eslint-disable-next-line
          console.error(error);
        })
    }
  },
  created () {
    this.getMessage()
  }
}
</script>

  • front_vue/src/components/HelloWorld.vue
<template>
  <div class="container">
    <button type="button" class="btn btn-primary">{{ msg }}</button>
  </div>
</template>

<script>
import axios from 'axios'

export default {
  name: 'HelloWorld',
  data () {
    return {
      msg: 'Hello World FlaskVue!'
    }
  },
  methods: {
    getMessage () {
      const path = `http://localhost:5000/ping-tong`
      axios.get(path)
        .then((res) => {
          this.msg = res.data
        })
        .catch((error) => {
          // eslint-disable-next-line
          console.error(error)
        })
    }
  },
  created () {
    this.getMessage()
  }
}
</script>

  • front_vue/src/components/Resources.vue
<template>
  <div class="container bg-dark">
    <div class="row">
      <div class="col-sm-10">
        <h1><img src="../assets/logo.png" alt="學習資源"></h1>
        <hr><br><br>
        <alert :message="message" :variant="alertvariant" v-if="showMessage"></alert>
        <button type="button" class="btn btn-info btn-sm" v-b-modal.res-modal>添加資源</button>
        <br><br>
        <table class="table table-hover text-white">
          <thead>
            <tr>
              <th scope="col">編號</th>
              <th scope="col">老師</th>
              <th scope="col">已學習</th>
              <th></th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="(r, index) in resources" :key="index">
              <td>{{ r.sn }}</td>
              <td>{{ r.teacher }}</td>
              <td>
                  <span v-if="r.learnt">是</span>
                  <span v-else>否</span>
              </td>
              <td>
                <div class="btn-group" role="group">
                  <button
                      type="button"
                      class="btn btn-warning btn-sm"
                      v-b-modal.res-update-modal
                      @click="editRes(r)">
                    修改
                  </button>
                  <button
                      type="button"
                      class="btn btn-danger btn-sm"
                      @click="onDeleteRes(r)">
                    刪除
                  </button>
                </div>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
<b-modal ref="addResModal"
         id="res-modal"
         title="添加新資源"
         hide-footer>
  <b-form @submit="onSubmit" @reset="onReset" class="w-100">
  <b-form-group id="form-sn-group"
                label="編號:"
                label-for="form-sn-input">
      <b-form-input id="form-sn-input"
                    type="text"
                    v-model="addResForm.sn"
                    required
                    placeholder="輸入編號">
      </b-form-input>
    </b-form-group>
    <b-form-group id="form-teacher-group"
                  label="老師:"
                  label-for="form-teacher-input">
        <b-form-input id="form-teacher-input"
                      type="text"
                      v-model="addResForm.teacher"
                      required
                      placeholder="輸入老師姓名">
        </b-form-input>
      </b-form-group>
    <b-form-group id="form-learnt-group">
      <b-form-checkbox-group v-model="addResForm.learnt" id="form-checks">
        <b-form-checkbox value="true">已學習?</b-form-checkbox>
      </b-form-checkbox-group>
    </b-form-group>
    <b-button type="submit" variant="primary">提交</b-button>
    <b-button type="reset" variant="danger">重置</b-button>
  </b-form>
</b-modal>
<b-modal ref="editResModal"
         id="res-update-modal"
         title="Update"
         hide-footer>
  <b-form @submit="onSubmitUpdate" @reset="onResetUpdate" class="w-100">
  <b-form-group id="form-sn-edit-group"
                label="編號:"
                label-for="form-sn-edit-input">
      <b-form-input id="form-sn-edit-input"
                    type="text"
                    v-model="editForm.sn"
                    required
                    readonly>
      </b-form-input>
    </b-form-group>
    <b-form-group id="form-teacher-edit-group"
                  label="老師:"
                  label-for="form-teacher-edit-input">
        <b-form-input id="form-teacher-edit-input"
                      type="text"
                      v-model="editForm.teacher"
                      required
                      placeholder="輸入老師姓名">
        </b-form-input>
      </b-form-group>
    <b-form-group id="form-learnt-edit-group">
      <b-form-checkbox-group v-model="editForm.learnt" id="form-checks">
        <b-form-checkbox value="true">已學習?</b-form-checkbox>
      </b-form-checkbox-group>
    </b-form-group>
    <b-button-group>
      <b-button type="submit" variant="primary">更新</b-button>
      <b-button type="reset" variant="danger">取消</b-button>
    </b-button-group>
  </b-form>
</b-modal>
  </div>
</template>

<style>
html,
body {
    margin: 0;
    padding: 0;
    background-color:#343a40;
}
</style>

<script>

import axios from 'axios'
import Alert from './Alert.vue'

export default {
  data () {
    return {
      resources: [],
      addResForm: {
        sn: '',
        teacher: '',
        learnt: []
      },
      editForm: {
        sn: '',
        teacher: '',
        learnt: []
      },
      message: '',
      alertvariant: '',
      showMessage: false
    }
  },
  components: {
    alert: Alert
  },
  methods: {
    getResources () {
      const path = `http://localhost:5000/resources`
      axios.get(path)
        .then((res) => {
          this.resources = res.data.resources
        })
        .catch((error) => {
          // eslint-disable-next-line
          console.error(error);
        })
    },
    addRes (payload) {
      const path = `http://localhost:5000/resources`
      axios.post(path, payload)
        .then((res) => {
          this.message = res.data.message
          var success = res.data.status
          if (success === 'failed') {
            this.alertvariant = 'danger'
          } else if (success === 'success') {
            this.alertvariant = 'success'
          } else {
            this.alertvariant = 'info'
          }
          this.showMessage = true
          this.getResources()
        })
        .catch((error) => {
          // eslint-disable-next-line
          console.log(error)
          this.getResources()
        })
    },
    editRes (resource) {
      this.editForm = resource
    },
    removeRes (sn) {
      const path = `http://localhost:5000/resources/${sn}`
      axios.delete(path)
        .then((res) => {
          this.message = res.data.message
          var success = res.data.status
          if (success === 'failed') {
            this.alertvariant = 'danger'
          } else if (success === 'success') {
            this.alertvariant = 'success'
          } else {
            this.alertvariant = 'info'
          }
          this.showMessage = true
          this.getResources()
        })
        .catch((error) => {
          // eslint-disable-next-line
          console.error(error)
          this.getResources()
        })
    },
    onDeleteRes (r) {
      this.removeRes(r.sn)
    },
    initForm () {
      this.addResForm.sn = ''
      this.addResForm.teacher = ''
      this.addResForm.learnt = []
      this.editForm.sn = ''
      this.editForm.teacher = ''
      this.editForm.learnt = []
    },
    onSubmit (evt) {
      evt.preventDefault()
      this.$refs.addResModal.hide()
      let learnt = false
      if (this.addResForm.learnt[0]) learnt = true
      const payload = {
        sn: this.addResForm.sn,
        teacher: this.addResForm.teacher,
        learnt
      }
      this.addRes(payload)
      this.initForm()
    },
    onSubmitUpdate (evt) {
      evt.preventDefault()
      this.$refs.editResModal.hide()
      let learnt = false
      if (this.editForm.learnt[0]) learnt = true
      const payload = {
        sn: this.editForm.sn,
        teacher: this.editForm.teacher,
        learnt
      }
      this.updateRes(payload, this.editForm.sn)
    },
    updateRes (payload, sn) {
      const path = `http://localhost:5000/resources/${sn}`
      axios.put(path, payload)
        .then((res) => {
          this.message = res.data.message
          this.alertvariant = 'success'
          this.showMessage = true
          this.getResources()
        })
        .catch((error) => {
          // eslint-disable-next-line
          console.error(error);
          this.getResources()
        })
    },
    onReset (evt) {
      evt.preventDefault()
      this.$refs.addResModal.hide()
      this.initForm()
    },
    onResetUpdate (evt) {
      evt.preventDefault()
      this.$refs.editResModal.hide()
      this.initForm()
      this.getResources()
    }
  },
  created () {
    this.getResources()
  }
}
</script>


  • front_vue/src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Resources from '../components/Resources.vue'
import Door from '../components/Door.vue'
import HelloWorld from '../components/HelloWorld.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Resources',
    component: Resources
  },
  {
    path: '/ping-pong',
    name: 'HelloWorld',
    component: HelloWorld
  },
  {
    path: '/open',
    name: 'Door',
    component: Door
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

  • front_vue/src/App.vue
<template>
  <div id="app">
    <router-view/>
  </div>
</template>
  • front_vue/src/main.js
// 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 'bootstrap/dist/css/bootstrap.css'
import BootstrapVue from 'bootstrap-vue'
import Vue from 'vue'
import App from './App'
import router from './router'
import axios from 'axios'

Vue.prototype.axios = axios
Vue.config.productionTip = false
Vue.use(ElementUI)
Vue.use(BootstrapVue)

/* eslint-disable no-new */
new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

  • front_vue/src/views/About.vue
<template>
  <div class="about">
    <h1>This is an about page</h1>
  </div>
</template>
  • front_vue/src/views/Home.vue
<template>
  <div class="home">
    <img alt="Vue logo" src="../src/assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
  </div>
</template>

<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'

export default {
  name: 'Home',
  components: {
    HelloWorld
  }
}

</script>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM