Laravel - 结合 Vue 开发一个单页应用(附样例)
Laravel 是一个优秀的 php 框架,而 Vue.js 则是一个前端 MVVM 轻量级开源框架。将它们二者结合可以很轻松地实现一个单页应用。下面通过样例进行演示。
一、效果图
(1)在登录页(/login)填写正确用户名、密码后,点击登录按钮即可跳转到主页,否则弹出错误信息。


(2)主页(/)会自动重定向到列表页(/list),然后请求后台列表数据并显示。接着点击列表项又会跳转到详情页(/detail),并且同样请求后台详情数据显示。


(3)无论主页还是详情页,顶部都有个导航栏,显示当前用户信息,以及注销按钮(点击后返回登录页)。
(4)客户端这边使用了 Vue 导航守卫,如果用户没有登录直接通过路径访问主页或者详情页,也会自动跳回登录页。
二、准备工作
1,项目搭建
首先我们创建一个 Laravel 项目,具体步骤可以参考我之前写的文章:
2,安装相关组件
由于 Laravel 已经自带了 Vue,所以我们只需要安装引入 Vue Router 和 Vuex 即可。
(1)在项目根目录下运行如下命令安装 Vue Router:
npm install vue-router --save-dev
(2)在项目根目录下运行如下命令安装 Vuex:
npm install vuex --save-dev
三、后端相关工作
1,新增目录
为了对 API 和 Web 控制器进行分隔,我们创建如下两个文件夹:
- 创建 app/Http/Controllers/API 目录来存放 API 控制器
- 创建 app/Http/Controllers/Web 目录来存放 Web 控制器
2,Web 视图相关配置
(1)新增视图
- 由于我们构建的是单页面应用(SPA),所以在整个应用中只需要一个视图即可。首先我们在 resources 文件夹下创建一个 app.blade.php,其内容如下:
<!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link href="{{ asset('css/app.css') }}" rel="stylesheet" type="text/css"/> <link rel="icon" type="image/x-icon" href="/favicon.ico"> <title>hangge.com</title> <script type='text/javascript'> </script> </head> <body> <div id="app"> <router-view></router-view> </div> <script type="text/javascript" src="{{ asset('js/app.js') }}"></script> </body> </html>
(2)新增 Web 控制器
- 我们定义一个简单的 Web 控制器,并通过这个控制器返回 app.blade.php 视图。控制器文件路径为 app/Http/Controllers/Web/AppController.php,内容如下:
<?php namespace app\Http\Controllers\Web; use App\Http\Controllers\Controller; class AppController extends Controller { public function getApp() { return view('app'); } }
(3)新增 Web 路由
- 将 routes/web.php 里的内容修改成如下:
<?php Route::get('/', 'Web\AppController@getApp');
3,后端 API 接口编写
(1)新增 API 控制器
- 首先创建一个登录控制器,文件路径为 app/Http/Controllers/API/LoginController.php,内容如下:
<?php namespace app\Http\Controllers\API; use App\Http\Controllers\Controller; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; class LoginController extends Controller { // 验证用户信息 public function verifyUser(Request $request) { $username = $request->get('username'); $password = $request->get('password'); if ( $username == 'hangge' && $password == '123456' ) { return response()->json(['status' => true]); } else { return response()->json(['status' => false, 'message' => '用户密码不正确']); } } }
- 接着创建一个文章查询控制器,文件路径为 app/Http/Controllers/API/ArticleController.php,内容如下:
<?php namespace app\Http\Controllers\API; use App\Http\Controllers\Controller; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; class ArticleController extends Controller { // 返回文章列表 public function getList() { $result = array(["id" => "001", "title" => "文章001"], ["id" => "002", "title" => "文章002"], ["id" => "003", "title" => "文章003"]); return response()->json([ 'status' => true, 'result' => $result, ]); } // 根据id返回文章详情内容 public function getDetail($id) { $result = ["title" => "文章".$id, "content" => "这个是文章".$id."的详细内容"]; return response()->json([ 'status' => true, 'result' => $result, ]); } }
(2)新增 API 路由
- 将 routes/api.php 里的内容修改成如下:
<?php use Illuminate\Http\Request; // 请求地址则为 http://域名/api/verifyUser Route::post('/verifyUser', 'API\LoginController@verifyUser'); // 请求地址则为 http://域名/api/getList Route::get('/getList', 'API\ArticleController@getList'); // 请求地址则为 http://域名/api/getDetail/xxx Route::get('/getDetail/{id}', 'API\ArticleController@getDetail');
四、前端相关工作
1,修改 webpack.mix.js
Laravel 在项目根目录下自带一个默认的 webpack.mix.js 文件用于对前端资源进行处理,我们对其修改(主要是 app.js 路径),内容如下。
const mix = require('laravel-mix'); mix.js('resources/js/app.js', 'public/js') .sass('resources/sass/app.scss', 'public/css');
2,编辑 resources/js/app.js
主要进行一些基本配置(其中引入的 store 内容部分见下方第 3 点)。
/* jshint esversion: 6 */ window.axios = require('axios'); window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; import Vue from 'vue'; import router from './routes.js'; import store from './store'; new Vue({ router, store }).$mount('#app');
3,Vuex 相关
首先创建 resources/js/store 路径,接着在该路径下创建一个 index.js 文件,主要用于保存当前登录用户信息,内容如下:
/* jshint esversion: 6 */ import Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); const state={ //要设置的全局访问的state对象 username: "" //当前登录用户名 }; const getters = { //实时监听state值的变化(最新状态) isLogined() { return state.username !== ""; }, }; const mutations = { setUsername(state, username) { state.username = username; } }; const store = new Vuex.Store({ state, getters, mutations }); export default store;
4,HTTP 请求工具类
为方便前端请求后端 API,在 resources/js/api 下创建 httpAPI.js,内容如下:
/* jshint esversion: 6 */ export default { // 基础路径 BASE_URL: 'http://localhost:8000/api', // 用户名、密码验证 verifyUser: function( username, password ){ return axios.post( this.BASE_URL + '/verifyUser', { username: username, password: password } ); }, // 请求文章列表数据 getList: function(){ return axios.get( this.BASE_URL + '/getList'); }, getDetail: function(id){ return axios.get( this.BASE_URL + '/getDetail/' + id ); } }
5,创建 Vue 视图
(1)创建登录页面视图,路径 resources/js/pages/Login.vue,内容如下:
(2)创建主页面视图,路径 resources/js/pages/Home.vue,里面除了引用了一个导航组件外,还嵌套个子路由,内容如下:
<style> label { width: 50px; } </style> <template> <div> <div> <label>用户名</label> <input type="text" v-model="username"/> </div> <div> <label>密码</label> <input type="password" v-model="password"/> </div> <div> <a v-on:click="submit()">登录</a> </div> </div> </template> <script> import HttpAPI from '../api/httpAPI.js'; export default { data() { return { username: '', password: '' } }, methods: { // 登录按钮点击 submit() { // 发起请求 HttpAPI.verifyUser( this.username, this.password ) .then(( response ) => { if (response.data.status) { // 成功时保存用户名并自动跳转 this.$store.commit('setUsername', this.username); this.$router.push("/"); } else { // 失败时弹出错误信息 alert(response.data.message); } }) } } } </script>
(2)创建主页面视图,路径 resources/js/pages/Home.vue,里面除了引用了一个导航组件外,还嵌套个子路由,内容如下:
<style> </style> <template> <div> <div id="app-home"> <navigation></navigation> <router-view></router-view> </div> </div> </template> <script> import Navigation from '../components/Navigation.vue'; export default { components: { Navigation } } </script>
6,创建 Vue 组件
(1)创建导航组件,路径 resources/js/components/Navigation.vue,内容如下:
(2)创建文章列表组件,路径 resources/js/components/List.vue,内容如下:
(3)创建文章详情组件,路径 resources/js/components/Detail.vue,内容如下:
<style> </style> <template> <div> 您好:{{ username }}(<a v-on:click="logout()">注销</a>) <hr/> </div> </template> <script> export default { data () { return { username: this.$store.state.username } }, methods: { // 注销按钮点击 logout() { // 清除保存的用户名并跳回登录页 this.$store.commit('setUsername', ''); this.$router.push("/login"); } }, } </script>
(2)创建文章列表组件,路径 resources/js/components/List.vue,内容如下:
<style> </style> <template> <div> <ul id="example-1"> <li v-for="item in list"> <router-link :to="{ name: 'detail', params: { id: item.id} }"> {{ item.title }} </router-link> </li> </ul> </div> </template> <script> import HttpAPI from '../api/httpAPI.js'; export default { data() { return { list: [] } }, methods: { }, // 页面创建时自动加载列表数据 created() { // 发起请求 HttpAPI.getList( ) .then(( response ) => { if(response.data.status) { this.list = response.data.result; } }) } } </script>
(3)创建文章详情组件,路径 resources/js/components/Detail.vue,内容如下:
<style> </style> <template> <div> <div>标题:{{ detail.title }}</div> <div>内容:{{ detail.content }}</div> <br> <router-link :to="{ name: 'list'}">返回列表</router-link> </div> </template> <script> import HttpAPI from '../api/httpAPI.js'; export default { data() { return { detail: {} } }, // 页面创建时通过路由中的参数ID加载详情数据 created() { // 发起请求 HttpAPI.getDetail(this.$route.params.id) .then(( response ) => { if(response.data.status) { this.detail = response.data.result; } }) }, methods: { } } </script>
7,路由配置
编辑 resources/assets/js/routes.js 内容如下。注意:对于 /list 和 /detail 这两个路由我们添加了导航守卫,在前端实现认证路由保护。
/* jshint esversion: 6 */ import Vue from 'vue'; import VueRouter from 'vue-router'; import store from './store'; Vue.use( VueRouter ); // 对需要认证才能访问的路由调用该方法实现 Vue Router 导航守卫 function requireAuth(to, from, next) { // 如果用户已登录,可以继续访问路由,否则跳转到首页 // 这个功能类似 Laravel 中的 auth 中间件 if (store.getters.isLogined) { next(); } else { next('/login'); } } export default new VueRouter({ routes: [ { path: '/', redirect: {name: 'list'}, name: 'home', component: Vue.component( 'Home', require( './pages/Home.vue' ).default ), children: [ { path: 'list', name: 'list', beforeEnter: requireAuth, //登录验证 component: Vue.component('List', require('./components/List.vue').default ), }, { path: 'detail', name: 'detail', beforeEnter: requireAuth, //登录验证 component: Vue.component('Detail', require('./components/Detail.vue').default ), } ] }, { path: '/login', name: 'login', component: Vue.component( 'Login', require( './pages/Login.vue' ).default ) } ] });
五、项目运行
1,启动服务
在命令终端中进入项目目录,执行如下命令启动服务:
php artisan serve
2,编译客户端文件
(1)当我们修改了 js、vue、css 等文件是,可以再打开一个终端窗口,手动执行如下命令进行编译:
(2)如果是在线上生产环境构建应用,则运行如下命令:
npm run dev
(2)如果是在线上生产环境构建应用,则运行如下命令:
npm run production
(3)平时开发建议使用如下命令,这样就能实时监听 JavaScript/SCSS 文件的修改,只要文件修改保存后会立即对修改文件进行重新编译:
npm run watch
附:整个项目文件结构
通过上面一系列修改后,整个项目文件结构如下,红框表示有修改的部分。
