Laravel Vuejs 实战:开发知乎 (20)使用 Vuejs 组件化
1.vue 目录位置:resources/js/components
PhpStorm语法设置:
1.1 在reources/js/components文件夹内新建一个QuestionFollowButton.vue文件【问题关注按钮vue组件】
将原show.blade.php中的 关注按钮部分 移入新vue组件内部:
初步样式:
执行
1 npm install & npm run watch-poll
刷新页面看到
现在只是大致样式,还不能工作,因为要用ajax请求 所以后面要去掉blade中的if can逻辑 放到vue组件中。
2.ajax
注意 vue在laravel中使用的时候,参考 laravel 中使用ajax和vue总结
但一般使用axios ,参考:
入坑vue laravel,配置vue-router和axios手记
laravel 使用 prop 传递blade中的数据到vue组件中;
API requests with axios always unauthorized with Laravel API
axios请求要注意 :
then里面赋值的时候 报错property undefine 是因为在then里面 this作用域超出了,需要如下示例的代码才能起作用:
this.followable = response.data.followable;在then里面 this作用域超出了这样会超出作用域,要修改如下:
1 mounted() { 2 let currentObj = this; 3 axios.post('/api/questions/follow', {'question': this.question, 'user': this.user}) 4 .then(function (response) { 5 currentObj.followable = response.data.followable; 6 }); 7 }, 8 methods: { 9 follow() { 10 let currentObj = this; 11 axios.post('/api/questions/follow', {'question': this.question, 'user': this.user}) 12 .then(function (response) { 13 currentObj.followable = response.data.followable; 14 } 15 ); 16 }, 17 } 18
QuestionFollowButton.vue文件:
1 <template> 2 <button :href="href" 3 :class="classObject" 4 @click="follow" 5 v-text="text"> 6 </button> 7 </template> 8 9 <script> 10 export default { 11 props: ['question', 'user'], 12 name: "QuestionFollowButton", 13 data() { 14 return { 15 href: '', 16 followable: false, 17 } 18 }, 19 computed: { 20 text() { 21 return this.followable ? "关注用户" : "取消关注"; 22 }, 23 classObject() { 24 return this.followable ? "btn btn-block btn-primary" : "btn btn-block btn-danger"; 25 }, 26 }, 27 mounted() { 28 let currentObj = this; 29 axios.post('/api/questions/follow', {'question': this.question, 'user': this.user}) 30 .then(function (response) { 31 currentObj.followable = response.data.followable; 32 }); 33 }, 34 methods: { 35 follow() { 36 let currentObj = this; 37 axios.post('/api/questions/follow', {'question': this.question, 'user': this.user}) 38 .then(function (response) { 39 currentObj.followable = response.data.followable; 40 } 41 ); 42 }, 43 } 44 } 45 </script> 46 47 <style scoped> 48 49 </style>
api.php文件:
1 <?php 2 3 use Illuminate\Http\Request; 4 5 /* 6 |-------------------------------------------------------------------------- 7 | API Routes 8 |-------------------------------------------------------------------------- 9 | 10 | Here is where you can register API routes for your application. These 11 | routes are loaded by the RouteServiceProvider within a group which 12 | is assigned the "api" middleware group. Enjoy building your API! 13 | 14 */ 15 16 Route::middleware('auth:api')->get('/user', function (Request $request) { 17 return $request->user(); 18 }); 19 20 Route::middleware('api')->get('/topics', function (Request $request) { 21 $query = $request->query('q'); 22 return \App\Topic::query()->where('name', 'like', '%' . $query . '%')->get(); 23 }); 24 25 Route::middleware('api')->post('/questions/follow', 'QuestionController@followThroughApi'); 26
show.blade.php文件:
1 @extends('layouts.app') 2 @section('content') 3 <div class="container"> 4 <div class="row"> 5 <div class="col-md-8 col-md offset-1"> 6 {{--问题--}} 7 <div class="card"> 8 <div class="card-header"> 9 {{ $question->title }} 10 11 @foreach(['success','warning','danger'] as $info) 12 @if(session()->has($info)) 13 <div class="alert alert-{{$info}}">{{ session()->get($info) }}</div> 14 @endif 15 @endforeach 16 17 @can('update',$question) 18 <a href="{{ route('questions.edit',$question) }}" class="btn btn-warning">编辑</a> 19 @endcan 20 21 @can('destroy',$question) 22 <form action="{{ route('questions.destroy',$question) }}" method="post"> 23 @csrf 24 @method('DELETE') 25 <button type="submit" class="btn btn-danger">删除</button> 26 </form> 27 @endcan 28 29 @forelse($question->topics as $topic) 30 <button class="btn btn-secondary float-md-right m-1">{{ $topic->name }}</button> 31 @empty 32 <p class="text text-warning float-md-right"> "No Topics"</p> 33 @endforelse 34 35 <p class="text text-info float-md-right"> 已有{{ count($question->answers) }}个回答</p> 36 37 </div> 38 <div class="card-body"> 39 {!! $question->content !!} 40 </div> 41 </div> 42 43 44 {{--回答提交form--}} 45 {{--只有登录用户可以提交回答--}} 46 @if(auth()->check()) 47 <div class="card mt-2"> 48 <div class="card-header"> 49 提交回答 50 </div> 51 <div class="card-body"> 52 <form action="{{ route('answers.store',$question) }}" method="post"> 53 @csrf 54 <!-- 回答编辑器容器 --> 55 <script id="container" name="content" type="text/plain" 56 style="width: 100%;height: 200px">{!! old('content') !!}</script> 57 <p class="text text-danger"> @error('content') {{ $message }} @enderror </p> 58 <!--提交按钮--> 59 <button type="submit" class="btn btn-primary float-md-right mt-2">提交回答</button> 60 </form> 61 </div> 62 </div> 63 @else 64 {{--显示请登录--}} 65 <a href="{{ route('login') }}" class="btn btn-success btn-block mt-4">登录提交答案</a> 66 @endif 67 {{--展示答案--}} 68 @forelse($question->answers as $answer) 69 <div class="card mt-4"> 70 <div class="card-header"> 71 <div class="float-left"> 72 <img src="{{ $answer->user->avatar }}" class="img-thumbnail imgWrap" 73 style="height: 50px" alt="{{ $answer->user->name }}"> 74 <span class="text text-info">{{ $answer->user->name }}</span> 75 </div> 76 <span class="float-right text text-info m-auto">{{ $answer->updated_at }}</span> 77 </div> 78 79 <div class="card-body"> 80 {!! $answer->content !!} 81 </div> 82 </div> 83 84 @empty 85 86 @endforelse 87 </div> 88 89 <div class="col-md-3"> 90 <div class="card"> 91 <div class="card-header"> 92 <h2> {{ $question->followers_count }}</h2> 93 <span>关注者</span> 94 </div> 95 96 <div class="card-body"> 97 <div id="app"> 98 <question-follow-button question="{{$question->id}}" 99 user="{{ auth()->user()->id }}"user()->id }}"> 100 </question-follow-button> 101 </div> 102 </div> 103 </div> 104 </div> 105 </div> 106 </div> 107 @endsection 108 @section('footer-js') 109 @include('questions._footer_js') 110 @endsection 111 112
QuestionController.php文件:
1 <?php 2 3 namespace App\Http\Controllers; 4 5 use App\Http\Requests\QuestionStoreRequest; 6 use App\Models\Question; 7 use App\Repositories\QuestionRepository; 8 use App\User; 9 use Illuminate\Http\Request; 10 11 class QuestionController extends Controller 12 { 13 14 /** 15 * @var QuestionRepository 16 */ 17 private $questionRepository; 18 19 public function __construct(QuestionRepository $questionRepository) 20 { 21 $this->middleware( 22 'auth', 23 [ 24 'except' => 25 [ 26 'index', 27 'show', 28 'followThroughApi' 29 ]//非注册用户只能查看不能编辑添加更改删除 30 ] 31 ); 32 33 $this->questionRepository = $questionRepository; 34 } 35 36 37 /** Display a listing of the resource. 38 * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View 39 */ 40 public function index() 41 { 42 // 43 $questions = $this->questionRepository->getQuestionPublished(); 44 return view('questions.index', compact('questions')); 45 } 46 47 48 /** 49 * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View 50 */ 51 public function create() 52 { 53 // 54 return view('questions.create'); 55 } 56 57 58 /** 59 * @param QuestionStoreRequest $request 60 * @return \Illuminate\Http\RedirectResponse 61 */ 62 public function store(QuestionStoreRequest $request)//依赖注入QuestionStoreRequest实例 63 { 64 // 65 // $data = $request->validate([ 66 // 'title' => 'required|min:8', 67 // 'content' => 'required|min:28', 68 // ]); 69 //存储topics 70 $topics = $this->questionRepository->normalizeTopics($request->get('topics')); 71 //初始化question要用到的数据 72 $data = $request->all(); 73 $data['user_id'] = auth()->user()->id; 74 75 // $question=Question::create($data); 被下方代码取代 76 $question = $this->questionRepository->create($data); 77 78 //使用我们再question model里面添加的topics方法获得 topics关联,再使用attach方法 79 $question->topics()->attach($topics); 80 81 return redirect()->route('questions.show', $question); 82 } 83 84 85 /** 86 * @param Question $question 87 * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View 88 */ 89 public function show(Question $question) 90 { 91 //使用关系关联加载,with方法会将分类之下的主题一起查询出来,而且不会出现N+1影响性能的问题 92 $question->with('topics')->get(); 93 //使用关系关联加载,with方法会将分类之下的回答一起查询出来,而且不会出现N+1影响性能的问题 94 $question->with('answers')->get(); 95 96 return view('questions.show', compact('question')); 97 } 98 99 100 /**判断权限 返回视图 101 * @param Question $question 102 * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\View\View 103 */ 104 public function edit(Question $question) 105 { 106 if (auth()->user()->can('update', $question)) //判断当前用户是否有权编辑更新该question实例 107 { 108 //返回编辑视图 109 return view('questions.edit', compact('question')); 110 } else { 111 //返回警告 没有权限 112 return redirect()->back()->with('warning', '你不能编辑不属于你的问题!'); 113 } 114 } 115 116 117 /** Update the specified resource in storage. 118 * @param QuestionStoreRequest $questionStoreRequest 119 * @param Question $question 120 * @return \Illuminate\Http\RedirectResponse 121 */ 122 public function update(QuestionStoreRequest $questionStoreRequest, Question $question) 123 { 124 //更新前 判断下权限 125 if (!(auth()->user()->can('update', $question))) { 126 //返回警告 没有权限 127 return redirect()->back()->with('warning', '你不能编辑不属于你的问题!'); 128 } 129 //取得更新的字段 使用Eloquent提供的update方法执行问题更新 130 $question->update([ 131 'title' => $questionStoreRequest->get('title'), 132 'content' => $questionStoreRequest->get('content'), 133 ]); 134 135 136 //topics的操作这时候看起来有点臃肿 可以使用TopicController来管理,暂时省略 137 //存储topics 138 $topics = $this->questionRepository->normalizeTopics($questionStoreRequest->get('topics')); 139 //使用我们再question model里面添加的topics方法获得 topics关联, 140 //再使用sync方法同步tag 【删除的会被删除掉,没删除的就保留,新的就增加】 141 $question->topics()->sync($topics); 142 143 //更新完成,跳转回去 144 return redirect()->back(); 145 } 146 147 148 /**Remove the specified resource from storage. 149 * @param Question $question 150 * @return \Illuminate\Http\RedirectResponse 151 * @throws \Exception 152 */ 153 public function destroy(Question $question) 154 { 155 // 156 if (auth()->user()->can('destroy', $question)) { 157 $question->delete(); 158 return redirect()->route('questions.index')->with('success', "删除成功!"); 159 } 160 return redirect()->back()->with('danger', "你不能删除不属于你的问题!"); 161 } 162 163 164 public function follow(Question $question) 165 { 166 if (auth()->user()->can('follow', $question)) //通过QuestionPolicy的follow方法判断用户是否可以关注问题 167 { 168 $message = "关注"; 169 } else { 170 $message = "取关"; 171 } 172 //同步记录 173 auth()->user()->followQuestions()->toggle($question); 174 $question->followers_count = $question->followUsers()->count(); 175 $question->save(); 176 return redirect()->back()->with('success', $message . '成功!'); 177 } 178 179 public function followThroughApi(Request $request) 180 { 181 $user = User::find($request->get('user')); 182 $question = Question::find($request->get('question')); 183 184 if ($user->can('follow', $question)) { 185 $followable = false; 186 } else { 187 $followable = true; 188 } 189 //同步记录 190 $user->followQuestions()->toggle($question->id); 191 $question->followers_count = $question->followUsers()->count(); 192 $question->update(); 193 return response()->json([ 194 'followable' => $followable, 195 ]); 196 } 197 } 198
QuestionPlolicy.php文件:
1 <?php 2 3 namespace App\Policies; 4 5 use App\Models\Question; 6 use App\User; 7 use Illuminate\Auth\Access\HandlesAuthorization; 8 9 class QuestionPolicy 10 { 11 use HandlesAuthorization; 12 13 /** 14 * Create a new policy instance. 15 * 16 * @return void 17 */ 18 public function __construct() 19 { 20 // 21 22 } 23 24 25 /** 26 * 判断用户是否有权编辑更新问题 27 * @param User $user 28 * @param Question $question 29 * @return bool 30 */ 31 public function update(User $user, Question $question) 32 { 33 return $user->id === $question->user_id; 34 } 35 36 37 /** 38 * 判断用户是否有权删除问题 39 * @param User $user 40 * @param Question $question 41 * @return bool 42 */ 43 public function destroy(User $user, Question $question) 44 { 45 return $user->id === $question->user_id; 46 } 47 48 49 /** 用户是否可以关注问题,未登录不行,关注了不行 50 * @param User $user 51 * @param Question $question 52 * @return bool 53 */ 54 public function follow(User $user, Question $question) 55 { 56 //axiox api 需要auth:api 先不实现,注释掉 57 // if (auth()->check()) { 58 // return !$user->followQuestions->contains('id', $question->id); 59 // } else { 60 // abort(401, "请登录"); 61 // } 62 return !$user->followQuestions->contains('id', $question->id); 63 } 64 } 65