Laravel + Vue 制作一款标签选择器

前言

最近在撸一款个人的博客,虽然是第一次使用 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
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 4

😂为什么不直接用UI库,而是选择单个的库。

4年前 评论

@fangmuke 有试过element ui, 觉得安装完后,app.js太大了,达到5MB多。

4年前 评论
fangmuke 4年前
tsin (作者) (楼主) 4年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!