×

带大家学习一款很实用的Vue多选列表组件:multiselect

作者:Web前端之家2021.03.30来源:Web前端之家浏览:371评论:0
关键词:jsvuejsmultiselect
微信公众号

微信公众号

500.jpg

多选列表是一种将所有选项列出,使用 MultiSelect 属性可以指定用户是否能够在窗体的列表框中使用多重选择以及如何进行多重选择;Byte型,可读写。接下来让我们一起来通过实例了解下它,并且如何应用到我们的项目中。

安装

通过npm

npm install vue-multiselect --save

通过CDN

<script src="https://unpkg.com/vue-multiselect@2.1.0"></script> 
<link rel="stylesheet" href="https://unpkg.com/vue-multiselect@2.1.0/dist/vue-multiselect.min.css">

基本用法

通过npm

<!-- Vue component -->
<template>
  <div>
    <multiselect v-model="value" :options="options"></multiselect>
  </div>
</template>

<script>
  import Multiselect from 'vue-multiselect'

  // register globally
  Vue.component('multiselect', Multiselect)

  export default {
    // OR register locally
    components: { Multiselect },
    data () {
      return {
        value: null,
        options: ['list', 'of', 'options']
      }
    }
  }
</script>

<!-- New step!
     Add Multiselect CSS. Can be added as a static asset or inside a component. -->
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>

<style>
  your styles
</style>

通过CDN

// register globally
Vue.component('vue-multiselect', window.VueMultiselect.default)

例子

单选

基本的单选择/下拉菜单不需要太多配置。

options道具必须是Array

可选配置标志:

  • :searchable="false" –禁用搜索功能

  • :close-on-select="false" –选择选项后,下拉菜单保持打开状态

  • :show-labels="false" –高亮显示的选项上没有标签

//pug

div
  label.typo__label Single select
  multiselect(
    v-model="value",
    :options="options",
    :searchable="false",
    :close-on-select="false",
    :show-labels="false"
    placeholder="Pick a value"
  )
  pre.language-json
    code.
      {{ value  }}


//HTML

<div>
  <label class="typo__label">Single select</label>
  <multiselect v-model="value" :options="options" :searchable="false" :close-on-select="false" :show-labels="false" placeholder="Pick a value"></multiselect>
  <pre class="language-json"><code>{{ value  }}</code></pre>
</div>


//JS

import Multiselect from 'vue-multiselect'

export default {
  components: {
    Multiselect
  },
  data () {
    return {
      value: '',
      options: ['Select option', 'options', 'selected', 'mulitple', 'label', 'searchable', 'clearOnSelect', 'hideSelected', 'maxHeight', 'allowEmpty', 'showLabels', 'onChange', 'touched']
    }
  }
}

单选(对象)

使用对象时,必须提供其他道具:labeltrack-by

track-by用于标识选项列表中的选项,因此其值必须唯一。在此示例中,该name属性在所有选项中都是唯一的,因此可以将其用作track-by值。

label 用于显示选项。

可选配置标志:

  • :searchable="false" –禁用搜索功能

  • :allow-empty="false" –一旦有一个值,就不能取消选择

  • deselect-label="Can't remove this value"–突出显示时,已选择的选项将具有“无法删除此值帮助器”标签。对于不允许空选择的单选很有用。

//pug

div
  label.typo__label Single select / dropdown
  multiselect(
    v-model="value",
    deselect-label="Can't remove this value",
    track-by="name",
    label="name",
    placeholder="Select one",
    :options="options",
    :searchable="false",
    :allow-empty="false"
  )
    template(slot="singleLabel", slot-scope="{ option }")
      strong {{ option.name }}
      |  is written in
      strong   {{ option.language }}
  pre.language-json
    code.
      {{ value  }}


//HTML

<div>
  <label class="typo__label">Single select / dropdown</label>
  <multiselect v-model="value" deselect-label="Can't remove this value" track-by="name" label="name" placeholder="Select one" :options="options" :searchable="false" :allow-empty="false">
    <template slot="singleLabel" slot-scope="{ option }"><strong>{{ option.name }}</strong> is written in<strong>  {{ option.language }}</strong></template>
  </multiselect>
  <pre class="language-json"><code>{{ value  }}</code></pre>
</div>


//JS

import Multiselect from 'vue-multiselect'

