2026-09-02 16:10:57 +08:00

297 lines
9.8 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>