vue + element-ui(el-select)双向绑定 ##需求:
为了方便管理后台的操作,用户一般会有个默认项目. 用户可以减少切换页面,每次都都要手动选择项目。 同时,查询页面,需要查看别的项目的信息,但是不能影响默认项目 页面头部有一个默认项目搜索提示框,选择默认项目有筛选功能的页面,页面内部还有筛选搜索框。用户登陆,自动选择默认项目 (存储在cookie中)用户选择默认项目,新打开任何页面,都会选择默认项目用户改变默认项目,则该页面搜索项目需要改变。单个页面,修改过滤项目,默认项目不变。整体git地址:https://github.com/destinym/chainedSelect
参考element-ui的文档,我们快速做一个搜索选择框。
只有一个主页面
主页面代码:
<template> <div> <div> <h1> v0.1版本</h1> </div> <hr> <el-row> <NavSelect></NavSelect> <div> <span>服务名称:</span> <el-select v-model="project" :remote-method="queryService" clearable filterable remote placeholder="请选择筛选服务"> <el-option v-for="item in projectList" :key="item.value" :label="item.label" :value="item.value"/> </el-select> <div> 当前页面的项目为: {{ project}} </div> </div> </el-row> </div> </template> <script> import {queryProjectList} from '@/utils/query' import NavSelect from '@/components/NavSelect' export default { components: { NavSelect }, data () { return { project: '', projectList: [] } }, created () { // to init data this.$store.dispatch('Init') this.projectList = queryProjectList() }, methods: { queryService () { this.projectList = queryProjectList() } } } </script>问题,如果大部分页面都要使用怎么办? 所以,我们必须写成组件的形式,方便重用
我们修改代码为组件代码。看似很简单。 但是却发现一个严重的问题,我们选择的结果和搜索的结果完全不同。
组件代码:
<template> <div> <span v-show="header">服务名称:</span> <el-select v-model="project" :remote-method="queryService" clearable filterable remote placeholder="请选择筛选服务" > <el-option v-for="item in projectList" :key="item.value" :label="item.label" :value="item.value"/> </el-select> </div> </template> <script> import {queryProjectList} from '@/utils/query' export default { name: 'LocalSelect', data: function () { return { projectList: [], project: '' } }, props: { header: { type: Boolean, default: true } }, created () { this.projectList = queryProjectList() }, methods: { queryService (query) { this.projectList = queryProjectList(query) } } } </script>主页面代码
<template> <div> <div> <h1> v0.2版本</h1> </div> <hr> <el-row> <NavSelect></NavSelect> <LocalSelect></LocalSelect> <div> 当前页面的项目为: {{ project}} </div> </el-row> </div> </template> <script> import LocalSelect from '@/components/LocalSelectV0.2' import NavSelect from '@/components/NavSelect' import store from '@/store' export default { components: { NavSelect, LocalSelect }, data () { return { project: '' } }, created () { store.dispatch('Init') }, methods: { setValue (val) { this.project = val } } } </script>原因是什么? vue2.0之后 页面和组件内部不支持双向绑定了。 参考: https://segmentfault.com/a/1190000008662112 https://www.jianshu.com/p/1ebc15645abe
思路 参考了上面文章,大家的基本思路一致
组件使用props属性来project增加一个变量p_project,使得p_project=project,接收原始数据组件内部数据变化,p_project发送变化。监听p_project,通过emit通知父组件组件代码:
<template> <div> <span v-show="header">服务名称:</span> <el-select v-model="project" :remote-method="queryService" clearable filterable remote placeholder="请选择筛选服务" > <el-option v-for="item in projectList" :key="item.value" :label="item.label" :value="item.value"/> </el-select> </div> </template> <script> import {queryProjectList} from '@/utils/query' export default { name: 'LocalSelect', data: function () { return { projectList: [], p_project: '' } }, props: { header: { type: Boolean, default: true }, project: { type: String, default: '' } }, created () { this.projectList = queryProjectList() }, watch: { p_project: function () { this.$emit('change', this.p_project) } }, methods: { queryService (query) { this.projectList = queryProjectList(query) }, change (value) { this.p_project = value this.projectList = queryProjectList() } } } </script>主页代码
<template> <div> <div> <h1> v0.3版本</h1> </div> <hr> <el-row> <NavSelect></NavSelect> <LocalSelect></LocalSelect> <div> 当前页面的项目为: {{ project}} </div> </el-row> </div> </template> <script> import LocalSelect from '@/components/LocalSelectV0.3' import NavSelect from '@/components/NavSelect' import store from '@/store' export default { components: { NavSelect, LocalSelect }, data () { return { project: '' } }, created () { store.dispatch('Init') }, methods: { setValue (val) { this.project = val } } } </script>发现错误:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "project" found in ---> <LocalSelect> at src/components/LocalSelectV0.3/index.vue <HelloWorld> at src/views/v0.3.vue <App> at src/App.vue <Root>挫折
看上去很美好,实际使用的时候。发现还是报错。 这句的意思是我们不能修改组件内props的值。但是,我们并没有修改project这个变量,为什么还会报错。
仔细分析后发现,我们使用了el-select组件,它在响应change事件的时候,其实已经修改了props中project的值。 所以我们无论怎么写代码,响应change事件的时候,必然会报错。
思考+新思路。 山重水复疑无路,柳暗花明又一村。 经过几次尝试,我们整理下思路:
组件内数据修改页面数据,必须通过emit.props的值不能直接修改。所以,我们还是想使用el-select。那么我们必须使用data来存储project。 同时建立监听器,如果data变化,再通知主页面。
组件代码
<template> <div> <span v-show="header">服务名称:</span> <el-select v-model="project" :remote-method="queryService" clearable filterable remote placeholder="请选择筛选服务" > <el-option v-for="item in projectList" :key="item.value" :label="item.label" :value="item.value"/> </el-select> </div> </template> <script> import {queryProjectList} from '@/utils/query' export default { name: 'LocalSelect', data: function () { return { projectList: [], project: '' } }, props: { header: { type: Boolean, default: true } }, created () { this.projectList = queryProjectList() }, watch: { project: function () { this.$emit('value-change', this.project) } }, methods: { queryService (query) { this.projectList = queryProjectList(query) }, change (value) { this.project = value this.projectList = queryProjectList() } } } </script>主页代码
<template> <div> <div> <h1> v0.4版本</h1> </div> <hr> <el-row> <NavSelect></NavSelect> <LocalSelect @value-change="setValue"></LocalSelect> <div> 当前页面的项目为: {{ project}} </div> </el-row> </div> </template> <script> import LocalSelect from '@/components/LocalSelectV0.4' import NavSelect from '@/components/NavSelect' import store from '@/store' export default { components: { NavSelect, LocalSelect }, data () { return { project: '' } }, created () { store.dispatch('Init') }, methods: { setValue (val) { this.project = val } } } </script>最难的问题解决了.
此时,我们继续做开发,发现有个不爽的地方,就是我们手动输入部分字母,点击搜索或者单击鼠标的时候,希望搜索的为匹配的第一个。
但是这个时候确发现,搜索框又变回了之前的。 为此,我们需要优化blur函数,让失去焦点后,能选择第一个匹配的。
ps:default-first-option只能解决,敲击回车之后选择第一个,不能解决鼠标点击的问题
组件代码:
<template> <div> <span v-show="header">服务名称:</span> <el-select v-model="project" :remote-method="queryService" default-first-option clearable filterable remote placeholder="请选择筛选服务" @blur="blur" @change="change" @clear="clear"> <el-option v-for="item in projectList" :key="item.value" :label="item.label" :value="item.value"/> </el-select> </div> </template> <script> import {queryProjectList} from '@/utils/query' export default { name: 'LocalSelect', props: { header: { type: Boolean, default: true } }, data: function () { return { projectList: [], project: '' } }, watch: { project (val) { this.$emit('value-change', val) } }, created () { this.projectList = queryProjectList() }, methods: { queryService (query) { this.projectList = queryProjectList(query) }, blur ($event) { if (this.projectList.length !== 0) { this.project = this.projectList[0].value } else { this.project = '' } this.projectList = queryProjectList() }, change (value) { this.project = value this.projectList = queryProjectList() }, clear () { this.project = '' this.projectList = queryProjectList() } } } </script>主页代码:
<template> <div> <div> <h1> v0.5版本</h1> </div> <hr> <el-row> <NavSelect></NavSelect> <LocalSelect @value-change="setValue"></LocalSelect> <div> 当前页面的项目为: {{ project}} </div> </el-row> </div> </template> <script> import LocalSelect from '@/components/LocalSelectV0.5' import NavSelect from '@/components/NavSelect' import store from '@/store' export default { components: { NavSelect, LocalSelect }, data () { return { project: '' } }, created () { store.dispatch('Init') }, methods: { setValue (val) { this.project = val } } } </script>我们希望完成默认项目功能。
增加一个全局组件要使用cookie存储模式项目,这样下次访问才不会失效。默认项目要使用vuex存储一份,这样有任何变化的时候,local compent才能监听到,才能发生变化全局组件代码:
<template> <el-select v-model="project" :remote-method="queryService" default-first-option clearable filterable remote placeholder="请选择默认服务" @blur="blur" @change="change" @clear="clear"> <el-option v-for="item in projectList" :key="item.value" :label="item.label" :value="item.value"/> </el-select> </template> <script> import { queryProjectList } from '@/utils/query' import store from '@/store' export default { name: 'GlobalSelect', data () { return { project: '', projectList: [] } }, created () { this.projectList = queryProjectList() this.project = this.$store.state.project }, methods: { queryService (query) { this.projectList = queryProjectList(query) }, blur ($event) { if (this.projectList.length !== 0) { this.project = this.projectList[0].value this.change(this.project) } else { this.project = '' this.clear() } this.projectList = queryProjectList() }, change (value) { store.commit('SET_PROJECT', this.project) this.projectList = queryProjectList() }, clear () { store.commit('CLR_PROJECT') this.project = '' this.projectList = queryProjectList() } } } </script> <style scoped> </style>局部组件
<template> <div> <span v-show="header">服务名称:</span> <el-select v-model="project" :remote-method="queryService" default-first-option clearable filterable remote placeholder="请选择筛选服务" @blur="blur" @change="change" @clear="clear"> <el-option v-for="item in projectList" :key="item.value" :label="item.label" :value="item.value"/> </el-select> </div> </template> <script> import {queryProjectList} from '@/utils/query' import store from '@/store' export default { name: 'LocalSelect', props: { header: { type: Boolean, default: true } }, data: function () { return { projectList: [], project: '' } }, watch: { project (val) { this.$emit('value-change', val) }, '$store.state.project': { handler: function (newer, older) { this.project = newer } } }, created () { this.projectList = queryProjectList() this.project = store.state.project }, methods: { queryService (query) { this.projectList = queryProjectList(query) }, blur ($event) { if (this.projectList.length !== 0) { this.change(this.projectList[0].value) } else { this.clear() } }, change (value) { this.project = value this.projectList = queryProjectList() }, clear () { this.project = '' this.projectList = queryProjectList() } } } </script>主页:
<template> <div> <div> <h1> v0.6版本</h1> </div> <hr> <el-row> <NavSelect></NavSelect> 全局服务: <GlobalSelect></GlobalSelect> <LocalSelect @value-change="setValue"></LocalSelect> <div> 当前页面的项目为: {{ project}} </div> </el-row> </div> </template> <script> import GlobalSelect from '@/components/GlobalSelect' import NavSelect from '@/components/NavSelect' import LocalSelect from '@/components/LocalSelectV0.6' import store from '@/store' export default { components: { GlobalSelect, LocalSelect, NavSelect }, data () { return { project: '' } }, created () { store.dispatch('Init') }, methods: { setValue (val) { this.project = val } } } </script>