export default {
  components: {
    Multiselect
  },
  data () {
    return {
      value: null,
      options: [
        { name: 'Vue.js', language: 'JavaScript' },
        { name: 'Rails', language: 'Ruby' },
        { name: 'Sinatra', language: 'Ruby' },
        { name: 'Laravel', language: 'PHP', $isDisabled: true },
        { name: 'Phoenix', language: 'Elixir' }
      ]
    }
  }
}

通过搜索选择

默认情况下searchable设置为true,因此使用搜索不需要任何道具。

内部搜索引擎基于labelprop。换句话说,在搜索时,vue-multiselect仅将选项标签与当前搜索查询进行比较。如果要在其他对象属性中进行搜索,请查看ajax搜索示例。

custom-label接受一个以option对象为第一个参数的函数。它应该返回一个字符串,然后将其用于显示自定义标签。

//pug

div
  label.typo__label Select with search
  multiselect(
    v-model="value",
    :options="options",
    :custom-label="nameWithLang"
    placeholder="Select one",
    label="name",
    track-by="name"
  )
  pre.language-json
    code.
      {{ value  }}


//HTML

<div>
  <label class="typo__label">Select with search</label>
  <multiselect v-model="value" :options="options" :custom-label="nameWithLang" placeholder="Select one" label="name" track-by="name"></multiselect>
  <pre class="language-json"><code>{{ value  }}</code></pre>
</div>


//JS

import Multiselect from 'vue-multiselect'

export default {
  components: {
    Multiselect
  },
  data () {
    return {
      value: { name: 'Vue.js', language: 'JavaScript' },
      options: [
        { name: 'Vue.js', language: 'JavaScript' },
        { name: 'Rails', language: 'Ruby' },
        { name: 'Sinatra', language: 'Ruby' },
        { name: 'Laravel', language: 'PHP' },
        { name: 'Phoenix', language: 'Elixir' }
      ]
    }
  },
  methods: {
    nameWithLang ({ name, language }) {
      return `${name} — [${language}]`
    }
  }
}

多选

要允许多个选择通过:multiple="true"道具。

可选配置标志:

  • :close-on-select="false" –选择选项后,下拉菜单保持打开状态

  • :clear-on-select="false" –选择一个选项后,搜索查询保持不变

v2.0.0中的新增功能稳定版:

  • 现在,您可以通过<template slot="tag" slot-scope="props"><Your code></template>对所选选项(标签)使用不同的标记

//pug

div
  label.typo__label Simple select / dropdown
  multiselect(
    v-model="value",
    :options="options",
    :multiple="true",
    :close-on-select="false",
    :clear-on-select="false",
    :preserve-search="true",
    placeholder="Pick some"
    label="name",
    track-by="name",
    :preselect-first="true"
  )
    template(
      slot="selection"
      slot-scope="{ values, search, isOpen }"
    )
      span.multiselect__single(v-if="values.length && !isOpen")
        | {{ values.length }} options selected
  pre.language-json
    code.
      {{ value  }}


//HTML

<div>
  <label class="typo__label">Simple select / dropdown</label>
  <multiselect v-model="value" :options="options" :multiple="true" :close-on-select="false" :clear-on-select="false" :preserve-search="true" placeholder="Pick some" label="name" track-by="name" :preselect-first="true">
    <template slot="selection" slot-scope="{ values, search, isOpen }"><span class="multiselect__single" v-if="values.length &amp;&amp; !isOpen">{{ values.length }} options selected</span></template>
  </multiselect>
  <pre class="language-json"><code>{{ value  }}</code></pre>
</div>


//JS

import Multiselect from 'vue-multiselect'

export default {
  components: {
    Multiselect
  },
  data () {
    return {
      value: [],
      options: [
        { name: 'Vue.js', language: 'JavaScript' },
        { name: 'Adonis', language: 'JavaScript' },
        { name: 'Rails', language: 'Ruby' },
        { name: 'Sinatra', language: 'Ruby' },
        { name: 'Laravel', language: 'PHP' },
        { name: 'Phoenix', language: 'Elixir' }
      ]
    }
  }
}

异步选择

Vue-Multiselect支持即时更改选项列表,因此也可以使用快速搜索框。

要对搜索查询的更改做出反应,请在@search-change事件上设置处理函数。它接收searchQuery作为第一个参数,可用于进行异步API调用。

