配置composer.json
,修改为中国镜像,或者全局配置好
"repositories": {
"packagist": {
"type": "composer",
"url": "https://packagist.phpcomposer.com"
}
}
提前配置好国内镜像,否则很慢...
composer create-project --prefer-dist laravel/laravel video
#打开packagist.org搜索 laravel-ide-helper
#提前配置好中国镜像
composer require barryvdh/laravel-ide-helper
#在config/app.php中,找到providers在最后一行增加配置项
Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,
#然后再执行
php artisan ide-helper:generate
#重启编辑器,找到有一个路由文件Config::试试
mysql版本最好是5.7,找到.env,然后修改配置。
更改config/database.php,
把
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
修改成
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
就可以支持mysql低版本了
#执行迁移,如果版本过低就会报错
php artisan migrate
这个时候会有3张表(users, password_reset, migrations)
执行以下命令创建模型,如果加上 -m 代表一同创建数据迁移文件。
创建完毕之后在app目录会有一个Model目录并里面就会有模型。
database/migrations也会多一个数据迁移文件。
php artisan make:model Model/Admin -m
默认的users作为前台用户表,我们使用admins作为后台用户表,在迁移文件中,增加admins字段(username 非重,password)
执行数据迁移
php artisan migrate
找到database/factories/UserFactory.php文件,改名为AdminFactory.php修改其中的代码
$factory->define(\App\Model\Admin::class, function (Faker $faker) {
static $password;//$password第一次为null,再一次就有值了,之后密码都一样。
return [
'username' => $faker->name,
'password' => $password ?: $password = bcrypt('admin888'),
];
});
然后在命令行执行tinker命令来灌数据
php artisan tinker
在命令行输入,创建3条用户表的数据
factory(App\Model\Admin::class,3)->create();
创建控制器,会在app/Http/Controllers生成控制器
php artisan make:controller Admin/EntryController
在控制器中建立一个login方法,然后需要设置路由访问,
不能直接通过get参数的形式访问。laravel 3 有,laravel 4 取消了这样的模式
找到routes/web.php建立路由
Route::get('/login', 'Admin\EntryController@login');
我们也可以把路由分组,路由组允许共享路由属性,例如中间件和命名空间等,我们没有必要为每个路由单独设置共有属性,共有属性会以数组的形式放到 Route::group 方法的第一个参数中。
Route::group(
['prefix'=>'admin','namespace'=>'Admin'],
function(){
//prefix是admin,所以匹配包含 "/admin/login" 的 URL
//namespace为Admin,会自动寻找App\Http\Controllers\Admin下面的EntryController
Route::get('/login','EntryController@login')
});
也可以在web.php写入,把路由按照文件夹分开
include __DIR__ . '/admin/web.php';
如果get和post同时请求一个方法可以使用match
Route::match( ['get','post'],'/login', 'EntryController@login' );
在app目录下面建立Libraries目录,建立helper.php写入p函数
更改composer.json,在autoload选项中加入
"files": [
"app/Libraries/helper.php"
]
还要执行 composer dump
默认视图位置resource/views/,载入模板可以通过
return view('welcome');
也可以
return \View::make('welcome');
如果要建立文件夹views/admin/entry/login.blade.php
,那么需要:
return view('admin.entry.login');
可将 Mr.Xiong 的后台模板作为我们项目所用,静态资源放入到public目录,可使用{{asset(“静态资源”)}}
引入静态资源,asset函数使用当前请求的协议(HTTP或HTTPS)为资源文件生成 URL。
表单提交需要在form元素中加
{{csrf_field()}}
使用laravel提供的用户验证
更改config/auth.php
文件
//增加admin守卫(guards),驱动为session,交给提供者(provider)的admins处理,然后再找到App\Model\Admin模型,模型需要继承Illuminate\Foundation\Auth\User
'guards' => [
'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'admins' => [
'driver' => 'eloquent',
'model' => App\Model\Admin::class,
]
],
Admin模型
<?php
namespace App\Model;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\User as Authenticatable;
class Admin extends Authenticatable
{
//
}
在控制器(先判断是否为post提交):
$status = Auth::guard('admin')->attempt([
'username'=>Request::input('username'),
'password'=>Request::input('password'),
]);
如果登陆成功或者登陆失败
if($status){
//跳转到后台首页,注意写路由
return redirect('/admin/index');
}
//重新跳转回来,并且分配闪存属性
return redirect('/admin/login')->with('error','用户名或者密码错误');
页面代码:
@if(session('error'))
<div class="alert alert-danger">
{{session('error')}}
</div>
@endif
通过中间件来验证登陆
php artisan make:middleware LoginMiddleware
在中间件中写入检测是否登陆
if(!Auth::guard('admin')->check()){
return redirect('/admin/login');
}
写入配置项app/Http/Kernel.php的$routeMiddleware
'admin.auth'=>LoginMiddleware::class
在Entry控制器构造方法调用中间件,除了登陆方法
public function __construct() {
$this->middleware('admin.auth')->except(['login']);
}
模板继承:
resources/views/admin/entry新建index.blade.php,让后台首页方法载入
return view('admin.entry.index')
然后建立resources/views/admin/layout文件夹放入继承的父模板master.blade.php找到不是共用的部分,写入占位符
@yield('content')
在index.blade.php写入以下代码
@extends('admin.layout.master')
@section('content')
中间内容
@endsection
构建控制器方法logout
Auth::guard('admin')->logout();
return redirect('/admin/login');
更改令牌app/Model/Admin.php
protected $rememberTokenName = '';
//html页面
{{Auth::guard('admin')->user()->username}}
创建控制器,配置路由指向控制器方法changePassword,更改父模板a链接,构建修改密码页面
php artisan make:controller Admin/MyController
public function password(){
return view('admin.my.password');
}
在password.blade.php页面的form表单指向到/admin/changePassword,别忘记设计路由
创建表单验证,在 app/Http/Requests 会生成 PasswordPost.php 文件。这样的验证时单写一个类,好处就是在任何控制器中需要处理直接引入改类即可,类似于 Tp5 中的验证器。
php artisan make:request PasswordPost
authorize方法
表单的请求类内包含了 authorize 方法。在这个方法中,你可以确认用户是否真的通过了授权,以便更新指定数据,比如是否登陆?如果自己处理验证,那么返回true
public function authorize()
{
return true;
}
rules方法是规则,
confirme
验证字段值必须和 foo_confirmation 的字段值一致。例如,如果要验证的字段是 password,就必须和输入数据里的 password_confirmation 的值保持一致。
public function rules() {
return [
'old_password' => 'sometimes|required',
'password' => 'sometimes|required|confirmed',
'password_confirmation' => 'sometimes|required',
];
}
在password.blade.php页面显示错误消息
@if (count($errors) > 0)
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
在app/Http/Controllers/Admin/MyController::changePassword方法中,注入验证对象
public function changePassword(PasswordPost $request){
$model = Auth::guard('admin')->user();
$model->password = bcrypt($request->input('password'));
$model->save();
}
改成汉语的错误提示:
在app/Http/Requests/PasswordPost.php你可以通过重写表单请求的 messages 方法来自定义错误消息。此方法必须返回一个数组,其中含有成对的属性或规则以及对应的错误消息
public function messages()
{
return [
'old_password.required' => '原始密码不能为空',
'old_password.required' => '原始密码不能为空',
'password.required' => '新密码不能为空',
'password.confirmed' => '两次密码不一致',
'password_confirmation.required' => '确认密码不能为空'
];
}
验证原密码是否正确,新建方法passwordValidator,然后在rules调用
use Validator;
use Hash;
use Auth;
public function passwordValidator(){
Validator::extend('check_password', function ($attribute, $value, $parameters, $validator) {
return Hash::check($value,Auth::guard('admin')->user()->password);
});
}
rule调用,并且制定check_password规则,别忘记在messages增加提示信息
public function rules() {
$this->passwordValidator();
return [
'old_password' => 'sometimes|required|check_password',
'password' => 'sometimes|required|confirmed',
'password_confirmation' => 'sometimes|required',
];
}
在packagist.org
搜索flash
组件
composer require laracasts/flash
把以下代码放在config/app.php的provider配置下
Laracasts\Flash\FlashServiceProvider::class,
在master.blade.php文件尾部写入
@include('flash::message')
<script>
require(['bootstrap'],function($){
$('#flash-overlay-modal').modal();
setTimeout(function(){
$('#flash-overlay-modal').modal('hide');
},2000)
});
</script>
生成消息提示模板,也可以直接更改模板,会在resources/views生成vendor目录
php artisan vendor:publish --provider="Laracasts\Flash\FlashServiceProvider"
在控制器方法中使用flash方法
public function changePassword(PasswordPost $request){
if($request->isMethod('post')){
$model = Auth::guard('admin')->user();
$model->password = bcrypt($request->input('password'));
$model->save();
flash()->overlay('密码修改成功', 'Mr.Xiong-友情提示');
}
return view('admin.my.changePassword');
}
Laravel 资源路由可以将典型的「CURD」路由指定到一个控制器上
php artisan make:controller Admin/TagController --resource
设置路由器
Route::resource('tag','TagController');
GET => /tag => index方法
GET => /tag/create => create方法
POST => /tag => store方法
GET => /tag/{id} => show方法
GET => /tag/{id}/edit => edit方法
...
构建标签列表和标签添加模板,让各个方法create
、index
载入模板。
创建标签模型,顺便创建表
php artisan make:model Model/Tag -m
数据迁移文件中创建字段name并执行迁移
Schema::create('tags', function (Blueprint $table) {
$table->increments('id');
$table->string('name',50);
$table->timestamps();
});
post请求就会到store方法,form表单写法
<form action="/admin/tag" method="post" ...
创建表单验证,会在app/Http/Requests目录生成表单验证类,按照passwordPost.php修改就好了。
php artisan make:request TagPost
更改控制器中的store方法注入
public function store(TagPost $request)
{
if($request->isMethod('post')){
p($request->input());
}
}
别忘记在模板添加错误提示
@if (count($errors) > 0)
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
批量填充,默认不允许
public function store(TagPost $request,Tag $model)
{
if($request->isMethod('post')){
$model->create($request->all());
flash()->overlay('添加成功','友情提示');
}
}
需要在App\Model\Tag模型中指定guarded = []
class Tag extends Model
{
//guarded警戒的意思,为空代表谁也不警戒
protected $guarded = [];
}
添加成功提示
public function store(TagPost $request,Tag $model)
{
if($request->isMethod('post')){
$model->create($request->all());
flash()->overlay('添加成功','友情提示');
return redirect('/admin/tag');
}
}
列表页处理
public function index(Tag $model)
{
$data = $model->get();
//或者不传参数获取所有数据 一个集合
$data = Tag::all();
return view('admin.tag.index',compact('data'));
}
页面foreach循环
@foreach($data as $v)
<tr>
<td>{{$v['id']}}</td>
<td>{{$v['name']}}</td>
<td>
<div class="btn-group">
<a href="/admin/tag/{{$v['id']}}/edit" class="btn btn-default">编辑</a>
<a href="javascript:del({{$v['id']}});" class="btn btn-danger">删除</a>
</div>
</td>
</tr>
@endforeach
书写js的del方法
<script>
function del(id){
require(['hdjs'],function(hdjs){
hdjs.confirm('确定删除吗?',function(){
$.ajax({
url:"{{asset('/admin/tag')}}/" + id,
method:'DELETE',//Laravel要求,查看手册资源控制器操作处理
success:function(response){
}
})
});
})
}
</script>
@endsection
但是监控异步发现需要使用csrf_token,可以全局异步设置csrf_token传输,在master.blade.php模板中设置以下代码
<meta name="csrf-token" content="{{ csrf_token() }}">
<script>
//放在require.js下面
require(['jquery'],function($){
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
}
});
})
</script>
控制器destroy方法操作
public function destroy($id)
{
Tag::destroy($id);
return response()->json(['status'=>true,'message'=>'删除成功']);
//也可以自己定义成功失败方法在公共控制器当中,来一个$message参数即可
return $this->success('删除成功');
}
异步的success方法
success:function(response){
hdjs.message(response.message,'refresh');
}
edit方法
public function edit($id)
{
$oldData = Tag::find($id);
return view('admin.tag.edit',compact('oldData'));
}
模板
<input type="text" name="name" id="inputID" class="form-control" value="{{$oldData['name']}}" title="">
修改动作,需要模拟put请求,action需要带上参数,才能访问update方法
<form action="{{asset('admin/tag')}}/{{$oldData['id']}}"....
{{method_field('PUT')}}
update方法
public function update(Request $request, $id)
{
$model = Tag::find($id);
$model['name'] = $request->input('name');
$model->save();
flash()->overlay('编辑成功','友情提示');
return redirect('/admin/tag');
}
建立公共控制器,通过建立构造方法,然后让自控制器继承,来完成验证,需要排除一些方法。
php artisan make:controller Admin/CommonController
创建控制器
php artisan make:controller Admin/LessonController --resource
配置资源路由
Route::resource('lesson','LessonController');
创建模型,顺便创建数据迁移文件,别忘记执行数据迁移。
php artisan make:model Model/Lesson -m
更改迁移文件,增加字段
Schema::create('lessons', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->string('introduce');
$table->string('preview');
$table->tinyInteger('iscommend');
$table->tinyInteger('ishot');
$table->smallInteger('click');
$table->timestamps();
});
执行迁移
php artisan migrate
模板处理,直接复制tag,然后修改。
在课程下面使用Vue处理视频
<script>
require(['vue'],function(Vue){
new Vue({
el:'#app',
data:{
videos:[{title:'',path:''}]
},
methods:{
add(){
this.videos.push({title:'',path:''});
},
del(k){
this.videos.splice(k,1);
}
}
})
})
</script>
页面可以查看数据,注意加上@符号,代表laravel不要以模板引擎方法来解析,因为这是vue模板引擎。
@{{videos}}
创建视频video
模型与表
php artisan make:model Model/Video -m
数据迁移文件
Schema::create('videos', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->string('path');
$table->integer('lesson_id');
$table->timestamps();
});
执行迁移
php artisan migrate
更改Model/Video模型,允许批量填充
protected $guarded = [];
在Model/Lesson模型中,声明多表关联,也就是一门课程拥有多个视频
//允许批量填充
protected $guarded = [];
public function videos(){
//videos表和lessons表关联自动,会自动以当前模型(lesson)的名称加上_id,也就是lesson_id就是video表的关联字段【Snake Case】
return $this->hasMany(Video::class);
}
手动在表中插入一些模拟数据,然后测试:
$data = Lesson::find(1)->videos()->get();
p($data->toArray());
上传需要用到fileinfo扩展,需要在宝塔后台安装:fileinfo
,如果安装失败,在Linux工具箱,把swap交换分区调整成1024M
将以下代码放在课程添加页面,别忘记改name名称
<div class="col-sm-11">
<div class="input-group">
<input type="text" class="form-control" name="preview" readonly="" value="">
<div class="input-group-btn">
<button onclick="upImage(this)" class="btn btn-default" type="button">选择图片</button>
</div>
</div>
<div class="input-group" style="margin-top:5px;">
<img src="{{asset('images/nopic.jpg')}}" class="img-responsive img-thumbnail" width="150">
<em class="close" style="position:absolute; top: 0px; right: -14px;" title="删除这张图片" onclick="removeImg(this)">×</em>
</div>
</div>
<script>
//上传图片
function upImage(obj) {
require(['util'], function (util) {
options = {
multiple: false,//是否允许多图上传
};
util.image(function (images) { //上传成功的图片,数组类型
$("[name='preview']").val(images[0]);
$(".img-thumbnail").attr('src', images[0]);
}, options)
});
}
//移除图片
function removeImg(obj) {
$(obj).prev('img').attr('src', '{{asset('images/nopic.jpg')}}');
$(obj).parent().prev().find('input').val('');
}
</script>
图片地址可以是
<img src="/images/nopic.jpg"...
也可以使用asset('images/nopic.jpg')
生成,区别就是asset函数可以生成带有http的地址,并且指向public
目录,例如:http://nickblog.cn/images/nopic.jpg
<img src="{{asset('images/nopic.jpg')}}"...
修改配置项config/filesystems.php
,复制一份local
配置,把local
和storage_path('app')
改成attachment
,这样attachment
就会在public
目录下面生成,因为storage_path()
会指向storage
目录,不在public
目录下将来不便于访问
'attachment' => [
'driver' => 'local',
'root' => 'attachment',
],
建立后台控制器配合hdjs上传
php artisan make:controller Component/UploadController
建立uploader
和filesLists
方法:
//上传文件 查看手册综合话题->文件存储->文件上传
public function uploader( Request $request ) {
$upload = $request->file;
//可以查看$upload的方法,比如获得大小,类型等...
//$arr = get_class_methods($upload);
//p($arr);
//判断是否上传成功 查看手册基础功能->请求->验证成功上传
if($upload->isValid()){
$path = $upload->store(date('ymd'));
return ['valid'=>1,'message'=>asset('attachment/' . $path)];
}
return ['valid'=>0,'message'=>'上传失败'];
}
配置路由,不属于后台,直接在routes/web.php配置就好了
Route::match(['get', 'post'], '/component/uploader', 'Component\UploadController@uploader');
Route::match(['get', 'post'], '/component/filesLists', 'Component\UploadController@uploader');
或者写成另一种写法
Route::any('/component/uploader', 'Component\UploadController@uploader');
Route::any('/component/filesLists', 'Component\UploadController@uploader');
更改master.blade.php中的hdjs的配置,之后进行图片上传测试
//HDJS组件需要的配置
window.hdjs={};
//组件目录必须绝对路径(在网站根目录时不用设置)
window.hdjs.base = '{{asset("admin/node_modules/hdjs")}}';
//上传文件后台地址
window.hdjs.uploader = '{{asset("/component/uploader")}}';
//获取文件列表的后台地址
window.hdjs.filesLists = '{{asset("/component/filesLists")}}?';
创建标签与课程的中间表
php artisan make:model Model/LessonTag -m
设置模型
protected $guarded = [];
数据迁移
Schema::create('lesson_tags', function (Blueprint $table) {
$table->integer('lesson_id');
$table->integer('tag_id');
$table->timestamps();
});
php artisan migrate
在LessonController中的store方法添加数据
public function store( Request $request ) {
//添加课程
$lesson = new Lesson();
$lesson->title = $request->input( 'title' );
$lesson->introduce = $request->input( 'introduce' );
$lesson->preview = $request->input( 'preview' );
$lesson->iscommend = $request->input( 'iscommend' );
$lesson->ishot = $request->input( 'ishot' );
$lesson->click = $request->input( 'click' );
$lesson->save();
//添加中间表
foreach ( $request->input( 'tid' ) as $tid ) {
//添加标签与课程的中间表
( new LessonTag() )->create(
[
'lesson_id' => $lesson->id,
'tag_id' => $tid
]
);
}
//添加视频
foreach ( json_decode( $request->videos, true ) as $v ) {
$lesson->videos()->create(
[
'title'=>$v['title'],
'path'=>$v['path']
]
);
}
flash()->overlay('添加成功','友情提示');
return redirect( '/admin/lesson' );
}
新建oss,进入到
https://oss.console.aliyun.com/overview
开通阿里云的oss,
友情提示:在阿里云存入1-2元钱,以免播放没钱,不能播放了。
公共读
*
,Allowed允许所有请求,Allowed Headers:也设置为*访问控制
访问控制
控制台,点击用户管理
,然后新建用户
,勾选下面的“为改用自动生成AccessKey”,然后保存AccessKey授权
,让其拥有操作OSS的权限(管理开放存储服务(OSS)权限)外网域名
和块的名称
复制保存。在组件中新建控制器
php artisan make:controller Component/OssController
打开HDJS手册
https://www.kancloud.cn/houdunwang/hdjs/387581
复制手册中的sign方法到OssController中,注意把static
去掉,然后设置信息。
class OssController extends Controller
{
//生成供前台使用的签名
public function sign()
{
//阿里云 AccessKeyId
$id = '**********';
//阿里云 AccessKeySecret
$key = '********************';
//OSS外网域名: 在阿里云后台OSS bucket中查看
$host = 'http://******.oss-cn-qingdao.aliyuncs.com';
//oss中本次上传存放文件的目录
$dir = $_GET['dir'];
function gmt_iso8601($time)
{
$dtStr = date("c", $time);
$mydatetime = new \DateTime($dtStr);
$expiration = $mydatetime->format(\DateTime::ISO8601);
$pos = strpos($expiration, '+');
$expiration = substr($expiration, 0, $pos);
return $expiration."Z";
}
$now = time();
$expire = 30; //设置该policy超时时间是10s. 即这个policy过了这个有效时间,将不能访问
$end = $now + $expire;
$expiration = gmt_iso8601($end);
//最大文件大小.用户可以自己设置
$condition = [0 => 'content-length-range', 1 => 0, 2 => 1048576000];
$conditions[] = $condition;
//表示用户上传的数据,必须是以$dir开始, 不然上传会失败,这一步不是必须项,只是为了安全起见,防止用户通过policy上传到别人的目录
$start = [0 => 'starts-with', 1 => '$key', 2 => $dir];
$conditions[] = $start;
$arr = ['expiration' => $expiration, 'conditions' => $conditions];
//return;
$policy = json_encode($arr);
$base64_policy = base64_encode($policy);
$string_to_sign = $base64_policy;
$signature = base64_encode(hash_hmac('sha1', $string_to_sign, $key, true));
$response = [];
$response['accessid'] = $id;
$response['host'] = $host;
$response['policy'] = $base64_policy;
$response['signature'] = $signature;
$response['expire'] = $end;
//这个参数是设置用户上传指定的前缀
$response['dir'] = $dir;
return json_encode($response);
}
}
配置路由
Route::any('/component/oss', 'Component\OssController@sign');
复制以下代码页面做测试
<div class="panel panel-default father-box" v-for="(v,k) in videos" style="position: relative">
<i class="fa fa-times-circle fa-2x son-box" @click="del(k)" style="position: absolute;top:-6px;left: -6px;display: none;cursor: pointer" aria-hidden="true"></i>
<div class="panel-body">
<div class="form-group">
<label for="" class="col-sm-2 control-label">标题</label>
<div class="col-sm-10">
<input type="text" v-model="v.title" class="form-control" >
</div>
</div>
<div class="form-group">
<label for="" class="col-sm-2 control-label">视频地址</label>
<div class="col-sm-10">
<div class="input-group">
<input class="form-control" v-model="v.path" value="">
<div class="input-group-btn">
<button type="button" class="btn btn-default" :id="'selectfiles'+v.id">选择文件</button>
<button type="button" class="btn btn-default" :id="'postfiles'+v.id">开始上传</button>
</div>
</div>
<div class="progress" style="display: none">
<div :id="'progress'+v.id" class="progress-bar progress-bar-success progress-bar-striped" role="progressbar" style="width: 0%">
0%
</div>
</div>
</div>
</div>
</div>
</div>
<script>
require(['vue'],function (Vue) {
new Vue({
el:'#app',
data:{
videos:[]
},
methods:{
add:function () {
var field= {title:'',path:'',id:Date.parse(new Date())};
this.videos.push(field);
setTimeout(function () {
upload(field);
},100);
},
del:function(k){
this.videos.splice(k,1);
}
},
})
})
function upload(field) {
require(['oss'], function (oss) {
var uploader = oss.upload({
//容器
container: 'container',
//文件选择按钮
pick: 'selectfiles'+field.id,
//开始上传按钮
upButton: 'postfiles'+field.id,
//获取签名
serverUrl: "{{asset('/component/oss')}}?",
//上传目录
dir: 'file/',
//local_name本地文件名 random_name随机文件名
name_type: 'random_name',
//允许上传类型
filters: {
//文件类型
mime_types: [
//只允许上传图片和zip,rar文件
{title: "Image files", extensions: "jpg,gif,png,bmp,jpeg"},
{title: "Zip files", extensions: "zip,rar"},
{title: "Video", extensions: "mp4"}
],
//最大只能上传10mb的文件
max_file_size: '10mb',
//不允许选取重复文件
prevent_duplicates: true
},
event: {
//选择文件
select: function (file) {
$('#progress'+field.id).text('0%').css({width:'0%'})
//$('h1').html('0%');
},
//开始上传
start: function (up, file) {
//console.log('开始上传');
$('#progress'+field.id).parent().show();
},
progress: function (up, file) {
// console.log();
$('#progress'+field.id).text(file.percent + '%').css({width:file.percent + '%'})
},
success: function (up, file, info) {
field.path = 'http://laravel-video-oss.oss-cn-beijing.aliyuncs.com/'+file.name;
// console.log('http://laravel-video-oss.oss-cn-beijing.aliyuncs.com/'+file.name);
//file.name = "{!! \Config::get('oss.host') !!}/" + file.name;
$('#progress'+field.id).parent().hide();
},
error: function (up, file, info) {
alert(info.response);
}
}
});
})
}
</script>
代码如下
public function edit( $id ) {
$oldData = Lesson::find($id);
$tagData = Tag::all();
$checkTag = DB::table('lesson_tags')->where('lesson_id',$id)->pluck('tag_id')->toArray();
//JSON_UNESCAPED_UNICODE(中文不转为unicode ,对应的数字 256)
//JSON_UNESCAPED_SLASHES (不转义反斜杠,对应的数字 64)
$videos = json_encode($oldData->videos()->get()->toArray(),JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
return view('admin.lesson.edit',compact('tagData','oldData','checkTag','videos'));
}
Vue代码
require(['vue'], function (Vue) {
new Vue({
el: '#app',
data: {
//videos:JSON.parse('{{$videos}}'),//会报错,所以要原样输出
videos: JSON.parse('{!! $videos !!}')
},
mounted(){
this.videos.forEach(function(v){
upload(v);
});
},
methods: {
add() {
var field = {title: '', path: '', id: 'hd' + Date.parse(new Date())};
this.videos.push(field);
setTimeout(function () {
upload(field);
}, 200);
},
del(k) {
this.videos.splice(k, 1);
}
}
})
})
执行修改
html页面
<form action="{{asset('admin/lesson')}}/{{$oldData['id']}}" method="post" ....
{{method_field('PUT')}}
update方法
public function update( Request $request, $id ) {
//编辑课程
$lesson = Lesson::find($id);
$lesson->title = $request->input( 'title' );
$lesson->introduce = $request->input( 'introduce' );
$lesson->preview = $request->input( 'preview' );
$lesson->iscommend = $request->input( 'iscommend' );
$lesson->ishot = $request->input( 'ishot' );
$lesson->click = $request->input( 'click' );
$lesson->save();
//编辑中间表
//删除
DB::table('lesson_tags')->where('lesson_id',$id)->delete();
//添加中间表
foreach ( $request->input( 'tid' ) as $tid ) {
//添加标签与课程的中间表
( new LessonTag())->create(
[
'lesson_id' => $lesson->id,
'tag_id' => $tid
]
);
}
//编辑视频
//删除视频
$lesson->videos()->delete();
//添加视频
foreach ( json_decode( $request->videos, true ) as $v ) {
$lesson->videos()->create(
[
'title' => $v['title'],
'path' => $v['path']
]
);
}
flash()->overlay('编辑成功','友情提示');
return redirect( '/admin/lesson' );
}
public function destroy($id)
{
Lesson::destroy($id);
LessonTag::where('lesson_id',$id)->delete();
Video::where('lesson_id',$id)->delete();
return $this->success('删除成功');
}
利用vue的脚手架来创建一个webapp应用,具体流程查看 Mr.Xiong 博客
运行脚手架
cd webapp
cnpm run dev
提取公共的css,放到index.html
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css" />
<link rel="stylesheet" type="text/css" href="css/iconfont.css"/>
把css,js,images都放入到静态资源static目录
把链接指向static目录
<link rel="stylesheet" href="static/css/swiper-3.4.1.min.css" />
<link rel="stylesheet" type="text/css" href="static/css/bootstrap.min.css" />
<link rel="stylesheet" type="text/css" href="static/css/iconfont.css"/>
复制一个Hello.vue改名为Home.vue,其中的name值就是在vue报错误的时候,能标明是哪个组件
<template></template>
<script>
export default {
name: 'home',
data () {
return {
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>
复制模板index.html的body中间的内容,放入到Home.vue中的template中,但是template中间不能有任何的Js代码。
设置路由
在router/index.js中
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
}
]
})
设置首页的css,把index.css里面的代码复制到Home.vue中的style中,再把App.vue中的多余的元素删掉,另外“今日推荐”留一个就可以了。
js
import Video from '@/components/Video'
{
path: '/video',
name: 'Video',
component: Video
}
和上面一样,不再赘述
其实就是把之前的a
,替换成router-link
,例如Page.vue:
<a href="" class="iconfont back"></a>
//替换成
<router-link to="/" class="iconfont back"></router-link>
安装插件(command+t新建Terminal)
cd webapp
npm install vue-awesome-swiper --save
在main.js让vue扩展出swiper功能
import Vue from 'vue'
import VueAwesomeSwiper from 'vue-awesome-swiper'
Vue.use(VueAwesomeSwiper)
Home.vue的html代码
<!--轮播图-->
<swiper :options="swiperOption" :not-next-tick="notNextTick" ref="mySwiper">
<!-- slides -->
<swiper-slide v-for="v in slide" :key="v.id">
<router-link to="/video">
<img :src="v.path" alt="">
</router-link>
</swiper-slide>
<!-- Optional controls -->
<!-- Optional controls -->
<div class="swiper-pagination" slot="pagination"></div>
</swiper>
<!--轮播图结束-->
然后在Home.vue页面的Js代码
export default {
name: 'home',
data() {
return {
slide: [
{id:1,path:'static/images/1.jpg'},
{id:2,path:'static/images/2.jpg'},
{id:3,path:'static/images/3.jpg'},
],
notNextTick: true,
swiperOption: {
autoplay: 3000,
// direction : 'vertical',
grabCursor : true,
setWrapperSize :true,
autoHeight: true,
pagination : '.swiper-pagination',
paginationClickable :true,
mousewheelControl : true,
observeParents:true,
}
}
}
}
列表页标签页是用swiper
调整Video.vue的swiper,可以查看swiper的demo
https://surmon-china.github.io/vue-awesome-swiper/
<!-- swiper -->
<swiper :options="swiperOption">
<swiper-slide v-for="v in tags" :key="v.id">{{v.title}}</swiper-slide>
</swiper>
<!--导航条结束-->
export default {
name: 'video',
data() {
return {
tags:[
{id:1,'title':'PHP'},
{id:2,'title':'JS'},
{id:3,'title':'PHP'},
{id:4,'title':'JS'},
{id:5,'title':'PHP'},
{id:6,'title':'JS'},
],
swiperOption: {
pagination: '.swiper-pagination',
slidesPerView: 3,
spaceBetween: 30
}
}
},
}
php artisan make:controller Api/ContentController
php artisan make:controller Api/CommonControlller
CommonController的代码
abstract class CommonControlller extends Controller {
protected function response( $data, $code = '200' ) {
return [ 'data' => $data, 'code' => $code ];
}
}
ContentController
class ContentController extends CommonControlller
{
public function tags(){
return $this->response(Tag::get());
}
}
建立路由
在web.php中
include __DIR__ . '/api.php';
在api.php中,访问/api/tags测试
Route::group(
[ 'prefix' => 'api', 'namespace' => 'Api' ],
function () {
Route::match( [ 'get' ], '/tags', 'ContentController@tags' );
} );
Route::group(
[ 'prefix' => 'api', 'namespace' => 'Api' ],
function () {
Route::match( [ 'get' ], '/tags', 'ContentController@tags' );
Route::match( [ 'get' ], '/lessons/{tid}', 'ContentController@lessons' );
} );
public function lessons( $tid ) {
if ( $tid ) {
$data = DB::table( 'lessons' )
->select( 'lessons.*' )
->join( 'lesson_tags', 'id', '=', 'lesson_id' )
->where( [ 'tag_id' => $tid ] )
->get();
} else {
$data = Lesson::get();
}
return $this->response( $data );
}
public function commendLessons($rows){
$data = Lesson::where(['iscommend'=>1])->limit($rows)->get();
return $this->response($data);
}
public function hotLessons($rows){
$data = Lesson::where(['ishot'=>1])->limit($rows)->get();
return $this->response($data);
}
Route::match( [ 'get' ], '/commendLessons/{rows}', 'ContentController@commendLessons' );
Route::match( [ 'get' ], '/hotLessons/{rows}', 'ContentController@hotLessons' );
public function videos($lid){
$data = Video::where('lesson_id',$lid)->get();
return $this->response($data);
}
Route::match( [ 'get' ], '/videos/{lid}', 'ContentController@videos' );
在Npm官网搜索vue-axios
或者直接打开网址
https://www.npmjs.com/search?q=vue-axios
https://github.com/imcvampire/vue-axios
安装
cd webapp
cnpm install --save axios vue-axios
在main.js
import axios from 'axios'
import VueAxios from 'vue-axios'
Vue.use(VueAxios, axios)
默认情况下前台发送Ajax是不允许跨域请求的。我们可以在后台进行相关设置然后允许前台跨域请求。
允许单个域名访问
header('Access-Control-Allow-Origin:http://www.xiongweiyang.com');
允许多个域名
$origin = isset($_SERVER['HTTP_ORIGIN'])? $_SERVER['HTTP_ORIGIN'] : '';
$allow_origin = array(
'http://www.xiongweiyang.com',
'http://www.xiongweiyang.com'
);
if(in_array($origin, $allow_origin)){
header('Access-Control-Allow-Origin:'.$origin);
}
允许所有域名请求,放入到router/api.php中
header('Access-Control-Allow-Origin:*');
data() {
return {
slide: [
{id:1,path:'static/images/1.jpg'},
{id:2,path:'static/images/2.jpg'},
{id:3,path:'static/images/3.jpg'},
],
notNextTick: true,
swiperOption: {
autoplay: 3000,
// direction : 'vertical',
grabCursor : true,
setWrapperSize :true,
autoHeight: true,
pagination : '.swiper-pagination',
paginationClickable :true,
mousewheelControl : true,
observeParents:true,
},
commend:[],
hot:[],
}
},
mounted(){
this.axios.get('http://test.com/api/commendLessons/4').then((response)=>{
this.commend = response.data.data;
});
this.axios.get('http://test.com/api/hotLessons/4').then((response)=>{
this.hot = response.data.data;
});
},
<router-link :to="{name:'Page',params:{lid:v.id}}" v-for="v in commend" :key="v.id">
<img :src="v.preview" alt="" />
<i class="iconfont icon-bofang"></i>
<span class="time">22:56</span>
<span class="title">{{v.title}}</span>
</router-link>
<router-link :to="{name:'Page',params:{lid:v.id}}" v-for="v in hot" :key="v.id">
<img :src="v.preview"/>
</router-link>
路由
export default new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
//?在正则表达式当中表示可有可无
path: '/video/:tid?',
name: 'Video',
component: Video
},
{
path: '/Page/:lid',
name: 'Page',
component: Page
}
]
})
<li v-for="v in videos"><a @click.prevent="play(v)">{{v.title}}</a></li>
export default {
name: 'Page',
data() {
return {
videos:[],
current:{}
}
},
mounted(){
let lid = this.$route.params.lid;
this.axios.get('http://test.com/api/videos/' + lid).then((response)=>{
if(response.status==200 && response.data.code==200){
this.videos = response.data.data;
this.current = this.videos[0];
}else{
alert('视频获取失败,稍后再试');
}
})
},
methods:{
play(v){
this.current = v;
}
}
}
默认第一个视频
<video :src="current.path" controls="controls"></video>
<h1>{{current.title}}</h1>
返回按钮处理
<a href="" class="iconfont back" @click.prevent="back()"></a>
methods:{
play(v){
this.current = v;
},
back(){
this.$router.back();
}
}
<!--导航条-->
<!-- swiper -->
<swiper :options="swiperOption">
<swiper-slide v-for="v in tags" :key="v.id">
<router-link :to="{name:'Video',params:{tid:v.id}}" >
{{v.name}}
</router-link>
</swiper-slide>
</swiper>
<!--导航条结束-->
<!--视频列表-->
<ul id="videolist">
<li v-for="v in lessons">
<router-link :to="{name:'Page',params:{lid:v.id}}" class="pic">
<img :src="v.preview"/>
<span>08:26</span>
<i class="iconfont icon-bofang"></i>
</router-link>
<a href="" class="title">{{v.title}}</a>
</li>
</ul>
<!--视频列表结束-->
export default {
name: 'video',
data() {
return {
tags: [],
lessons: [],
swiperOption: {
pagination: '.swiper-pagination',
slidesPerView: 3,
spaceBetween: 30
}
}
},
mounted() {
this.loadData();
},
watch:{
'$route'(to,from){
this.loadData();
}
},
methods:{
loadData(){
this.axios.get('http://test.com/api/tags').then((response) => {
this.tags = response.data.data;
})
let tid = this.$route.params.tid;
tid = tid ? tid : 0;
this.axios.get('http://test.com/api/lessons/' + tid).then((response) => {
this.lessons = response.data.data;
})
}
},
}
bootcdn.cn搜搜animate.css,在index.html中引入https://cdn.bootcss.com/animate.css/3.5.2/animate.min.css
在App.vue设置
<transition enter-active-class="animated tada">
<router-view></router-view>
</transition>
把所有代码上传到服务器,然后设置好地址,
cnpm run build
//生成线上运行的编译文件
让子域名绑定到dist目录
注册登陆
http://www.apicloud.com/signin
1. 左侧创建应用
;
2. 选择web
;
3. 输入你上线项目的网址;
4. 选择右侧的云编译
,
5. 选择Android
,然后选择正式版
;点击云编译
;
6. 正式版要求上传证书;
7. 选择右上角的一键上传证书
;
8. 密码填写就可以了;
9. 找到左侧的端设置
然后上传图片;
9. 再次云编译就可以上传证书了;
10. 等一会就编译好了,就可以安装了。