Maison > cadre php > Laravel > Vous apprendre à implémenter des fonctions de publication, d'édition et de navigation d'articles basées sur les composants Laravel+Vue

Vous apprendre à implémenter des fonctions de publication, d'édition et de navigation d'articles basées sur les composants Laravel+Vue

藏色散人
Libérer: 2020-11-13 14:08:50
avant
2902 Les gens l'ont consulté


La colonne tutorielle suivante de Laravel vous présentera comment implémenter les fonctions de publication, d'édition et de navigation d'articles basées sur les composants Laravel+Vue . J'espère que cela vous aidera si vous en avez besoin.

Vous apprendre à implémenter des fonctions de publication, d'édition et de navigation d'articles basées sur les composants Laravel+Vue

Nous fournirons l'interface backend basée sur Laravel, Vue.js comme cadre de développement de composants JavaScript front-end et Bootstrap comme cadre CSS.

Interface backend Laravel

Tout d'abord, sur la base du contrôleur de ressources créé dans le tutoriel précédent PostController, nous avons rapidement écrit le code d'implémentation de l'interface backend ajouter, supprimer, modifier, vérifier :

<?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;
    }
}
Copier après la connexion

Except En plus des méthodes fournies avec le contrôleur de ressources Laravel, nous proposons deux méthodes supplémentaires : all et data, qui sont utilisées pour obtenir des données de liste d'articles et des données de détails d'articles via des requêtes AJAX dans le composant Vue respectivement. Il faut donc ajouter les routes correspondant à ces deux méthodes avant d'enregistrer la route de ressource dans le fichier de routage 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);
Copier après la connexion

A noter qu'ici nous utilisons la fonction de liaison de modèle implicite fournie par le routage Laravel pour obtenir rapidement l'instance de modèle. De plus, les chemins des modèles de vue correspondants ont également été ajustés. Nous présenterons prochainement ces fichiers de modèles de vue.

Remplissez les données de test via le remplisseur

Si vous avez ajouté d'autres données basées sur les données de test remplies dans le didacticiel précédent, vous pouvez exécuter la commande php artisan migrate:refresh pour reconstruire la table de données et effacez rapidement les données existantes et rechargez-les.

Si vous ne souhaitez pas voir les détails du format de données d'instance renvoyé, vous pouvez définir le code de remplissage dans le remplissage intégré 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();
    }
}
Copier après la connexion

, puis exécuter le php artisan migrate:refresh --seed commande pour compléter les données en une seule étape Reconstruction de table, test de vidage et de repeuplement des données :

Vous apprendre à implémenter des fonctions de publication, dédition et de navigation darticles basées sur les composants Laravel+Vue

Vous apprendre à implémenter des fonctions de publication, dédition et de navigation darticles basées sur les composants Laravel+Vue

Reconstruction de modèles de vue via l'héritage de modèles

Puisque nous utilisons le code d'échafaudage frontal Bootstrap et Vue fourni par le package d'extension laravel/ui fourni par Laravel, le package d'extension fournit également l'implémentation du code d'échafaudage lié à l'authentification utilisateur et fournit un fichier de mise en page de modèle de vue. resources/views/layouts/app.blade.php, dont nous hériterons en fonction de Ce fichier de mise en page est utilisé pour reconstruire les fichiers de modèle de vue liés à la liste d'articles, au formulaire et à la page de détails afin d'unifier l'interface utilisateur globale.

Définir différents titres pour différentes pages

Nous avons précédemment transmis la valeur PostController comme titre de différentes pages pour les fichiers de vue rendus par toutes les routes GET dans pageTitle Pour implémenter cette fonction. , Il est nécessaire de modifier la valeur du texte de l'étiquette correspondant à la balise resources/views/layouts/app.blade.php dans le fichier de mise en page title :

<title>{{ $pageTitle ?? config('app.name', 'Laravel') }}</title>
Copier après la connexion

Vue liste d'articles

Ensuite, déplacez tous les articles originaux liés à l'article afficher les fichiers dans le répertoire resources/views/posts, réécrire le code du modèle de fichier d'affichage de la liste d'articles comme suit (renommer l'original posts.blade.php en index.blade.php) :

@extends('layouts.app')

@section('content')
    <p>
        <post-list></post-list>
    </p>
@endsection
Copier après la connexion

Vue de publication d'articles

Renommer l'original form.blade.php Comme create.blade.php, et écrivez le code du modèle de fichier de vue de page du formulaire de publication d'article comme suit :

@extends('layouts.app')

@section('content')
    <p>
        </p><p>
            <post-form>
            </post-form>
        </p>
    
@endsection
Copier après la connexion

Étant donné que les formulaires de publication et d'édition d'article partagent un composant de formulaire Vue, nous transmettons quelques attributs d'accessoires supplémentaires au modèle de composant ici, y compris le titre du formulaire (title), le type d'opération (action), l'URL de soumission du formulaire (url), des ajustements aux composants du formulaire seront introduits prochainement.

Vue d'édition d'article

