Menu

32. Vue 回复显示组件

本节说明

  • 对应视频第 32 小节:A Vue Reply Component

本节内容

本节我们来为回复编写一个 Vue 组件。首先修改reply视图:
forum\resources\views\threads\reply.blade.php

<reply inline-template>
    <div id="reply-{{ $reply->id }}" class="panel panel-default">
        <div class="panel-heading">
            <div class="level">
                <h5 class="flex">
                    <a href="{{ route('profile',$reply->owner) }}"> {{ $reply->owner->name }}</a>
                    回复于
                    {{ $reply->created_at->diffForHumans() }}
                </h5>

                <div>
                    <form method="POST" action="/replies/{{ $reply->id }}/favorites">
                        {{ csrf_field() }}

                        <button type="submit" class="btn btn-default" {{ $reply->isFavorited() ? 'disabled' : '' }}>
                            {{ $reply->favorites_count }} {{ str_plural('Favorite',$reply->favorites_count) }}
                        </button>
                    </form>
                </div>
            </div>
        </div>

        <div class="panel-body">
            {{ $reply->body }}
        </div>

        @can('update',$reply)
            <div class="panel-footer level">
                <button class="btn btn-xs mr-1">Edit</button>

                <form method="POST" action="/replies/{{ $reply->id }}">
                    {{ csrf_field() }}
                    {{ method_field('DELETE') }}

                    <button type="submit" class="btn btn-danger btn-xs">Delete</button>
                </form>
            </div>
        @endcan
    </div>
</reply>

我们通过在组件中添加inline-template属性,向 Vue 指示内部内容是其模板。接下来ti添加组件:
forum\resources\assets\js\components\Reply.vue

<script>
    export default {

    }
</script>

注册组件:
forum\resources\assets\js\app.js


/**
 * First we will load all of this project's JavaScript dependencies which
 * includes Vue and other libraries. It is a great starting point when
 * building robust, powerful web applications using Vue and Laravel.
 */

require('./bootstrap');

/**
 * Next, we will create a fresh Vue application instance and attach it to
 * the page. Then, you may begin adding components to this application
 * or customize the JavaScript scaffolding to fit your unique needs.
 */

Vue.component('flash', require('./components/Flash.vue'));
Vue.component('reply', require('./components/Reply.vue'));

const app = new Vue({
    el: '#app'
});

我们在回复视图使用了:

<button class="btn btn-xs mr-1">Edit</button>

但是我们没有编写mr-1的样式,我们先在主视图文件暂时定义一下样式,以后我们把样式单独放到样式文件中:
forum\resources\views\layouts\app.blade.php

.
.
<style>
    body{ padding-bottom: 100px; }
    .level { display: flex;align-items: center; }
    .flex { flex: 1 }
    .mr-1 {margin-right: 1em;}
</style>
.
.

编译后访问页面:
file
接下来我们修改回复的视图跟组件,增加回复的编辑区域:
forum\resources\views\threads\reply.blade.php

<reply :attributes="{{ $reply }}" inline-template>
        .
        .
        <div class="panel-body">
            <div v-if="editing">
                <textarea class="form-control" v-model="body"></textarea>
            </div>

            <div v-else>
                {{ $reply->body }}
            </div>
        </div>

        @can('update',$reply)
            <div class="panel-footer level">
                <button class="btn btn-xs mr-1" @click="editing = true">Edit</button>

                <form method="POST" action="/replies/{{ $reply->id }}">
                    {{ csrf_field() }}
                    {{ method_field('DELETE') }}

                    <button type="submit" class="btn btn-danger btn-xs">Delete</button>
                </form>
            </div>
        @endcan
    </div>
</reply>

forum\resources\assets\js\components\Reply.vue

<script>
    export default {
        props: ['attributes'],

        data() {
            return {
              editing: false,
              body: this.attributes.body
            };
        }
    }
</script>

我们通过:attributes="{{ $reply }}"reply的属性以json的格式放到组件的属性中,还增加了一个回复的编辑区域<textarea class="form-control" v-model="body"></textarea>,并设置Edit按钮@click="editing = true",通过点击按钮激活回复的编辑区域。现在编译后刷新页面,点击Edit按钮可以看到可编辑的回复区域:
file

