Laravel + Vue 製作一款標籤選擇器

tsin發表於2020-03-16

前言

最近在擼一款個人的部落格,雖然是第一次使用 Laravel 寫專案,但心中的原則不能背棄——就是要擼一款能愉快寫作,賞心悅目閱讀的部落格應用。所以,一款能多選,又能同時建立標籤又能隨時刪除標籤的選擇器當然少不了。話不多少,直接開始吧。

選一個輪子

作為非專業前端,當然是直接拿起輪子就開幹,jQuery 的輪子是不少,但 Vue 實在太好用了,所以這裡就選基於 Vue 的 Vue-multiselect

安裝

本文假設你已經安裝好 Laravel 的 Vue腳手架。安裝 Vue-multiselect:

npm install vue-multiselect --save

封裝成自己需要的 Vue 元件

新建一個檔案:resources/js/component/MultiSelectComponent.vue,寫入如下程式碼:

<template>
    <div>
        <input type="hidden" :name="fieldName" :value="myTagIds">
        <multiselect
            v-model="value"
            tag-placeholder="新增為新標籤"
            placeholder="請新增文章標籤(選擇或者直接輸入)"
            select-label="按 Enter 選擇"
            selected-label="已選"
            label="name"
            track-by="id"
            :options="options"
            :multiple="true"
            :taggable="true"
            :hide-selected="true"
            @tag="addTag"
        ></multiselect>
    </div>
</template>

<script>
    import Multiselect from 'vue-multiselect'
    export default {
        components: { Multiselect },
        props: {
            fieldName: {
                type: String,
                required: true
            },
            tagIds: {
                type: Array,
                default: () => [],
            },
            tags: {
                type: Array,
                required: true,
            },
        },
        data() {
            return {
                value: [],
                options: this.tags,
                myTagIds: this.tagIds
            }
        },
        methods: {
            addTag(newTag) {
                const tag = {
                    name: newTag,
                    id: newTag + '~' + Math.random().toString(36).substring(2)
                }
                this.options.push(tag);
                this.value.push(tag);
            }
        },
        mounted() {
            if (this.myTagIds && this.options.length > 0) {
                this.options.map((item) => {
                    if (this.myTagIds.includes(item.id)) {
                        this.value.push(item);
                    }
                })
            }
        },
        watch: {
            value(n, o) {
                let tagIds = [];
                n.map((item) => {
                    tagIds.push(item.id);
                });
                this.myTagIds = tagIds;
            }
        }
    }
</script>

幾點說明:

  1. <input type="hidden" :name="fieldName" :value="myTagIds">這裡新增一個表單欄位,可傳入欄位名,myTagIds為表單需要的值;
  2. v-model="value"value為選中的標籤物件;
  3. tag-placeholderplaceholderselect-label等為一些提示,原來是英文的這裡替換為中文,更多屬性值得設定可以參考該擴充套件的文件
  4. track-by需是一個唯一值,這裡使用標籤的id;
  5. :options="options"options為已有的標籤資料,後面說明如何從 laravel 模板中傳過來;
  6. multipletrue表示支援多選,taggabletrue表示可以直接輸入建立標籤,而@tag是輸入標籤名建立標籤時觸發的事件,後面實現該事件(addTag方法);
  7. props屬性接收的值分別為:表單欄位名、已選的標籤名和標籤資料;
  8. data中,myTagIds: this.tagIds,將外部傳進來的資料傳給myTagIds,後面可以直接操作改變myTagIds。(直接在元件裡面改變外部傳入的資料(tagIds)Vue會報出警告);
  9. addTag方法中,給新增的標籤生成一個唯一的id,且用一個特殊符號連線,這個是計劃myTagsId(所有選中的標籤ID,包括新建立的)傳到後端後,對於這些帶特殊符號的ID值分割出標籤名(也就是新建的標籤);
  10. mounted()方法,載入的時候,顯示出文章已有的標籤;
  11. watchvalue方法,可用於監聽value的變動,第一個引數為新的值,第二個為舊的值。這裡把新的value值迴圈取出id值儲存到myTagIds

元件的使用

註冊元件

封裝好元件,就可以到處使用了。首先,是要引入元件:
修改/resources/js/app.js

.
.
.
window.Vue = require('vue');
.
.
.
// Vue.component('example-component', require('./components/ExampleComponent.vue').default);

// ** 註冊元件,新增這一行 **
Vue.component('multi-select-component', require('./components/MultiSelectComponent.vue').default);
.
.
.
// ** 這一段也註釋掉,我們將在具體的頁面例項化Vue例項 ** 
/*const app = new Vue({
    el: '#app',
});*/

程式碼修改注意看上面的註釋說明

Laravel 準備資料

接著,要準備好元件需要的資料,本例項的場景是文章和標籤多對多的關聯模型。在控制器中,大概是這樣輸出資料:

public function create(Post $post)
    {
        $tags = Tag::all(['id', 'name'])->toArray();
        return view('posts.create_and_edit', compact('post', 'tags'));
    }

另外,前端頁面還需要一個文章所有標籤的id資料,所以,在文章模型中新增一個獲取器:
(假定多對多關聯已經定義好)

public function getTagIdsAttribute()
{
    return $this->tags()->allRelatedIds();
}

這樣,透過文章的例項就可以獲取到所有的標籤id,像這樣:$post->tag_ids

Blade 模板中使用元件並繫結資料

<div class="form-group">
    <multi-select-component
          field-name = "tag_ids"
          :tags = "tags"
          @if(old('tag_ids', $post->tag_ids))
          :tag-ids="{{ old('tag_ids', $post->tag_ids) }}"
          @endif
    ></multi-select-component>
</div>

在Blade模板末尾的JS部分,這樣寫:

@section('script')
    <script>
        const app = new Vue({
            el: "#app",
            data: {
                tags: {!! json_encode($tags) !!},
            }
        })
    </script>
@endsection

以上程式碼的幾點說明:

  1. @if(old('tag_ids', $post->tag_ids))判斷,如果tag_ids沒有任何值,就不要傳值了,這樣讓元件預設顯示「請選擇標籤」提示;
  2. tags: {!! json_encode($tags) !!},,這使用{!! json_encode($tags) !!}給元件傳值。當然,還有另一種方法,是使用@json指令,在上面的元件標籤裡面,使用:tags = @json($tags)也是可以的。

成果驗收

寫完收工,測試一下結果。

Laravel + Vue 製作一款標籤選擇器(詳細過程)

元件中的資料結構:
Laravel + Vue 製作一款標籤選擇器

最後,限於個人水平有限,有任何不足之處,敬請指出。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
Was mich nicht umbringt, macht mich stärker

相關文章