您好,登錄后才能下訂單哦!
小編給大家分享一下基于Laravel+Vue組件實現文章發布、編輯和瀏覽功能的示例,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!
我們將基于 Laravel 提供后端接口,基于 Vue.js 作為前端 JavaScript 組件開發框架,基于 Bootstrap 作為 CSS 框架。
首先,我們基于上篇教程創建的資源控制器 PostController
快速編寫后端增刪改查接口實現代碼:
<?php namespace App\Http\Controllers; use App\Models\Post; use Exception; use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\View; use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; class PostController extends Controller { public function __construct() { $this->middleware('auth')->except('index', 'all', 'show', 'data'); } /** * Display a listing of the resource. * * @return Application|Factory|View|Response|\Illuminate\View\View */ public function index() { return view('posts.index', ['pageTitle' => '文章列表頁']); } /** * Show the form for creating a new resource. * * @return Application|Factory|View|Response|\Illuminate\View\View */ public function create() { return view('posts.create', ['pageTitle' => '發布新文章']); } /** * Store a newly created resource in storage. * * @param Request $request * @return array */ public function store(Request $request) { $data = $request->validate([ 'title' => 'required|max:128', 'content' => 'required' ]); $post = new Post($data); $post->status = 1; $post->user_id = Auth::user()->id; if ($post->save()) { return ['success' => true, 'message' => '文章發布成功']; } return ['success' => false, 'message' => '保存文章數據失敗']; } /** * Display the specified resource. * * @param Post $post * @return Application|Factory|View|Response|\Illuminate\View\View */ public function show(Post $post) { return view('posts.show', ['id' => $post->id, 'pageTitle' => $post->title]); } /** * Show the form for editing the specified resource. * * @param Post $post * @return Application|Factory|View|Response|\Illuminate\View\View */ public function edit(Post $post) { return view('posts.edit', ['pageTitle' => '編輯文章', 'id' => $post->id]); } /** * Update the specified resource in storage. * * @param Request $request * @param Post $post * @return array */ public function update(Request $request, Post $post) { $data = $request->validate([ 'title' => 'required|max:128', 'content' => 'required' ]); $post->fill($data); $post->status = 1; if ($post->save()) { return ['success' => true, 'message' => '文章更新成功']; } return ['success' => false, 'message' => '更新文章數據失敗!']; } /** * Remove the specified resource from storage. * * @param Post $post * @return array * @throws Exception */ public function destroy(Post $post) { if ($post->delete()) { return ['success' => true, 'message' => '文章刪除成功']; } return ['success' => false, 'message' => '刪除文章失敗']; } /** * 獲取所有文章數據 * * @return Collection */ public function all() { return Post::orderByDesc('created_at')->get(); } /** * 獲取單個文章數據 * * @param Post $post * @return Post */ public function data(Post $post) { $post->author_name = $post->author->name; return $post; } }
除了 Laravel 資源控制器自帶的方法之外,我們額外提供了 all
和 data
兩個方法,分別用于在 Vue 組件中通過 AJAX 請求獲取文章列表數據和文章詳情數據。因此,需要在路由文件 routes/web.php
中注冊資源路由之前添加這兩個方法對應的路由:
use App\Http\Controllers\PostController; Route::get('posts/all', [PostController::class, 'all']); Route::get('posts/{post}/data', [PostController::class, 'data']); Route::resource('posts', PostController::class);
注意這里我們使用了 Laravel 路由提供的隱式模型綁定功能快速獲取模型實例。此外,相應的視圖模板路徑也做了調整,我們馬上會介紹這些視圖模板文件。
如果你在上篇教程填充的測試數據基礎上新增過其他數據,可以運行 php artisan migrate:refresh
命令重建數據表快速清空已有數據并重新填充。
如果你不想查看返回實例數據格式的細節,可以在自帶填充器 database/seeders/DatabaseSeeder.php
中定義填充代碼:
<?php namespace Database\Seeders; use App\Models\Post; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { /** * Seed the application's database. * * @return void */ public function run() { // \App\Models\User::factory(10)->create(); Post::factory(10)->create(); } }
然后運行 php artisan migrate:refresh --seed
命令即可一步到位完成數據表重建、測試數據清空和重新填充:
cdn.xueyuanjun.com/storage/uploads/images/gallery/2020-10/16036945542122.jpg">
由于我們使用的是 Laravel 提供的 laravel/ui
擴展包提供的 Bootstrap 和 Vue 前端腳手架代碼,該擴展包還提供了用戶認證相關腳手架代碼實現,并且提供了一個視圖模板布局文件 resources/views/layouts/app.blade.php
,我們將通過模板繼承基于這個布局文件來重構文章列表、表單、詳情頁相關視圖模板文件,讓整體 UI 統一。
我們前面在 PostController
中,為所有 GET 路由渲染的視圖文件傳遞了 pageTitle
值作為不同頁面的標題,要實現該功能,需要修改 resources/views/layouts/app.blade.php
布局文件中 title
標簽對應的標簽文本值:
<title>{{ $pageTitle ?? config('app.name', 'Laravel') }}</title>
接下來,將原來的文章相關視圖文件都移動到 resources/views/posts
目錄下,改寫文章列表視圖文件模板代碼如下(將原來的 posts.blade.php
重命名為 index.blade.php
):
@extends('layouts.app') @section('content') <p class="container"> <post-list></post-list> </p> @endsection
將原來的 form.blade.php
重命名為 create.blade.php
,并編寫文章發布表單頁面視圖文件模板代碼如下:
@extends('layouts.app') @section('content') <p class="container"> <p class="row justify-content-center"> <post-form title="發布新文章" action="create" url="{{ route('posts.store') }}"> </post-form> </p> </p> @endsection
由于文章發布和編輯表單共用一個 Vue 表單組件,所以我們這里額外傳遞了一些 props 屬性到組件模板,包括表單標題(title
)、操作類型(action
)、表單提交 URL(url
),后面馬上會介紹表單組件的調整。
在 resources/views/posts
目錄下新建一個 edit.blade.php
作為文件編輯頁面視圖文件,并編寫模板代碼如下:
@extends('layouts.app') @section('content') <p class="container"> <p class="row justify-content-center"> <post-form title="編輯文章" action="update" id="{{ $id }}" url="{{ route('posts.update', ['post' => $id]) }}"> </post-form> </p> </p> @endsection
同樣也使用 post-form
模板渲染文章編輯表單,只不過額外傳遞了一個 id 屬性,用于在表單組件初始化待編輯的文章數據。
文章詳情頁視圖后面單獨介紹。
為了適配文章編輯表單,以及后端接口返回數據格式的調整,我們需要修改 Vue 表單組件實現代碼:
<template> <FormSection @store="store"> <template slot="title">{{ title }}</template> <template slot="input-group"> <p class="form-group"> <Label name="title" label="標題"></Label> <InputText name="title" v-model="form.title" @keyup="clear('title')"></InputText> <ErrorMsg :error="form.errors.get('title')"></ErrorMsg> </p> <p class="form-group"> <Label name="content" label="內容"></Label> <TextArea name="content" v-model="form.content" @keyup="clear('content')"></TextArea> <ErrorMsg :error="form.errors.get('content')"></ErrorMsg> </p> </template> <template slot="action"> <Button type="submit">立即發布</Button> </template> <template slot="toast"> <ToastMsg :success="form.success" :validated="form.validated"> {{ form.message }} </ToastMsg> </template> </FormSection> </template> <script> import FormSection from './form/FormSection'; import InputText from './form/InputText'; import TextArea from './form/TextArea'; import Button from './form/Button'; import ToastMsg from './form/ToastMsg'; import Label from "./form/Label"; import ErrorMsg from "./form/ErrorMsg"; export default { components: {FormSection, InputText, TextArea, Label, ErrorMsg, Button, ToastMsg}, props: ['title', 'url', 'action', 'id'], data() { return { form: new Form({ title: '', content: '' }) } }, mounted() { let post_id = Number(this.id); if (this.action === 'update' && post_id > 0) { this.load(post_id); } }, methods: { load(id) { this.form.title = '加載中...'; this.form.content = '加載中...'; let url = '/posts/' + id + '/data'; axios.get(url).then(resp => { this.form.title = resp.data.title; this.form.content = resp.data.content; }).catch(error => { alert('從服務端初始化表單數據失敗'); }); }, store() { if (this.action === 'create') { this.form.post(this.url) .then(data => { // 發布成功后跳轉到列表頁 window.location.href = '/posts'; }) .catch(data => console.log(data)); // 自定義表單提交失敗處理邏輯 } else { this.form.put(this.url) .then(data => { // 更新成功后跳轉到詳情頁 window.location.href = '/posts/' + this.id; }) .catch(data => console.log(data)); // 自定義表單提交失敗處理邏輯 } }, clear(field) { this.form.errors.clear(field); } } } </script>
文章發布和編輯頁面需要通過標題予以區分,所以我們通過 title
屬性從父級作用域傳遞該標題值。
對于文章編輯表單,首先,我們會根據父級作用域傳遞的 id
屬性值在 mounted
鉤子函數中調用新增的 load
方法從后端接口 /posts/{post}/data
加載對應文章數據填充表單。
現在后端接口可以自動獲取當前認證用戶的 ID,所以 author
字段就沒有必要填寫了,直接將其移除。
文章創建和編輯對應的請求方式是不一樣的,操作成功后處理邏輯也是不一樣的(前者重定向到列表頁,后者重定向到詳情頁),所以根據 action
屬性值分別進行了處理。
此外,由于后端對表單數據進行驗證后,保存數據階段依然可能失敗,所以前端提交表單后返回的響應狀態碼為 200 并不表示表單提交處理成功,還需要借助響應實體(JSON 格式)中的 success
字段進一步判斷,進而通過 ToastMsg
子組件渲染成功或失敗的提示文本。
ToastMsg
是從之前的 SuccessMsg
組件升級而來,直接將 SuccessMsg
組件重命名為 ToastMsg
并改寫組件代碼如下:
<style scoped> .alert { margin-top: 10px; } </style> <template> <p class="alert" :class="{'alert-success': success, 'alert-danger': !success}" role="alert" v-show="validated"> <slot></slot> </p> </template> <script> export default { props: ['validated', 'success'] } </script>
可以看到,如果表單提交處理成功(依然基于父級作用域傳遞的 form.success
屬性)則顯示成功提示樣式及文案,否則顯示失敗提示樣式和文案,而是否渲染該組件取決于表單驗證是否成功,該字段基于父級作用域傳遞的 form.validated
屬性,之前是沒有這個屬性的,所以我們需要額外添加,在 resources/js/form.js
中,調整相關代碼實現如下:
class Form { constructor(data) { ... this.validated = false; } ... /** * 表單提交處理 * * @param {string} url * @param {string} method */ submit(url, method) { return new Promise((resolve, reject) => { axios[method](url, this.data()) .then(response => { this.onSuccess(response.data); this.validated = true; if (this.success === true) { resolve(response.data); } else { reject(response.data); } }) .catch(error => { this.onFail(error.response.data.errors); reject(error.response.data); }); }); } /** * 處理表單提交成功 * * @param {object} data */ onSuccess(data) { this.success = data.success; this.message = data.message; this.reset(); } ... }
這樣一來,文章發布和編輯共用的 Vue 表單組件就重構好了。
我們接著來實現文章詳情頁。
在 component-practice/resources/js/components
目錄下新建一個 PostDetail.vue
文件作為渲染文章詳情的 Vue 單文件組件,并編寫組件代碼如下:
<style scoped> .post-detail { width: 100%; } .post-title { margin-bottom: .25rem; font-size: 2.5rem; } .post-meta { margin-bottom: 1.25rem; color: #999; } .post-content { font-size: 1.1rem; font-weight: 400; line-height: 1.5; color: #212529; } </style> <template> <p class="spinner-border" role="status" v-if="!loaded"> <span class="sr-only">Loading...</span> </p> <p class="post-detail" v-else> <h3 class="post-title">{{ title }}</h3> <p class="post-meta"> Created at {{ created_at | diff_for_human }} by <a href="#">{{ author_name }}</a>, Status: {{ status | post_status_readable }}, Action: <a :href="'/posts/' + id + '/edit'">編輯</a> </p> <p class="post-content"> {{ content }} </p> </p> </template> <script> export default { props: ['post_id'], data() { return { id: this.post_id, title: '', content: '', status: '', author_name: '', created_at: '', loaded: false } }, mounted() { if (!this.loaded) { this.load(Number(this.id)); } }, methods: { load(id) { axios.get('/posts/' + this.id + '/data').then(resp => { this.title = resp.data.title; this.content = resp.data.content; this.status = resp.data.status; this.author_name = resp.data.author_name; this.created_at = resp.data.created_at; this.loaded = true; }).catch(err => { alert('加載文章數據失敗'); }); } } } </script>
這個組件功能比較簡單,在 mounted
鉤子函數中通過父級作用域傳遞的 id
屬性值調用 load
函數加載后端接口返回的文章數據,并通過數據綁定將其渲染到模板代碼中,在加載過程中,會有一個動態的加載狀態提示用戶文章數據正在加載。
這里我們還使用了過濾器對數據進行格式化,日期過濾器已經是全局的了,狀態過濾器之前是本地的,這里我們將其從文章列表卡片組件 CardItem
中將其遷移到 app.js
中作為全局過濾器:
Vue.filter('post_status_readable', status => { switch(status) { case 0: return '草稿'; case 1: return '已發布'; default: return '未知狀態'; } });
然后就可以在任何 Vue 組件中調用它了(CardItem
中過濾器調用代碼做一下相應調整)。
在 app.js
中注冊這個組件:
Vue.component('post-detail', require('./components/PostDetail.vue').default);
再到 component-practice/resources/views/posts
目錄下新建 show.blade.php
視圖文件引用 post-detail
組件即可:
@extends('layouts.app') @section('content') <p class="container"> <post-detail post_id="{{ $id }}"></post-detail> </p> @endsection
最后,我們到文章列表組件中新增一個發布文章入口。
打開子組件 ListSection
,在視圖模式切換按鈕右側新增一個插槽,用于從父級作用域傳遞更多額外操作按鈕:
<style scoped> .card-header h6 { margin-top: 0.5em; display: inline-block; } .card-header .float-right { float: right; } </style> <template> <p class="card"> <p class="card-header"> <h6><slot name="title"></slot></h6> <p class="float-right"> <button class="btn btn-success view-mode" @click.prevent="switch_view_mode"> {{ view.switch_to }} </button> <slot name="more-actions"></slot> </p> </p> ...
然后在 PostList
中將發布文章按鈕放到這個插槽中(樣式代碼也做了微調):
<style scoped> .post-list { width: 100%; } </style> <template> <p class="post-list"> <ListSection :view_mode="view_mode" @view-mode-changed="change_view_mode"> <template #title>文章列表</template> <template #more-actions> <a href="/posts/create" class="btn btn-primary">新文章</a> </template> <template v-if="view_mode === 'list'"> <ListItem v-for="post in posts" :key="post.id" :url="'/posts/' + post.id"> {{ post.title }} </ListItem> </template> ...
順便也為文章列表所有文章設置詳情頁鏈接,ListItem
鏈接是從 PostList 通過 props 屬性傳遞的,CardItem
需要去子組件中設置:
<a :href="'/posts/' + post.id" class="btn btn-primary"><slot name="action-label"></slot></a>
至此,我們就完成了文章列表、發布、編輯和詳情頁的所有前后端功能代碼編寫。
如果你已經在本地運行了 npm run watch
并且通過 php arstisan serve
啟動 PHP 內置 Web 服務器的話,就可以在瀏覽器通過 http://127.0.0.1:3002/posts
(啟用了 BrowserSync 代理)訪問新的文章列表頁了:
點擊任意文章鏈接,即可進入文章詳情頁,加載數據成功之前,會有如下動態加載效果:
你可以點擊「編輯」鏈接對這篇文章進行編輯:
更新成功后,會跳轉到文章詳情頁,對應字段均已更新,并且狀態也從草稿變成了已發布:
當然,文章發布和編輯功能需要用戶處于已登錄狀態(目前未做權限驗證),如果未登錄的話,點擊編輯和新文章按鈕會先跳轉到登錄頁面(該功能由 PostController
控制器構造函數中定義的中間件方法實現),我們在已登錄情況下在文章列表頁點擊右上角的「新文章」按鈕進入文章發布頁面:
發布成功后,頁面會跳轉到文章列表頁,并在列表中出現剛剛創建的文章:
增刪改查還剩下一個「刪」,下篇教程,就來給大家演示文章刪除功能實現,為什么單獨介紹呢,因為我想結合刪除功能演示基于 Vue 組件的模態框、對話框以及過渡效果的實現。
看完了這篇文章,相信你對“基于Laravel+Vue組件實現文章發布、編輯和瀏覽功能的示例”有了一定的了解,如果想了解更多相關知識,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。