Home  >  Article  >  PHP Framework  >  Teach you how to implement article publishing, editing and browsing functions based on Laravel+Vue components

Teach you how to implement article publishing, editing and browsing functions based on Laravel+Vue components

藏色散人
藏色散人forward
2020-11-13 14:08:502891browse


The following tutorial column will introduce to you how to implement article publishing, editing and browsing functions based on Laravel Vue components. I hope it will help friends who need it. Helps!

We will provide the backend interface based on Laravel, Vue.js as the front-end JavaScript component development framework, and Bootstrap as the CSS framework.

Teach you how to implement article publishing, editing and browsing functions based on Laravel+Vue componentsLaravel backend interface

First of all, we based on the resource controller created in the previous tutorial

PostController

Quickly write the backend add, delete, modify, check interface implementation code:

<?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;
    }
}

In addition to the methods that come with the Laravel resource controller, we provide two additional methods, all and

data

, which are used to obtain articles through AJAX requests in the Vue component. List data and article detail data. Therefore, you need to add the routes corresponding to these two methods before registering the resource route in the routing file routes/web.php: <pre class="brush:php;toolbar:false">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);</pre>Note that here we use the implicit model provided by Laravel routing The binding function quickly obtains model instances. In addition, the corresponding view template paths have also been adjusted. We will introduce these view template files shortly. Fill the test data through the filler

If you have added other data based on the test data filled in the previous tutorial, you can run the

php artisan migrate:refresh

command Rebuild the data table to quickly clear existing data and refill it.

If you don’t want to see the details of the returned instance data format, you can define the filling code in the built-in filler database/seeders/DatabaseSeeder.php:

<?php namespace Database\Seeders;

use App\Models\Post;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application&#39;s database.
     *
     * @return void
     */
    public function run()
    {
        // \App\Models\User::factory(10)->create();
        Post::factory(10)->create();
    }
}

Then run php artisan migrate:refresh --seed The command can complete the data table reconstruction, test data clearing and refilling in one step:

Teach you how to implement article publishing, editing and browsing functions based on Laravel+Vue components

Reconstructing view templates through template inheritance

Teach you how to implement article publishing, editing and browsing functions based on Laravel+Vue componentsSince we are using the Bootstrap and Vue front-end scaffolding code provided by the

laravel/ui

extension package provided by Laravel, the extension package also Provides scaffolding code implementation related to user authentication, and provides a view template layout file

resources/views/layouts/app.blade.php

. We will reconstruct the article list based on this layout file through template inheritance. Form and detail page related view template files make the overall UI unified. Set different titles for different pagesWe previously passed the

pageTitle

value as a different page in the view file rendered by all GET routes in

PostController

title. To implement this function, you need to modify the label text value corresponding to the title label in the resources/views/layouts/app.blade.php layout file: <pre class="brush:php;toolbar:false">&lt;title&gt;{{ $pageTitle ?? config('app.name', 'Laravel') }}&lt;/title&gt;</pre>Article list viewNext, move all the original article-related view files to the

resources/views/posts

directory, and rewrite the article list view file template code as follows (replace the original

posts.blade.php

Renamed to index.blade.php): <pre class="brush:php;toolbar:false">@extends('layouts.app') @section('content')     &lt;p&gt;         &lt;post-list&gt;&lt;/post-list&gt;     &lt;/p&gt; @endsection</pre>Post publishing viewRename the original

form. blade.php

Rename to

create.blade.php

, and write the article publishing form page view file template code as follows: <pre class="brush:php;toolbar:false">@extends('layouts.app') @section('content')     &lt;p&gt;         &lt;/p&gt;&lt;p&gt;             &lt;post-form&gt;             &lt;/post-form&gt;         &lt;/p&gt;     </pre>

@endsection Since the article publishing and editing forms share a Vue form component, so we pass some additional props attributes to the component template here, including form title (title), action type (action

), and form submission URL (url), we will introduce the adjustment of form components shortly. Article editing viewCreate a new

edit.blade.php

in the

resources/views/posts

directory as a file editing page view file, and Write the template code as follows: <pre class="brush:php;toolbar:false">@extends('layouts.app') @section('content')     &lt;p&gt;         &lt;/p&gt;&lt;p&gt;             &lt;post-form&gt; $id]) }}&quot;&gt;             &lt;/post-form&gt;         &lt;/p&gt;     </pre>

