Laravel5.5 + Vue 开发单页应用

上次我用 laravel5.3 + Vue 开发了一个简单的单页应用,这次我打算将其升级到 laravel5.5,在升级的过程中,做一下记录,其源码放在 github 上面,源码地址

开发环境#

软件包 版本
Laravel 5.5
Vue > 2.5.7
axios > 0.17
vue-router > 3.0.1
vuex > 3.0.1

安装#

# 安装 laravel
composer create-project --prefer-dist laravel/laravel laravel-vue "5.5.*"

# 安装前端依赖包
npm install

安装完之后,为了便于开发,使用热更新模式。

# 在 webpack.mix.js 中添加配置
mix.browserSync({
  proxy: 'localhost:8000'
});

# 执行 `php artisan serve`
php artisan serve

# 执行 npm run watch
npm run watch

打开浏览器访问 http://localhost:3000 就可以看到 laravel 的欢迎页面了

目标#

开发三个页面,首页,列表,详情,相对应准备 3 个 api 接口

migration#

php artisan make:migration news --create=news
# database/migrations/create_news.php
Schema::create('news', function (Blueprint $table) {
  $table->increments('id');
  $table->string('title');
  $table->text('content');
  $table->integer('is_recommend')->default(0);
  $table->timestamps();
});

model#

这里使用命令生成 model 文件,编辑对应的文件内容。

php artisan make:model News
# app/News.php
namespace App;

use Illuminate\Database\Eloquent\Model;

class News extends Model
{
  public $fillable = ['title', 'content'];
}

controller#

生成控制器,在控制器中定义三个接口对应的方法。

php artisan make:controller NewsController
# app/Http/Controllers/NewsController
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\News;

class NewsController extends Controller
{
  /**
   * 推荐列表
   */
  public function recommend()
  {
      $list = News::where('is_recommend', 1)->get();
      foreach ($list as $key => $value) {
          $list[$key]->created = $list[$key]->created_at->diffForHumans();
      }
      return $list;
  }

/**
 * 新闻列表
 */
  public function index()
  {
    $list = News::get();
      foreach ($list as $key => $value) {
          $list[$key]->created = $list[$key]->created_at->diffForHumans();
      }
      return $list;
  }

  /**
   * 新闻详情
   */
  public function show($id)
  {
    $row = News::findOrFail($id);
    return $row;
  }
}

router#

定义 api 的接口路由,在 routes/api.php 文件中定义。

# routes/api.php
Route::get('/news', 'NewsController@index');
Route::get('/news/recommend', 'NewsController@recommend');
Route::get('/news/{id}', 'NewsController@show');

Vue 单页#

到这里我们准备工作已经完毕,接下来正式开发 Vue 的单页应用,在开发单页应用中,对应 Route Api Vuex Components 这些,下面我们就来定义这些。

首先在首页引入对应的 app.cssapp.js 文件。

# resource/vies/welcome.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="csrf-token" content="@{{ csrf_token }}">
  <title>Laravel & Vue</title>
  <link rel="stylesheet" type="text/css" href="/css/app.css">
</head>
<body>
  <div id="app">
    <nav class="navbar navbar-inverse">
      <div class=" container">
         <div class="navbar-header">
            <a class="navbar-brand" href="/">LaravelVue</a>
        </div>
      </div>
    </nav>    
    <div class="container main">
      <router-view />
    </div>
  </div>
  <script type="text/javascript" src="/js/app.js"></script>
</body>
</html>

配置启动 app.js#

应用对应的入口文件是 app.js,所以在这个入口文件中,我们实例化 Vue 实例,初始化和加载所需的组件。

# resource/assets/js/app.js
require('./bootstrap');

window.Vue = require('vue');

import VueRouter from 'vue-router';
Vue.use(VueRouter);
import store from './store/'; // vuex 数据存储所需对象
import routes from './routes';    // 路由配置文件
// 实例化路由
const router = new VueRouter({
    routes
})

var vm = new Vue({
  store,
  router
}).$mount('#app');

路由#

前端页面主要有 3 个路由,如下

# resource/assets/js/routes.js
export default[
  { path: '', redirect: '/index' },
  { path: '/index', component: require('./page/App.vue') },
  { path: '/list', component: require('./page/List.vue') },
  { path: '/detail/:id', component: require('./page/Detail.vue') }
];

Vuex#

Vuex 集中式存储管理应用的所有组件的状态,这里使用的是多模块方式记录数据。

# resource/assets/js/store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import news from './news';
Vue.use(Vuex);
export default new Vuex.Store({
  // 可以设置多个模块
  modules: {
    news
  }
});

# resource/assets/js/store/news.js
import api from '../api';
export default{
  state: {
    recommend: [], // 推荐
    lists: [],  // 列表
    detail: {}  // 详情
  },
  mutations: {
    // 注意,这里可以设置 state 属性,但是不能异步调用,异步操作写到 actions 中
    SETRECOMMEND(state, lists) {
      state.recommend = lists;
    },
    SETLISTS(state, lists) {
      state.lists = lists;
    },
    SETDETAIL(state, detail) {
      state.detail = detail;
    }
  },
  actions: {
    getNewsDetail({commit}, id) {
      // 获取详情,并调用 mutations 设置 detail
      api.getNewsDetail(id).then(function(res) {
        commit('SETDETAIL', res.data);
        document.body.scrollTop = 0;
      });
    },
    getNewsRecommend({commit}) {
      api.getNewsRecommend().then(function(res) {
        commit('SETRECOMMEND', res.data);
      });
    },
    getNewsLists({commit}) {
      api.getNewsLists().then(function(res) {
        commit('SETLISTS', res.data);
      });
    }
  }
}

