297 lines
9.8 KiB
Plaintext
297 lines
9.8 KiB
Plaintext
<template>
|
||
<picker-view class="l-picker-item__group" :style="{opacity: options.length > 0 ? 1 : 0}" :mask-style="platformMaskStyles.common"
|
||
:mask-top-style="platformMaskStyles.top" :mask-bottom-style="platformMaskStyles.bottom" :indicator-style="indicatorStyles"
|
||
mask-class="l-picker-item__mask" :value="innerIndex" @change="handlePick"
|
||
indicator-class="l-picker-item__indicator">
|
||
<picker-view-column class="l-picker-item__wrapper">
|
||
<!-- #ifdef APP-ANDROID || APP-IOS -->
|
||
<view ref="itemRef" :style="[itemStyles]"></view>
|
||
<!-- #endif -->
|
||
<!-- #ifdef APP-HARMONY -->
|
||
<view :style="[itemStyles]">
|
||
<!-- 鸿蒙不使用canvas的原因是不能超长高度 -->
|
||
<view ref="itemRef" style="height: 1000000px"></view>
|
||
</view>
|
||
<!-- #endif -->
|
||
<!-- #ifndef APP-ANDROID || APP-IOS || APP-HARMONY -->
|
||
<text class="l-picker-item__group-item" v-for="(item, i) in options"
|
||
:style="[itemStyles, curIndex == i ? itemActiveStyles: {}]"
|
||
:class="{'l-picker-item__group-item--active': curIndex == i}" :key="item.value">{{item.label}}</text>
|
||
<!-- #endif -->
|
||
</picker-view-column>
|
||
</picker-view>
|
||
</template>
|
||
<script lang="uts" setup>
|
||
/**
|
||
* PickerItem 选择器子项组件
|
||
* @description 用于构建多列选择器的单个列项,通常作为 Picker 组件的子组件使用
|
||
* <br>插件类型:LPickerItemComponentPublicInstance
|
||
* @tutorial https://ext.dcloud.net.cn/plugin?name=lime-picker
|
||
*
|
||
* @property {PickerColumnItem[]} options 当前列的选项列表(必填)
|
||
* @property {PickerValue} value 当前选中值
|
||
* @property {number} column 列索引标识(从0开始计数)
|
||
* @property {string | number} name 列名称标识符
|
||
*/
|
||
import { unitConvert } from '@/uni_modules/lime-shared/unitConvert'
|
||
import { clamp } from '@/uni_modules/lime-shared/clamp'
|
||
import { usePickerMask } from './usePickerMask'
|
||
import type { PickerItemProps, ManageChildInList, OnPick, UpdateItems } from './type';
|
||
import type { PickerColumnItem, PickerValue } from '../l-picker/type';
|
||
|
||
// #ifdef APP-ANDROID || APP-IOS || APP-HARMONY
|
||
import { type PickerCanvasConfig, PickerCanvasRenderer } from './PickerRenderer.uts'
|
||
// #endif
|
||
|
||
const themeMode = inject('limeConfigProviderTheme', computed(() => 'light'))
|
||
const instance = getCurrentInstance()!;
|
||
const props = withDefaults(defineProps<PickerItemProps>(), {
|
||
options: [] as PickerColumnItem[],
|
||
column: -1,
|
||
resetIndex: false
|
||
})
|
||
const picker = inject<LPickerComponentPublicInstance | null>('limePicker', null);
|
||
const pickerItemInstanceArray = inject<LPickerItemComponentPublicInstance[] | null>('limePickerItems', null);
|
||
const manageChildInList = inject<ManageChildInList | null>('limePickerManageChildInList', null);
|
||
manageChildInList?.(instance.proxy! as LPickerItemComponentPublicInstance, true)
|
||
const onPick = inject<OnPick | null>('limePickerOnPick', null);
|
||
const updateItems = inject<UpdateItems | null>('limePickerUpdateItems', null);
|
||
// web 如果初始是0 当数据加载后 无法指向0
|
||
// #ifdef WEB
|
||
const curIndex = ref(-1)
|
||
// #endif
|
||
// #ifndef WEB
|
||
const curIndex = ref(0)
|
||
// #endif
|
||
const isInitialized = ref(false)
|
||
const curValue = ref<PickerValue | null>(props.value);
|
||
const innerIndex = computed(() : number[] => [curIndex.value])
|
||
const isDarkMode = computed(() : boolean => themeMode.value == 'dark')
|
||
const column = computed(() : number => props.column != -1 ? props.column : pickerItemInstanceArray?.indexOf(instance.proxy! as LPickerItemComponentPublicInstance) ?? props.column);
|
||
const { platformMaskStyles } = usePickerMask(
|
||
computed(():string|null => picker?.bgColor),
|
||
isDarkMode,
|
||
computed(():string[]|null => picker?.maskColors),
|
||
isInitialized,
|
||
);
|
||
|
||
const indicatorStyles = computed(() : string => {
|
||
let style = `height: ${picker?.itemHeight ?? '50px'};border-bottom-color: transparent;`
|
||
return style + (isInitialized.value ? `border-top-color: rgba(0,0,0,0.001);` : `border-top-color: transparent;`)
|
||
})
|
||
|
||
const itemStyles = computed(() : Map<string, any> => {
|
||
const style = new Map<string, any>();
|
||
// #ifndef APP-ANDROID || APP-IOS || APP-HARMONY
|
||
if (picker?.itemColor != null) {
|
||
style.set('color', picker.itemColor!)
|
||
}
|
||
if (picker?.itemFontSize != null) {
|
||
style.set('font-size', picker.itemFontSize!)
|
||
}
|
||
if (picker?.itemHeight != null) {
|
||
style.set('line-height', picker.itemHeight!)
|
||
style.set('height', picker.itemHeight!)
|
||
}
|
||
// #endif
|
||
// #ifdef APP-ANDROID || APP-IOS || APP-HARMONY
|
||
style.set('height', unitConvert(picker?.itemHeight ?? 50) * props.options.length + 'px')
|
||
// #endif
|
||
return style
|
||
})
|
||
const itemActiveStyles = computed(() : Map<string, any> => {
|
||
const style = new Map<string, any>();
|
||
if (picker?.itemActiveColor != null) {
|
||
style.set('color', picker.itemActiveColor!)
|
||
}
|
||
if (picker?.itemActiveFontWeight != null) {
|
||
style.set('font-weight', picker.itemActiveFontWeight!)
|
||
}
|
||
return style
|
||
})
|
||
|
||
|
||
const getIndexByValue = (val : PickerValue | null) => {
|
||
let defaultIndex = 0;
|
||
if (val != null) {
|
||
defaultIndex = props.options.findIndex((item) => item.value == val);
|
||
}
|
||
return defaultIndex < 0 ? 0 : defaultIndex;
|
||
};
|
||
|
||
let lastCount = props.options.length
|
||
const setIndex = (index : number) => {
|
||
let lastIndex = curIndex.value
|
||
let _index = clamp(index, 0, props.options.length - 1)
|
||
if (props.options.length > _index) {
|
||
// #ifndef APP-IOS
|
||
curIndex.value = _index;
|
||
curValue.value = props.options[_index].value
|
||
// #endif
|
||
// #ifdef APP-IOS
|
||
if(props.options.length == lastCount) {
|
||
curIndex.value = _index;
|
||
curValue.value = props.options[_index].value
|
||
} else {
|
||
// ios 长度变化时可能会导致无法滚动到指定项
|
||
requestAnimationFrame(()=>{
|
||
curIndex.value = _index - 1;
|
||
curIndex.value = _index;
|
||
curValue.value = props.options[_index].value
|
||
lastCount = props.options.length
|
||
})
|
||
}
|
||
// #endif
|
||
}
|
||
// #ifdef WEB
|
||
if (lastIndex == _index || lastIndex == -1) return
|
||
if (instance.proxy!.$el) {
|
||
const childs = Array.from(instance.proxy!.$el.parentElement.children).slice(column.value + 1);
|
||
childs.forEach((child) => {
|
||
(child as HTMLElement).style.setProperty('--picker-duration', '300ms');
|
||
setTimeout(function () {
|
||
(child as HTMLElement).style.setProperty('--picker-duration', '0ms');
|
||
}, 299);
|
||
})
|
||
}
|
||
// #endif
|
||
|
||
|
||
}
|
||
const setValue = (value : PickerValue | null) => {
|
||
if (value == curValue.value) return
|
||
curValue.value = value
|
||
const index = getIndexByValue(value)
|
||
setIndex(index)
|
||
}
|
||
const setOptions = () => { }
|
||
const setUpdateItems = () => {
|
||
const index = clamp(curIndex.value, 0, props.options.length - 1)
|
||
const curItem = props.options.length > index ? props.options[index] : null;
|
||
if (curItem == null) return
|
||
updateItems?.(curItem, index, column.value);
|
||
}
|
||
|
||
const handlePick = (e : UniPickerViewChangeEvent) => {
|
||
if (props.options.length == 0) return
|
||
const index = clamp(e.detail.value[0], 0, props.options.length - 1);
|
||
const curItem = props.options[index];
|
||
if (index == curIndex.value) return
|
||
setIndex(index)
|
||
onPick?.(curItem, index, column.value);
|
||
}
|
||
|
||
const stopValue = watch(() : PickerValue | null => props.value, (v : PickerValue | null) => {
|
||
setValue(v);
|
||
setUpdateItems();
|
||
}, { immediate: true })
|
||
|
||
// #ifdef APP-ANDROID || APP-IOS || APP-HARMONY
|
||
const itemRef = ref<UniElement | null>(null)
|
||
// 在组件中使用
|
||
let canvasRenderer : PickerCanvasRenderer | null = null
|
||
|
||
// 提取配置的计算属性
|
||
const canvasWidth = ref<number>(0)
|
||
const canvasHeight = ref<number>(0)
|
||
const canvasConfig = computed(() : PickerCanvasConfig => ({
|
||
itemHeight: unitConvert(picker?.itemHeight ?? 50),
|
||
itemFontSize: unitConvert(picker?.itemFontSize ?? 16),
|
||
itemActiveFontWeight: picker?.itemActiveFontWeight,
|
||
itemColor: picker?.itemColor,
|
||
itemActiveColor: picker?.itemActiveColor,
|
||
canvasWidth: canvasWidth.value,
|
||
canvasHeight: canvasHeight.value
|
||
} as PickerCanvasConfig));
|
||
|
||
|
||
const updateItemStyle = () => {
|
||
if (itemRef.value == null) return
|
||
if(canvasRenderer == null) {
|
||
canvasRenderer = new PickerCanvasRenderer(itemRef.value!);
|
||
}
|
||
canvasRenderer?.render(
|
||
props.options,
|
||
curIndex.value,
|
||
canvasConfig.value,
|
||
isDarkMode.value
|
||
);
|
||
}
|
||
|
||
const getBoundingClientRect = () => {
|
||
requestAnimationFrame(() => {
|
||
itemRef.value?.getBoundingClientRectAsync()?.then(res => {
|
||
canvasWidth.value = res.width
|
||
canvasHeight.value = res.height
|
||
})
|
||
})
|
||
}
|
||
const resizeObserver = new UniResizeObserver((entries : Array<UniResizeObserverEntry>) => {
|
||
getBoundingClientRect()
|
||
})
|
||
|
||
let timerId: null|number = null
|
||
let renderCount = 0
|
||
const stopOptionsWatch = watch([()=> props.options, canvasHeight, isDarkMode, curIndex], () => {
|
||
if(canvasHeight.value == 0) return
|
||
updateItemStyle()
|
||
if(renderCount > 2) return
|
||
if(timerId != null) {
|
||
clearTimeout(timerId!)
|
||
}
|
||
timerId = setTimeout(()=> {
|
||
updateItemStyle()
|
||
// 鸿蒙可能首次会画出不出
|
||
renderCount++
|
||
},50)
|
||
});
|
||
|
||
const stopItemRefWatch = watch(() : UniElement | null => itemRef.value, (el : UniElement | null) => {
|
||
if (el == null) return
|
||
resizeObserver.observe(el)
|
||
// #ifdef APP-IOS
|
||
getBoundingClientRect()
|
||
// #endif
|
||
})
|
||
// #endif
|
||
|
||
// #ifdef APP-IOS
|
||
onMounted(() => {
|
||
nextTick(() => {
|
||
setTimeout(() => {
|
||
isInitialized.value = true
|
||
}, 400)
|
||
})
|
||
})
|
||
// #endif
|
||
// #ifdef APP-HARMONY
|
||
// 需要初始化后才会有动画
|
||
onMounted(() => {
|
||
nextTick(() => {
|
||
setTimeout(() => {
|
||
isInitialized.value = true
|
||
}, 50)
|
||
})
|
||
})
|
||
// #endif
|
||
onBeforeUnmount(() => {
|
||
manageChildInList?.(instance.proxy! as LPickerItemComponentPublicInstance, false)
|
||
// #ifdef APP-ANDROID || APP-IOS || APP-HARMONY
|
||
stopOptionsWatch()
|
||
stopItemRefWatch()
|
||
resizeObserver.disconnect()
|
||
canvasRenderer = null
|
||
// #endif
|
||
})
|
||
|
||
defineExpose({
|
||
setIndex,
|
||
setValue,
|
||
// setOptions,
|
||
// setUpdateItems,
|
||
getIndexByValue
|
||
})
|
||
</script>
|
||
<style lang="scss">
|
||
@import './index';
|
||
</style> |