@endsection also uses the post-form template to render the article editing form, but an additional id attribute is passed to initialize the article data to be edited in the form component. .

The article details page view will be introduced separately later. Reconstruct the Vue form component code

In order to adapt to the article editing form and adjust the data format returned by the back-end interface, we need to modify the Vue form component implementation code:

<template>
    <formsection>
        <template>{{ title }}</template>
        <template>
            <p>
                <label></label>
                <inputtext></inputtext>
                <errormsg></errormsg>
            </p>

            <p>
                <label></label>
                <textarea></textarea>
                <errormsg></errormsg>
            </p>
        </template>
        <template>
            <button>立即发布</button>
        </template>
        <template>
            <toastmsg>
                {{ form.message }}
            </toastmsg>
        </template>
    </formsection>
</template>

<script>
import FormSection from &#39;./form/FormSection&#39;;
import InputText from &#39;./form/InputText&#39;;
import TextArea from &#39;./form/TextArea&#39;;
import Button from &#39;./form/Button&#39;;
import ToastMsg from &#39;./form/ToastMsg&#39;;
import Label from "./form/Label";
import ErrorMsg from "./form/ErrorMsg";

export default {

    components: {FormSection, InputText, TextArea, Label, ErrorMsg, Button, ToastMsg},

    props: [&#39;title&#39;, &#39;url&#39;, &#39;action&#39;, &#39;id&#39;],

    data() {
        return {
            form: new Form({
                title: &#39;&#39;,
                content: &#39;&#39;
            })
        }
    },

    mounted() {
        let post_id = Number(this.id);
        if (this.action === &#39;update&#39; && post_id > 0) {
            this.load(post_id);
        }
    },

    methods: {
        load(id) {
            this.form.title = &#39;加载中...&#39;;
            this.form.content = &#39;加载中...&#39;;
            let url = &#39;/posts/&#39; + id + &#39;/data&#39;;
            axios.get(url).then(resp => {
                this.form.title = resp.data.title;
                this.form.content = resp.data.content;
            }).catch(error => {
                alert(&#39;从服务端初始化表单数据失败&#39;);
            });
        },
        store() {
            if (this.action === &#39;create&#39;) {
                this.form.post(this.url)
                    .then(data => {
                        // 发布成功后跳转到列表页
                        window.location.href = &#39;/posts&#39;;
                    })
                    .catch(data => console.log(data)); // 自定义表单提交失败处理逻辑
            } else {
                this.form.put(this.url)
                    .then(data => {
                        // 更新成功后跳转到详情页
                        window.location.href = &#39;/posts/&#39; + this.id;
                    })
                    .catch(data => console.log(data)); // 自定义表单提交失败处理逻辑
            }
        },
        clear(field) {
            this.form.errors.clear(field);
        }
    }
}
</script>

Article publishing and editing pages need to be distinguished by titles, so we pass the title value from the parent scope through the

title

attribute.

For the article editing form, first, we will call the new load in the mounted

hook function based on the

id attribute value passed by the parent scope. Method loads the corresponding article data from the backend interface /posts/{post}/data to fill in the form. <p>现在后端接口可以自动获取当前认证用户的 ID,所以 <code>author 字段就没有必要填写了,直接将其移除。

文章创建和编辑对应的请求方式是不一样的,操作成功后处理逻辑也是不一样的(前者重定向到列表页,后者重定向到详情页),所以根据 action 属性值分别进行了处理。

此外,由于后端对表单数据进行验证后,保存数据阶段依然可能失败,所以前端提交表单后返回的响应状态码为 200 并不表示表单提交处理成功,还需要借助响应实体(JSON 格式)中的 success 字段进一步判断,进而通过 ToastMsg 子组件渲染成功或失败的提示文本。

ToastMsg 是从之前的 SuccessMsg 组件升级而来,直接将 SuccessMsg 组件重命名为 ToastMsg 并改写组件代码如下:

<style>
.alert {
    margin-top: 10px;
}
</style>

<template>
    <p>
        <slot></slot>
    </p>
</template>

<script>
export default {
    props: [&#39;validated&#39;, &#39;success&#39;]
}
</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 表单组件就重构好了。

文章详情页视图和 Vue 组件实现

我们接着来实现文章详情页。

PostDetail 组件

component-practice/resources/js/components 目录下新建一个 PostDetail.vue 文件作为渲染文章详情的 Vue 单文件组件,并编写组件代码如下:

<style>
.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>
        <span>Loading...</span>
    </p>
    <p>
        </p>
<h2>{{ title }}</h2>
        <p>
            Created at {{ created_at | diff_for_human }} by <a>{{ author_name }}</a>,
            Status: {{ status | post_status_readable }},
            Action: <a>编辑</a>
        </p>
        <p>
            {{ content }}
        </p>
    </template>

<script> export default { props: [&#39;post_id&#39;], data() { return { id: this.post_id, title: &#39;&#39;, content: &#39;&#39;, status: &#39;&#39;, author_name: &#39;&#39;, created_at: &#39;&#39;, loaded: false } }, mounted() { if (!this.loaded) { this.load(Number(this.id)); } }, methods: { load(id) { axios.get(&#39;/posts/&#39; + this.id + &#39;/data&#39;).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(&#39;加载文章数据失败&#39;); }); } } } </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>
    <post-detail></post-detail>
</p>
@endsection

优化文章列表组件

最后,我们到文章列表组件中新增一个发布文章入口。

打开子组件 ListSection,在视图模式切换按钮右侧新增一个插槽,用于从父级作用域传递更多额外操作按钮:

<style>
.card-header h5 {
    margin-top: 0.5em;
    display: inline-block;
}
.card-header .float-right {
    float: right;
}
</style>

<template>
<p>
    </p>
<p>
        </p>
<h5><slot></slot></h5>
        <p>
            <button>
                {{ view.switch_to }}
            </button>
            <slot></slot>
        </p>
    
    
    ...</template>

然后在 PostList 中将发布文章按钮放到这个插槽中(样式代码也做了微调):

<style>
.post-list {
    width: 100%;
}
</style>

<template>
    <p>
    <listsection>
        <template>文章列表</template>
        <template>
            <a>新文章</a>
        </template>
        <template>
            <listitem>
                {{ post.title }}
            </listitem>
        </template>
    ...</listsection></p></template>

顺便也为文章列表所有文章设置详情页链接,ListItem 链接是从 PostList 通过 props 属性传递的,CardItem 需要去子组件中设置:

<a><slot></slot></a>

至此,我们就完成了文章列表、发布、编辑和详情页的所有前后端功能代码编写。

整体测试

如果你已经在本地运行了 npm run watch 并且通过 php arstisan serve 启动 PHP 内置 Web 服务器的话,就可以在浏览器通过 http://127.0.0.1:3002/posts (启用了 BrowserSync 代理)访问新的文章列表页了:

Teach you how to implement article publishing, editing and browsing functions based on Laravel+Vue components

点击任意文章链接,即可进入文章详情页,加载数据成功之前,会有如下动态加载效果:

Teach you how to implement article publishing, editing and browsing functions based on Laravel+Vue components

Teach you how to implement article publishing, editing and browsing functions based on Laravel+Vue components

你可以点击「编辑」链接对这篇文章进行编辑:

Teach you how to implement article publishing, editing and browsing functions based on Laravel+Vue components

更新成功后,会跳转到文章详情页,对应字段均已更新,并且状态也从草稿变成了已发布:

Teach you how to implement article publishing, editing and browsing functions based on Laravel+Vue components

Of course, the article publishing and editing functions require the user to be logged in (currently no permission verification is done). If not logged in, clicking the edit and new article buttons will first jump to the login page (this function is provided by PostController Implementation of the middleware method defined in the controller constructor), when we are logged in, click the "New Article" button in the upper right corner of the article list page to enter the article publishing page:

Teach you how to implement article publishing, editing and browsing functions based on Laravel+Vue components

After the publication is successful, the page will jump to the article list page, and the article just created will appear in the list:

Teach you how to implement article publishing, editing and browsing functions based on Laravel+Vue components

Add, delete, modify and check. There is still one left. "Delete", in the next tutorial, I will demonstrate the implementation of the article deletion function. Why introduce it separately? Because I want to combine the deletion function to demonstrate the implementation of modal boxes, dialog boxes and transition effects based on Vue components.

The above is the detailed content of Teach you how to implement article publishing, editing and browsing functions based on Laravel+Vue components. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:xueyuanjun.com. If there is any infringement, please contact admin@php.cn delete