提交 d47fc3bc 编写于 作者: S suzigang

fix: picker ts error

上级 492cb33e
......@@ -178,9 +178,9 @@ setup() {
| container-id | 在 useWindow 属性为 false 的时候,自定义设置节点ID | String | `''` |
| load-more-txt | “没有更多数”据展示文案 | String | `'哎呀,这里是底部了啦'` |
| is-open-refresh | 是否开启下拉刷新 | Boolean | `false` |
| pull-icon | 下拉刷新[图标名称](#/icon) | String | `https://img10.360buyimg.com/imagetools/jfs/t1/169863/6/4565/6306/60125948E7e92774e/40b3a0cf42852bcb.png` |
| pull-icon | 下拉刷新[图标名称](#/icon) | String | <img src="https://img10.360buyimg.com/imagetools/jfs/t1/169863/6/4565/6306/60125948E7e92774e/40b3a0cf42852bcb.png" width=40/> |
| pull-txt | 下拉刷新提示文案 | String | `松手刷新` |
| load-icon | 上拉加载[图标名称](#/icon) | Boolean | `https://img10.360buyimg.com/imagetools/jfs/t1/169863/6/4565/6306/60125948E7e92774e/40b3a0cf42852bcb.png` |
| load-icon | 上拉加载[图标名称](#/icon) | Boolean | <img src="https://img10.360buyimg.com/imagetools/jfs/t1/169863/6/4565/6306/60125948E7e92774e/40b3a0cf42852bcb.png" width=40 /> |
| load-txt | 上拉加载提示文案 | String | `加载中...` |
### Events
......
......@@ -12,17 +12,23 @@
<view-block
class="nut-picker__item"
:key="index"
v-for="(item, index) in state.options"
v-for="(item, index) in options"
>{{ dataType === 'cascade' ? item.text : item }}</view-block
>
</view-block>
</view-block>
</template>
<script lang="ts">
import { reactive, ref, watch, computed } from 'vue';
import { reactive, ref, watch, computed, toRefs, onMounted } from 'vue';
import { createComponent } from '@/utils/create';
import { useTouch } from '@/utils/useTouch';
import { commonProps } from './commonProps';
import {
PickerObjOpt,
PickerOption,
PickerObjectColumn,
PickerObjectColumns
} from './types';
const MOMENTUM_LIMIT_DISTANCE = 15;
const MOMENTUM_LIMIT_TIME = 300;
const DEFAULT_DURATION = 200;
......@@ -34,7 +40,6 @@ function stopPropagation(event: Event) {
event.stopPropagation();
}
function preventDefault(event: Event, isStopPropagation?: boolean) {
/* istanbul ignore else */
if (typeof event.cancelable !== 'boolean' || event.cancelable) {
event.preventDefault();
}
......@@ -44,7 +49,7 @@ function preventDefault(event: Event, isStopPropagation?: boolean) {
}
}
function getElementTranslateY(element) {
function getElementTranslateY(element: Element) {
const style = window.getComputedStyle(element);
const transform = style.transform || style.webkitTransform;
const translateY = transform.slice(7, transform.length - 1).split(', ')[5];
......@@ -54,7 +59,7 @@ export function isObject(val: unknown): val is Record<any, any> {
return val !== null && typeof val === 'object';
}
function isOptionDisabled(option) {
function isOptionDisabled(option: PickerObjectColumn) {
return isObject(option) && option.disabled;
}
......@@ -66,46 +71,57 @@ export default create({
emits: ['click', 'change'],
setup(props, { emit }) {
let moving;
let startOffset, touchStartTime, momentumOffset, transitionEndTrigger;
const wrapper = ref();
const state = reactive({
index: props.defaultIndex,
offset: 0,
duration: 0,
options: props.listData
options: props.listData as PickerObjectColumn[],
moving: false,
startOffset: 0,
touchStartTime: 0,
momentumOffset: 0,
transitionEndTrigger: null as null | Function
});
watch(
() => props.listData,
val => {
if (val) {
state.options = val;
}
}
);
const wrapper = ref();
const touch = useTouch();
const count = () => state.options.length;
const _show = ref(false);
const getIndexByOffset = offset =>
range(Math.round(-offset / props.itemHeight), 0, count() - 1);
const baseOffset = () =>
(props.itemHeight * (props.visibleItemCount - 1)) / 2;
const wrapperStyle = computed(() => ({
transform: `translate3d(0, ${state.offset + baseOffset()}px, 0)`,
transitionDuration: `${state.duration}ms`,
transitionProperty: state.duration ? 'all' : 'none'
}));
const handleClick = (event: Event) => {
emit('click', event);
};
const getIndexByOffset = (offset: number) => {
return range(
Math.round(-offset / +props.itemHeight),
0,
state.options.length - 1
);
};
const baseOffset = () => {
return (+props.itemHeight * (+props.visibleItemCount - 1)) / 2;
};
const stopMomentum = () => {
moving = false;
state.moving = false;
state.duration = 0;
if (transitionEndTrigger) {
transitionEndTrigger();
transitionEndTrigger = null;
if (state.transitionEndTrigger) {
state.transitionEndTrigger();
state.transitionEndTrigger = null;
}
};
const adjustIndex = index => {
index = range(index, 0, count());
const adjustIndex = (index: number) => {
index = range(index, 0, state.options.length);
for (let i = index; i < count(); i++) {
for (let i = index; i < state.options.length; i++) {
if (!isOptionDisabled(state.options[i])) return i;
}
for (let i = index - 1; i >= 0; i--) {
......@@ -113,10 +129,10 @@ export default create({
}
};
const setIndex = (index, emitChange = false) => {
const setIndex = (index: number, emitChange = false) => {
index = adjustIndex(index) || 0;
const offset = -index * props.itemHeight;
const offset = -index * +props.itemHeight;
const trigger = () => {
if (index !== state.index) {
state.index = index;
......@@ -127,22 +143,16 @@ export default create({
}
};
if (moving && offset !== state.offset) {
transitionEndTrigger = trigger;
if (state.moving && offset !== state.offset) {
state.transitionEndTrigger = trigger;
} else {
trigger();
}
state.offset = offset;
};
watch(
() => props.defaultIndex,
val => {
setIndex(val);
}
);
setIndex(props.defaultIndex);
const momentum = (distance, duration) => {
const momentum = (distance: number, duration: number) => {
const speed = Math.abs(distance / duration);
distance = state.offset + (speed / 0.003) * (distance < 0 ? -1 : 1);
......@@ -151,57 +161,58 @@ export default create({
setIndex(index, true);
};
const onTouchStart = event => {
const onTouchStart = (event: Event) => {
if (props.readonly) {
return;
}
touch.start(event);
if (moving) {
if (state.moving) {
const translateY = getElementTranslateY(wrapper.value);
state.offset = Math.min(0, translateY - baseOffset());
startOffset = state.offset;
state.startOffset = state.offset;
} else {
startOffset = state.offset;
state.startOffset = state.offset;
}
state.duration = 0;
touchStartTime = Date.now();
momentumOffset = startOffset;
transitionEndTrigger = null;
state.touchStartTime = Date.now();
state.momentumOffset = state.startOffset;
state.transitionEndTrigger = null;
};
const onTouchMove = event => {
const onTouchMove = (event: Event) => {
if (props.readonly) {
return;
}
moving = true;
state.moving = true;
touch.move(event);
if (touch.isVertical()) {
moving = true;
state.moving = true;
preventDefault(event, true);
}
const moveOffset = startOffset + touch.deltaY.value;
const moveOffset = state.startOffset + touch.deltaY.value;
if (moveOffset > props.itemHeight) {
state.offset = props.itemHeight;
state.offset = props.itemHeight as number;
} else {
state.offset = startOffset + touch.deltaY.value;
state.offset = state.startOffset + touch.deltaY.value;
}
const now = Date.now();
if (now - touchStartTime > MOMENTUM_LIMIT_TIME) {
touchStartTime = now;
momentumOffset = state.offset;
if (now - state.touchStartTime > MOMENTUM_LIMIT_TIME) {
state.touchStartTime = now;
state.momentumOffset = state.offset;
}
};
const onTouchEnd = () => {
const index = getIndexByOffset(state.offset);
state.duration = DEFAULT_DURATION;
setIndex(index, true);
const distance = state.offset - momentumOffset;
const duration = Date.now() - touchStartTime;
const distance = state.offset - state.momentumOffset;
const duration = Date.now() - state.touchStartTime;
const allowMomentum =
duration < MOMENTUM_LIMIT_TIME &&
......@@ -212,25 +223,37 @@ export default create({
return;
}
};
const handleClick = (event: Event) => {
emit('click', event);
};
const wrapperStyle = computed(() => ({
transform: `translate3d(0, ${state.offset + baseOffset()}px, 0)`,
transitionDuration: `${state.duration}ms`,
transitionProperty: state.duration ? 'all' : 'none'
}));
onMounted(() => {
setIndex(+props.defaultIndex);
});
watch(
() => props.listData,
val => {
if (val) {
state.options = val as PickerObjectColumn[];
}
}
);
watch(
() => props.defaultIndex,
val => {
setIndex(+val);
}
);
return {
...toRefs(state),
wrapper,
onTouchStart,
onTouchMove,
onTouchEnd,
wrapperStyle,
state,
stopMomentum,
columns: state.options,
height: Number(props.visibleItemCount) * props.itemHeight
height: Number(props.visibleItemCount) * +props.itemHeight
};
}
});
......
export const commonProps = {
listData: {
type: Array,
default: []
default: () => {
return [];
}
},
readonly: {
type: Boolean,
default: false
},
visibleItemCount: {
type: [Number],
type: [Number, String],
default: 7
},
defaultIndex: {
......@@ -16,7 +18,7 @@ export const commonProps = {
default: 0
},
itemHeight: {
type: [Number],
type: [Number, String],
default: 35
}
};
......@@ -95,8 +95,8 @@ export default createDemo({
}`
);
const desc3 = ref(
`${listData3[0].text}
${listData3[0].children[0].text}
`${listData3[0].text}
${listData3[0].children[0].text}
${listData3[0].children[0].children[0].text}`
);
const descList = [desc, desc2, desc3];
......@@ -110,16 +110,16 @@ export default createDemo({
desc,
desc2,
desc3,
open: index => {
open: (index: number) => {
showList[index - 1].value = true;
},
confirm: res => {
confirm: (res: any) => {
desc.value = res;
},
confirm2: res => {
confirm2: (res: any) => {
desc2.value = res.join(' ');
},
confirm3: res => {
confirm3: (res: any) => {
desc3.value = res.join(' ');
}
};
......
# picker组件
# Picker组件
### 介绍
......
<template>
<view-block class="nut-picker">
<view-block :class="classes">
<nut-popup
position="bottom"
:style="{ height: height + 56 + 'px' }"
......@@ -7,9 +7,9 @@
@close="close"
>
<view-block class="nut-picker__bar">
<view-block class="nut-picker__left" @click="close()"> 取消</view-block>
<view-block class="nut-picker__left" @click="close">取消</view-block>
<view-block> {{ title }}</view-block>
<view-block @click="confirm()"> 确定</view-block>
<view-block @click="confirm()">确定</view-block>
</view-block>
<view-block class="nut-picker__column">
......@@ -45,12 +45,18 @@
</view-block>
</template>
<script lang="ts">
import { reactive, ref, watch, computed, toRaw } from 'vue';
import { reactive, watch, computed, toRaw, toRefs } from 'vue';
import { createComponent } from '@/utils/create';
import column from './Column.vue';
import popup from '@/packages/popup/index.vue';
import { commonProps } from './commonProps';
const { create } = createComponent('picker');
import {
PickerObjOpt,
PickerOption,
PickerObjectColumn,
PickerObjectColumns
} from './types';
const { create, componentName } = createComponent('picker');
export default create({
children: [column, popup],
......@@ -65,61 +71,82 @@ export default create({
},
...commonProps
},
components: { column },
emits: ['close', 'change', 'confirm', 'update:isVisible'],
setup(props, { emit }) {
const show = ref(false);
const defaultIndex = ref(props.defaultIndex);
const formattedColumns: any = ref(props.listData);
//临时变量,当点击确定时候赋值
let _defaultIndex = props.defaultIndex;
const childrenKey = 'children';
const valuesKey = 'values';
const state = reactive({
show: false,
formattedColumns: props.listData as PickerObjectColumn[],
defaultIndex: props.defaultIndex as number
});
//临时变量,当点击确定时候赋值
let _defaultIndex = props.defaultIndex;
let defaultIndexList: number[] = [];
watch(
() => props.isVisible,
val => {
show.value = val;
}
);
const classes = computed(() => {
const prefixCls = componentName;
return {
[prefixCls]: true
};
});
watch(
() => props.listData,
val => {
formattedColumns.value = val;
}
);
const top = computed(() => {
return (Number(+props.visibleItemCount - 1) / 2) * +props.itemHeight;
});
const addDefaultIndexList = listData => {
defaultIndexList = [];
listData.forEach(res => {
defaultIndexList.push(res.defaultIndex);
});
};
const dataType = computed(() => {
const firstColumn = formattedColumns.value[0] || {};
const height = computed(() => {
return Number(props.visibleItemCount) * +props.itemHeight;
});
const dataType = computed(() => {
const firstColumn = state.formattedColumns[0] as PickerObjectColumn;
if (typeof firstColumn === 'object') {
if (firstColumn?.[childrenKey]) {
if (firstColumn[childrenKey]) {
return 'cascade';
} else if (firstColumn?.[valuesKey]) {
addDefaultIndexList(props.listData);
//多列
addDefaultIndexList(props.listData as PickerObjectColumn[]);
return 'multipleColumns';
}
}
return 'text';
});
const formatCascade = (listData, defaultIndex) => {
const formatted: any[] = [];
let children = listData;
const columnList = computed(() => {
if (dataType.value === 'text') {
return [
{ values: state.formattedColumns, defaultIndex: state.defaultIndex }
];
} else if (dataType.value === 'multipleColumns') {
return state.formattedColumns;
} else if (dataType.value === 'cascade') {
return formatCascade(
state.formattedColumns as PickerObjectColumn[],
state.defaultIndex
);
}
return state.formattedColumns;
});
const addDefaultIndexList = (listData: PickerObjectColumn[]) => {
defaultIndexList = [];
listData.forEach(res => {
defaultIndexList.push(res.defaultIndex as number);
});
};
const formatCascade = (
listData: PickerObjectColumn[],
defaultIndex: number
) => {
const formatted: PickerObjectColumn[] = [];
let children = listData as PickerObjectColumns;
children.defaultIndex = defaultIndex;
while (children) {
formatted.push({
values: children,
defaultIndex: children.defaultIndex
defaultIndex: defaultIndex
});
children = children?.[children.defaultIndex || 0].children;
}
......@@ -127,94 +154,104 @@ export default create({
return formatted;
};
const columnList = computed(() => {
if (dataType.value === 'text') {
return [
{ values: formattedColumns.value, defaultIndex: defaultIndex.value }
];
} else if (dataType.value === 'multipleColumns') {
return formattedColumns.value;
} else if (dataType.value === 'cascade') {
return formatCascade(formattedColumns.value, defaultIndex.value);
}
return formattedColumns.value;
});
const getCascadeData = (listData, defaultIndex) => {
let arr = listData;
const getCascadeData = (
listData: PickerObjectColumn[],
defaultIndex: number
) => {
let arr = listData as PickerObjectColumns;
arr.defaultIndex = defaultIndex;
const dataList: string[] = [];
while (arr) {
const item = arr[arr.defaultIndex ?? 0];
dataList.push(item.text);
dataList.push(item.text as string);
arr = item.children;
}
return dataList;
};
return {
show,
column,
title: props.title,
dataType,
columnList,
top: (Number(props.visibleItemCount - 1) / 2) * props.itemHeight,
height: Number(props.visibleItemCount) * props.itemHeight,
close: () => {
emit('close');
emit('update:isVisible', false);
},
changeHandler: (columnIndex, dataIndex) => {
if (dataType.value === 'cascade') {
let cursor: any = toRaw(formattedColumns.value);
//最外层使用props.defaultIndex作为初始index
if (columnIndex === 0) {
defaultIndex.value = dataIndex;
} else {
let i = 0;
while (cursor) {
if (i === columnIndex) {
cursor.defaultIndex = dataIndex;
} else if (i > columnIndex) {
cursor.defaultIndex = 0;
}
cursor = cursor[cursor.defaultIndex || 0].children;
i++;
const close = () => {
emit('close');
emit('update:isVisible', false);
};
const changeHandler = (columnIndex: number, dataIndex: number) => {
if (dataType.value === 'cascade') {
let cursor = toRaw(state.formattedColumns) as PickerObjectColumns;
if (columnIndex === 0) {
state.defaultIndex = dataIndex;
} else {
let i = 0;
while (cursor) {
if (i === columnIndex) {
cursor.defaultIndex = dataIndex;
} else if (i > columnIndex) {
cursor.defaultIndex = 0;
}
cursor = cursor[cursor.defaultIndex || 0].children;
i++;
}
} else if (dataType.value === 'text') {
_defaultIndex = dataIndex;
} else if (dataType.value === 'multipleColumns') {
defaultIndexList[columnIndex] = dataIndex;
const val = defaultIndexList.map(
(res, i) => toRaw(formattedColumns.value)[i].values[res]
);
console.log('val', defaultIndexList);
emit('change', val);
}
},
confirm: () => {
if (dataType.value === 'text') {
defaultIndex.value = _defaultIndex;
emit('confirm', formattedColumns.value[_defaultIndex]);
} else if (dataType.value === 'multipleColumns') {
for (let i = 0; i < defaultIndexList.length; i++) {
formattedColumns.value[i].defaultIndex = defaultIndexList[i];
}
const checkedArr = toRaw(formattedColumns.value).map(
(res: any) => res.values[res.defaultIndex]
);
console.log(formattedColumns.value);
emit('confirm', checkedArr);
} else if (dataType.value === 'cascade') {
emit(
'confirm',
getCascadeData(toRaw(formattedColumns.value), defaultIndex.value)
);
} else if (dataType.value === 'text') {
_defaultIndex = dataIndex;
} else if (dataType.value === 'multipleColumns') {
defaultIndexList[columnIndex] = dataIndex;
const val = defaultIndexList.map(
(res, i) =>
toRaw(state.formattedColumns as PickerObjectColumns)[i].values[res]
);
emit('change', val);
}
};
const confirm = () => {
if (dataType.value === 'text') {
state.defaultIndex = _defaultIndex as number;
emit('confirm', state.formattedColumns[_defaultIndex as number]);
} else if (dataType.value === 'multipleColumns') {
for (let i = 0; i < defaultIndexList.length; i++) {
state.formattedColumns[i].defaultIndex = defaultIndexList[i];
}
const checkedArr = toRaw(state.formattedColumns).map(
(res: PickerObjectColumn) =>
res.values && res.values[res.defaultIndex as number]
);
emit('confirm', checkedArr);
} else if (dataType.value === 'cascade') {
emit(
'confirm',
getCascadeData(toRaw(state.formattedColumns), state.defaultIndex)
);
}
emit('update:isVisible', false);
};
watch(
() => props.isVisible,
val => {
state.show = val;
}
);
emit('update:isVisible', false);
watch(
() => props.listData,
val => {
state.formattedColumns = val as PickerObjectColumns;
}
);
return {
classes,
...toRefs(state),
column,
dataType,
columnList,
top,
height,
close,
changeHandler,
confirm
};
}
});
......
export type PickerObjOpt = {
text?: string;
[key: string]: any;
};
export type PickerOption = string | PickerObjOpt;
export type PickerObjectColumn = {
values?: PickerOption[];
defaultIndex?: number;
children?: PickerOption[];
[key: string]: any;
};
export type PickerObjectColumns = PickerObjectColumn & PickerObjOpt[];
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册