l-picker.vue 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. <template>
  2. <view class="l-picker" :style="[styles]">
  3. <view class="l-picker__toolbar" v-if="cancelBtn || title || confirmBtn">
  4. <text class="l-picker__cancel" :style="cancelStyle" v-if="cancelBtn" @click="onCancel">{{cancelBtn}}</text>
  5. <text class="l-picker__title" :style="titleStyle" v-if="title">{{title}}</text>
  6. <text class="l-picker__confirm" :style="confirmStyle" v-if="confirmBtn" @click="onConfirm">{{confirmBtn}}</text>
  7. </view>
  8. <slot name="header"></slot>
  9. <view class="l-picker__main" :style="[groupHeight ? { height: groupHeight}: {}]">
  10. <slot>
  11. <l-picker-item v-for="(options, i) in columns" :options="options" :key="i" :column="i" :value="pickerValue.length > i ? pickerValue[i]: null"></l-picker-item>
  12. </slot>
  13. <view class="l-picker__empty" v-if="isEmpty">
  14. <slot name="empty"></slot>
  15. </view>
  16. </view>
  17. <slot name="footer" />
  18. <view class="l-picker__loading" ref="loadingRef" v-if="loading" :style="[loadingMaskColor ? {background: loadingMaskColor}: {}]">
  19. <l-loading :size="loadingSize" :color="loadingColor"></l-loading>
  20. </view>
  21. </view>
  22. </template>
  23. <script lang="ts">
  24. // @ts-nocheck
  25. /**
  26. * Picker 选择器组件
  27. * @description 多列数据选择器,支持级联数据展示和自定义样式配置
  28. * @tutorial https://ext.dcloud.net.cn/plugin?name=lime-picker
  29. *
  30. * @property {string} cancelBtn 取消按钮文字
  31. * @property {string | UTSJSONObject} cancelStyle 取消按钮样式
  32. * @property {string} confirmBtn 确定按钮文字
  33. * @property {string | UTSJSONObject} confirmStyle 确定按钮样式
  34. * @property {string} title 标题文字
  35. * @property {string | UTSJSONObject} titleStyle 标题样式
  36. * @property {UTSJSONObject} keys 字段别名配置(例:{value: 'id', label: 'name'})
  37. * @property {PickerColumn[]} columns 选择器列数据(必填)
  38. * @property {PickerValue[]} modelValue 选中值(支持v-model)
  39. * @property {PickerValue[]} defaultValue 默认选中值
  40. * @property {PickerValue[]} value 选中值(兼容旧版)
  41. * @property {boolean} loading 是否显示加载状态
  42. * @property {string} loadingColor 加载图标颜色
  43. * @property {string} loadingMaskColor 加载遮罩颜色
  44. * @property {string} loadingSize 加载图标尺寸
  45. * @property {string} itemHeight 选项行高度
  46. * @property {string} itemColor 选项文字颜色
  47. * @property {string} itemFontSize 选项字体大小
  48. * @property {string} itemActiveColor 选中项颜色
  49. * @property {string} indicatorStyle 指示器样式
  50. * @property {string} bgColor 背景颜色
  51. * @property {string} groupHeight 选项组高度
  52. * @property {string} radius 圆角半径
  53. * @property {boolean} resetIndex 是否重置选中索引
  54. *
  55. * @event {Function} confirm 点击确定时触发(事件参数:PickerConfirmEvent)
  56. * @event {Function} cancel 点击取消时触发
  57. * @event {Function} change 值变化时触发(事件参数:PickerPickEvent)
  58. * @event {Function} column-change 列数据变化时触发(事件参数:PickerChangeInfo)
  59. */
  60. import type { PickerProps, PickerColumn, PickerValue, PickerColumnItem, PickerConfirmEvent, PickerPickEvent } from './type';
  61. import { defineComponent, computed, ref, watch, onMounted, nextTick, onBeforeUnmount, provide, reactive, toRaw } from '@/uni_modules/lime-shared/vue';
  62. import pickerProps from './props';
  63. export default defineComponent({
  64. name: 'l-picker',
  65. props: pickerProps,
  66. emits: ['change', 'cancel', 'pick','confirm' ,'update:modelValue', 'update:value'],
  67. setup(props, {emit, expose}) {
  68. const pickerItemInstanceArray = ref<LPickerItemComponentPublicInstance[]>([]);
  69. const modelValue = ref<PickerValue[]>(props.value || props.modelValue || props.defaultValue || [])
  70. const pickerValue = computed({
  71. set(value: PickerValue[]) {
  72. if(value.join('') == modelValue.value.join('')) return
  73. modelValue.value = value;
  74. emit('update:modelValue', value)
  75. emit('change', value)
  76. // #ifdef VUE2
  77. emit('input', value)
  78. // #endif
  79. },
  80. get():PickerValue[] {
  81. return props.value || props.modelValue || modelValue.value
  82. }
  83. } as WritableComputedOptions<PickerValue[]>)
  84. const isEmpty = computed(():boolean => {
  85. return props.columns.length == 0 && pickerItemInstanceArray.value.every(child => child.options.length == 0)
  86. })
  87. const styles = computed(()=>{
  88. const style:Record<string, any> = {}
  89. if(props.bgColor) {
  90. style['background'] = props.bgColor!
  91. }
  92. if(props.radius) {
  93. style['border-top-left-radius'] = props.radius!
  94. style['border-top-right-radius'] = props.radius!
  95. }
  96. return style
  97. })
  98. const curIndexArray = ref<number[]>([]);
  99. const curValueArray = ref([...pickerValue.value]);
  100. const curItemArray:PickerColumnItem[] = []
  101. const realColumns = computed(():PickerColumn[] => {
  102. const pickerColumns = pickerItemInstanceArray.value.map((child):PickerColumn => child.options)
  103. if(pickerColumns.length > 0) {
  104. return pickerColumns
  105. }
  106. return props.columns
  107. })
  108. const valueArrayEquals = computed(():boolean => pickerValue.value.join('') == curValueArray.value.join(''))
  109. const manageChildInList = (child: LPickerItemComponentPublicInstance, shouldAdd: boolean) => {
  110. const index = pickerItemInstanceArray.value.indexOf(child);
  111. if(shouldAdd) {
  112. if(index != -1) return
  113. pickerItemInstanceArray.value.push(child)
  114. } else {
  115. if(index == -1) return
  116. pickerItemInstanceArray.value.splice(index, 1);
  117. }
  118. }
  119. const updateItems = (item: PickerColumnItem, index:number, column: number) => {
  120. curIndexArray.value[column] = index
  121. curValueArray.value[column] = item.value
  122. curItemArray[column] = item;
  123. // clearTimeout(timer)
  124. // timer = setTimeout(()=>{
  125. // emit('change', [...curValueArray.value])
  126. // },50)
  127. };
  128. const updatePickerItems = () => {
  129. const _indexs : number[] = []
  130. const _values : any[] = []
  131. pickerItemInstanceArray.value.forEach((child, column)=>{
  132. if(child.options.length == 0) return
  133. const value = curValueArray.value.length > column ? curValueArray.value[column] : null
  134. // #ifdef VUE3
  135. const index = value == null ? 0 : child._.exposed.getIndexByValue(value)
  136. child._.exposed.setIndex(index)
  137. // #endif
  138. // #ifdef VUE2
  139. const index = value == null ? 0 : child.getIndexByValue(value)
  140. child.setIndex(index)
  141. // #endif
  142. const item = child.options[index]
  143. _indexs.push(index)
  144. _values.push(item.value)
  145. // curIndexArray.value[column] = index
  146. // curValueArray.value[column] = item.value
  147. curItemArray[column] = item
  148. // 不能改变单向数据流, 只有值不存在时候才处理
  149. // if(pickerValue.value.length == 0) {
  150. // pickerValue.value = [...curValueArray.value]
  151. // }
  152. // if(pickerValue.value.join('') == curValueArray.value.join('')) return
  153. // pickerValue.value = [...curValueArray.value]
  154. })
  155. if (curValueArray.value.join('') == _values.join('') && curIndexArray.value.join('') == _indexs.join('')) return
  156. // if (curIndexArray.value.join('') == _indexs.join('')) return
  157. curIndexArray.value = _indexs
  158. curValueArray.value = _values
  159. // if(pickerValue.value.length == 0) {
  160. pickerValue.value = [...curValueArray.value]
  161. // }
  162. }
  163. const onPick = (item: PickerColumnItem, index:number, column: number) => {
  164. if( curIndexArray.value[column] == index &&
  165. curValueArray.value[column] == item.value) return
  166. curIndexArray.value[column] = index
  167. curValueArray.value[column] = item.value
  168. curItemArray[column] = item
  169. const obj:PickerPickEvent = {
  170. values: curValueArray.value,
  171. column,
  172. index
  173. }
  174. pickerValue.value = [...curValueArray.value]
  175. emit('pick', obj)
  176. };
  177. const onCancel = (e: UniPointerEvent) => {
  178. updatePickerItems()
  179. emit('cancel', e)
  180. }
  181. const onConfirm = (e: UniPointerEvent) => {
  182. const values = [...curValueArray.value];
  183. const indexs = [...curIndexArray.value];
  184. const items = curItemArray.map((item):PickerColumnItem => toRaw(item))
  185. if(pickerValue.value.join('') != values.join('')) {
  186. pickerValue.value = values;
  187. }
  188. const obj:PickerConfirmEvent = {
  189. values,
  190. indexs,
  191. items
  192. }
  193. emit('confirm', obj)
  194. }
  195. const stopPickerValue = watch(pickerValue, () => {
  196. nextTick(()=>{
  197. curValueArray.value = pickerValue.value.map((item: PickerValue) => item);
  198. updatePickerItems()
  199. })
  200. })
  201. const stopColumns = watch(realColumns, ()=>{
  202. // nextTick(()=>{
  203. // updatePickerItems()
  204. // })
  205. updatePickerItems()
  206. })
  207. onMounted(()=>{
  208. nextTick(()=>{
  209. if(
  210. !valueArrayEquals.value &&
  211. pickerValue.value.length > 0) {
  212. curValueArray.value = [...pickerValue.value]
  213. updatePickerItems()
  214. }
  215. })
  216. })
  217. onBeforeUnmount(()=> {
  218. stopPickerValue()
  219. stopColumns()
  220. })
  221. const getSelectedOptions = (): PickerConfirmEvent => {
  222. const values = [...curValueArray.value];
  223. const indexs = [...curIndexArray.value];
  224. const items = curItemArray.map((item) : PickerColumnItem => toRaw(item))
  225. if (pickerValue.value.join('') != values.join('')) {
  226. pickerValue.value = values;
  227. }
  228. const obj : PickerConfirmEvent = {
  229. values,
  230. indexs,
  231. items
  232. }
  233. return obj
  234. }
  235. // #ifdef VUE3
  236. expose({
  237. confirm: onConfirm,
  238. getSelectedOptions,
  239. })
  240. // #endif
  241. provide('limePicker', props)
  242. provide('limePickerOnPick', onPick)
  243. provide('limePickerUpdateItems', updateItems)
  244. provide('limePickerItems', pickerItemInstanceArray)
  245. provide('limePickerManageChildInList', manageChildInList)
  246. return {
  247. styles,
  248. pickerValue,
  249. isEmpty,
  250. onCancel,
  251. onConfirm,
  252. // #ifdef VUE2
  253. confirm: onConfirm,
  254. getSelectedOptions,
  255. // #endif
  256. }
  257. }
  258. })
  259. </script>
  260. <style lang="scss">
  261. @import './index.scss';
  262. </style>