1.请先安装 Composer:
windows下安装软件请见附件【Composer-Setup.zip】,解压安装即可。
查看是否安装成功:
composer --version |
2.安装 Laravel:
通过 Composer 的 create-project 命令来安装 Laravel 应用,blog 为项目名称:
composer create-project --prefer-dist laravel/laravel blog |
注意:详细说明请看 Laravel5.4 文档。建议配置虚拟主机。
3.文件配置:
在根目录下有个 package.json 文件,配置内容如下(自动生成的文件中有很多包依赖没有,如果想使用 Element
,请先修改成下面配置,也可以后期逐个安装依赖包):
{
"private": true,
"scripts": {
"dev": "npm run development",
"development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch-poll": "npm run watch -- --watch-poll",
"hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
"prod": "npm run production",
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
},
"devDependencies": {
"axios": "^0.16.2",
"bootstrap-sass": "^3.3.7",
"cross-env": "^5.0.1",
"element-ui": "^2.3.7",
"jquery": "^3.1.1",
"laravel-mix": "^1.0",
"lodash": "^4.17.4",
"vue": "^2.1.10",
"vue-router": "^2.4.0",
"qs": "^6.5.2"
}
}
|
注:请使用在线json检测工具确保该json格式正确。红色是原项目所带依赖包,其他是我们添加的依赖包。
4.安装相关依赖 ,进入到项目目录执行:
cnpm install |
注:请自行安装 npm ,速度慢的请自行切换淘宝镜像 cnpm ,建议使用 cnpm。可以参考我的 Vue 笔记。
5.引入 Vue Router:
构建大型项目时,使用 Vue Router 将是一个好的方式,它可以帮助你更好的组织代码,优化路由。
1).
在 resources/assets/js 目录下新建目录 router ,同时在 router 目录下新建 index.js 文件,内容如下:
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
//加入需要渲染的页面
import Home from '../components/Home'
import Index from '../components/Index'
import Bar from '../components/Bar'
import Foo from '../components/Foo'
import Role from '../components/Role'
import Not_Found from '../components/Not_Found'
Vue.use(VueRouter);
const routes=[
{
path: '/',
component: Home,
children:[
{
path: '/',
name: 'index',
component: Index
},
{
path: 'index',
name: 'goindex',
redirect: '/'
},
{
path: 'bar',
name: 'bar',
component: Bar
},
{
path: 'foo',
name: 'foo',
component: Foo,
},
{
path: 'role',
name: 'rolw',
component: Role,
},
{
path: '404',
name: '404',
component: Not_Found
},
{
path: '*',
name: 'to404',
redirect: '/404'
},
]
},
{
path: '*',
name: 'go404',
redirect: '/404'
}
];
const router = new VueRouter({
routes: routes,
// mode: 'history'
});
router.push('/index'); //一进入就跳到该路由
export default router
|
注:具体组件下面将一一介绍。
2).设置顶级路由出口:
在 resources/assets/js 下新建 App.vue 文件,内容如下:
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
|
6.配置Vue主文件:
修改 resources/assets/js/app.js 文件,引入路由、axios 和 Element 等组件:
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./bootstrap');
window.Vue = require('vue');
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
import App from './App.vue'
import router from './router'
import Axios from "axios"
import qs from 'qs'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.prototype.$axios = Axios
Vue.use(ElementUI)
const app = new Vue({
el: '#app',
router,
render: h => h(App)
});
|
注:app.js 是构建 Vue 项目的主文件,最后所有组件都会被引入到该文件,在引入所有组件之前,bootstrap.js 文件做了一些初始化的操作。同时,因为有了 window.Vue = require('vue'); 这句话,就不再需要使用 import Vue from 'vue' 重复导入 Vue 了。
7.新建组件:(
在 resources/assets/js/components 目录或者子目录下新建各个组件)
1). Header.vue
<template>
<div class="header">
<el-menu :default-active="activeIndex2" class="el-menu-demo" mode="horizontal"
@select="handleSelect" background-color="#545c64" text-color="#fff" active-text-color="#ffd04b">
<el-menu-item index="/index">个人中心</el-menu-item>
<el-submenu index="2">
<template slot="title">我的工作台</template>
<el-menu-item index="/bar">选项1-Bar</el-menu-item>
<el-menu-item index="/foo">选项2-Foo</el-menu-item>
<el-menu-item index="/user">选项3-User</el-menu-item>
<el-submenu index="2-4">
<template slot="title">选项4</template>
<el-menu-item index="/2-4-1">选项1</el-menu-item>
<el-menu-item index="/2-4-2">选项2</el-menu-item>
<el-menu-item index="/2-4-3">选项3</el-menu-item>
</el-submenu>
</el-submenu>
<el-menu-item index="3" disabled>消息中心</el-menu-item>
<el-menu-item index="4"><a href="http://element-cn.eleme.io/#/zh-CN/component/table" target="_blank">订单管理</a></el-menu-item>
<el-submenu index="user" id="user">
<template slot="title">{{name}}</template>
<el-menu-item index="login_out" @click="logout">退出</el-menu-item>
</el-submenu>
</el-menu>
</div>
</template>
<script>
export default {
data() {
return {
activeIndex: '1',
activeIndex2: '1',
name : '时间旅者'
};
},
methods: {
handleSelect(key, keyPath) {
window.location.href = location.origin + "/#" + key;
console.log(key,keyPath);
},
logout(){
console.log("logout");
}
}
}
</script>
<style scoped>
#user{
float : right;
margin-right: 93px;
}
</style>
|
2). Home.vue
<template>
<div class="header">
<Header/>
<router-view></router-view>
</div>
</template>
<script>
import Header from "./Header"
export default {
data() {
return {
};
},
components:{
Header,
}
}
</script>
<style scoped>
</style>
|
3). Bar.vue
<template>
<div class="mycarousel">
<el-carousel :interval="5000" arrow="always" height="410px">
<el-carousel-item v-for="item in imgs" :key="item.key">
<img :src="item.path" height="415px" />
</el-carousel-item>
</el-carousel>
</div>
</template>
<script>
export default {
name:"foo",
data(){
return {
imgs: [
{
key:1,
path:"/images/myimg/1.jpg"
},
{
key:2,
path:"/images/myimg/2.jpg"
},
{
key:3,
path:"/images/myimg/3.jpg"
},
{
key:4,
path:"/images/myimg/4.jpg"
},
{
key:5,
path:"/images/myimg/5.jpg"
},
{
key:6,
path:"/images/myimg/6.jpg"
},
]
}
}
}
</script>
<style scoped>
.el-carousel__item:nth-child(2n) {
background-color: #99a9bf;
text-align: center;
}
.el-carousel__item:nth-child(2n+1) {
background-color: #d3dce6;
text-align: center;
}
</style>
|
4). Index.vue
<template>
<div class="m_index">
<el-table
:data="tableData"
border
height="540"
style="width: 100%">
<el-table-column
fixed
prop="id"
label="编号"
width="80">
</el-table-column>
<el-table-column
fixed
prop="date"
label="日期"
width="150">
</el-table-column>
<el-table-column
prop="name"
label="姓名"
width="150">
</el-table-column>
<el-table-column
prop="province"
label="省份"
width="150">
</el-table-column>
<el-table-column
prop="city"
label="市区"
width="150">
</el-table-column>
<el-table-column
prop="address"
label="地址"
width="350">
</el-table-column>
<el-table-column
prop="zip"
label="邮编"
width="150">
</el-table-column>
<el-table-column
fixed="right"
label="操作"
width="150">
<template slot-scope="scope">
<el-button @click="showDetail(scope.row)" type="text" size="small">查看</el-button>
<el-button @click="showDetail(scope.row)" type="text" size="small">编辑</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pagesize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
<el-dialog
title="提示"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose" >
<span>日期:{{ gridData.date }}</span></br>
<span>姓名:{{ gridData.name }}</span></br>
<span>省份:{{ gridData.province }}</span></br>
<span>市区:{{ gridData.city }}</span></br>
<span>地址:{{ gridData.address }}</span></br>
<span>邮编:{{ gridData.zip }}</span></br>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogVisible = false">确 定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
pagesize: 10,
currentPage: 1,
total: 0,
tableData: [],
gridData:{},
dialogVisible : false,
}
},
methods:{
getData:function(){
this.$axios("/getData",{
params:{
pagesize:this.pagesize,
currentPage:this.currentPage
}
})
.then(function(res) {
this.tableData = res.data.data;
this.total = res.data.total;
console.log(this.tableData);
}.bind(this))
.catch(function(error){
console.log(error);
})
},
showDetail:function(row) {
this.gridData = {
address:row.address,
city:row.city,
date:row.date,
name:row.name,
province:row.province,
zip:row.zip,
};
this.dialogVisible = true;
},
handleSizeChange: function (size) {
this.pagesize = size;
this.getData();
},
handleCurrentChange: function(currentPage){
this.currentPage = currentPage;
this.getData();
},
handleClose:function(done) {
this.$confirm('确认关闭?')
.then(_ => {
done();
})
.catch(_ => {});
}
},
created:function(){
this.getData();
}
}
</script>
<style>
.el-pagination{
text-align: center;
}
</style>
|
5). Role.vue
<template>
<div class="block">
<span class="demonstration">时间范围选择</span><br/>
<el-date-picker
v-model="value5"
type="datetimerange"
:picker-options="pickerOptions2"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
align="right">
</el-date-picker><br/>
您选择的时间范围为:{{ value5[0] }} 至 {{ value5[1] }}
</div>
</template>
<script>
export default {
data() {
return {
pickerOptions2: {
shortcuts: [{
text: '最近一周',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
picker.$emit('pick', [start, end]);
}
}, {
text: '最近一个月',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
picker.$emit('pick', [start, end]);
}
}, {
text: '最近三个月',
onClick(picker) {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
picker.$emit('pick', [start, end]);
}
}]
},
value5: [new Date(2000, 10, 10, 10, 10), new Date(2000, 10, 11, 10, 10)],
};
}
};
</script>
<style scoped>
.block{
height: 500px;
}
</style>
|
6). Foo.vue
<template>
<div>
<el-tabs type="border-card">
<el-tab-pane label="用户管理">
<Index/>
</el-tab-pane>
<el-tab-pane label="配置管理">
<Bar/>
</el-tab-pane>
<el-tab-pane label="角色管理">
<Role/>
</el-tab-pane>
<el-tab-pane label="定时任务补偿">
定时任务补偿
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import Index from "./Index"
import Bar from "./Bar"
import Role from "./Role"
export default {
name:"foo",
data(){
return {
msg: "我是foo组件"
}
},
components:{
Index,
Bar,
Role
}
}
</script>
<style scoped>
</style>
|
7). Not_Found.vue
<template>
<div class="notfound">
<a href="/">
<img src="/images/myimg/404.jpg" class="c404" />
</a>
</div>
</template>
<script>
export default {
data() {
return {
};
},
}
</script>
<style scoped>
.notfound{
width:100%;
overflow: hidden;
background-size:cover;
}
.c404{
width:100%;
}
</style>
|
8.后台提供数据:
由于 Index.vue 组件使用了后台提供的数据,故我们需要在后台进行处理,以此将 Laravel 跟 Vue 结合起来。
1).在 app 目录下新建 DataFrom.php 文件:
<?php
namespace App;
class DataFrom
{
public $id;
public $date;
public $name;
public $province;
public $city;
public $address;
public $zip;
}
|
2).在 app\Http\Controllers 下新建 UserController.php 文件:
在控制台使用 artisan 工具生成:
php artisan make:controller UserController |
修改代码如下:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use App\DataFrom;
class UserController extends Controller
{
public function getData(Request $request){
//生成数据并进行分页传给前端
$arr = [];
for($i=1;$i<1002;$i++){
$dataFrom = new DataFrom();
$dataFrom->id = $i;
$dataFrom->date = '2016-05-0'.$i;
$dataFrom->name = '王小虎'.$i;
$dataFrom->province = '上海'.$i;
$dataFrom->city = '普陀区'.$i;
$dataFrom->address = '上海市普陀区金沙江路 1518 弄'.$i;
$dataFrom->zip = '20033'.$i;
$arr[] = $dataFrom;
}
$currentPage = $request->input('currentPage', 1); // 获取页码
$pagesize = $request->input('pagesize', 10); //每页的条数
$offset = ($currentPage * $pagesize) - $pagesize;//计算每页分页的初始位置
$res = new LengthAwarePaginator(array_slice($arr,$offset,$pagesize,true),count($arr),$pagesize,
$currentPage,['path' => $request->url(), 'query' => $request->query()]);
return response()->json($res);
}
}
|
3).在 routes\web.php 文件中配置路由:
<?php
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
return view('welcome'); //主视图
});
Route::get('
/getData
', '
UserController@getData
');
|
说明:以后如果要添加组件,只需要在 resources/assets/js/components 目录下新建 vue 文件,然后再在 resources/assets/js/router/index.js 文件里引入,并配置路由,然后就可以通过路由访问了。如果组件里面访问了后台数据,则在 app\Http\Controllers 下新建相应的 Controller 文件,并在 routes\web.php 文件配置后台路由即可。
9. 配置 Laravel 主视图(该文件也是Vue主入口文件):
修改 resources/view/welcome.blade.php 文件:
<!DOCTYPE html>
<html lang="{{ config('app.locale') }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{csrf_token()}}">
<link href="{{ asset('css/main.css') }}" rel="stylesheet" type="text/css" />
<title>天下博客</title>
</head>
<body>
<div id="app"></div>
<!--<script src="{{ asset('js/manifest.js') }}"></script> -->
<!--<script src="{{ asset('js/vendor.js') }}"></script> -->
<script src="{{ asset('js/app.js') }}"></script>
</body>
</html>
|
10. 运行:
cnpm run dev |
该命令默认会去执行根目录下的 webpack.mix.js,使用 Laravel 提供的 Laravel Mix 编译资源,并将编译好的资源放在根目录 public 文件夹下。
说明:前端编译工具有许多,比如 gulp、webpack 等等,Laravel 也为自己提供了开箱即用的编译工具,比如 Laravel5.3 及更早版本提供基于 gulp 的 Laravel Elixir 和从 Laravel5.4 开始提供基于 webpack 的 Laravel Mix,当然你也可以不使用官方提供的工具,自己去配置编译工具。这些编译工具的作用都是一样的,使用方法也大同小异。
11. 热部署运行(开发过程中建议使用该方式):
npm run watch |
注:如遇到跨域问题,请修改 resources/assets/js/bootstrap.js 文件。
12.CSRF token not found 错误:
环境搭建完成后,访问项目,打开开发者模式,切换到 Console ,会看到报
CSRF token not found: 错误。
Laravel 为了避免应用遭到跨站请求伪造攻击(CSRF),自动为每一个有效用户会话生成一个 CSRF 令牌,该令牌用于验证授权用户和发起请求者是否是同一个人。
如遇到该错误,请修改 welcome.blade.php 文件,增加下面代码(前面其实已经加上了):
<meta name="csrf-token" content="{{csrf_token()}}"> |
13.代码拆分优化:
代码拆分是将一些不经常变动的代码提取出来,以避免每次都要重新编译,如果你频繁更新应用的 JavaScript,需要考虑对 vendor 库进行提取和拆分,这样的话,一次修改应用代码不会影响 vendor.js 文件的缓存。
Laravel Mix 的 extract 方法可以实现这样的功能:
修改项目根目录下的 webpack.mix.js 文件:
mix.js('resources/assets/js/app.js', 'public/js')
.sass('resources/assets/sass/app.scss', 'public/css')
.extract(['vue','axios']);
|
extract 方法接收包含所有库的数组或你想要提取到 vendor.js 文件的模块,使用上述代码作为示例,Mix 将会生成如下文件:
文件
|
说明
|
public/js/manifest.js
|
Webpack manifest runtime
|
public/js/vendor.js
|
vendor 库
|
public/js/app.js
|
应用代码
|
修改主入口文件
resources/view/welcome.blade.php :
<!DOCTYPE html>
<html lang="{{ config('app.locale') }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="X-CSRF-TOKEN" content="{{csrf_token()}}">
<meta name="csrf-token" content="{{csrf_token()}}">
<link href="{{ asset('css/main.css') }}" rel="stylesheet" type="text/css" />
<title>天下博客</title>
</head>
<body>
<div id="app"></div>
<script src="{{ mix('js/manifest.js') }}"></script>
<script src="{{ mix('js/vendor.js') }}"></script>
<script src="{{ mix('js/app.js') }}"></script>
</body>
</html>
|
注:全局的 mix 函数会根据 public/mix-manifest.json 中的路径去加载对应的文件,同时注意引入三个 js 文件的顺序,以避免出错。
说明:我们在前面使用 assets 函数,此处却改为 mix 函数,这两个函数的主要区别就是 assets 函数会直接使用所填路径去 public 文件夹下找文件,而 mix 函数会自动加载带 hash 值的前端资源。这是和 Laravel 前端资源的缓存刷新相关的。
重新执行命令,就可以了:
npm run watch |
14.总结:
到目前为止,项目搭建完毕。前端编译工具使用基于 webpack 的 Laravel Mix,一般情况下,它可以满足大部分的需求,当然你也可以完全抛弃 Laravel Mix,配置自己的 webpack。如果前端需要其他 Vue 插件,直接安装引入即可。
15.本文全部代码,可以到 github 上获取:
https://github.com/EighthRoute520/blog.git