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 协议》,转载必须注明作者和本文链接
本帖由系统于 6年前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
讨论数量: 20

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

6年前 评论
yanrui

感谢,博主发的例子

6年前 评论

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

6年前 评论

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

5年前 评论
Flex

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

5年前 评论

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

5年前 评论

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

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

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

4年前 评论

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

4年前 评论
ruodee

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

4年前 评论

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

4年前 评论

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

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

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!