uniapp自定义picker城市多级联动组件

  • 支持多端——h5、app、微信小程序、支付宝小程序...
  • 支持自定义配置picker插件级数
  • 支持无限级

注意事项:插件传入数据格式为children树形格式,内部包含:id、name

参数 类型 描述 默认值 必选
title string 标题 ''
layer number 控制几级联动 1
data arr 数据 如:[{text: '', adcode: '', children: [{text: '', adcode: ''}]}] []

组件运行图示:

组件选择后返回数据如:

引用示例:

  1. <template>
  2. <view class="content">
  3. <view class="aui-content" :style="{height: contentHeight}">
  4. <view class="aui-btn aui-btn-blue" @click.stop="showPicker($event)">picker无限级联动</view>
  5. </view>
  6. <aui-picker
  7. ref="picker"
  8. :title="auiPicker.title"
  9. :layer="auiPicker.layer"
  10. :data="auiPicker.data"
  11. @callback="pickerCallback"
  12. ></aui-picker>
  13. </view>
  14. </template>
  15. <script>
  16. import auiPicker from '@/components/aui-picker/aui-picker.vue';
  17. export default {
  18. components: {
  19. auiPicker
  20. },
  21. data() {
  22. return {
  23. auiPicker: {
  24. title: 'picker多级联动',
  25. layer: null,
  26. data: []
  27. },
  28. }
  29. },
  30. created(){
  31. },
  32. mounted() {
  33. },
  34. methods: {
  35. //显示picker多级联动弹窗
  36. showPicker(e){
  37. const _this = this;
  38. _this.auiPicker.data=[{
  39. id: "1001",
  40. name: "一级菜单1",
  41. children: [{
  42. id: "1002",
  43. name: "二级菜单1-1",
  44. children: [{
  45. id: "1003",
  46. name: "三级菜单1-1",
  47. children: [{
  48. id: "1004",
  49. name: "四级菜单1-1"
  50. }]
  51. }]
  52. }]
  53. },
  54. {
  55. id: "1005",
  56. name: "一级菜单2",
  57. children: [{
  58. id: "1006",
  59. name: "二级菜单2-1",
  60. children: [{
  61. id: "1007",
  62. name: "三级菜单2-1",
  63. children: [{
  64. id: "1008",
  65. name: "四级菜单2-1"
  66. }]
  67. }]
  68. }]
  69. }];
  70. _this.$refs.picker.open().then(function(){
  71. console.log('picker打开');
  72. });
  73. },
  74. //picker多级联动回调
  75. pickerCallback(e){
  76. const _this = this;
  77. console.log(e);
  78. let result = '';
  79. e.data.forEach(function(item, index){
  80. result += item.name + ' ';
  81. });
  82. uni.showModal({
  83. title: '提示',
  84. content: result,
  85. success: function (res) {
  86. if (res.confirm) {
  87. console.log('用户点击确定');
  88. } else if (res.cancel) {
  89. console.log('用户点击取消');
  90. }
  91. }
  92. });
  93. }
  94. }
  95. }
  96. </script>
  97. <style>
  98. .aui-content{padding: 15px 0 0 0;}
  99. </style>

aui-picker组件完整代码:

