前端
环境配置
首先安装 vue
npm install vue
创建 vue 工程
# 使用 webpack 打包工具初始化一个名为 frontend 的工程 vue init webpack frontend
安装依赖
# 进入工程目录 cd frontend # 安装 vue-router npm install vue-router --save-dev # 安装 element-ui npm i element-ui -S # 安装 SASS 加载器 npm install sass-loader node-sass --save-dev # 安装依赖 npm install
启动工程
1npm run dev
此时,一个最简 vue 应用就完成了。
我们看一下 src 文件夹,这里就是我们写前端代码的地方了
如下文件的作用
- assets:用于存放资源文件
- components:用于存放 Vue 功能组件
- views:用于存放 Vue 视图组件
- router:用于存放 vue-router 配置
- api:存放编写的 api 调用代码
- config:用于存放一些公共配置,如后端 url 等
- utils:公共方法
- App.vue:组件模板
- main.js:项目的入口文件
下面我们就简单实现一个登陆功能,来进一步理解下各个文件的作用。
添加代码
首先处理配置信息,在 config 文件夹中创建 url.js 文件
const devUrl = 'http://127.0.0.1:9980'; //const proUrl = 'http://apiUrl.com'; export default{ apiUrl: devUrl, apiPrefix: 'api', gitHub: '' }
在 api 文件夹中创建 https.js 文件
import axios from 'axios' import qs from 'qs' import Config from '../config'; axios.defaults.timeout = 5000; axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'; axios.defaults.baseURL = ''; function buildApiUrl(url) { return `${Config.apiUrl}/${Config.apiPrefix}/${url}`; } axios.interceptors.request.use((config) => { if(config.method == 'post'){ config.data = qs.stringify(config.data); } return config; }, (error) => { console.log('error params') return Promise.reject(error); } ); axios.interceptors.response.use((res) => { if(!res.data.success) { return Promise.resolve(res); } return res; }, (error) => { console.log('Network error') return Promise.reject(error); } ); //返回一个Promise(发送post请求) export function fetchPost(url, params) { let apiUrl = buildApiUrl(url); return new Promise((resolve, reject) => { axios.post(apiUrl, params) .then(response => { resolve(response); }, err => { reject(err); }) .catch((error) => { reject(error) }) }) } ////返回一个Promise(发送get请求) export function fetchGet(url, param) { let apiUrl = buildApiUrl(url); return new Promise((resolve, reject) => { axios.get(apiUrl, {params: param}) .then(response => { resolve(response) }, err => { reject(err) }) .catch((error) => { reject(error) }) }) } export default { fetchGet, fetchPost }
这里封装了 axios 的 post 和 get 请求。
在 views 下面创建首页视图 Main.vue
<template> <div> 首页 </div> </template> <script> export default{ name: "Main" } </script> <style> </style>
在 views 下面创建登陆视图 Login.vue
<template> <div> <el-form ref="loginForm" :model="form" :rules="rules" label-width="80px" class="login-box"> <h3 class="login-title">欢迎登录</h3> <el-form-item label="账号" prop="username"> <el-input type="text" placeholder="请输入账号" v-model="form.username"/> </el-form-item> <el-form-item label="密码" prop="password"> <el-input type="password" placeholder="请输入密码" v-model="form.password"/> </el-form-item> <el-form-item> <el-button class="login-button" type="primary" v-on:click="onSubmit('loginForm')">登录</el-button> </el-form-item> </el-form> <el-dialog title="温馨提示" :visible.sync="dialogVisible" width="30%" :before-close="handleClose"> <span>请输入账号和密码</span> <span slot="footer" class="dialog-footer"> <el-button type="primary" @click="dialogVisible = false">确 定</el-button> </span> </el-dialog> <el-dialog title="温馨提示" :visible.sync="dialogVisible1" width="30%" :before-close="handleClose"> <span>错误的账号或密码</span> <span slot="footer" class="dialog-footer"> <el-button type="primary" @click="dialogVisible1 = false">确 定</el-button> </span> </el-dialog> </div> </template> <script> import https from '../api/https.js' export default { name: "Login", data() { return { form: { username: '', password: '' }, // 表单验证,需要在 el-form-item 元素中增加 prop 属性 rules: { username: [ {required: true, message: '账号不可为空', trigger: 'blur'} ], password: [ {required: true, message: '密码不可为空', trigger: 'blur'} ] }, // 对话框显示和隐藏 dialogVisible: false, dialogVisible1: false, } }, methods: { onSubmit(formName) { // 为表单绑定验证功能 this.$refs[formName].validate((valid) => { var username = this.form['username']; var pwd = this.form['password']; var login_info = {username: username, password: pwd}; if (valid) { https.fetchPost('login', login_info).then((data) => { console.log(data.data['code']) if (data.data['code'] == 200) { this.$router.push("/home"); } else { this.dialogVisible1 = true; return false; } }) // 使用 vue-router 路由到指定页面,该方式称之为编程式导航 //this.$router.push("/main"); } else { this.dialogVisible = true; return false; } }); }, handleClose() { } } } </script> <style lang="scss" scoped> .login-button { text-align: center; } .login-box { border: 1px solid #DCDFE6; width: 350px; margin: 180px auto; padding: 35px 35px 15px 35px; border-radius: 5px; -webkit-border-radius: 5px; -moz-border-radius: 5px; box-shadow: 0 0 25px #909399; } .login-title { text-align: center; margin: 0 auto 40px auto; color: #303133; } </style>
修改 router 下面路由函数 index.js
import Vue from 'vue' import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' import Login from '@/views/Login' import Main from '@/views/Main' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'HelloWorld', component: HelloWorld }, { // Main 页面 path: '/main', name: 'Main', component: Main }, { // 登陆页面 path: '/login', name: 'Login', component: Login }, ] })
修改 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
// 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 Vue from 'vue' import App from './App' import router from './router' import VueRouter from 'vue-router' // 导入 elementUI import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' // 导入 axios import axios from 'axios' import QS from 'qs' Vue.prototype.$axios = axios Vue.prototype.qs = QS Vue.config.productionTip = false Vue.use(VueRouter) Vue.use(ElementUI) /* eslint-disable no-new */ new Vue({ el: '#app', router, //components: { App }, //template: '<App/>' render: h => h(App) })
下面我们启动我们的前端程序
npm run dev
如果看到类似的
Your application is running here: http://localhost:8080
说明我们的前端代码构建成功。
现在我们在浏览器中打开上面的地址,就可以得到页面如下:
后端
后端代码,我准备用 flask + flask_restful 来搭建
class LoginView(Resource): def post(self): try: username = request.get_json()['username'] pwd = request.get_json()['password'] user = User.query.filter_by(username=username).first() if user is not None and user.verify_password(pwd): login_user(user) return {'code': 200, 'message': 'you are login now!'} else: return {'code': 403, 'message': 'wrong account or password'} except: raise api_login.add_resource(LoginView, '/login')
这里仅仅给出了最核心的 api 代码,还是非常简单的。更多的关于 flask_restful,可以查看其官网。
至此,一个简单的前后端分离的单页面应用就完成了。
看完本文,你可以按着步骤自己实现下。刚接触的伙伴在看的过程中在某些地方可能有疑惑,其实我也研究了好久,也有好多存疑的地方。不过,我还是建议不要妄求每个点都了解的特别清楚,先明白关键点,试着实现一下,回头去看相关资料的时候,也更有感触一些。