import { defineComponent, inject, ref, Ref, watch, VNode, computed, getCurrentInstance, onMounted, ExtractPropTypes, WritableComputedRef, } from 'vue' import { extend, isString } from '@vue/shared' import { Props, GetPickerViewColumn } from '../picker-view' import { parseStyleText, getComponentSize } from '../helpers' type ScrollOptions = { showScrollbar: boolean scrollToBegin: boolean decelerationRate: number scrollY: boolean scrollTop?: number } type PickerColumnProps = ExtractPropTypes const dom = weex.requireModule('dom') const isAndroid = weex.config.env.platform.toLowerCase() === 'android' function getStyle(val: string) { return extend({}, isString(val) ? parseStyleText(val) : val) } const props = { length: { type: [Number, String], default: 0, }, } export default defineComponent({ name: 'PickerViewColumn', props, data: () => ({ _isMounted: false, }), setup(props, { slots }) { const instance = getCurrentInstance()! const rootRef: Ref = ref(null) const contentRef: Ref = ref(null) const scrollViewItemRef: Ref = ref(null) const indicatorRef: Ref = ref(null) const pickerViewProps = inject('pickerViewProps')! const getPickerViewColumn = inject( 'getPickerViewColumn' ) as GetPickerViewColumn const current = getPickerViewColumn(instance) const indicatorStyle = computed(() => getStyle(pickerViewProps.indicatorStyle) ) // const maskStyle = computed(() => getStyle(pickerViewProps.maskStyle)) const maskTopStyle = computed(() => getStyle(pickerViewProps.maskTopStyle)) const maskBottomStyle = computed(() => getStyle(pickerViewProps.maskBottomStyle) ) let indicatorHeight = ref(0) indicatorHeight.value = getHeight(indicatorStyle.value) let pickerViewHeight = ref(0) pickerViewHeight.value = parseFloat(pickerViewProps.height as string) const { setCurrent, onScrollend } = usePickerColumnScroll( props, current, contentRef, indicatorHeight ) const checkMounted = () => { let height_: number let indicatorHeight_: number setTimeout(() => { Promise.all([ getComponentSize(rootRef.value!).then(({ height }) => { height_ = pickerViewHeight.value = height }), isAndroid && props.length ? getComponentSize(scrollViewItemRef.value!).then(({ height }) => { indicatorHeight_ = indicatorHeight.value = height / parseFloat(props.length as string) }) : getComponentSize(indicatorRef.value!).then(({ height }) => { indicatorHeight_ = indicatorHeight.value = height }), ]).then(() => { if (height_ && indicatorHeight_) { // 初始化时iOS直接滚动经常出错 setTimeout(() => { instance.data._isMounted = true setCurrent(current.value, false, true) }, 50) } else { checkMounted() } }) }, 50) } onMounted(checkMounted) const createScrollViewChild = (item?: VNode[]) => { if (!item) return null return isAndroid ? (
{item}
) : ( item ) } return () => { const children = slots.default && slots.default() let padding = (pickerViewHeight.value - indicatorHeight.value) / 2 const maskPosition = `${pickerViewHeight.value - padding}px` const scrollOptions: ScrollOptions = { showScrollbar: false, scrollToBegin: false, decelerationRate: 0.3, scrollY: true, } if (!isAndroid) { scrollOptions.scrollTop = current.value * indicatorHeight.value } return ( {createScrollViewChild(children)} ) } }, styles: [ { 'uni-picker-view-column': { '': { flex: 1, position: 'relative', alignItems: 'stretch', overflow: 'hidden', }, }, 'uni-picker-view-mask': { '': { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, pointerEvents: 'none', }, }, 'uni-picker-view-mask-top': { '': { bottom: 0, backgroundImage: 'linear-gradient(to bottom,rgba(255, 255, 255, 0.95),rgba(255, 255, 255, 0.6))', }, }, 'uni-picker-view-mask-bottom': { '': { top: 0, backgroundImage: 'linear-gradient(to top,rgba(255, 255, 255, 0.95),rgba(255, 255, 255, 0.6))', }, }, 'uni-picker-view-group': { '': { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }, }, 'uni-picker-view-content': { '': { flexDirection: 'column', paddingTop: 0, paddingRight: 0, paddingBottom: 0, paddingLeft: 0, }, }, 'uni-picker-view-indicator': { '': { position: 'absolute', left: 0, right: 0, top: 0, height: '34px', pointerEvents: 'none', borderColor: '#e5e5e5', borderTopWidth: '1px', borderBottomWidth: '1px', }, }, }, ], }) function getHeight(style: Record) { const height = style.height || style.lineHeight || '' const res = height.match(/(-?[\d\.]+)px/) let value = 0 if (res) { value = parseFloat(res[1]) } return value } function usePickerColumnScroll( props: PickerColumnProps, current: WritableComputedRef, contentRef: Ref, indicatorHeight: Ref ) { let scrollToElementTime: number watch( () => props.length, () => { setTimeout(() => { setCurrent(current.value, true, true) }, 150) } ) watch( () => current.value, (_current) => { dom.scrollToElement(contentRef.value, { offset: _current * indicatorHeight.value, animated: true, }) scrollToElementTime = Date.now() } ) const setCurrent = (_current: number, animated = true, force: Boolean) => { if (current.value === _current && !force) { return } dom.scrollToElement(contentRef.value, { offset: _current * indicatorHeight.value, animated, }) current.value = _current if (animated) { scrollToElementTime = Date.now() } } const onScrollend = (event: { detail: { contentOffset: { x: number; y: number } } }) => { if (Date.now() - scrollToElementTime < 340) { return } const y = event.detail.contentOffset.y const _current = Math.round(y / indicatorHeight.value) if (y % indicatorHeight.value) { setCurrent(_current, true, true) } else { current.value = _current } } return { setCurrent, onScrollend } }