使用vue和element组件交互----关联选择查询 (双向绑定)

vue + element-ui(el-select)双向绑定

需求:

 为了方便管理后台的操作,用户一般会有个默认项目.  
 用户可以减少切换页面,每次都都要手动选择项目。 
 同时,查询页面,需要查看别的项目的信息,但是不能影响默认项目
  1. 页面头部有一个默认项目搜索提示框,选择默认项目
  2. 有筛选功能的页面,页面内部还有筛选搜索框。
  3. 用户登陆,自动选择默认项目 (存储在cookie中)
  4. 用户选择默认项目,新打开任何页面,都会选择默认项目
  5. 用户改变默认项目,则该页面搜索项目需要改变。
  6. 单个页面,修改过滤项目,默认项目不变。

整体git地址:https://github.com/destinym/chainedSelect

V0.1版本 (基础版本)

参考element-ui的文档,我们快速做一个搜索选择框。

v0.1.2018-10-28 21_52_37.gif

只有一个主页面

主页面代码:

<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>

问题,如果大部分页面都要使用怎么办?
所以,我们必须写成组件的形式,方便重用

V0.2版本 (组件--错误)

我们修改代码为组件代码。看似很简单。
但是却发现一个严重的问题,我们选择的结果和搜索的结果完全不同。

v0.2.2018-10-28 21_54_01.gif

组件代码:

<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

V0.3版本 (组件--错误)

思路
参考了上面文章,大家的基本思路一致

  • 组件使用props属性来project
  • 增加一个变量p_project,使得p_project=project,接收原始数据
  • 组件内部数据变化,p_project发送变化。
  • 监听p_project,通过emit通知父组件
v0.3.2018-10-28 21_55_33.gif

组件代码:

<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事件的时候,必然会报错。

V0.4版本 (组件--可运行)

思考+新思路。
山重水复疑无路,柳暗花明又一村。
经过几次尝试,我们整理下思路:

  • 组件内数据修改页面数据,必须通过emit.
  • props的值不能直接修改。

所以,我们还是想使用el-select。那么我们必须使用data来存储project。 同时建立监听器,如果data变化,再通知主页面。

v0.4.2018-10-28 21_56_08.gif

组件代码

<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>

最难的问题解决了.

V0.5版本(组件--优化)

此时,我们继续做开发,发现有个不爽的地方,就是我们手动输入部分字母,点击搜索或者单击鼠标的时候,希望搜索的为匹配的第一个。

但是这个时候确发现,搜索框又变回了之前的。 为此,我们需要优化blur函数,让失去焦点后,能选择第一个匹配的。

ps:default-first-option只能解决,敲击回车之后选择第一个,不能解决鼠标点击的问题

v0.5.2018-10-28 21_57_12.gif

组件代码:

<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>

V0.6版本 (完整版本)

我们希望完成默认项目功能。

  • 增加一个全局组件
  • 要使用cookie存储模式项目,这样下次访问才不会失效。
  • 默认项目要使用vuex存储一份,这样有任何变化的时候,local compent才能监听到,才能发生变化
v0.6.2018-10-28 21_59_59.gif

全局组件代码:

<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>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343