未验证 提交 06403021 编写于 作者: K kaixuan 提交者: GitHub

feat:datepicker (#398)

* feat: add datepicker

* fix: 修复datepicker问题

* fix: datepicker解决导入相关问题
上级 6f6629c3
......@@ -175,6 +175,16 @@
"sort": 5,
"show": true,
"author": "yangkaixuan"
},
{
"version": "3.0.0",
"name": "DatePicker",
"type": "component",
"cName": "选择器",
"desc": "提供多个选型集合供用户选择,支持单列选择和多列级联,通常与弹出层配合使用",
"sort": 5,
"show": true,
"author": "yangkaixuan"
}
]
},
......
<template>
<div class="demo">
<h2>每列不显示中文</h2>
<nut-cell title="日期选择" :desc="desc1" @click="open(0)"></nut-cell>
<h2>限制开始结束时间</h2>
<nut-cell title="日期选择" :desc="desc2" @click="open(1)"></nut-cell>
<h2>限制开始结束时间(有默认值)</h2>
<nut-cell title="日期时间选择" :desc="desc3" @click="open(2)"></nut-cell>
<h2>12小时制</h2>
<nut-cell title="日期选择" :desc="desc4" @click="open(3)"></nut-cell>
<h2>限制开始结束小时</h2>
<nut-cell title="时间选择" :desc="desc5" @click="open(4)"></nut-cell>
<h2>分钟数递增步长设置</h2>
<nut-cell title="时间选择" :desc="desc6" @click="open(5)"></nut-cell>
<nut-datepicker
v-model="currentDate"
title="日期选择"
@confirm="
val => {
confirm(0, val);
}
"
v-model:is-visible="show"
:is-show-chinese="false"
></nut-datepicker>
<nut-datepicker
v-model="currentDate"
title="日期选择"
:minDate="minDate"
:maxDate="maxDate"
@confirm="
val => {
confirm(1, val);
}
"
v-model:is-visible="show2"
:is-show-chinese="false"
></nut-datepicker>
<nut-datepicker
v-model="currentDate"
title="日期时间选择"
type="datetime"
:minDate="minDate"
:maxDate="maxDate"
@confirm="
val => {
confirm(2, val);
}
"
v-model:is-visible="show3"
></nut-datepicker>
<nut-datepicker
v-model="currentDate"
title="时间选择"
type="time"
:minDate="minDate"
:maxDate="maxDate"
:is-use12-hours="true"
@confirm="
val => {
confirm(3, val);
}
"
v-model:is-visible="show4"
></nut-datepicker>
<nut-datepicker
v-model="currentDate"
title="时间选择"
type="time"
:minDate="minDate"
:maxDate="maxDate"
@confirm="
val => {
confirm(4, val);
}
"
v-model:is-visible="show5"
></nut-datepicker>
<nut-datepicker
v-model="currentDate"
title="时间选择"
type="time"
:minDate="minDate"
:minute-step="5"
:maxDate="maxDate"
@confirm="
val => {
confirm(5, val);
}
"
v-model:is-visible="show6"
></nut-datepicker>
</div>
</template>
<script lang="ts">
import { toRefs, watch, ref } from 'vue';
import { createComponent } from '@/utils/create';
const { createDemo } = createComponent('datepicker');
export default createDemo({
props: {},
setup() {
const show = ref(false);
const show2 = ref(false);
const show3 = ref(false);
const show4 = ref(false);
const show5 = ref(false);
const show6 = ref(false);
const showList = [show, show2, show3, show4, show5, show6];
const currentDate = ref(new Date(2020, 0, 1));
const today = currentDate.value;
const desc1 = ref('2020-1-1');
const desc2 = ref('2020-1-1');
const desc3 = ref('2020年-1月-1日-0时-0分');
const desc4 = ref('0时-0分-上午');
const desc5 = ref('0时-0分-0秒');
const desc6 = ref('0时-0分-0秒');
const descList = [desc1, desc2, desc3, desc4, desc5, desc6];
return {
show,
show2,
show3,
show4,
show5,
show6,
desc1,
desc2,
desc3,
desc4,
desc5,
desc6,
currentDate,
minDate: new Date(2020, 0, 1),
maxDate: new Date(2025, 10, 1),
open: index => {
showList[index].value = true;
},
confirm: (index, val) => {
console.log(val);
descList[index].value = val.join('-');
}
};
}
});
</script>
<style lang="scss" scoped></style>
# datepicker组件
### 介绍
时间选择器,支持日期、年月、时分等维度,通常与弹出层组件配合使用。
### 安装
```javascript
import { createApp } from 'vue';
import { Picker } from '@nutui/nutui';
const app = createApp();
app.use(Picker);
```
## 代码演示
### 日期选择-每列不显示中文
```html
<nut-datepicker
v-model="currentDate"
@confirm="confirm"
v-model:is-visible="show"
:is-show-chinese="false"
></nut-datepicker>
```
```javascript
<script>
export default createDemo({
setup(props, { emit }) {
const show = ref(false);
const desc = ref('2020-1-1');
return {
show,
desc
open: (index) => {
show.value = true;
},
confirm: (res) => {
desc.value = val.join('-');
}
};
}
});
</script>
```
### 日期选择-限制开始结束时间
```html
<nut-datepicker
v-model="currentDate"
:minDate="minDate"
:maxDate="maxDate"
@confirm="confirm"
v-model:is-visible="show"
:is-show-chinese="false"
></nut-datepicker>
```
```javascript
<script>
export default createDemo({
setup(props, { emit }) {
const show = ref(false);
const desc = ref('2020-1-1');
return {
show,
desc,
minDate: new Date(2020, 0, 1),
maxDate: new Date(2025, 10, 1),
open: (index) => {
show.value = true;
},
confirm: (res) => {
desc.value = val.join('-');
}
};
}
});
</script>
```
### 日期时间-限制开始结束时间(有默认值)
```html
<nut-datepicker
v-model="currentDate"
:minDate="minDate"
:maxDate="maxDate"
type="datetime"
@confirm="confirm"
v-model:is-visible="show"
></nut-datepicker>
```
```javascript
<script>
export default createDemo({
setup(props, { emit }) {
const show = ref(false);
const desc = ref('2020年-1月-1日-0时-0分');
return {
show,
desc,
minDate: new Date(2020, 0, 1),
maxDate: new Date(2025, 10, 1),
open: (index) => {
show.value = true;
},
confirm: (res) => {
desc.value = val.join('-');
}
};
}
});
</script>
```
### 时间选择-12小时制
```html
<nut-datepicker
v-model="currentDate"
type="time"
:minDate="minDate"
:maxDate="maxDate"
:is-use12-hours="true"
@confirm="confirm"
v-model:is-visible="show"
></nut-datepicker>
```
### 时间选择-分钟数递增步长设置
```html
<nut-datepicker
v-model="currentDate"
type="time"
:minute-step="5"
:minDate="minDate"
:maxDate="maxDate"
:is-use12-hours="true"
@confirm="confirm"
v-model:is-visible="show"
></nut-datepicker>
```
## API
### Props
| 参数 | 说明 | 类型 | 默认值 |
|--------------|----------------------------------|--------|------------------|
| type | 类型,日期'date', 日期时间'datetime',时间'time' | String |'date' |isVisible | 是否可见 | Boolean | boolean | false |
| isUse12Hours | 是否十二小时制度,只限类型为'time'时使用 | boolean | false |
| minuteStep | 分钟步进值 | number | 1 |
| isShowChinese | 每列是否展示中文 | boolean | false |
| title | 设置标题 | string | null |
| startDate | 开始日期 | Date | 十年前 |
| endDate | 结束日期 | Date | 十年后 |
### Events
| 事件名 | 说明 | 回调参数 |
|--------|----------------|--------------|
| confirm | 点击确定按钮时触发 | event: Event |
| close | 关闭时触发 | event: Event |
\ No newline at end of file
<template>
<view-block>
<nut-picker
:is-visible="show"
@close="closeHandler"
:list-data="columns"
@change="changeHandler"
:title="title"
@confirm="confirm"
></nut-picker>
</view-block>
</template>
<script lang="ts">
import { toRefs, watch, ref, computed } from 'vue';
import picker from '@/packages/picker/index.vue';
import { createComponent } from '@/utils/create';
const { componentName, create } = createComponent('datepicker');
const currentYear = new Date().getFullYear();
function isDate(val: Date): val is Date {
return (
Object.prototype.toString.call(val) === '[object Date]' &&
!isNaN(val.getTime())
);
}
const zhCNType = {
day: '',
year: '',
month: '',
hour: '',
minute: '',
seconds: ''
};
export default create({
children: [picker],
props: {
modelValue: null,
isVisible: {
type: Boolean,
default: false
},
title: {
type: String,
default: ''
},
type: {
type: String,
default: 'date'
},
isUse12Hours: {
type: Boolean,
default: false
},
isShowChinese: {
type: Boolean,
default: true
},
minuteStep: {
type: Number,
default: 1
},
minDate: {
type: Date,
default: () => new Date(currentYear - 10, 0, 1),
validator: isDate
},
maxDate: {
type: Date,
default: () => new Date(currentYear + 10, 11, 31),
validator: isDate
}
},
components: {},
emits: ['click', 'close', 'update:isVisible', 'confirm'],
setup(props, { emit }) {
const show = ref(false);
const title = ref(props.title);
const formatValue = value => {
if (!isDate(value)) {
value = props.minDate;
}
value = Math.max(value, (props.minDate as any).getTime());
value = Math.min(value, (props.maxDate as any).getTime());
return new Date(value);
};
const currentDate = ref(formatValue(props.modelValue));
watch(
() => props.title,
val => {
title.value = val;
}
);
watch(
() => props.isVisible,
val => {
show.value = val;
}
);
function getMonthEndDay(year: number, month: number): number {
return 32 - new Date(year, month - 1, 32).getDate();
}
const getBoundary = (type, value) => {
const boundary = props[`${type}Date`];
const year = boundary.getFullYear();
let month = 1;
let date = 1;
let hour = 0;
let minute = 0;
if (type === 'max') {
month = 12;
date = getMonthEndDay(value.getFullYear(), value.getMonth() + 1);
hour = 23;
minute = 59;
}
const seconds = minute;
if (value.getFullYear() === year) {
month = boundary.getMonth() + 1;
if (value.getMonth() + 1 === month) {
date = boundary.getDate();
if (value.getDate() === date) {
hour = boundary.getHours();
if (value.getHours() === hour) {
minute = boundary.getMinutes();
}
}
}
}
return {
[`${type}Year`]: year,
[`${type}Month`]: month,
[`${type}Date`]: date,
[`${type}Hour`]: hour,
[`${type}Minute`]: minute,
[`${type}Seconds`]: seconds
};
};
const ranges = computed(() => {
const {
maxYear,
maxDate,
maxMonth,
maxHour,
maxMinute,
maxSeconds
} = getBoundary('max', currentDate.value);
const {
minYear,
minDate,
minMonth,
minHour,
minMinute,
minSeconds
} = getBoundary('min', currentDate.value);
let result = [
{
type: 'year',
range: [minYear, maxYear]
},
{
type: 'month',
range: [minMonth, maxMonth]
},
{
type: 'day',
range: [minDate, maxDate]
},
{
type: 'hour',
range: [minHour, maxHour]
},
{
type: 'minute',
range: [minMinute, maxMinute]
},
{
type: 'seconds',
range: [minSeconds, maxSeconds]
}
];
switch (props.type) {
case 'date':
result = result.slice(0, 3);
break;
case 'datetime':
result = result.slice(0, 5);
break;
case 'time':
if (props.isUse12Hours) {
result = result.slice(3, 5);
} else {
result = result.slice(3, 6);
}
break;
case 'month-day':
result = result.slice(1, 3);
break;
case 'datehour':
result = result.slice(0, 4);
break;
}
return result;
});
const changeHandler = val => {
let formatDate = [];
if (props.isShowChinese) {
formatDate = val.forEach((res: string) => {
Number(res.slice(0, res.length - 2));
});
} else {
formatDate = val;
}
currentDate.value = formatValue(
new Date(formatDate[0], formatDate[1] - 1, formatDate[2])
);
};
const generateValue = (
min: number,
max: number,
val: number,
type: string
) => {
if (!(max > min)) return;
const arr: Array<number | string> = [];
let index = 0;
// let stopAdd = false;
while (min <= max) {
if (props.isShowChinese) {
arr.push(min + zhCNType[type]);
} else {
arr.push(min);
}
if (type === 'minute') {
min += props.minuteStep;
} else {
min++;
}
if (min <= val) {
index++;
}
}
return { values: arr, defaultIndex: index };
};
const getDateIndex = type => {
if (type === 'year') {
return currentDate.value.getFullYear();
} else if (type === 'month') {
return currentDate.value.getMonth() + 1;
} else if (type === 'day') {
return currentDate.value.getDate();
} else if (type === 'hour') {
return currentDate.value.getHours();
} else if (type === 'minute') {
return currentDate.value.getMinutes();
} else if (type === 'seconds') {
return currentDate.value.getSeconds();
}
return 0;
};
const columns = computed(() => {
const val = ranges.value.map(res => {
return generateValue(
res.range[0],
res.range[1],
getDateIndex(res.type),
res.type
);
});
if (props.type === 'time' && props.isUse12Hours) {
val.push({ values: ['上午', '下午'], defaultIndex: 0 });
}
return val;
});
const handleClick = (event: Event) => {
emit('click', event);
};
return {
show,
title,
changeHandler,
closeHandler: () => {
emit('update:isVisible', false);
},
confirm: val => {
emit('update:isVisible', false);
emit('confirm', val);
},
columns
};
}
});
</script>
<style lang="scss">
@import 'index.scss';
</style>
<template>
<view
<view-block
class="nut-picker__content"
:style="{ height: height + 'px' }"
@touchstart="onTouchStart"
......@@ -8,15 +8,15 @@
@touchcancel="onTouchEnd"
@transitionend="stopMomentum"
>
<view class="nut-picker__wrapper" ref="wrapper" :style="wrapperStyle">
<view
<view-block class="nut-picker__wrapper" ref="wrapper" :style="wrapperStyle">
<view-block
class="nut-picker__item"
:key="index"
v-for="(item, index) in state.options"
>{{ dataType === 'cascade' ? item.text : item }}</view
>{{ dataType === 'cascade' ? item.text : item }}</view-block
>
</view>
</view>
</view-block>
</view-block>
</template>
<script lang="ts">
import { reactive, ref, watch, computed } from 'vue';
......
......@@ -38,6 +38,7 @@
position: relative;
}
&__columnitem {
width: 0;
flex-grow: 1;
height: 100%;
}
......
<template>
<view>
<view-block class="nut-picker">
<nut-popup
position="bottom"
:style="{ height: height + 56 + 'px' }"
v-model:show="show"
@close="close"
>
<view class="nut-picker__bar">
<view class="nut-picker__left" @click="close()"> 取消</view>
<view> {{ title }}</view>
<view @click="confirm()"> 确定</view>
</view>
<view-block class="nut-picker__bar">
<view-block class="nut-picker__left" @click="close()"> 取消</view-block>
<view-block> {{ title }}</view-block>
<view-block @click="confirm()"> 确定</view-block>
</view-block>
<view class="nut-picker__column">
<view
<view-block class="nut-picker__column">
<view-block
class="nut-picker__mask"
:style="{ backgroundSize: `100% ${top}px` }"
></view>
<view class="nut-picker__hairline" :style="{ top: ` ${top}px` }"></view>
<view
></view-block>
<view-block
class="nut-picker__hairline"
:style="{ top: ` ${top}px` }"
></view-block>
<view-block
class="nut-picker__columnitem"
v-for="(item, columnIndex) in columnList"
:key="columnIndex"
......@@ -36,10 +39,10 @@
}
"
></nut-picker-column>
</view>
</view>
</view-block>
</view-block>
</nut-popup>
</view>
</view-block>
</template>
<script lang="ts">
import { reactive, ref, watch, computed, toRaw } from 'vue';
......@@ -62,12 +65,13 @@ export default create({
},
...commonProps
},
emits: ['close', 'confirm', 'update:isVisible'],
components: { column },
emits: ['close', 'change', 'confirm', 'update:isVisible'],
setup(props, { emit }) {
const show = ref(false);
const defaultIndex = ref(props.defaultIndex);
const listData: any = reactive(props.listData);
const formattedColumns: any = ref(props.listData);
//临时变量,当点击确定时候赋值
let _defaultIndex = props.defaultIndex;
const childrenKey = 'children';
......@@ -81,6 +85,13 @@ export default create({
}
);
watch(
() => props.listData,
val => {
formattedColumns.value = val;
}
);
const addDefaultIndexList = listData => {
defaultIndexList = [];
listData.forEach(res => {
......@@ -88,7 +99,7 @@ export default create({
});
};
const dataType = computed(() => {
const firstColumn = listData[0] || {};
const firstColumn = formattedColumns.value[0] || {};
if (typeof firstColumn === 'object') {
if (firstColumn?.[childrenKey]) {
......@@ -118,13 +129,15 @@ export default create({
const columnList = computed(() => {
if (dataType.value === 'text') {
return [{ values: listData, defaultIndex: defaultIndex.value }];
return [
{ values: formattedColumns.value, defaultIndex: defaultIndex.value }
];
} else if (dataType.value === 'multipleColumns') {
return listData;
return formattedColumns.value;
} else if (dataType.value === 'cascade') {
return formatCascade(listData, defaultIndex.value);
return formatCascade(formattedColumns.value, defaultIndex.value);
}
return listData;
return formattedColumns.value;
});
const getCascadeData = (listData, defaultIndex) => {
let arr = listData;
......@@ -152,7 +165,7 @@ export default create({
},
changeHandler: (columnIndex, dataIndex) => {
if (dataType.value === 'cascade') {
let cursor: any = listData;
let cursor: any = toRaw(formattedColumns.value);
//最外层使用props.defaultIndex作为初始index
if (columnIndex === 0) {
defaultIndex.value = dataIndex;
......@@ -172,22 +185,32 @@ export default create({
_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', listData[_defaultIndex]);
emit('confirm', formattedColumns.value[_defaultIndex]);
} else if (dataType.value === 'multipleColumns') {
for (let i = 0; i < defaultIndexList.length; i++) {
listData[i].defaultIndex = defaultIndexList[i];
formattedColumns.value[i].defaultIndex = defaultIndexList[i];
}
const checkedArr = toRaw(listData).map(
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(listData), defaultIndex.value));
emit(
'confirm',
getCascadeData(toRaw(formattedColumns.value), defaultIndex.value)
);
}
emit('update:isVisible', false);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册