每当请求正在进行时,将:loadingprop设置为都很方便true。查看提供的asyncFind方法以获取示例用法。

可选配置标志:

  • :hide-selected="true" –已经选择的选项将不会显示在下拉菜单中

  • :internal-search="false"–禁用多重选择的内部搜索引擎。如果这样做,则必须手动更新可用的:options

  • :limit="3" –将可见结果限制为3。

  • :limit-text="limitText"–接收当前所选选项计数并应返回字符串以显示:limit计数超过该值的函数

  • :options-limit="300"–将显示的选项限制为300。用于优化目的。

v2.0.0中的新增功能稳定版:

  • id="ajax"–每个事件都以此为第二参数发出。对于识别哪个组件实例触发了该方法很有用(例如,在循环中)。新:也可以用于指向<label :for="id">

  • open-direction="bottom"–强制多选始终在下面打开。使用topabove始终在上方打开。默认情况下,一旦下方没有足够的空间可打开,多选将在有更多空间的任何地方打开maxHeight

//pug

div
  label.typo__label(for="ajax") Async multiselect
  multiselect(
    v-model="selectedCountries",
    id="ajax",
    label="name",
    track-by="code",
    placeholder="Type to search",
    open-direction="bottom",
    :options="countries",
    :multiple="true",
    :searchable="true",
    :loading="isLoading",
    :internal-search="false",
    :clear-on-select="false",
    :close-on-select="false",
    :options-limit="300",
    :limit="3",
    :limit-text="limitText",
    :max-height="600",
    :show-no-results="false",
    :hide-selected="true",
    @search-change="asyncFind"
  )
    template(slot="tag", slot-scope="{ option, remove }")
      span.custom__tag
        span {{ option.name }}
        span.custom__remove(@click="remove(option)") 
    template(slot="clear", slot-scope="props")
      div.multiselect__clear(
        v-if="selectedCountries.length",
        @mousedown.prevent.stop="clearAll(props.search)"
      )
    span(slot="noResult").
      Oops! No elements found. Consider changing the search query.
  pre.language-json
    code.
      {{ selectedCountries  }}


//HTML

<div>
  <label class="typo__label" for="ajax">Async multiselect</label>
  <multiselect v-model="selectedCountries" id="ajax" label="name" track-by="code" placeholder="Type to search" open-direction="bottom" :options="countries" :multiple="true" :searchable="true" :loading="isLoading" :internal-search="false" :clear-on-select="false" :close-on-select="false" :options-limit="300" :limit="3" :limit-text="limitText" :max-height="600" :show-no-results="false" :hide-selected="true" @search-change="asyncFind">
    <template slot="tag" slot-scope="{ option, remove }"><span class="custom__tag"><span>{{ option.name }}</span><span class="custom__remove" @click="remove(option)"></span></span></template>
    <template slot="clear" slot-scope="props">
      <div class="multiselect__clear" v-if="selectedCountries.length" @mousedown.prevent.stop="clearAll(props.search)"></div>
    </template><span slot="noResult">Oops! No elements found. Consider changing the search query.</span>
  </multiselect>
  <pre class="language-json"><code>{{ selectedCountries  }}</code></pre>
</div>


//JS

import Multiselect from 'vue-multiselect'
import { ajaxFindCountry } from './countriesApi'

export default {
  components: {
    Multiselect
  },
  data () {
    return {
      selectedCountries: [],
      countries: [],
      isLoading: false
    }
  },
  methods: {
    limitText (count) {
      return `and ${count} other countries`
    },
    asyncFind (query) {
      this.isLoading = true
      ajaxFindCountry(query).then(response => {
        this.countries = response
        this.isLoading = false
      })
    },
    clearAll () {
      this.selectedCountries = []
    }
  }
}

标记

要将标记功能添加到单/多选中,请将:taggableprop设置为true。每当您在可用选项中键入不完全匹配的短语时,这都会在选项列表的开头添加一个附加选项。选择此临时选项将以@tag当前输入的搜索查询作为第一个参数来发出事件。事件处理程序应将接收到的标签添加到选项列表和值中。

请记住,将对象用作选项时,必须将接收到的标签字符串转换为与选项列表的对象结构匹配的对象。在此示例中,该addTag方法生成具有唯一code属性的对象。

