269 lines
9.7 KiB
Plaintext
269 lines
9.7 KiB
Plaintext
<template>
|
||
<view class="l-picker" :style="[styles]" ref="pickerRef">
|
||
<view class="l-picker__toolbar" v-if="cancelBtn != null || title != null || confirmBtn != null">
|
||
<text class="l-picker__cancel" :style="[cancelStyle??{}]" v-if="cancelBtn != null" @click="onCancel">{{cancelBtn}}</text>
|
||
<!-- #ifdef APP-HARMONY -->
|
||
<text v-else class="l-picker__cancel"></text>
|
||
<!-- #endif -->
|
||
<text class="l-picker__title" :style="[titleStyle??{}]">{{title}}</text>
|
||
<text class="l-picker__confirm" :style="[confirmStyle??{}]" v-if="confirmBtn != null" @click="onConfirm">{{confirmBtn}}</text>
|
||
<!-- #ifdef APP-HARMONY -->
|
||
<text v-else class="l-picker__confirm"></text>
|
||
<!-- #endif -->
|
||
</view>
|
||
<slot name="header"></slot>
|
||
<view class="l-picker__main" :style="[groupHeight != null ? { height: groupHeight}: {}]">
|
||
<slot>
|
||
<l-picker-item v-for="(options, i) in props.columns" :options="options" :key="i" :column="i" :value="pickerValue.length > i ? pickerValue[i]: null"></l-picker-item>
|
||
</slot>
|
||
<view class="l-picker__empty" v-if="isEmpty">
|
||
<slot name="empty"></slot>
|
||
</view>
|
||
<view class="l-picker__indicator" :style="indicatorStyle ?? ''" v-if="!isEmpty"></view>
|
||
</view>
|
||
<slot name="footer" />
|
||
<view class="l-picker__loading" ref="loadingRef" v-if="loading" :style="[loadingMaskColor != null ? {background: loadingMaskColor}: {}]">
|
||
<!-- #ifndef APP -->
|
||
<l-loading :size="loadingSize" :color="loadingColor"></l-loading>
|
||
<!-- #endif -->
|
||
</view>
|
||
</view>
|
||
</template>
|
||
<script lang="uts" setup>
|
||
/**
|
||
* Picker 选择器组件
|
||
* @description 多列数据选择器,支持级联数据展示和自定义样式配置
|
||
* <br>插件类型:LPickerComponentPublicInstance
|
||
* @tutorial https://ext.dcloud.net.cn/plugin?name=lime-picker
|
||
*
|
||
* @property {string} cancelBtn 取消按钮文字
|
||
* @property {string | UTSJSONObject} cancelStyle 取消按钮样式
|
||
* @property {string} confirmBtn 确定按钮文字
|
||
* @property {string | UTSJSONObject} confirmStyle 确定按钮样式
|
||
* @property {string} title 标题文字
|
||
* @property {string | UTSJSONObject} titleStyle 标题样式
|
||
* @property {UTSJSONObject} keys 字段别名配置(例:{value: 'id', label: 'name'})
|
||
* @property {PickerColumn[]} columns 选择器列数据(必填)
|
||
* @property {PickerValue[]} modelValue 选中值(支持v-model)
|
||
* @property {PickerValue[]} defaultValue 默认选中值
|
||
* @property {PickerValue[]} value 选中值(兼容旧版)
|
||
* @property {boolean} loading 是否显示加载状态
|
||
* @property {string} loadingColor 加载图标颜色
|
||
* @property {string} loadingMaskColor 加载遮罩颜色
|
||
* @property {string} loadingSize 加载图标尺寸
|
||
* @property {string} itemHeight 选项行高度
|
||
* @property {string} itemColor 选项文字颜色
|
||
* @property {string} itemFontSize 选项字体大小
|
||
* @property {string} itemActiveColor 选中项颜色
|
||
* @property {string} indicatorStyle 指示器样式
|
||
* @property {string[]} maskColors 遮罩颜色
|
||
* @property {string} bgColor 背景颜色
|
||
* @property {string} groupHeight 选项组高度
|
||
* @property {string} radius 圆角半径
|
||
* @property {boolean} resetIndex 是否重置选中索引
|
||
*
|
||
* @event {Function} confirm 点击确定时触发(事件参数:PickerConfirmEvent)
|
||
* @event {Function} cancel 点击取消时触发
|
||
* @event {Function} change 值变化时触发(事件参数:PickerPickEvent)
|
||
* @event {Function} column-change 列数据变化时触发(事件参数:PickerChangeInfo)
|
||
*/
|
||
import type { PickerProps, PickerColumn, PickerValue, PickerColumnItem, PickerConfirmEvent, PickerPickEvent } from './type';
|
||
import { arrayEqual } from '@/uni_modules/lime-shared/arrayEqual'
|
||
import { assignAtIndex } from '@/uni_modules/lime-shared/assignAtIndex'
|
||
import { unitConvert } from '@/uni_modules/lime-shared/unitConvert'
|
||
// #ifdef APP
|
||
import { useLoading } from '@/uni_modules/lime-loading'
|
||
// #endif
|
||
const emit = defineEmits(['change', 'cancel', 'pick', 'confirm', 'update:modelValue']);
|
||
const props = withDefaults(defineProps<PickerProps>(), {
|
||
columns: [] as PickerColumn[],
|
||
loading: false,
|
||
resetIndex: false,
|
||
loadingSize: '32px'
|
||
})
|
||
const pickerItemInstanceArray = reactive<LPickerItemComponentPublicInstance[]>([]);
|
||
const modelValue = ref<PickerValue[]>(props.value ?? props.modelValue ?? props.defaultValue ?? [])
|
||
const pickerValue = computed({
|
||
set(value : PickerValue[]) {
|
||
if (arrayEqual(value, modelValue.value)) return
|
||
modelValue.value = value;
|
||
emit('update:modelValue', value)
|
||
emit('change', value)
|
||
},
|
||
get() : PickerValue[] {
|
||
return props.value ?? props.modelValue ?? modelValue.value
|
||
}
|
||
} as WritableComputedOptions<PickerValue[]>)
|
||
|
||
const isEmpty = computed(() : boolean => {
|
||
return props.columns.length == 0 && pickerItemInstanceArray.every(child => child.options.length == 0)
|
||
})
|
||
const styles = computed(() : Map<string, any> => {
|
||
const style = new Map<string, any>()
|
||
if (props.bgColor != null) {
|
||
style.set('background', props.bgColor!)
|
||
}
|
||
if (props.radius != null) {
|
||
style.set('border-top-left-radius', props.radius!)
|
||
style.set('border-top-right-radius', props.radius!)
|
||
}
|
||
return style
|
||
})
|
||
|
||
const curIndexArray = ref<number[]>([]);
|
||
const curValueArray = ref([...pickerValue.value]);
|
||
const curItemArray : PickerColumnItem[] = []
|
||
const realColumns = computed(() : PickerColumn[] => {
|
||
const pickerColumns = pickerItemInstanceArray.map((child) : PickerColumn => child.options)
|
||
if (pickerColumns.length > 0) {
|
||
return pickerColumns
|
||
}
|
||
return props.columns
|
||
})
|
||
|
||
const manageChildInList = (child : LPickerItemComponentPublicInstance, shouldAdd : boolean) => {
|
||
const index = pickerItemInstanceArray.indexOf(child);
|
||
if (shouldAdd) {
|
||
if (index != -1) return
|
||
pickerItemInstanceArray.push(child)
|
||
} else {
|
||
if (index == -1) return
|
||
pickerItemInstanceArray.splice(index, 1);
|
||
}
|
||
}
|
||
|
||
const updateItems = (item : PickerColumnItem, index : number, column : number) => {
|
||
assignAtIndex(curIndexArray.value, column, index)
|
||
assignAtIndex(curValueArray.value, column, item.value)
|
||
assignAtIndex(curItemArray, column, item)
|
||
};
|
||
|
||
const updatePickerItems = () => {
|
||
const _indexs : number[] = []
|
||
const _values : any[] = []
|
||
pickerItemInstanceArray.forEach((child, column) => {
|
||
if (child.options.length == 0) return
|
||
const value = curValueArray.value.length > column ? curValueArray.value[column] : null;
|
||
// #ifndef APP
|
||
const index = value == null ? 0 : child._.exposed.getIndexByValue(value);
|
||
child._.exposed.setIndex(index)
|
||
// #endif
|
||
// #ifdef APP
|
||
// const index = value == null ? 0 : child.getIndexByValue(value)
|
||
// child.setIndex(index)
|
||
const index : number = (value == null ? 0 : child.$callMethod('getIndexByValue', value)) as number
|
||
child.$callMethod('setIndex', index)
|
||
// #endif
|
||
const item = child.options[index]
|
||
_indexs.push(index)
|
||
_values.push(item.value)
|
||
|
||
assignAtIndex(curItemArray, column, item)
|
||
})
|
||
if (arrayEqual(curValueArray.value, _values) && arrayEqual(curIndexArray.value, _indexs)) return
|
||
curIndexArray.value = _indexs
|
||
curValueArray.value = _values
|
||
pickerValue.value = [...curValueArray.value]
|
||
}
|
||
|
||
const onPick = (item : PickerColumnItem, index : number, column : number) => {
|
||
if (curIndexArray.value[column] == index) return
|
||
assignAtIndex(curIndexArray.value, column, index)
|
||
assignAtIndex(curValueArray.value, column, item.value)
|
||
assignAtIndex(curItemArray, column, item)
|
||
const obj : PickerPickEvent = {
|
||
values: curValueArray.value,
|
||
column,
|
||
index
|
||
}
|
||
pickerValue.value = [...curValueArray.value]
|
||
emit('pick', obj)
|
||
};
|
||
|
||
const onCancel = (e : UniPointerEvent) => {
|
||
updatePickerItems()
|
||
emit('cancel', e)
|
||
}
|
||
const onConfirm = () => {
|
||
const values = [...curValueArray.value];
|
||
const indexs = [...curIndexArray.value];
|
||
const items = curItemArray.map((item) : PickerColumnItem => toRaw(item))
|
||
if (!arrayEqual(pickerValue.value, values)) {
|
||
pickerValue.value = values;
|
||
}
|
||
const obj : PickerConfirmEvent = {
|
||
values,
|
||
indexs,
|
||
items
|
||
}
|
||
emit('confirm', obj)
|
||
}
|
||
const stopPickerValue = watch(pickerValue, () => {
|
||
if (arrayEqual(pickerValue.value, curValueArray.value)) return
|
||
curValueArray.value = pickerValue.value.map((item : PickerValue) => item);
|
||
updatePickerItems()
|
||
})
|
||
|
||
const stopColumns = watch(realColumns, () => {
|
||
updatePickerItems()
|
||
})
|
||
|
||
onMounted(() => {
|
||
nextTick(() => {
|
||
if (
|
||
!arrayEqual(pickerValue.value, curValueArray.value) &&
|
||
pickerValue.value.length > 0) {
|
||
curValueArray.value = [...pickerValue.value]
|
||
updatePickerItems()
|
||
}
|
||
})
|
||
})
|
||
// #ifdef APP
|
||
const loadingRef = ref<UniElement | null>(null);
|
||
// const {play, clear, failed} = useLoading(loadingRef, 'circular', props.loadingColor?? '#3283ff', unitConvert(props.loadingSize))
|
||
const loadingAni = useLoading(loadingRef)
|
||
loadingAni.type = 'circular'
|
||
loadingAni.color = props.loadingColor ?? '#3283ff'
|
||
loadingAni.ratio = unitConvert(props.loadingSize)
|
||
watchEffect(() => {
|
||
if (props.loading) {
|
||
loadingAni.play()
|
||
} else {
|
||
loadingAni.clear()
|
||
}
|
||
})
|
||
// #endif
|
||
|
||
onBeforeUnmount(() => {
|
||
stopPickerValue()
|
||
stopColumns()
|
||
})
|
||
|
||
|
||
defineExpose({
|
||
confirm: onConfirm,
|
||
getSelectedOptions: (): PickerConfirmEvent => {
|
||
const values = [...curValueArray.value];
|
||
const indexs = [...curIndexArray.value];
|
||
const items = curItemArray.map((item) : PickerColumnItem => toRaw(item))
|
||
if (!arrayEqual(pickerValue.value, values)) {
|
||
pickerValue.value = values;
|
||
}
|
||
const obj : PickerConfirmEvent = {
|
||
values,
|
||
indexs,
|
||
items
|
||
}
|
||
return obj
|
||
}
|
||
})
|
||
|
||
provide('limePicker', props)
|
||
provide('limePickerOnPick', onPick)
|
||
provide('limePickerUpdateItems', updateItems)
|
||
provide('limePickerItems', pickerItemInstanceArray)
|
||
provide('limePickerManageChildInList', manageChildInList)
|
||
</script>
|
||
<style lang="scss">
|
||
@import './index.scss';
|
||
</style> |