项目components文件夹下创建aui-picker夹,此文件夹下创建aui-picker.vue——多级联动组件

  1. <template name="aui-picker">
  2. <view class="aui-picker" v-if="SHOW" :class="{
  3. 'aui-picker-in': FADE==1,
  4. 'aui-picker-out': FADE==0}"
  5. >
  6. <view class="aui-mask" @click.stop="close"></view>
  7. <view class="aui-picker-main">
  8. <view class="aui-picker-header">
  9. <view class="aui-picker-title" v-if="title">{{title}}</view>
  10. <view class="aui-picker-close iconfont iconclose" @click.stop="close"></view>
  11. </view>
  12. <view class="aui-picker-nav">
  13. <view class="aui-picker-navitem"
  14. v-if="nav.length>0"
  15. v-for="(item, index) in nav"
  16. :key="index"
  17. :data-index="index"
  18. :class="[index==navCurrentIndex ? 'active' : '', 'aui-picker-navitem-'+index]"
  19. :style="{margin: nav.length>2 ? '0 10px 0 0' : '0 30px 0 0'}"
  20. @click.stop="_changeNav($event)"
  21. >{{item.name}}</view>
  22. <view class="aui-picker-navitem"
  23. :key="nav.length"
  24. :data-index="nav.length"
  25. :class="[nav.length==navCurrentIndex ? 'active' : '', 'aui-picker-navitem-'+nav.length]"
  26. :style="{margin: nav.length>2 ? '0 10px 0 0' : '0 30px 0 0'}"
  27. @click.stop="_changeNav($event)"
  28. >请选择</view>
  29. <view class="aui-picker-navborder" :style="{left: navBorderLeft+'px'}"></view>
  30. </view>
  31. <view class="aui-picker-content">
  32. <view class="aui-picker-lists">
  33. <view class="aui-picker-list"
  34. v-for="(list, index) in queryItems.length + 1"
  35. :key="index"
  36. :data-index="index"
  37. :class="[index==navCurrentIndex ? 'active' : '']"
  38. >
  39. <view class="aui-picker-list-warp" v-if="index == 0">
  40. <view class="aui-picker-item"
  41. v-for="(item, key) in items"
  42. v-if="item.pid=='0'"
  43. :key="key"
  44. :data-pindex="index"
  45. :data-index="key"
  46. :data-id="item.id"
  47. :data-pid="item.pid"
  48. :data-name="item.name"
  49. :class="{'active': result.length>index && result[index].id==item.id}"
  50. :style="{'background': touchConfig.index==key && touchConfig.pindex==index ? touchConfig.style.background : ''}"
  51. @click.stop="_chooseItem($event)"
  52. @touchstart="_btnTouchStart($event)"
  53. @touchmove="_btnTouchEnd($event)"
  54. @touchend="_btnTouchEnd($event)"
  55. >{{item.name}}</view>
  56. </view>
  57. <view class="aui-picker-list-warp" v-else>
  58. <view class="aui-picker-item"
  59. v-for="(item, key) in queryItems[index-1]"
  60. :key="key"
  61. :data-pindex="index"
  62. :data-index="key"
  63. :data-id="item.id"
  64. :data-pid="item.pid"
  65. :data-name="item.name"
  66. :class="{'active': result.length>index && result[index].id==item.id}"
  67. :style="{'background': touchConfig.index==key && touchConfig.pindex==index ? touchConfig.style.background : ''}"
  68. @click.stop="_chooseItem($event)"
  69. @touchstart="_btnTouchStart($event)"
  70. @touchmove="_btnTouchEnd($event)"
  71. @touchend="_btnTouchEnd($event)"
  72. >{{item.name}}</view>
  73. </view>
  74. </view>
  75. </view>
  76. </view>
  77. </view>
  78. </view>
  79. </template>
  80. <script>
  81. export default {
  82. name: 'aui-picker',
  83. props: {
  84. title: { //标题
  85. type: String,
  86. default: ''
  87. },
  88. layer: { //控制几级联动,默认无限级(跟随数据有无下级)
  89. type: Number,
  90. default: null
  91. },
  92. data: { //数据 如:[{id: '', name: '', children: [{id: '', name: ''}]}]
  93. type: Array,
  94. default (){
  95. return [
  96. // [{id: '', name: '', children: [{id: '', name: ''}]}]
  97. ]
  98. }
  99. }
  100. },
  101. data(){
  102. return {
  103. SHOW: false,
  104. FADE: -1,
  105. nav: [],
  106. items: [],
  107. queryItems: [],
  108. navCurrentIndex: 0,
  109. navBorderLeft: 40,
  110. result: [],
  111. touchConfig: {
  112. index: -1,
  113. pindex: -1,
  114. style: {
  115. color: '#197DE0',
  116. background: '#EFEFEF'
  117. }
  118. }
  119. }
  120. },
  121. created(){
  122. const _this = this;
  123. },
  124. watch:{
  125. data(){
  126. const _this = this;
  127. const data = _this.data;
  128. _this.items = _this._flatten(data, '0')
  129.     }  
  130.   },
  131. mounted(){
  132. },
  133. methods:{
  134. // 打开
  135. open(){
  136. const _this = this;
  137. _this.reset(); //打开时重置picker
  138. return new Promise(function(resolve, reject){
  139. _this.SHOW = true;
  140. _this.FADE = 1;
  141. resolve();
  142. });
  143. },
  144. // 关闭
  145. close(){
  146. const _this = this;
  147. return new Promise(function(resolve, reject){
  148. _this.FADE = 0;
  149. const _hidetimer = setTimeout(()=>{
  150. _this.SHOW = false;
  151. _this.FADE = -1;
  152. clearTimeout(_hidetimer);
  153. resolve();
  154. },100)
  155. });
  156. },
  157. //重置
  158. reset(){
  159. const _this = this;
  160. _this.queryItems = [];
  161. _this.nav = [];
  162. _this.navBorderLeft = 40;
  163. _this.navCurrentIndex = 0;
  164. _this.result = [];
  165. },
  166. //导航栏切换
  167. _changeNav(e){
  168. const _this = this;
  169. const index = Number(e.currentTarget.dataset.index);
  170. _this.navCurrentIndex = index;
  171. const _el = uni.createSelectorQuery().in(this).select(".aui-picker-navitem-"+index);
  172. _el.boundingClientRect(data => {
  173. _this.navBorderLeft = data.left + 20;
  174. }).exec();
  175. },
  176. //数据选择
  177. _chooseItem(e){
  178. const _this = this;
  179. const id = e.currentTarget.dataset.id;
  180. const name = e.currentTarget.dataset.name;
  181. const pid = e.currentTarget.dataset.pid;
  182. const _arr = [];
  183. _this.result[_this.navCurrentIndex] = {id: id, name: name, pid: pid};
  184. if(
  185. (!_this._isDefine(_this.layer) && _this._isDefine(_this._deepQuery(_this.data, id).children))
  186. ||
  187. (_this.navCurrentIndex < (Number(_this.layer) - 1) && _this._isDefine(_this._deepQuery(_this.data, id).children))
  188. )
  189. { //有下级数据
  190. _this._deepQuery(_this.data, id).children.forEach(function(item, index){
  191. _arr.push({id: item.id, name: item.name, pid: id});
  192. });
  193. if(_this.navCurrentIndex == _this.queryItems.length)
  194. { //选择数据
  195. _this.queryItems.push(_arr);
  196. _this.nav.push({name: name});
  197. }
  198. else
  199. { //重新选择数据
  200. _this.queryItems.splice(_this.navCurrentIndex+1, 1);
  201. _this.nav.splice(_this.navCurrentIndex+1, 1);
  202. _this.queryItems.splice(_this.navCurrentIndex, 1, _arr);
  203. _this.nav.splice(_this.navCurrentIndex, 1, {name: name});
  204. }
  205. _this.navCurrentIndex = _this.navCurrentIndex + 1;
  206. const _el = uni.createSelectorQuery().in(this).select(".aui-picker-navitem-"+_this.navCurrentIndex);
  207. setTimeout(()=>{
  208. _el.boundingClientRect(data => {
  209. _this.navBorderLeft = data.left + 20;
  210. }).exec();
  211. },100)
  212. }
  213. else
  214. { //无下级数据
  215. _this.close().then(()=>{
  216. _this.$emit("callback", {status: 0, data: _this.result});
  217. });
  218. }
  219. },
  220. //递归遍历——将树形结构数据转化为数组格式
  221. _flatten(tree, pid) {
  222. return tree.reduce((arr, {id, name, children = []}) =>
  223. arr.concat([{id, name, pid}], this._flatten(children, id)), [])
  224. },
  225. //根据id查询对应的数据(如查询id=10100对应的对象)
  226. _deepQuery(tree, id) {
  227. let isGet = false;
  228. let retNode = null;
  229. function deepSearch(tree, id){
  230. for(let i = 0; i < tree.length; i++) {
  231. if(tree[i].children && tree[i].children.length > 0) {
  232. deepSearch(tree[i].children, id);
  233. }
  234. if(id === tree[i].id || isGet) {
  235. isGet||(retNode = tree[i]);
  236. isGet = true;
  237. break;
  238. }
  239. }
  240. }
  241. deepSearch(tree, id);
  242. return retNode;
  243. },
  244. /***判断字符串是否为空
  245. @param {string} str 变量
  246. @example: aui.isDefine("变量");
  247. */
  248. _isDefine(str){
  249. if (str==null || str=="" || str=="undefined" || str==undefined || str=="null" || str=="(null)" || str=='NULL' || typeof (str)=='undefined'){
  250. return false;
  251. }else{
  252. str = str + "";
  253. str = str.replace(/\s/g, "");
  254. if (str == ""){return false;}
  255. return true;
  256. }
  257. },
  258. _btnTouchStart(e){
  259. const _this = this,
  260. index = Number(e.currentTarget.dataset.index),
  261. pindex = Number(e.currentTarget.dataset.pindex);
  262. _this.touchConfig.index = index;
  263. _this.touchConfig.pindex = pindex;
  264. },
  265. _btnTouchEnd(e){
  266. const _this = this,
  267. index = Number(e.currentTarget.dataset.index),
  268. pindex = Number(e.currentTarget.dataset.pindex);
  269. _this.touchConfig.index = -1;
  270. _this.touchConfig.pindex = -1;
  271. },
  272. }
  273. }
  274. </script>
  275. <style scoped>
  276. /* ====================
  277. 多级联动弹窗
  278. =====================*/
  279. .aui-picker{
  280. width: 100vw;
  281. height: 100vh;
  282. opacity: 0;
  283. position: fixed;
  284. top: 0;
  285. left: 0;
  286. z-index: 999;
  287. /* display: none; */
  288. }
  289. .aui-picker.aui-picker-in{
  290. -moz-animation: aui-fade-in .1s ease-out forwards;
  291. -ms-animation: aui-fade-in .1s ease-out forwards;
  292. -webkit-animation: aui-fade-in .1s ease-out forwards;
  293. animation: aui-fade-in .1s ease-out forwards;
  294. }
  295. .aui-picker.aui-picker-out{
  296. -moz-animation: aui-fade-out .1s ease-out forwards;
  297. -ms-animation: aui-fade-out .1s ease-out forwards;
  298. -webkit-animation: aui-fade-out .1s ease-out forwards;
  299. animation: aui-fade-out .1s ease-out forwards;
  300. }
  301. .aui-picker-main{
  302. width: 100vw;
  303. height: 50vh;
  304. background: #FFF;
  305. border-radius: 15px 15px 0 0;
  306. position: absolute;
  307. left: 0px;
  308. bottom: -50vh;
  309. z-index: 999;
  310. }
  311. .aui-picker.aui-picker-in .aui-picker-main{
  312. -moz-animation: aui-slide-up-screen .2s ease-out forwards;
  313. -ms-animation: aui-slide-up-screen .2s ease-out forwards;
  314. -webkit-animation: aui-slide-up-screen .2s ease-out forwards;
  315. animation: aui-slide-up-screen .2s ease-out forwards;
  316. }
  317. .aui-picker.aui-picker-out .aui-picker-main{
  318. -moz-animation: aui-slide-down-screen .2s ease-out forwards;
  319. -ms-animation: aui-slide-down-screen .2s ease-out forwards;
  320. -webkit-animation: aui-slide-down-screen .2s ease-out forwards;
  321. animation: aui-slide-down-screen .2s ease-out forwards;
  322. }
  323. .aui-picker-header{
  324. width: 100%;
  325. min-height: 50px;
  326. position: relative;
  327. z-index: 999;
  328. background: #F2F2F2;
  329. border-radius: 15px 15px 0 0;
  330. }
  331. .aui-picker-header::after{
  332. content: '';
  333. width: 100%;
  334. height: 1px;
  335. background: rgba(100,100,100,.3);
  336. -moz-transform: scaleY(.3);
  337. -ms-transform: scaleY(.3);
  338. -webkit-transform: scaleY(.3);
  339. transform: scaleY(.3);
  340. position: absolute;
  341. left: 0;
  342. bottom: 0;
  343. z-index: 999;
  344. }
  345. .aui-picker-title{
  346. line-height: 20px;
  347. text-align: center;
  348. font-size: 17px;
  349. color: #333;
  350. padding: 15px;
  351. box-sizing: border-box;
  352. position: absolute;
  353. left: 50px;
  354. right: 50px;
  355. top: 0;
  356. }
  357. .aui-picker-close.iconfont{
  358. width: 50px;
  359. height: 50px;
  360. line-height: 50px;
  361. text-align: center;
  362. font-size: 20px;
  363. color: #aaa;
  364. border-radius: 0 10px 0 0;
  365. position: absolute;
  366. right: 0;
  367. top: 0;
  368. }
  369. .aui-picker-content{
  370. width: 100%;
  371. height: -webkit-calc(100% - 100px);
  372. height: calc(100% - 100px);
  373. }
  374. .aui-picker-nav{
  375. width: 100%;
  376. height: 50px;
  377. text-align: left;
  378. padding: 0 20px;
  379. margin: 0 0 1px 0;
  380. justify-content: flex-start;
  381. white-space: nowrap;
  382. box-sizing: border-box;
  383. position: relative;
  384. }
  385. .aui-picker-nav::after{
  386. content: '';
  387. width: 100%;
  388. height: 1px;
  389. background: rgba(100,100,100,.3);
  390. -moz-transform: scaleY(.3);
  391. -ms-transform: scaleY(.3);
  392. -webkit-transform: scaleY(.3);
  393. transform: scaleY(.3);
  394. position: absolute;
  395. left: 0;
  396. bottom: 0;
  397. z-index: 999;
  398. }
  399. .aui-picker-navitem{
  400. width: 80px;
  401. line-height: 50px;
  402. font-size: 16px;
  403. margin: 0 30px 0 0;
  404. text-align: center;
  405. display: inline-block;
  406. overflow: hidden;
  407. white-space: nowrap;
  408. text-overflow: ellipsis;
  409. }
  410. .aui-picker-navitem.active{
  411. color: #197DE0;
  412. }
  413. .aui-picker-navborder{
  414. width: 40px;
  415. height: 3px;
  416. background: #197DE0;
  417. border-radius: 5px;
  418. transition: left .15s;
  419. position: absolute;
  420. left: 40px;
  421. bottom: 0;
  422. }
  423. .aui-picker-lists{
  424. width: 100%;
  425. height: 100%;
  426. justify-content: space-around;
  427. white-space: nowrap;
  428. }
  429. .aui-picker-list{
  430. width: 100%;
  431. height: 100%;
  432. overflow: hidden;
  433. overflow-y: scroll;
  434. display: none;
  435. vertical-align: top;
  436. }
  437. .aui-picker-list.active{
  438. display: inline-block;
  439. }
  440. .aui-picker-list-warp{
  441. width: 100%;
  442. height: auto;
  443. box-sizing: border-box;
  444. padding: 15px 0;
  445. display: inline-block;
  446. }
  447. .aui-picker-item{
  448. width: 100%;
  449. height: 50px;
  450. line-height: 50px;
  451. padding: 0 15px;
  452. box-sizing: border-box;
  453. font-size: 15px;
  454. color: #333;
  455. position: relative;
  456. }
  457. .aui-picker-item.active{
  458. color: #197DE0;
  459. }
  460. .aui-picker-item.active::after{
  461. content: '';
  462. font-size: 15px;
  463. color: #197DE0;
  464. position: absolute;
  465. top: 0px;
  466. right: 10px;
  467. }
  468. </style>