可选配置标志:

  • tag-placeholder="Add this as new tag" –突出显示刚刚键入的标签建议时将显示的帮助者标签。

  • tag-position="bottom"–默认情况下,标签位置将设置为“顶部”,新标签将显示在搜索结果上方。将标记位置改为“底部”将恢复此行为,并将优先搜索结果。

//pug

div
  label.typo__label Tagging
  multiselect(
    v-model="value",
    tag-placeholder="Add this as new tag",
    placeholder="Search or add a tag",
    label="name",
    track-by="code",
    :options="options",
    :multiple="true",
    :taggable="true",
    @tag="addTag"
  )
  pre.language-json
    code.
      {{ value  }}


//HTML

<div>
  <label class="typo__label">Tagging</label>
  <multiselect v-model="value" tag-placeholder="Add this as new tag" placeholder="Search or add a tag" label="name" track-by="code" :options="options" :multiple="true" :taggable="true" @tag="addTag"></multiselect>
  <pre class="language-json"><code>{{ value  }}</code></pre>
</div>


//JS

import Multiselect from 'vue-multiselect'

export default {
  components: {
    Multiselect
  },
  data () {
    return {
      value: [
        { name: 'Javascript', code: 'js' }
      ],
      options: [
        { name: 'Vue.js', code: 'vu' },
        { name: 'Javascript', code: 'js' },
        { name: 'Open Source', code: 'os' }
      ]
    }
  },
  methods: {
    addTag (newTag) {
      const tag = {
        name: newTag,
        code: newTag.substring(0, 2) + Math.floor((Math.random() * 10000000))
      }
      this.options.push(tag)
      this.value.push(tag)
    }
  }
}

自定义选项模板

您可以使用option 作用域插槽提供自定义选项模板。可用的props包括props.optionprops.search。查看提供的示例以了解更多详细信息。

为确保键盘导航正常工作,请记住将其设置为:option-height等于选项模板的高度。默认情况下,组件假定选项高度为40px。

可选配置标志:

  • :option-height="104" –自定义选项模板的高度。

//pug

div
  label.typo__label Custom option template
  multiselect(
    v-model="value",
    placeholder="Fav No Man’s Sky path",
    label="title",
    track-by="title",
    :options="options",
    :option-height="104",
    :custom-label="customLabel",
    :show-labels="false"
  )
    template(slot="singleLabel", slot-scope="props")
      img.option__image(:src="props.option.img", alt="No Man’s Sky")
      span.option__desc
        span.option__title {{ props.option.title }}
    template(slot="option", slot-scope="props")
      img.option__image(:src="props.option.img", alt="No Man’s Sky")
      .option__desc
        span.option__title {{ props.option.title }}
        span.option__small {{ props.option.desc }}
  pre.language-json
    code.
      {{ value  }}


//HTML

<div>
  <label class="typo__label">Custom option template</label>
  <multiselect v-model="value" placeholder="Fav No Man’s Sky path" label="title" track-by="title" :options="options" :option-height="104" :custom-label="customLabel" :show-labels="false">
    <template slot="singleLabel" slot-scope="props"><img class="option__image" :src="props.option.img" alt="No Man’s Sky"><span class="option__desc"><span class="option__title">{{ props.option.title }}</span></span></template>
    <template slot="option" slot-scope="props"><img class="option__image" :src="props.option.img" alt="No Man’s Sky">
      <div class="option__desc"><span class="option__title">{{ props.option.title }}</span><span class="option__small">{{ props.option.desc }}</span></div>
    </template>
  </multiselect>
  <pre class="language-json"><code>{{ value  }}</code></pre>
</div>


//JS

import Multiselect from 'vue-multiselect'

export default {
  components: {
    Multiselect
  },
  data () {
    return {
      value: { title: 'Explorer', desc: 'Discovering new species!', img: 'static/posters/creatures.png' },
      options: [
        { title: 'Space Pirate', desc: 'More space battles!', img: 'static/posters/fleet.png' },
        { title: 'Merchant', desc: 'PROFIT!', img: 'static/posters/trading_post.png' },
        { title: 'Explorer', desc: 'Discovering new species!', img: 'static/posters/creatures.png' },
        { title: 'Miner', desc: 'We need to go deeper!', img: 'static/posters/resource_lab.png' }
      ]
    }
  },
  methods: {
    customLabel ({ title, desc }) {
      return `${title} – ${desc}`
    }
  }
}

