tui-picker.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. <template>
  2. <view class="tui-picker__box">
  3. <view class="tui-mask__screen" :style="{zIndex}" :class="[visible?'tui-picker__mask-show':'']" @tap="maskClick">
  4. </view>
  5. <view class="tui-picker__wrap" :style="{backgroundColor:backgroundColor,zIndex:getIndex}"
  6. :class="[visible?'tui-picker__show':'',radius?'tui-picker__radius':'']">
  7. <view class="tui-picker__header" :style="{backgroundColor:headerBgColor}">
  8. <view class="tui-picker__btn-cancle" hover-class="tui-picker__opcity" :hover-stay-time="150"
  9. @tap.stop="hidePicker"
  10. :style="{color:cancelColor,fontSize:btnSize+'rpx',fontWeight:bold?'bold':'normal'}">{{cancelText}}
  11. </view>
  12. <view class="tui-picker__title" :style="{fontSize:titleSize+'rpx',color:titleColor}">{{title}}</view>
  13. <view class="tui-picker__btn-sure" hover-class="tui-picker__opcity" :hover-stay-time="150"
  14. @tap.stop="picker"
  15. :style="{color:getConfirmColor,fontSize:btnSize+'rpx',fontWeight:bold?'bold':'normal'}">
  16. {{confirmText}}
  17. </view>
  18. </view>
  19. <view @touchstart.stop="pickstart">
  20. <picker-view :key="maskStyle+layer" :mask-style="maskStyle" :indicator-style="indicatorStyle"
  21. class="tui-picker__view" :value="vals" :immediate-change="immediate" @change="columnPicker"
  22. @pickend="pickend">
  23. <picker-view-column>
  24. <view :style="{color:color,fontSize:size+'px'}" v-for="(item,index) in layer1__data"
  25. :key="index" class="tui-picker__item">{{item}}</view>
  26. </picker-view-column>
  27. <picker-view-column v-if="layer==2 || layer==3">
  28. <view :style="{color:color,fontSize:size+'px'}" v-for="(item,index) in layer2__data"
  29. :key="index" class="tui-picker__item">{{item}}</view>
  30. </picker-view-column>
  31. <picker-view-column v-if="layer==3">
  32. <view :style="{color:color,fontSize:size+'px'}" v-for="(item,index) in layer3__data"
  33. :key="index" class="tui-picker__item">{{item}}</view>
  34. </picker-view-column>
  35. </picker-view>
  36. </view>
  37. </view>
  38. </view>
  39. </template>
  40. <script>
  41. export default {
  42. name: "tui-picker",
  43. emits: ['pickstart', 'pickend', 'hide', 'change'],
  44. props: {
  45. //数据层级
  46. layer: {
  47. type: [Number, String],
  48. default: 1
  49. },
  50. //data数据
  51. pickerData: {
  52. type: Array,
  53. default () {
  54. return []
  55. }
  56. },
  57. textField: {
  58. type: String,
  59. default: 'text'
  60. },
  61. valueField: {
  62. type: String,
  63. default: 'value'
  64. },
  65. childrenField: {
  66. type: String,
  67. default: 'children'
  68. },
  69. //是否显示
  70. show: {
  71. type: Boolean,
  72. default: false
  73. },
  74. //默认值,text集合
  75. value: {
  76. type: Array,
  77. default () {
  78. return []
  79. }
  80. },
  81. //设置选择器中间选中框的样式
  82. indicatorStyle: {
  83. type: String,
  84. default: 'height: 48px;'
  85. },
  86. //设置蒙层的样式
  87. maskStyle: {
  88. type: String,
  89. default: ''
  90. },
  91. //是否显示圆角
  92. radius: {
  93. type: Boolean,
  94. default: false
  95. },
  96. //header背景色
  97. headerBgColor: {
  98. type: String,
  99. default: '#fff'
  100. },
  101. //显示标题
  102. title: {
  103. type: String,
  104. default: ''
  105. },
  106. //标题字体大小
  107. titleSize: {
  108. type: [Number, String],
  109. default: 34
  110. },
  111. //标题字体颜色
  112. titleColor: {
  113. type: String,
  114. default: '#333'
  115. },
  116. //确认按钮文本
  117. confirmText: {
  118. type: String,
  119. default: '确定'
  120. },
  121. //确认按钮文本颜色
  122. confirmColor: {
  123. type: String,
  124. default: ''
  125. },
  126. //取消按钮文本
  127. cancelText: {
  128. type: String,
  129. default: '取消'
  130. },
  131. //取消按钮文本颜色
  132. cancelColor: {
  133. type: String,
  134. default: '#888'
  135. },
  136. //按钮字体大小
  137. btnSize: {
  138. type: [Number, String],
  139. default: 32
  140. },
  141. //按钮字体是否加粗
  142. bold: {
  143. type: Boolean,
  144. default: true
  145. },
  146. //内容背景色
  147. backgroundColor: {
  148. type: String,
  149. default: '#fff'
  150. },
  151. //内容字体颜色
  152. color: {
  153. type: String,
  154. default: '#333'
  155. },
  156. //picker内容字体大小 px
  157. size: {
  158. type: [Number, String],
  159. default: 16
  160. },
  161. //点击遮罩 是否可关闭
  162. maskClosable: {
  163. type: Boolean,
  164. default: true
  165. },
  166. //自定义参数
  167. params: {
  168. type: [Number, String],
  169. default: 0
  170. },
  171. zIndex: {
  172. type: [Number, String],
  173. default: 998
  174. }
  175. },
  176. computed: {
  177. getConfirmColor() {
  178. return this.confirmColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc'
  179. },
  180. getIndex() {
  181. return Number(this.zIndex) + 2
  182. }
  183. },
  184. data() {
  185. let immediate = true;
  186. // #ifdef MP-TOUTIAO
  187. immediate = false
  188. // #endif
  189. return {
  190. immediate,
  191. visible: false,
  192. vals: [],
  193. layer1__data: [],
  194. layer2__data: [],
  195. layer3__data: [],
  196. isEnd: true,
  197. firstShow: false
  198. };
  199. },
  200. created() {
  201. this.initData(-1, 0, 0);
  202. this.$nextTick(() => {
  203. setTimeout(() => {
  204. this.setDefaultOptions()
  205. }, 50)
  206. })
  207. this.visible = this.show;
  208. if (this.visible) {
  209. this.firstShow = true
  210. }
  211. },
  212. watch: {
  213. show(val) {
  214. this.visible = val;
  215. if (val) {
  216. setTimeout(() => {
  217. this.firstShow = true
  218. }, 260)
  219. }
  220. },
  221. value(vals) {
  222. if (vals && vals.length > 0) {
  223. setTimeout(() => {
  224. this.setDefaultOptions()
  225. }, 20)
  226. }
  227. },
  228. pickerData(newVal) {
  229. this.initData(-1, 0, 0)
  230. this.$nextTick(() => {
  231. setTimeout(() => {
  232. this.setDefaultOptions()
  233. }, 50)
  234. })
  235. }
  236. },
  237. methods: {
  238. hidePicker() {
  239. this.visible = false;
  240. this.$emit('hide', {
  241. params: this.params
  242. })
  243. },
  244. maskClick() {
  245. if (!this.maskClosable) return;
  246. this.hidePicker()
  247. },
  248. getValue(key = 'text', layer = 1) {
  249. let vals = this.vals;
  250. let data = this.pickerData;
  251. let result = ''
  252. if (layer == 1) {
  253. result = data[vals[0]][key]
  254. } else if (layer == 2) {
  255. if (data[vals[0]][this.childrenField]) {
  256. result = data[vals[0]][this.childrenField][vals[1]][key]
  257. }
  258. } else {
  259. if (data[vals[0]][this.childrenField] && data[vals[0]][this.childrenField][vals[1]][this
  260. .childrenField]) {
  261. result = data[vals[0]][this.childrenField][vals[1]][this.childrenField][vals[2]][key]
  262. }
  263. }
  264. return result;
  265. },
  266. loop(index = 0) {
  267. if (this.isEnd) {
  268. this.pickerChange()
  269. } else {
  270. index++
  271. if (index >= 30) {
  272. this.isEnd = true
  273. }
  274. setTimeout(() => {
  275. this.loop(index)
  276. }, 50)
  277. }
  278. },
  279. picker() {
  280. this.hidePicker()
  281. this.loop()
  282. },
  283. pickerChange() {
  284. let text = [];
  285. let value = [];
  286. let result = '';
  287. if (this.pickerData.length > 0) {
  288. if (this.layer == 1) {
  289. text = this.getValue(this.textField);
  290. value = this.getValue(this.valueField);
  291. result = text;
  292. } else if (this.layer == 2) {
  293. text = [this.getValue(this.textField), this.getValue(this.textField, 2)];
  294. value = [this.getValue(this.valueField), this.getValue(this.valueField, 2)];
  295. result = text.join('');
  296. } else {
  297. text = [this.getValue(this.textField), this.getValue(this.textField, 2), this.getValue(this.textField, 3)];
  298. value = [this.getValue(this.valueField), this.getValue(this.valueField, 2), this.getValue(this
  299. .valueField, 3)];
  300. result = text.join('');
  301. }
  302. }
  303. this.$emit('change', {
  304. [this.textField]: text,
  305. [this.valueField]: value,
  306. index: this.vals,
  307. result: result,
  308. params: this.params
  309. })
  310. },
  311. toArr(data) {
  312. let arr = [];
  313. if (data && data.length > 0) {
  314. for (let item of data) {
  315. arr.push(item[this.textField]);
  316. }
  317. }
  318. return arr;
  319. },
  320. checkChildrenData(data, layer, index, idx) {
  321. let arr = [];
  322. if (layer == 1) {
  323. if (data[index])
  324. arr = data[index][this.childrenField] || [];
  325. } else {
  326. if (data[index] && data[index][this.childrenField] && data[index][this.childrenField][idx])
  327. arr = data[index][this.childrenField][idx][this.childrenField] || [];
  328. }
  329. return arr;
  330. },
  331. setDefaultOptions() {
  332. let textArr = this.value;
  333. let vals = [];
  334. if (this.layer1__data.length > 0 && textArr.length > 0) {
  335. textArr.forEach((item, idx) => {
  336. let index = this[`layer${idx+1}__data`].indexOf(item);
  337. if (idx == 0) {
  338. this.layer2__data = this.toArr(this.checkChildrenData(this.pickerData, 1, index))
  339. } else if (idx == 1) {
  340. this.layer3__data = this.toArr(this.checkChildrenData(this.pickerData, 2, vals[0],
  341. index))
  342. }
  343. if (index == -1) {
  344. vals.push(0)
  345. } else {
  346. vals.push(index)
  347. }
  348. })
  349. } else {
  350. if (this.layer == 1) {
  351. vals = [0]
  352. } else if (this.layer == 2) {
  353. vals = [0, 0];
  354. this.layer2__data = this.toArr(this.checkChildrenData(this.pickerData, 1, 0))
  355. } else {
  356. vals = [0, 0, 0];
  357. this.layer2__data = this.toArr(this.checkChildrenData(this.pickerData, 1, 0))
  358. this.layer3__data = this.toArr(this.checkChildrenData(this.pickerData, 2, 0,
  359. 0))
  360. }
  361. }
  362. if (this.vals.join(',') == vals.join(',')) return;
  363. this.$nextTick(() => {
  364. setTimeout(() => {
  365. this.vals = vals;
  366. }, 200)
  367. })
  368. },
  369. initData(layer, index, idx) {
  370. let data = this.pickerData;
  371. if (!data || data.length == 0) {
  372. this.layer1__data = [];
  373. this.layer2__data = [];
  374. this.layer3__data = [];
  375. return;
  376. }
  377. if (this.layer == 1) {
  378. this.layer1__data = this.toArr(data)
  379. } else if (this.layer == 2) {
  380. if (layer == -1)
  381. this.layer1__data = this.toArr(data)
  382. this.layer2__data = this.toArr(this.checkChildrenData(data, 1, index))
  383. } else {
  384. if (layer == -1)
  385. this.layer1__data = this.toArr(data)
  386. if (layer == 0 || layer == -1)
  387. this.layer2__data = this.toArr(this.checkChildrenData(data, 1, index))
  388. this.layer3__data = this.toArr(this.checkChildrenData(data, 2, index, idx))
  389. }
  390. },
  391. columnPicker: function(e) {
  392. let value = e.detail.value;
  393. if (!this.firstShow || value.length != this.layer) return;
  394. if (this.layer == 1) {
  395. this.layer__one(value)
  396. } else if (this.layer == 2) {
  397. this.layer__two(value)
  398. } else {
  399. this.layer__three(value)
  400. }
  401. this.isEnd = true
  402. },
  403. layer__one(value) {
  404. if (this.vals[0] != value[0]) {
  405. this.vals = value;
  406. }
  407. },
  408. layer__two(value) {
  409. if (this.vals[0] != value[0]) {
  410. this.initData(0, value[0])
  411. this.vals = [value[0], 0]
  412. } else {
  413. this.vals = value
  414. }
  415. },
  416. layer__three(value) {
  417. if (this.vals[0] != value[0]) {
  418. this.initData(0, value[0], 0)
  419. this.vals = [value[0], 0, 0]
  420. } else if (this.vals[1] != value[1]) {
  421. this.initData(0, value[0], value[1])
  422. this.vals = [value[0], value[1], 0]
  423. } else {
  424. this.vals = value
  425. }
  426. },
  427. pickstart(e) {
  428. this.isEnd = false;
  429. this.$emit('pickstart', {
  430. params: this.params
  431. })
  432. },
  433. pickend(e) {
  434. //仅微信小程序支持
  435. this.$emit('pickend', {
  436. params: this.params
  437. })
  438. }
  439. }
  440. }
  441. </script>
  442. <style scoped>
  443. .tui-mask__screen {
  444. position: fixed;
  445. top: 0;
  446. left: 0;
  447. right: 0;
  448. bottom: 0;
  449. background: rgba(0, 0, 0, 0.6);
  450. transition: all 0.3s ease-in-out;
  451. opacity: 0;
  452. visibility: hidden;
  453. }
  454. .tui-picker__mask-show {
  455. opacity: 1;
  456. visibility: visible;
  457. }
  458. .tui-picker__wrap {
  459. width: 100%;
  460. position: fixed;
  461. left: 0;
  462. right: 0;
  463. bottom: 0;
  464. visibility: hidden;
  465. transform: translate3d(0, 100%, 0);
  466. transform-origin: center;
  467. transition: all 0.3s ease-in-out;
  468. min-height: 20rpx;
  469. }
  470. .tui-picker__radius {
  471. border-top-left-radius: 24rpx;
  472. border-top-right-radius: 24rpx;
  473. overflow: hidden;
  474. }
  475. .tui-picker__show {
  476. transform: translate3d(0, 0, 0);
  477. visibility: visible;
  478. }
  479. .tui-picker__header {
  480. width: 100%;
  481. height: 92rpx;
  482. padding: 0 30rpx;
  483. display: flex;
  484. justify-content: space-between;
  485. align-items: center;
  486. box-sizing: border-box;
  487. position: relative;
  488. }
  489. .tui-picker__header::after {
  490. content: '';
  491. position: absolute;
  492. border-bottom: 1rpx solid rgba(0, 0, 0, .1);
  493. -webkit-transform: scaleY(0.5);
  494. transform: scaleY(0.5);
  495. bottom: 0;
  496. right: 0;
  497. left: 0;
  498. }
  499. .tui-picker__btn-cancle {
  500. padding: 20rpx;
  501. flex-shrink: 0;
  502. /* #ifdef H5 */
  503. cursor: pointer;
  504. /* #endif */
  505. }
  506. .tui-picker__btn-sure {
  507. padding: 20rpx;
  508. flex-shrink: 0;
  509. /* #ifdef H5 */
  510. cursor: pointer;
  511. /* #endif */
  512. }
  513. .tui-picker__title {
  514. white-space: nowrap;
  515. overflow: hidden;
  516. text-overflow: ellipsis;
  517. flex: 1;
  518. padding: 0 30rpx;
  519. box-sizing: border-box;
  520. text-align: center;
  521. }
  522. .tui-picker__view {
  523. width: 100%;
  524. height: 260px;
  525. }
  526. .tui-picker__item {
  527. line-height: 48px;
  528. text-align: center;
  529. }
  530. .tui-picker__opcity {
  531. opacity: 0.5;
  532. }
  533. </style>