基于Laravel+Vue組件實現(xiàn)文章發(fā)布、編輯和瀏覽功能的示例

小編給大家分享一下基于Laravel+Vue組件實現(xiàn)文章發(fā)布、編輯和瀏覽功能的示例,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!

創(chuàng)新互聯(lián)成立于2013年,是專業(yè)互聯(lián)網(wǎng)技術服務公司,擁有項目成都做網(wǎng)站、成都網(wǎng)站制作、成都外貿(mào)網(wǎng)站建設網(wǎng)站策劃,項目實施與項目整合能力。我們以讓每一個夢想脫穎而出為使命,1280元龍馬潭做網(wǎng)站,已為上家服務,為龍馬潭各地企業(yè)和個人服務,聯(lián)系電話:18982081108

我們將基于 Laravel 提供后端接口,基于 Vue.js 作為前端 JavaScript 組件開發(fā)框架,基于 Bootstrap 作為 CSS 框架。

Laravel 后端接口

首先,我們基于上篇教程創(chuàng)建的資源控制器 PostController 快速編寫后端增刪改查接口實現(xiàn)代碼:

<?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' => '發(fā)布新文章']);
    }

    /**
     * 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' => '文章發(fā)布成功'];
        }
        return ['success' => false, 'message' => '保存文章數(shù)據(jù)失敗'];
    }

    /**
     * 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' => '更新文章數(shù)據(jù)失??!'];
    }

    /**
     * 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' => '刪除文章失敗'];
    }

    /**
     * 獲取所有文章數(shù)據(jù)
     *
     * @return Collection
     */
    public function all()
    {
        return Post::orderByDesc('created_at')->get();
    }

    /**
     * 獲取單個文章數(shù)據(jù)
     *
     * @param Post $post
     * @return Post
     */
    public function data(Post $post)
    {
        $post->author_name = $post->author->name;
        return $post;
    }
}

除了 Laravel 資源控制器自帶的方法之外,我們額外提供了 alldata 兩個方法,分別用于在 Vue 組件中通過 AJAX 請求獲取文章列表數(shù)據(jù)和文章詳情數(shù)據(jù)。因此,需要在路由文件 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 路由提供的隱式模型綁定功能快速獲取模型實例。此外,相應的視圖模板路徑也做了調(diào)整,我們馬上會介紹這些視圖模板文件。

通過填充器填充測試數(shù)據(jù)

如果你在上篇教程填充的測試數(shù)據(jù)基礎上新增過其他數(shù)據(jù),可以運行 php artisan migrate:refresh 命令重建數(shù)據(jù)表快速清空已有數(shù)據(jù)并重新填充。

如果你不想查看返回實例數(shù)據(jù)格式的細節(jié),可以在自帶填充器 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 命令即可一步到位完成數(shù)據(jù)表重建、測試數(shù)據(jù)清空和重新填充:

基于Laravel+Vue組件實現(xiàn)文章發(fā)布、編輯和瀏覽功能的示例cdn.xueyuanjun.com/storage/uploads/images/gallery/2020-10/16036945542122.jpg">

基于Laravel+Vue組件實現(xiàn)文章發(fā)布、編輯和瀏覽功能的示例

通過模板繼承重構視圖模板

由于我們使用的是 Laravel 提供的 laravel/ui 擴展包提供的 Bootstrap 和 Vue 前端腳手架代碼,該擴展包還提供了用戶認證相關腳手架代碼實現(xiàn),并且提供了一個視圖模板布局文件 resources/views/layouts/app.blade.php,我們將通過模板繼承基于這個布局文件來重構文章列表、表單、詳情頁相關視圖模板文件,讓整體 UI 統(tǒng)一。

不同頁面設置不同標題

我們前面在 PostController 中,為所有 GET 路由渲染的視圖文件傳遞了 pageTitle  值作為不同頁面的標題,要實現(xiàn)該功能,需要修改 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
文章發(fā)布視圖

將原來的 form.blade.php 重命名為 create.blade.php,并編寫文章發(fā)布表單頁面視圖文件模板代碼如下:

@extends('layouts.app')

@section('content')
    <p class="container">
        <p class="row justify-content-center">
            <post-form title="發(fā)布新文章" action="create" url="{{ route('posts.store') }}">
            </post-form>
        </p>
    </p>
@endsection

由于文章發(fā)布和編輯表單共用一個 Vue 表單組件,所以我們這里額外傳遞了一些 props 屬性到組件模板,包括表單標題(title)、操作類型(action)、表單提交 URL(url),后面馬上會介紹表單組件的調(diào)整。

文章編輯視圖

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 屬性,用于在表單組件初始化待編輯的文章數(shù)據(jù)。

文章詳情頁視圖后面單獨介紹。

重構 Vue 表單組件代碼

為了適配文章編輯表單,以及后端接口返回數(shù)據(jù)格式的調(diào)整,我們需要修改 Vue 表單組件實現(xiàn)代碼:

<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="內(nèi)容"></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">立即發(fā)布</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('從服務端初始化表單數(shù)據(jù)失敗');
            });
        },
        store() {
            if (this.action === 'create') {
                this.form.post(this.url)
                    .then(data => {
                        // 發(fā)布成功后跳轉到列表頁
                        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>

文章發(fā)布和編輯頁面需要通過標題予以區(qū)分,所以我們通過 title 屬性從父級作用域傳遞該標題值。

對于文章編輯表單,首先,我們會根據(jù)父級作用域傳遞的 id 屬性值在 mounted 鉤子函數(shù)中調(diào)用新增的 load 方法從后端接口 /posts/{post}/data 加載對應文章數(shù)據(jù)填充表單。

現(xiàn)在后端接口可以自動獲取當前認證用戶的 ID,所以 author 字段就沒有必要填寫了,直接將其移除。

文章創(chuàng)建和編輯對應的請求方式是不一樣的,操作成功后處理邏輯也是不一樣的(前者重定向到列表頁,后者重定向到詳情頁),所以根據(jù) action 屬性值分別進行了處理。

此外,由于后端對表單數(shù)據(jù)進行驗證后,保存數(shù)據(jù)階段依然可能失敗,所以前端提交表單后返回的響應狀態(tài)碼為 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 中,調(diào)整相關代碼實現(xiàn)如下:

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();
    }
    
    ...

}

這樣一來,文章發(fā)布和編輯共用的 Vue 表單組件就重構好了。

文章詳情頁視圖和 Vue 組件實現(xiàn)

我們接著來實現(xiàn)文章詳情頁。

PostDetail 組件

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('加載文章數(shù)據(jù)失敗');
            });
        }
    }
}
</script>

這個組件功能比較簡單,在 mounted 鉤子函數(shù)中通過父級作用域傳遞的 id 屬性值調(diào)用 load 函數(shù)加載后端接口返回的文章數(shù)據(jù),并通過數(shù)據(jù)綁定將其渲染到模板代碼中,在加載過程中,會有一個動態(tài)的加載狀態(tài)提示用戶文章數(shù)據(jù)正在加載。

這里我們還使用了過濾器對數(shù)據(jù)進行格式化,日期過濾器已經(jīng)是全局的了,狀態(tài)過濾器之前是本地的,這里我們將其從文章列表卡片組件 CardItem 中將其遷移到 app.js 中作為全局過濾器:

Vue.filter('post_status_readable', status => {
    switch(status) {
        case 0:
            return '草稿';
        case 1:
            return '已發(fā)布';
        default:
            return '未知狀態(tài)';
    }
});

然后就可以在任何 Vue 組件中調(diào)用它了(CardItem 中過濾器調(diào)用代碼做一下相應調(diào)整)。

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

優(yōu)化文章列表組件

最后,我們到文章列表組件中新增一個發(fā)布文章入口。

打開子組件 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 中將發(fā)布文章按鈕放到這個插槽中(樣式代碼也做了微調(diào)):

<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>

至此,我們就完成了文章列表、發(fā)布、編輯和詳情頁的所有前后端功能代碼編寫。

整體測試

如果你已經(jīng)在本地運行了 npm run watch 并且通過 php arstisan serve 啟動 PHP 內(nèi)置 Web 服務器的話,就可以在瀏覽器通過 http://127.0.0.1:3002/posts (啟用了 BrowserSync 代理)訪問新的文章列表頁了:

基于Laravel+Vue組件實現(xiàn)文章發(fā)布、編輯和瀏覽功能的示例

點擊任意文章鏈接,即可進入文章詳情頁,加載數(shù)據(jù)成功之前,會有如下動態(tài)加載效果:

基于Laravel+Vue組件實現(xiàn)文章發(fā)布、編輯和瀏覽功能的示例

基于Laravel+Vue組件實現(xiàn)文章發(fā)布、編輯和瀏覽功能的示例

你可以點擊「編輯」鏈接對這篇文章進行編輯:

基于Laravel+Vue組件實現(xiàn)文章發(fā)布、編輯和瀏覽功能的示例

更新成功后,會跳轉到文章詳情頁,對應字段均已更新,并且狀態(tài)也從草稿變成了已發(fā)布:

基于Laravel+Vue組件實現(xiàn)文章發(fā)布、編輯和瀏覽功能的示例

當然,文章發(fā)布和編輯功能需要用戶處于已登錄狀態(tài)(目前未做權限驗證),如果未登錄的話,點擊編輯和新文章按鈕會先跳轉到登錄頁面(該功能由 PostController 控制器構造函數(shù)中定義的中間件方法實現(xiàn)),我們在已登錄情況下在文章列表頁點擊右上角的「新文章」按鈕進入文章發(fā)布頁面:

基于Laravel+Vue組件實現(xiàn)文章發(fā)布、編輯和瀏覽功能的示例

發(fā)布成功后,頁面會跳轉到文章列表頁,并在列表中出現(xiàn)剛剛創(chuàng)建的文章:

基于Laravel+Vue組件實現(xiàn)文章發(fā)布、編輯和瀏覽功能的示例

增刪改查還剩下一個「刪」,下篇教程,就來給大家演示文章刪除功能實現(xiàn),為什么單獨介紹呢,因為我想結合刪除功能演示基于 Vue 組件的模態(tài)框、對話框以及過渡效果的實現(xiàn)。

看完了這篇文章,相信你對“基于Laravel+Vue組件實現(xiàn)文章發(fā)布、編輯和瀏覽功能的示例”有了一定的了解,如果想了解更多相關知識,歡迎關注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!

分享標題:基于Laravel+Vue組件實現(xiàn)文章發(fā)布、編輯和瀏覽功能的示例
本文路徑:http://bm7419.com/article12/jjcggc.html

成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站排名品牌網(wǎng)站設計、標簽優(yōu)化、網(wǎng)站設計公司用戶體驗、響應式網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站建設