选项组

选项列表也可以包含组。它需要经过3个额外的道具:group-labelgroup-valuesgroup-selectgroup-label用于查找组标签。group-values应该指向该组的选项列表。group-select用于定义选择组标签是应选择/取消选择组中的所有值,还是不执行任何操作。

尽管将可用选项进行了分组,但所选选项仍存储为对象的平面阵列。

请查看所提供的示例,以获得示例选项列表结构。

//pug

div
  label.typo__label Groups
  multiselect(
    v-model="value",
    :options="options",
    :multiple="true",
    group-values="libs",
    group-label="language",
    :group-select="true",
    placeholder="Type to search",
    track-by="name",
    label="name",
  )
    span(slot="noResult").
      Oops! No elements found. Consider changing the search query.
  pre.language-json
    code.
      {{ value  }}


//HTML

<div>
  <label class="typo__label">Groups</label>
  <multiselect v-model="value" :options="options" :multiple="true" group-values="libs" group-label="language" :group-select="true" placeholder="Type to search" track-by="name" label="name"><span slot="noResult">Oops! No elements found. Consider changing the search query.</span></multiselect>
  <pre class="language-json"><code>{{ value  }}</code></pre>
</div>


//JS

import Multiselect from 'vue-multiselect'

export default {
  components: {
    Multiselect
  },
  data () {
    return {
      options: [
        {
          language: 'Javascript',
          libs: [
            { name: 'Vue.js', category: 'Front-end' },
            { name: 'Adonis', category: 'Backend' }
          ]
        },
        {
          language: 'Ruby',
          libs: [
            { name: 'Rails', category: 'Backend' },
            { name: 'Sinatra', category: 'Backend' }
          ]
        },
        {
          language: 'Other',
          libs: [
            { name: 'Laravel', category: 'Backend' },
            { name: 'Phoenix', category: 'Backend' }
          ]
        }
      ],
      value: []
    }
  }
}

Vuex支持

由于Vuex强制执行单向数据流,因此不应将其v-model用于操纵当前选定的值。因为Vue-Multiselect始终使用它自己的值的内部副本,所以它永远不会:value单独对其进行突变,这意味着它可以安全地与Vuex甚至Redux一起使用。

在Vue 2.0v-model中,只是:valueand的语法糖@input。因此,我们可以使用该@input事件来触发Vuex动作或变异。每当我们:value在Vuex中进行mutate时,Multiselect的内部值都会更新。

//pug

div
  label.typo__label Vuex example.
  multiselect(
    placeholder="Pick action",
    :value="value",
    :options="options",
    :searchable="false",
    @input="updateValueAction",
  )


//HTML

<div>
  <label class="typo__label">Vuex example.</label>
  <multiselect placeholder="Pick action" :value="value" :options="options" :searchable="false" @input="updateValueAction"></multiselect>
</div>


//JS

import Vue from 'vue'
import Vuex from 'vuex'
import Multiselect from 'vue-multiselect'

const { mapActions, mapState } = Vuex

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    value: 'Vuex',
    options: ['Vuex', 'Vue', 'Vuelidate', 'Vue-Multiselect', 'Vue-Router']
  },
  mutations: {
    updateValue (state, value) {
      state.value = value
    }
  },
  actions: {
    updateValueAction ({ commit }, value) {
      commit('updateValue', value)
    }
  }
})

export default {
  store,
  components: {
    Multiselect
  },
  computed: {
    ...mapState(['value', 'options'])
  },
  methods: {
    ...mapActions(['updateValueAction'])
  }
}

调度程序

该组件还可以充当不同动作/方法的调度程序。在这种情况下,不需要:value道具。取而代之的是@input您可以监听@select事件。两者之间的区别在于,@select仅接收当前选定的值,而不接收选定值的整个列表(如果选择多个)。

可选配置标志:

  • :reset-after="true" –在组件内部的每个选择操作之后,重置内部值。

//pug

div
  label.typo__label Open console to see logs.
  multiselect(
    placeholder="Pick action",
    :options="actions",
    :searchable="false",
    :reset-after="true",
    @select="dispatchAction"
  )


//HTML

<div>
  <label class="typo__label">Open console to see logs.</label>
  <multiselect placeholder="Pick action" :options="actions" :searchable="false" :reset-after="true" @select="dispatchAction"></multiselect>
