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
附:整个项目文件结构
通过上面一系列修改后,整个项目文件结构如下,红框表示有修改的部分。