接下来我们在回复的编辑区域下方添加两个按钮:UpdateCancel,并且点击Update按钮实现回复的更新,点击Cancel取消编辑回复。让我们先来为更新回复添加测试,我们需要添加两个测试,分别测试无权限用户和有权限用户更新回复的行为:
forum\tests\Feature\ParticipateInForumTest.php

    .
    .
    /** @test */
    public function unauthorized_users_cannot_update_replies()
    {
        $this->withExceptionHandling();

        $reply = create('App\Reply');

        $this->patch("/replies/{$reply->id}")
            ->assertRedirect('login');

        $this->signIn()
            ->patch("/replies/{$reply->id}")
            ->assertStatus(403);
    }

    /** @test */
    public function authorized_users_can_update_replies()
    {
        $this->signIn();

        $reply = create('App\Reply',['user_id' => auth()->id()]);

        $updatedReply = 'You have been changed,foo.';
        $this->patch("/replies/{$reply->id}",['body' => $updatedReply]);

        $this->assertDatabaseHas('replies',['id' => $reply->id,'body' => $updatedReply]);
    }
}

添加更新路由:
forum\routes\web.php

.
.
Route::patch('/replies/{reply}','RepliesController@update');
Route::delete('/replies/{reply}','RepliesController@destroy');
.
.

增加update()方法:
forum\app\Http\Controllers\RepliesController.php

.
.
public function update(Reply $reply)
{
    $this->authorize('update',$reply);

    $reply->update(request(['body']));
}
.
.

依次测试这两个测试:
file
测试通过,这意味着我们可以继续完善组件:
forum\resources\views\threads\reply.blade.php

.
.
<div class="panel-body">
    <div v-if="editing">
        <div class="form-group">
            <textarea class="form-control" v-model="body"></textarea>
        </div>

        <button class="btn btn-xs btn-primary" @click="update">Update</button>
        <button class="btn btn-xs btn-link" @click="editing = false">Cancel</button>
    </div>

    <div v-else>
        {{ $reply->body }}
    </div>
</div>
.
.

我们还需要在Reply.vue组件中增加update方法:

<script>
    export default {
        props: ['attributes'],

        data() {
            return {
              editing: false,
              body: this.attributes.body
            };
        },

        methods:{
            update() {
                axios.patch('/replies/' + this.attributes.id,{
                    body:this.body
                });

                this.editing = false;
            }
        }
    }
</script>

现在我们尝试更新:
file
会发现页面无变化。但是如果我们打开开发者工具栏,会发现其实我们的请求已经成功:
file
我们刷新页面会发现回复已经成功更新:
file
接下来我们把更新的内容显示出来:
forum\resources\views\threads\reply.blade.php

.
.
<div class="panel-body">
    <div v-if="editing">
        <div class="form-group">
            <textarea class="form-control" v-model="body"></textarea>
        </div>

        <button class="btn btn-xs btn-primary" @click="update">Update</button>
        <button class="btn btn-xs btn-link" @click="editing = false">Cancel</button>
    </div>

    <div v-else v-text="body"> </div>
</div>
.
.

现在我们已经可以把更新的内容显示出来了。但是我们还有几个地方需要优化:

  1. 当我们刷新页面的时候,可以看到回复编辑区域;
  2. 更新回复之后我们需要给出消息提示;

第一个问题是因为 HTML 绑定 Vue实例,在页面加载时会闪烁。我们使用v-cloak指令来解决这个问题:

这个指令保持在元素上直到关联实例结束编译。和 CSS 规则如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。

编辑视图:
forum\resources\views\threads\reply.blade.php

<reply :attributes="{{ $reply }}" inline-template v-cloak>
.
.

增加 CSS 样式:
forum\resources\views\layouts\app.blade.php

.
.
<style>
    body{ padding-bottom: 100px; }
    .level { display: flex;align-items: center; }
    .flex { flex: 1 }
    .mr-1 {margin-right: 1em;}
    [v-cloak] { display: none; }
</style>
.
.

最后,我们为更新动作加上消息提示:
forum\resources\assets\js\components\Reply.vue

<script>
    export default {
        props: ['attributes'],

        data() {
            return {
              editing: false,
              body: this.attributes.body
            };
        },

        methods:{
            update() {
                axios.patch('/replies/' + this.attributes.id,{
                    body:this.body
                });

                this.editing = false;

                flash('Updated!');
            }
        }
    }
</script>

刷新页面再次更新回复:
file

本文章首发在 Laravel China 社区
上一篇 下一篇
讨论数量: 0
发起讨论


暂无话题~
刻意练习,每日精进。
3
点赞
254
浏览
0
讨论

维护者
14
14