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 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 4

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

4年前 评论

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

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

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