Créez un nouveau resources/views/posts dans le répertoire edit.blade.php en tant que fichier de vue de page d'édition de fichier et écrivez le code du modèle comme suit :

@extends('layouts.app')

@section('content')
    <p>
        </p><p>
            <post-form> $id]) }}">
            </post-form>
        </p>
    
@endsection
Copier après la connexion

Utilisez également post-form Le modèle restitue le formulaire d'édition d'article, mais un attribut id supplémentaire est transmis pour initialiser les données d'article à modifier dans le composant de formulaire.

La vue de la page de détails de l'article sera présentée séparément plus tard.

Refactoriser le code du composant du formulaire Vue

Afin de nous adapter au formulaire d'édition d'article et d'ajuster le format des données renvoyées par l'interface backend, nous devons modifier le code d'implémentation du composant du formulaire Vue :

<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>
Copier après la connexion

Les pages de publication et d'édition d'articles doivent être distinguées par des titres, nous transmettons donc la valeur du titre de la portée parent via l'attribut title.

Pour le formulaire d'édition d'article, nous appellerons d'abord la nouvelle méthode id dans la fonction hook mounted en fonction de la valeur de l'attribut load transmise par la portée parent à charger depuis l'interface backend /posts/{post}/data Remplissez le formulaire avec les données de l'article correspondant.

现在后端接口可以自动获取当前认证用户的 ID,所以 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>
Copier après la connexion

可以看到,如果表单提交处理成功(依然基于父级作用域传递的 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();
    }
    
    ...

}
Copier après la connexion

这样一来,文章发布和编辑共用的 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>
Copier après la connexion

这个组件功能比较简单,在 mounted 钩子函数中通过父级作用域传递的 id 属性值调用 load 函数加载后端接口返回的文章数据,并通过数据绑定将其渲染到模板代码中,在加载过程中,会有一个动态的加载状态提示用户文章数据正在加载。

这里我们还使用了过滤器对数据进行格式化,日期过滤器已经是全局的了,状态过滤器之前是本地的,这里我们将其从文章列表卡片组件 CardItem 中将其迁移到 app.js 中作为全局过滤器:

Vue.filter('post_status_readable', status => {
    switch(status) {
        case 0:
            return '草稿';
        case 1:
            return '已发布';
        default:
            return '未知状态';
    }
});
Copier après la connexion

然后就可以在任何 Vue 组件中调用它了(CardItem 中过滤器调用代码做一下相应调整)。

app.js 中注册这个组件:

Vue.component('post-detail', require('./components/PostDetail.vue').default);
Copier après la connexion

文章详情页视图文件

再到 component-practice/resources/views/posts 目录下新建 show.blade.php 视图文件引用 post-detail 组件即可:

@extends('layouts.app')

@section('content')
<p>
    <post-detail></post-detail>
</p>
@endsection
Copier après la connexion

优化文章列表组件

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

打开子组件 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>
Copier après la connexion

然后在 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>
Copier après la connexion

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

<a><slot></slot></a>
Copier après la connexion

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

整体测试

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

Vous apprendre à implémenter des fonctions de publication, dédition et de navigation darticles basées sur les composants Laravel+Vue

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

Vous apprendre à implémenter des fonctions de publication, dédition et de navigation darticles basées sur les composants Laravel+Vue

Vous apprendre à implémenter des fonctions de publication, dédition et de navigation darticles basées sur les composants Laravel+Vue

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

Vous apprendre à implémenter des fonctions de publication, dédition et de navigation darticles basées sur les composants Laravel+Vue

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

Vous apprendre à implémenter des fonctions de publication, dédition et de navigation darticles basées sur les composants Laravel+Vue

Bien sûr, les fonctions de publication et d'édition d'articles nécessitent que l'utilisateur soit connecté (actuellement, aucune vérification d'autorisation n'est effectuée). S'il n'est pas connecté, cliquer sur les boutons Modifier et Nouvel article accédera d'abord à la page de connexion (cette fonction). est contrôlé par PostController (implémenté par la méthode middleware définie dans le constructeur), nous cliquons sur le bouton "Nouvel article" dans le coin supérieur droit de la page de liste d'articles pour accéder à la page de publication d'articles une fois connecté :

Vous apprendre à implémenter des fonctions de publication, dédition et de navigation darticles basées sur les composants Laravel+Vue

Après une publication réussie, la page passera à la page de liste d'articles et l'article nouvellement créé apparaîtra dans la liste :

Vous apprendre à implémenter des fonctions de publication, dédition et de navigation darticles basées sur les composants Laravel+Vue

Il reste encore une "suppression" dans l'ajout, la suppression, la modification et la vérification. Dans le prochain tutoriel, je démontrerai l'implémentation de la fonction de suppression d'article. Pourquoi l'introduire séparément Parce que je souhaite combiner la fonction de suppression ? démontrer l'implémentation de boîtes modales, de boîtes de dialogue et d'effets de transition basés sur les composants Vue.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:xueyuanjun.com
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal