当前位置: > > > Laravel - 结合 Vue 开发一个单页应用(附样例)

Laravel - 结合 Vue 开发一个单页应用(附样例)

    Laravel 是一个优秀的 php 框架,而 Vue.js 则是一个前端 MVVM 轻量级开源框架。将它们二者结合可以很轻松地实现一个单页应用。下面通过样例进行演示。

一、效果图

(1)在登录页(/login)填写正确用户名、密码后,点击登录按钮即可跳转到主页,否则弹出错误信息。
          

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

(3)无论主页还是详情页,顶部都有个导航栏,显示当前用户信息,以及注销按钮(点击后返回登录页)。
(4)客户端这边使用了 Vue 导航守卫,如果用户没有登录直接通过路径访问主页或者详情页,也会自动跳回登录页。

二、准备工作

1,项目搭建

首先我们创建一个 Laravel 项目,具体步骤可以参考我之前写的文章:

2,安装相关组件

由于 Laravel 已经自带了 Vue,所以我们只需要安装引入 Vue RouterVuex 即可。
(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,内容如下:
<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,内容如下:
<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)当我们修改了 jsvuecss 等文件是,可以再打开一个终端窗口,手动执行如下命令进行编译:
npm run dev 

(2)如果是在线上生产环境构建应用,则运行如下命令:
npm run production 

(3)平时开发建议使用如下命令,这样就能实时监听 JavaScript/SCSS 文件的修改,只要文件修改保存后会立即对修改文件进行重新编译:
npm run watch

附:整个项目文件结构

通过上面一系列修改后,整个项目文件结构如下,红框表示有修改的部分。
评论0