api#

我们在这里定义前端请求数据 api,这里使用的是 axios 包来请求数据,具体用法参考文档。

# resource/assets/js/api.js
import axios from 'axios'
export default {
  // 首页推荐接口
  getNewsRecommend: function (params) {
    return axios.get('api/news/recommend', {
      params: params
    })
  },
  // 列表接口
  getNewsLists: function (params) {
    return axios.get('api/news', {
      params: params
    })
  },
  // 详情接口
  getNewsDetail: function (id) {
    return axios.get('api/news/' + id)
  }
}

page#

我们在这里定义组件页面,将其页面放到 page 目录下面,Vue 定义组件的方式参考文档。
三个页面的具体写法定义如下:

# resource/assets/js/page/App.vue
<template>
    <div class="panel panel-default">
        <div class="panel-heading">新闻推荐
            <router-link to="/list" class="pull-right">更多</router-link>
        </div>
        <ul class="list-group">
            <li class="list-group-item"
                v-for="row in recommend">
                <router-link :to="{path:'/detail/' + row.id}">
                    {{ row.title }}
                </router-link>
                <span class="pull-right">{{ row.created }}</span>
            </li>
        </ul>
    </div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
export default({
    // 映射 vuex 上面的属性
    computed: mapState({
        recommend: state => state.news.recommend
    }),
    created() {
        // 获取推荐列表
        this.getNewsRecommend();
    },
    methods: {
        // 映射 vuex 对象上的方法
        ...mapActions([
            'getNewsRecommend'
        ])
    }
});
</script>

# resource/assets/js/page/List.vue
<template>
  <div class="panel panel-default">
    <div class="panel-heading">新闻列表</div>
    <ul class="list-group">
      <li class="list-group-item"
        v-for="row in lists">
        <router-link :to="{path:'/detail/' + row.id}">
          <span class="label label-success" v-if="row.is_recommend">推荐</span>
          {{ row.title }}
        </router-link>
        <span class="pull-right">{{ row.created }}</span>
      </li>
    </ul>
  </div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
export default({
  computed: mapState({
    lists: state => state.news.lists
  }),
  created() {
    this.getNewsLists();  
  },
  methods: {
    ...mapActions([
        'getNewsLists'
    ])
  }
});
</script>

# resource/assets/js/page/Detail.vue
<template>
  <div>
    <ol class="breadcrumb">
      <li><a href="/">首页</a></li>
      <li><router-link to="/list" class="pull-right">新闻</router-link></li>
      <li class="active">{{ detail.title }}</li>
    </ol>
    <h3><span class="label label-success" v-if="detail.is_recommend">推荐</span> {{ detail.title }}</h3>
    <p>创建时间:{{ detail.created_at }}</p>
    <div>
      {{ detail.content }}
    </div>
  </div>
</template>
<style>
.breadcrumb{
    padding: 8px 0;
}    
</style>
<script>
import { mapState, mapActions } from 'vuex';
export default({
  computed: mapState({
    detail: state => state.news.detail
  }),
  created() {
    // 获取路由参数id
    // js 中用 this.$route 获取当前路由,用 this.$router 获路由对象,全部路由信息
    // 在模板中用 $router  和 $router 直接调用
    var id = this.$route.params.id;
    this.getNewsDetail(id);
  },
  methods: {
    ...mapActions([
        'getNewsDetail'
    ])
  }
});
</script>

这里我们简单的单页应用就开发完毕了,使用 npm run watch 可以热更新,实时看到页面的变化。
文章地址

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 7年前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 20

laravel 与前端结合的一个方便的地方就是 Blade 中可以直接向 vue 组件 prop 传值,真不愧全栈框架~!

7年前 评论
yanrui

感谢,博主发的例子

6年前 评论

楼主,这个代理配置的正确吗?我一直不能热更新

6年前 评论

个人建议在末尾加上你程序运行后的项目 gif 能加分噢!.

6年前 评论
Flex

成功在 5.7 版本下 跑起来了 感谢 lz 分享

6年前 评论

file
执行 npm install 的时候报错请问博主,这是什么原因,本人小白一枚,请教了

6年前 评论

请问 Admin 模块下的 SPA 搭建时,该如何配置路由?

a.com/Admin/vue路由
5年前 评论

vue 初学,虽然有一部分代码看不懂,但是先抄着玩吧,谢谢题主分享

5年前 评论

按你的教程,php artisan serve 后,在浏览器访问后,服务器似乎就挂了,缺了什么呢?

5年前 评论
ruodee

给我一些指引,感谢分享。
看过之后,我觉得完全可以使用 Vue 构建应用视图, laravel 只用来提供 api

5年前 评论

@LW_aravel 报错里提醒的,没有 package.json 文件,

5年前 评论

感谢博主,按照步骤 走完之后,没有报错,程序也跑起来了,但是页面只有 导航栏,cosole 没有报错,有遇到这类情况的么?

5年前 评论
Flyertutor (楼主) 5年前
Toka (作者) 5年前