uniapp自定义picker城市多级联动组件的更多相关文章

  1. vue城市三级联动组件 vue-area-linkage

    Install the pkg with npm: // v5之前的版本 npm i --save vue-area-linkage // v5及之后的版本 npm i --save vue-area ...

  2. mpvue + 微信小程序 picker 实现自定义多级联动 超简洁

    微信小程序官网只提供了省市区的三级联动,实际开发中更多的是自定义的多级联动: 依照微信小程序官网提供的自定义多级联动,需要使用到picker 的多列选择器,即设置 mode = multiSelect ...

  3. 基于uniapp自定义Navbar+Tabbar组件「兼容H5+小程序+App端Nvue」

    uni-app跨端自定义navbar+tabbar组件|沉浸式导航条|仿咸鱼凸起标签栏 在跨端项目开发中,uniapp是个不错的框架.采用vue.js和小程序语法结构,使得入门开发更容易.拥有非常丰富 ...

  4. uni-app自定义Modal弹窗组件|仿ios、微信弹窗效果

    介绍 uniapp自定义弹窗组件uniPop,基于uni-app开发的自定义模态弹窗|msg信息框|alert对话框|confirm确认框|toast弱提示框 支持多种动画效果.多弹窗类型ios/an ...

  5. 微信小程序-多级联动

    微信小程序中的多级联动 这里用到的案例是城市选择器 先上代码: .wxml <view class="{{boxHide}}"> <view>{{nian} ...

  6. js 多级联动(省、市、区)

      js 多级联动(省.市.区) CreateTime--2018年4月9日17:10:38 Author:Marydon 方式一: 数据从数据库获取,ajax实现局部刷新 方式二: 数据从json文 ...

  7. jQuery制作简洁的多级联动Select下拉框

    今天我们要来分享一款很实用的jQuery插件,它是一个基于jQuery多级联动的省市地区Select下拉框,并且值得一提的是,这款联动下拉框是经过自定义美化过的,外观比浏览器自带的要漂亮许多.另外,这 ...

  8. vue移动端地址三级联动组件(一)

    vue移动端地区三级联动 省,市,县.用的vue+mintUi 因为多级联动以及地区的规则比较多.正好有时间自己写了一个.有问题以及建议欢迎指出.涉及到dom移动,所以依赖vue+jquery.这边数 ...

  9. C/C++ Qt 数据库与ComBox多级联动

    Qt中的SQL数据库组件可以与ComBox组件形成多级联动效果,在日常开发中多级联动效果应用非常广泛,例如当我们选择指定用户时,我们让其在另一个ComBox组件中列举出该用户所维护的主机列表,又或者当 ...

随机推荐

  1. 循序渐进VUE+Element 前端应用开发(23)--- 基于ABP实现前后端的附件上传,图片或者附件展示管理

    在我们一般系统中,往往都会涉及到附件的处理,有时候附件是图片文件,有时候是Excel.Word等文件,一般也就是可以分为图片附件和其他附件了,图片附件可以进行裁剪管理.多个图片上传管理,及图片预览操作 ...

  2. Linux下如何使用X86 CPU的GPIO

    目录 1.前言 2.linux pinctrl子系统 3. pin controller driver 4.手动构造device 1.前言 在arm嵌入式开发中,各个外设具有固定的物理地址,我们可以直 ...

  3. 【故障公告】访问高峰数据库服务器 CPU 100% 引发全站故障

    今天上午11:10,我们又中"奖"了,我们使用的阿里云 RDS 实例(SQL Server 2016 标准版,16核32G)突发出现 CPU 100%,引发全站故障,直到 12:1 ...

  4. windows搭建ftp

    控制面板                此时输入电脑用户名和密码可在自己电脑访问,但是其它电脑不能访问 接下来防火墙允许的应用将FTP服务器打钩 控制面板\系统和安全\Windows Defender ...

  5. WC2019 自闭记

    不咕了 Day 1 2019/1/24 辣么快就到冬令营了,还沉迷于被柿子吊打的状态的菜鸡一时半会还反应不过来.我们学校这次分头去的冬令营,差点上不了车.这次做的动车居然直达广州,强啊. 然鹅还是到太 ...

  6. 说说 C# 9 新特性的实际运用

    你一定会好奇:"老周,你去哪开飞机了?这么久没写博客了." 老周:"我买不起飞机,开了个铁矿,挖了一年半的石头.谁知铁矿垮了,压死了几条蜈蚣,什么也没挖着." ...

  7. 【SpringCloud】07.应用间的通信

    应用间通信 HTTP vs RPC Spring Cloud (HTTP) Dubbo (RPC) 1.SpringCloud中服务间两种restful调用方式 RestTemplate Feign ...

  8. php之cms后台文章管理及显示

    public function index(){ C('TOKEN_ON',false);//关闭表单令牌 读取配置 //查询指定id的栏目信息 $id=I('get.id');//类别ID $top ...

  9. 剑指Offer-Python(6-10)

    6.旋转数组的最小数字 class Solution: def minNumberInRotateArray(self, rotateArray): l = len(rotateArray) if l ...

  10. javaweb项目中jsp的from表单提交action内容与web.xml的servlet-mapping对应

    login.jsp <%@ page contentType="text/html;charset=UTF-8" language="java" %> ...