</div>


//JS

import Multiselect from 'vue-multiselect'

export default {
  components: {
    Multiselect
  },
  data () {
    return {
      actions: ['alert', 'console.log', 'scrollTop']
    }
  },
  methods: {
    dispatchAction (actionName) {
      switch (actionName) {
        case 'alert':
          window.alert('You just dispatched "alert" action!')
          break
        case 'console.log':
          console.log('You just dispatched "console.log" action!')
          break
        case 'scrollTop':
          window.scrollTo(0, 0)
          break
      }
    }
  }
}

自定义配置

触摸时显示错误,但未选择任何内容。

可选配置标志:

  • :max-height="150" –将下拉高度设置为150px

  • :max="3" –设置最大选择数

  • :allow-empty="false" –不允许删除最后一个选项(如果存在)

  • :block-keys="['Tab', 'Enter']"–阻止TabEnter键触发其默认行为

  • @close="onTouch" –关闭下拉菜单时发出的事件

//pug

div(
  :class="{ 'invalid': isInvalid }"
)
  label.typo__label Customized multiselect
  multiselect(
    placeholder="Pick at least one",
    select-label="Enter doesn’t work here!",
    :value="value",
    :options="options",
    :multiple="true",
    :searchable="true",
    :allow-empty="false",
    :hide-selected="true",
    :max-height="150",
    :max="3",
    :disabled="isDisabled",
    :block-keys="['Tab', 'Enter']",
    @input="onChange",
    @close="onTouch",
    @select="onSelect"
  )
  label.typo__label.form__label(v-show="isInvalid") Must have at least one value


//HTML

<div :class="{ 'invalid': isInvalid }">
  <label class="typo__label">Customized multiselect</label>
  <multiselect placeholder="Pick at least one" select-label="Enter doesn’t work here!" :value="value" :options="options" :multiple="true" :searchable="true" :allow-empty="false" :hide-selected="true" :max-height="150" :max="3" :disabled="isDisabled" :block-keys="['Tab', 'Enter']" @input="onChange" @close="onTouch" @select="onSelect"></multiselect>
  <label class="typo__label form__label" v-show="isInvalid">Must have at least one value</label>
</div>


//JS

import Multiselect from 'vue-multiselect'

export default {
  components: {
    Multiselect
  },
  data () {
    return {
      isDisabled: false,
      isTouched: false,
      value: [],
      options: ['Select option', 'Disable me!', 'Reset me!', 'mulitple', 'label', 'searchable']
    }
  },
  computed: {
    isInvalid () {
      return this.isTouched && this.value.length === 0
    }
  },
  methods: {
    onChange (value) {
      this.value = value
      if (value.indexOf('Reset me!') !== -1) this.value = []
    },
    onSelect (option) {
      if (option === 'Disable me!') this.isDisabled = true
    },
    onTouch () {
      this.isTouched = true
    }
  }
}

大家可以把案例放到开发软件里试下咯,有问题可以加群探讨咯。

温馨提示:本文作者系Web前端之家 ,经Web前端之家编辑修改或补充,转载请注明出处和本文链接:
https://jiangweishan.com/article/vuejs20210330sdfsdf.html

网友评论文明上网理性发言 已有0人参与

发表评论:

最新留言

  • aj008

    有时间的话,还是两个都要学哦,未来的市场谁也说不定。...

  • 访客

    看你的公司用啥了,其实感觉环境第一位。...

  • Web前端之家

    vue的生态应用在国内很强大,但是react更加适用于中大型公司。...

  • agg151

    感觉在国内,VUE应该要排第一。...

  • qianduan008

    三角形工具一直在用,这个CSS编辑器也很不错,希望继续分享新的前端工具哟!...

  • Web前端之家

    其实还好,但是中大型公司都是在用React。vue比较好入门。...

  • abner

    React比VUE难学啊。...

  • 访客

    多学技术永远是最好的...

首页|JavaScript|HTML|HTML4|HTML5|CSS3|开发工具|性能优化|移动开发|前端教程|性能优化|开发工具|酷站欣赏|UI设计|前端教程

Copyright © 2021 Web前端之家(www.jiangweishan.com) 版权所有 All Rights Reserved.
粤ICP备12067512号-1

Copyright Your WebSite.Some Rights Reserved.

Powered By Z-BlogPHP 1.6.7 Valyria