mirror of
https://gitee.com/vant-contrib/vant.git
synced 2026-07-12 17:02:37 +08:00
Compare commits
34 Commits
f327d5bbc2
...
3979ddc40a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3979ddc40a | ||
|
|
967cb56c18 | ||
|
|
c65d4f9672 | ||
|
|
73788f9e88 | ||
|
|
2acd47b5ee | ||
|
|
b3cf1dfb54 | ||
|
|
35a50012c7 | ||
|
|
83295e655d | ||
|
|
e2790fe95d | ||
|
|
b608fb23f0 | ||
|
|
829db06d5b | ||
|
|
2e053ad76b | ||
|
|
c942efb76b | ||
|
|
fdcf9931be | ||
|
|
e61bd487fa | ||
|
|
7deba88b91 | ||
|
|
980f976011 | ||
|
|
c00fa4cd70 | ||
|
|
ef8e66a924 | ||
|
|
e8ffc80dc6 | ||
|
|
3920ebe25b | ||
|
|
0a50e721aa | ||
|
|
9fa7840c61 | ||
|
|
5b407566db | ||
|
|
3038048b38 | ||
|
|
ba4e83df52 | ||
|
|
c51f1cfa5f | ||
|
|
5b7c8883a3 | ||
|
|
8acb93c43d | ||
|
|
a5ee18f31c | ||
|
|
0603479ee4 | ||
|
|
c1be4112e4 | ||
|
|
fbafac333f | ||
|
|
7e7c4e3b83 |
@ -27,7 +27,7 @@
|
||||
"author": "chenjiahan",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.13.15",
|
||||
"esbuild": "^0.14.21",
|
||||
"release-it": "^14.2.2",
|
||||
"typescript": "~4.5.2"
|
||||
},
|
||||
|
||||
@ -47,8 +47,8 @@
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.16.0",
|
||||
"@babel/preset-typescript": "^7.16.0",
|
||||
"@docsearch/css": "3.0.0-alpha.41",
|
||||
"@docsearch/js": "3.0.0-alpha.41",
|
||||
"@docsearch/css": "3.0.0-alpha.50",
|
||||
"@docsearch/js": "3.0.0-alpha.50",
|
||||
"@types/jest": "^27.0.3",
|
||||
"@vant/eslint-config": "^3.3.2",
|
||||
"@vant/markdown-vetur": "^2.2.0",
|
||||
@ -62,7 +62,7 @@
|
||||
"commander": "^8.3.0",
|
||||
"consola": "^2.15.3",
|
||||
"conventional-changelog": "^3.1.24",
|
||||
"esbuild": "^0.14.2",
|
||||
"esbuild": "^0.14.21",
|
||||
"eslint": "^8.1.0",
|
||||
"execa": "^5.1.1",
|
||||
"fast-glob": "^3.2.7",
|
||||
@ -86,7 +86,7 @@
|
||||
"stylelint": "^13.0.0",
|
||||
"transliteration": "^2.2.0",
|
||||
"typescript": "^4.5.2",
|
||||
"vite": "^2.7.10",
|
||||
"vite": "^2.8.0",
|
||||
"vite-plugin-html": "^2.1.1",
|
||||
"vite-plugin-md": "^0.11.4",
|
||||
"vue-router": "^4.0.12"
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
"@popperjs/core": "^2.9.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.13.15",
|
||||
"esbuild": "^0.14.21",
|
||||
"release-it": "^14.2.2",
|
||||
"typescript": "~4.5.2"
|
||||
},
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
"author": "chenjiahan",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.13.15",
|
||||
"esbuild": "^0.14.21",
|
||||
"release-it": "^14.0.2",
|
||||
"typescript": "~4.5.2",
|
||||
"vue": "^3.2.27"
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
其他:
|
||||
|
||||
- ActionBar: 调整 `--van-action-bar-icon-text-color` 变量的默认值为 `--van-text-color`
|
||||
- AddressEdit: 提交按钮调整为 `primary` 类型
|
||||
- Button: 默认圆角大小从 `2px` 调整为 `4px`
|
||||
- Button: 默认按钮的边框颜色调整为 `--van-gray-4`
|
||||
- Button: 调整 `font-smoothing`,默认使用粗体文字
|
||||
@ -34,5 +35,8 @@
|
||||
- Card: 调整 `--van-card-price-color` 变量的默认值为 `--van-text-color`
|
||||
- Card: 调整 `--van-card-desc-color` 变量的默认值为 `--van-text-color-2`
|
||||
- Field: 调整 `--van-field-label-color` 变量的默认值为 `--van-text-color`
|
||||
- Switch: 移除 `--van-switch-border` 变量
|
||||
- Switch: 调整 `--van-switch-size` 变量的默认值为 `26px`
|
||||
- Switch: 调整 `--van-switch-background` 变量的默认值为 `rgba(120, 120, 128, 0.16)`
|
||||
- Tabbar: 调整 `--van-tabbar-item-text-color` 变量的默认值为 `--van-text-color`
|
||||
- GridItem: 调整 `--van-grid-item-text-color` 变量的默认值为 `--van-text-color`
|
||||
|
||||
@ -16,6 +16,30 @@ Vant follows [Semantic Versioning 2.0.0](https://semver.org/lang/zh-CN/).
|
||||
|
||||
## Details
|
||||
|
||||
### [v3.4.5](https://github.com/compare/v3.4.3...v3.4.5)
|
||||
|
||||
`2022-02-14`
|
||||
|
||||
**Feature**
|
||||
|
||||
- CollapseItem: add lazy-render prop [#10270](https://github.com/issues/10270)
|
||||
- Dialog: support enter/esc keyboard event [#10261](https://github.com/issues/10261)
|
||||
- Locale: add Bangla (Bangladesh) [#10287](https://github.com/issues/10287)
|
||||
- Locale: add Indonesian translation [#10289](https://github.com/issues/10289)
|
||||
- Locale: add Italian translation [#10288](https://github.com/issues/10288)
|
||||
- Locale: add Ukrainian translation [#10291](https://github.com/issues/10291)
|
||||
- Locale: add Vietnamese translation [#10294](https://github.com/issues/10294)
|
||||
|
||||
**style**
|
||||
|
||||
- SubmitBar: using english colon [#10286](https://github.com/issues/10286)
|
||||
|
||||
**Types**
|
||||
|
||||
- IndexBar: index-list prop can includes number [#10273](https://github.com/issues/10273)
|
||||
- Toast: fix missing teleport prop [#10272](https://github.com/issues/10272)
|
||||
- Uploader: fix UploaderBeforeRead type [#10274](https://github.com/issues/10274)
|
||||
|
||||
### [v3.4.4](https://github.com/youzan/vant/compare/v3.4.4...v3.4.3)
|
||||
|
||||
`2022-02-07`
|
||||
|
||||
@ -16,6 +16,30 @@ Vant 遵循 [Semver](https://semver.org/lang/zh-CN/) 语义化版本规范。
|
||||
|
||||
## 更新内容
|
||||
|
||||
### [v3.4.5](https://github.com/compare/v3.4.3...v3.4.5)
|
||||
|
||||
`2022-02-14`
|
||||
|
||||
**Feature**
|
||||
|
||||
- CollapseItem: 新增 lazy-render 属性 [#10270](https://github.com/issues/10270)
|
||||
- Dialog: 支持通过 enter/esc 进行键盘操作 [#10261](https://github.com/issues/10261)
|
||||
- Locale: 新增 Bangla 孟加拉语 [#10287](https://github.com/issues/10287)
|
||||
- Locale: 新增 Indonesian 印度尼西亚语 [#10289](https://github.com/issues/10289)
|
||||
- Locale: 新增 Italian 意大利语 [#10288](https://github.com/issues/10288)
|
||||
- Locale: 新增 Ukrainian 乌克兰语 [#10291](https://github.com/issues/10291)
|
||||
- Locale: 新增 Vietnamese 越南语 [#10294](https://github.com/issues/10294)
|
||||
|
||||
**style**
|
||||
|
||||
- SubmitBar: 将冒号调整为英文冒号 [#10286](https://github.com/issues/10286)
|
||||
|
||||
**Types**
|
||||
|
||||
- IndexBar: 修复 index-list 属性无法传入 number 类型的问题 [#10273](https://github.com/issues/10273)
|
||||
- Toast: 修复缺少 teleport 属性定义的问题 [#10272](https://github.com/issues/10272)
|
||||
- Uploader: 修复 UploaderBeforeRead 类型定义不准确的问题 [#10274](https://github.com/issues/10274)
|
||||
|
||||
### [v3.4.4](https://github.com/youzan/vant/compare/v3.4.4...v3.4.3)
|
||||
|
||||
`2022-02-07`
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
- 支持通过 `v-model` 绑定当前选中的值,移除 `default-index` 属性
|
||||
- 重新定义了 `columns` 属性的结构
|
||||
- 移除了操作内部数据的实例方法,仅保留 `confirm` 方法
|
||||
- 新增 `getSelectedOptions` 实例方法
|
||||
- 调整了 `confirm`、`cancel`、`change` 事件的参数
|
||||
- 重命名 `item-height` 属性为 `option-height`
|
||||
- 重命名 `visible-item-count` 属性为 `visible-option-num`
|
||||
@ -89,6 +90,11 @@ emit('clickInput');
|
||||
|
||||
- 移除了 `click` 和 `disabled` 事件,请使用 `click-tab` 事件代替
|
||||
|
||||
#### AddressEdit
|
||||
|
||||
- `change-area` 事件的参数调整为 `PickerOption[]` 类型
|
||||
- 移除未在文档中标注的 `getArea` 实例方法
|
||||
|
||||
## 样式变量调整
|
||||
|
||||
### 移除 Less 变量
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vant",
|
||||
"version": "3.4.4",
|
||||
"version": "3.4.5",
|
||||
"description": "Mobile UI Components built on Vue",
|
||||
"main": "lib/vant.cjs.js",
|
||||
"module": "es/index.js",
|
||||
|
||||
@ -25,7 +25,7 @@ import {
|
||||
import { useExpose } from '../composables/use-expose';
|
||||
|
||||
// Components
|
||||
import { Area, AreaList, AreaColumnOption, AreaInstance } from '../area';
|
||||
import { Area, AreaList, AreaInstance } from '../area';
|
||||
import { Cell } from '../cell';
|
||||
import { Form } from '../form';
|
||||
import { Field, FieldRule } from '../field';
|
||||
@ -37,6 +37,8 @@ import AddressEditDetail from './AddressEditDetail';
|
||||
|
||||
// Types
|
||||
import type { AddressEditInfo, AddressEditSearchItem } from './types';
|
||||
import { PickerConfirmEventParams, PickerOption } from '../picker';
|
||||
import { AREA_EMPTY_CODE } from '../area/utils';
|
||||
|
||||
const [name, bem, t] = createNamespace('address-edit');
|
||||
|
||||
@ -121,9 +123,9 @@ export default defineComponent({
|
||||
);
|
||||
|
||||
const areaText = computed(() => {
|
||||
const { country, province, city, county, areaCode } = data;
|
||||
const { province, city, county, areaCode } = data;
|
||||
if (areaCode) {
|
||||
const arr = [country, province, city, county];
|
||||
const arr = [province, city, county];
|
||||
if (province && province === city) {
|
||||
arr.splice(1, 1);
|
||||
}
|
||||
@ -137,15 +139,6 @@ export default defineComponent({
|
||||
() => props.searchResult?.length && detailFocused.value
|
||||
);
|
||||
|
||||
const assignAreaValues = () => {
|
||||
if (areaRef.value) {
|
||||
const detail: Record<string, string> = areaRef.value.getArea();
|
||||
detail.areaCode = detail.code;
|
||||
delete detail.code;
|
||||
extend(data, detail);
|
||||
}
|
||||
};
|
||||
|
||||
const onFocus = (key: string) => {
|
||||
detailFocused.value = key === 'addressDetail';
|
||||
emit('focus', key);
|
||||
@ -191,30 +184,30 @@ export default defineComponent({
|
||||
emit('changeDetail', val);
|
||||
};
|
||||
|
||||
const onAreaConfirm = (values: AreaColumnOption[]) => {
|
||||
values = values.filter(Boolean);
|
||||
const assignAreaText = (options: PickerOption[]) => {
|
||||
data.province = options[0].text as string;
|
||||
data.city = options[1].text as string;
|
||||
data.county = options[2].text as string;
|
||||
};
|
||||
|
||||
if (values.some((value) => !value.code)) {
|
||||
const onAreaConfirm = ({
|
||||
selectedValues,
|
||||
selectedOptions,
|
||||
}: PickerConfirmEventParams) => {
|
||||
if (selectedValues.some((value) => value === AREA_EMPTY_CODE)) {
|
||||
Toast(t('areaEmpty'));
|
||||
} else {
|
||||
showAreaPopup.value = false;
|
||||
assignAreaValues();
|
||||
emit('changeArea', values);
|
||||
assignAreaText(selectedOptions as PickerOption[]);
|
||||
emit('changeArea', selectedOptions);
|
||||
}
|
||||
};
|
||||
|
||||
const onDelete = () => emit('delete', data);
|
||||
|
||||
// get values of area component
|
||||
const getArea = () => areaRef.value?.getValues() || [];
|
||||
|
||||
// set area code to area component
|
||||
const setAreaCode = (code?: string) => {
|
||||
data.areaCode = code || '';
|
||||
|
||||
if (code) {
|
||||
nextTick(assignAreaValues);
|
||||
}
|
||||
};
|
||||
|
||||
const onDetailBlur = () => {
|
||||
@ -234,7 +227,6 @@ export default defineComponent({
|
||||
'right-icon': () => (
|
||||
<Switch
|
||||
v-model={data.isDefault}
|
||||
size="24"
|
||||
onChange={(event) => emit('changeDefault', event)}
|
||||
/>
|
||||
),
|
||||
@ -253,21 +245,25 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
useExpose({
|
||||
getArea,
|
||||
setAreaCode,
|
||||
setAddressDetail,
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.areaList,
|
||||
() => setAreaCode(data.areaCode)
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.addressInfo,
|
||||
(value) => {
|
||||
extend(data, DEFAULT_DATA, value);
|
||||
setAreaCode(value.areaCode);
|
||||
nextTick(() => {
|
||||
const options = areaRef.value?.getSelectedOptions();
|
||||
if (
|
||||
options &&
|
||||
options.every(
|
||||
(option) => option && option.value !== AREA_EMPTY_CODE
|
||||
)
|
||||
) {
|
||||
assignAreaText(options as PickerOption[]);
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
@ -346,7 +342,7 @@ export default defineComponent({
|
||||
<Button
|
||||
block
|
||||
round
|
||||
type="danger"
|
||||
type="primary"
|
||||
text={props.saveButtonText || t('save')}
|
||||
class={bem('button')}
|
||||
loading={props.isSaving}
|
||||
@ -371,8 +367,8 @@ export default defineComponent({
|
||||
lazyRender={false}
|
||||
>
|
||||
<Area
|
||||
v-model={data.areaCode}
|
||||
ref={areaRef}
|
||||
value={data.areaCode}
|
||||
loading={!areaListLoaded.value}
|
||||
areaList={props.areaList}
|
||||
columnsPlaceholder={props.areaColumnsPlaceholder}
|
||||
|
||||
@ -102,14 +102,14 @@ export default {
|
||||
|
||||
| Event | Description | Arguments |
|
||||
| --- | --- | --- |
|
||||
| save | Emitted when the save button is clicked | content:form content |
|
||||
| focus | Emitted when field is focused | key: field name |
|
||||
| delete | Emitted when confirming delete | content:form content |
|
||||
| select-search | Emitted when a search result is selected | value: search content |
|
||||
| save | Emitted when the save button is clicked | _info: AddressEditInfo_ |
|
||||
| focus | Emitted when field is focused | _key: string_ |
|
||||
| delete | Emitted when confirming delete | _info: AddressEditInfo_ |
|
||||
| select-search | Emitted when a search result is selected | _value: string_ |
|
||||
| click-area | Emitted when the area field is clicked | - |
|
||||
| change-area | Emitted when area changed | values: area values |
|
||||
| change-detail | Emitted when address detail changed | value: address detail |
|
||||
| change-default | Emitted when switching default address | value: checked |
|
||||
| change-area | Emitted when area changed | _selectedOptions: PickerOption[]_ |
|
||||
| change-detail | Emitted when address detail changed | _value: string_ |
|
||||
| change-default | Emitted when switching default address | _checked: boolean_ |
|
||||
|
||||
### Slots
|
||||
|
||||
|
||||
@ -100,16 +100,16 @@ export default {
|
||||
|
||||
### Events
|
||||
|
||||
| 事件名 | 说明 | 回调参数 |
|
||||
| -------------- | -------------------------- | --------------------------- |
|
||||
| save | 点击保存按钮时触发 | content:表单内容 |
|
||||
| focus | 输入框聚焦时触发 | key: 聚焦的输入框对应的 key |
|
||||
| delete | 确认删除地址时触发 | content:表单内容 |
|
||||
| select-search | 选中搜索结果时触发 | value: 搜索结果 |
|
||||
| click-area | 点击收件地区时触发 | - |
|
||||
| change-area | 修改收件地区时触发 | values: 地区信息 |
|
||||
| change-detail | 修改详细地址时触发 | value: 详细地址内容 |
|
||||
| change-default | 切换是否使用默认地址时触发 | value: 是否选中 |
|
||||
| 事件名 | 说明 | 回调参数 |
|
||||
| --- | --- | --- |
|
||||
| save | 点击保存按钮时触发 | _info: AddressEditInfo_ |
|
||||
| focus | 输入框聚焦时触发 | _key: string_ |
|
||||
| delete | 确认删除地址时触发 | _info: AddressEditInfo_ |
|
||||
| select-search | 选中搜索结果时触发 | _value: string_ |
|
||||
| click-area | 点击收件地区时触发 | - |
|
||||
| change-area | 修改收件地区时触发 | _selectedOptions: PickerOption[]_ |
|
||||
| change-detail | 修改详细地址时触发 | _value: string_ |
|
||||
| change-default | 切换是否使用默认地址时触发 | _checked: boolean_ |
|
||||
|
||||
### Slots
|
||||
|
||||
|
||||
@ -0,0 +1,148 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`should render demo and match snapshot 1`] = `
|
||||
<div>
|
||||
<form class="van-form van-address-edit">
|
||||
<div class="van-address-edit__fields">
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label">
|
||||
<label id="van-field-label"
|
||||
for="van-field-input"
|
||||
>
|
||||
Name
|
||||
</label>
|
||||
</div>
|
||||
<div class="van-cell__value van-field__value">
|
||||
<div class="van-field__body">
|
||||
<input type="text"
|
||||
id="van-field-input"
|
||||
class="van-field__control"
|
||||
placeholder="Name"
|
||||
aria-labelledby="van-field-label"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label">
|
||||
<label id="van-field-label"
|
||||
for="van-field-input"
|
||||
>
|
||||
Phone
|
||||
</label>
|
||||
</div>
|
||||
<div class="van-cell__value van-field__value">
|
||||
<div class="van-field__body">
|
||||
<input type="tel"
|
||||
id="van-field-input"
|
||||
class="van-field__control"
|
||||
placeholder="Phone"
|
||||
aria-labelledby="van-field-label"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-cell--clickable van-field"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="van-cell__title van-field__label">
|
||||
<label id="van-field-label"
|
||||
for="van-field-input"
|
||||
>
|
||||
Area
|
||||
</label>
|
||||
</div>
|
||||
<div class="van-cell__value van-field__value">
|
||||
<div class="van-field__body">
|
||||
<input type="text"
|
||||
id="van-field-input"
|
||||
class="van-field__control"
|
||||
readonly
|
||||
placeholder="Area"
|
||||
aria-labelledby="van-field-label"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<i class="van-badge__wrapper van-icon van-icon-arrow van-cell__right-icon">
|
||||
</i>
|
||||
</div>
|
||||
<div class="van-cell van-field van-address-edit-detail">
|
||||
<div class="van-cell__title van-field__label">
|
||||
<label id="van-field-label"
|
||||
for="van-field-input"
|
||||
>
|
||||
Address
|
||||
</label>
|
||||
</div>
|
||||
<div class="van-cell__value van-field__value">
|
||||
<div class="van-field__body">
|
||||
<textarea id="van-field-input"
|
||||
rows="1"
|
||||
class="van-field__control"
|
||||
placeholder="Address"
|
||||
aria-labelledby="van-field-label"
|
||||
style="height: auto;"
|
||||
>
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label">
|
||||
<label id="van-field-label"
|
||||
for="van-field-input"
|
||||
>
|
||||
Postal
|
||||
</label>
|
||||
</div>
|
||||
<div class="van-cell__value van-field__value">
|
||||
<div class="van-field__body">
|
||||
<input type="tel"
|
||||
id="van-field-input"
|
||||
class="van-field__control"
|
||||
placeholder="Postal"
|
||||
aria-labelledby="van-field-label"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-cell--center van-address-edit__default">
|
||||
<div class="van-cell__title">
|
||||
<span>
|
||||
Set as the default address
|
||||
</span>
|
||||
</div>
|
||||
<div role="switch"
|
||||
class="van-switch"
|
||||
tabindex="0"
|
||||
aria-checked="false"
|
||||
>
|
||||
<div class="van-switch__node">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-address-edit__buttons">
|
||||
<button type="submit"
|
||||
class="van-button van-button--primary van-button--normal van-button--block van-button--round van-address-edit__button"
|
||||
>
|
||||
<div class="van-button__content">
|
||||
<span class="van-button__text">
|
||||
Save
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
<button type="button"
|
||||
class="van-button van-button--default van-button--normal van-button--block van-button--round van-address-edit__button"
|
||||
>
|
||||
<div class="van-button__content">
|
||||
<span class="van-button__text">
|
||||
Delete
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
@ -0,0 +1,373 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`should allow to custom validator with validator prop 1`] = `
|
||||
<div class="van-field__error-message">
|
||||
foo name
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`should render AddressEdit correctly 1`] = `
|
||||
<form class="van-form van-address-edit">
|
||||
<div class="van-address-edit__fields">
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label">
|
||||
<label id="van-field-label"
|
||||
for="van-field-input"
|
||||
>
|
||||
Name
|
||||
</label>
|
||||
</div>
|
||||
<div class="van-cell__value van-field__value">
|
||||
<div class="van-field__body">
|
||||
<input type="text"
|
||||
id="van-field-input"
|
||||
class="van-field__control"
|
||||
placeholder="Name"
|
||||
aria-labelledby="van-field-label"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label">
|
||||
<label id="van-field-label"
|
||||
for="van-field-input"
|
||||
>
|
||||
Phone
|
||||
</label>
|
||||
</div>
|
||||
<div class="van-cell__value van-field__value">
|
||||
<div class="van-field__body">
|
||||
<input type="tel"
|
||||
id="van-field-input"
|
||||
class="van-field__control"
|
||||
placeholder="Phone"
|
||||
aria-labelledby="van-field-label"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-cell--clickable van-field"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="van-cell__title van-field__label">
|
||||
<label id="van-field-label"
|
||||
for="van-field-input"
|
||||
>
|
||||
Area
|
||||
</label>
|
||||
</div>
|
||||
<div class="van-cell__value van-field__value">
|
||||
<div class="van-field__body">
|
||||
<input type="text"
|
||||
id="van-field-input"
|
||||
class="van-field__control"
|
||||
readonly
|
||||
placeholder="Area"
|
||||
aria-labelledby="van-field-label"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<i class="van-badge__wrapper van-icon van-icon-arrow van-cell__right-icon">
|
||||
</i>
|
||||
</div>
|
||||
<div class="van-cell van-field van-address-edit-detail">
|
||||
<div class="van-cell__title van-field__label">
|
||||
<label id="van-field-label"
|
||||
for="van-field-input"
|
||||
>
|
||||
Address
|
||||
</label>
|
||||
</div>
|
||||
<div class="van-cell__value van-field__value">
|
||||
<div class="van-field__body">
|
||||
<textarea id="van-field-input"
|
||||
rows="1"
|
||||
class="van-field__control"
|
||||
placeholder="Address"
|
||||
aria-labelledby="van-field-label"
|
||||
>
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-address-edit__buttons">
|
||||
<button type="submit"
|
||||
class="van-button van-button--primary van-button--normal van-button--block van-button--round van-address-edit__button"
|
||||
>
|
||||
<div class="van-button__content">
|
||||
<span class="van-button__text">
|
||||
Save
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
|
||||
exports[`should render AddressEdit with props correctly 1`] = `
|
||||
<form class="van-form van-address-edit">
|
||||
<div class="van-address-edit__fields">
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label">
|
||||
<label id="van-field-label"
|
||||
for="van-field-input"
|
||||
>
|
||||
Name
|
||||
</label>
|
||||
</div>
|
||||
<div class="van-cell__value van-field__value">
|
||||
<div class="van-field__body">
|
||||
<input type="text"
|
||||
id="van-field-input"
|
||||
class="van-field__control"
|
||||
placeholder="Name"
|
||||
aria-labelledby="van-field-label"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label">
|
||||
<label id="van-field-label"
|
||||
for="van-field-input"
|
||||
>
|
||||
Phone
|
||||
</label>
|
||||
</div>
|
||||
<div class="van-cell__value van-field__value">
|
||||
<div class="van-field__body">
|
||||
<input type="tel"
|
||||
id="van-field-input"
|
||||
class="van-field__control"
|
||||
placeholder="Phone"
|
||||
aria-labelledby="van-field-label"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-cell--clickable van-field"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="van-cell__title van-field__label">
|
||||
<label id="van-field-label"
|
||||
for="van-field-input"
|
||||
>
|
||||
Area
|
||||
</label>
|
||||
</div>
|
||||
<div class="van-cell__value van-field__value">
|
||||
<div class="van-field__body">
|
||||
<input type="text"
|
||||
id="van-field-input"
|
||||
class="van-field__control"
|
||||
readonly
|
||||
placeholder="Area"
|
||||
aria-labelledby="van-field-label"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<i class="van-badge__wrapper van-icon van-icon-arrow van-cell__right-icon">
|
||||
</i>
|
||||
</div>
|
||||
<div class="van-cell van-field van-address-edit-detail">
|
||||
<div class="van-cell__title van-field__label">
|
||||
<label id="van-field-label"
|
||||
for="van-field-input"
|
||||
>
|
||||
Address
|
||||
</label>
|
||||
</div>
|
||||
<div class="van-cell__value van-field__value">
|
||||
<div class="van-field__body">
|
||||
<textarea id="van-field-input"
|
||||
rows="1"
|
||||
class="van-field__control"
|
||||
placeholder="Address"
|
||||
aria-labelledby="van-field-label"
|
||||
>
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label">
|
||||
<label id="van-field-label"
|
||||
for="van-field-input"
|
||||
>
|
||||
Postal
|
||||
</label>
|
||||
</div>
|
||||
<div class="van-cell__value van-field__value">
|
||||
<div class="van-field__body">
|
||||
<input type="tel"
|
||||
id="van-field-input"
|
||||
class="van-field__control"
|
||||
placeholder="Postal"
|
||||
aria-labelledby="van-field-label"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-cell--center van-address-edit__default">
|
||||
<div class="van-cell__title">
|
||||
<span>
|
||||
Set as the default address
|
||||
</span>
|
||||
</div>
|
||||
<div role="switch"
|
||||
class="van-switch van-switch--on"
|
||||
tabindex="0"
|
||||
aria-checked="true"
|
||||
>
|
||||
<div class="van-switch__node">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-address-edit__buttons">
|
||||
<button type="submit"
|
||||
class="van-button van-button--primary van-button--normal van-button--block van-button--round van-address-edit__button"
|
||||
>
|
||||
<div class="van-button__content">
|
||||
<span class="van-button__text">
|
||||
Save
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
|
||||
exports[`should valid address detail and render error message correctly 1`] = `
|
||||
<div class="van-cell van-field van-address-edit-detail">
|
||||
<div class="van-cell__title van-field__label">
|
||||
<label id="van-field-label"
|
||||
for="van-field-input"
|
||||
>
|
||||
Address
|
||||
</label>
|
||||
</div>
|
||||
<div class="van-cell__value van-field__value">
|
||||
<div class="van-field__body">
|
||||
<textarea id="van-field-input"
|
||||
rows="1"
|
||||
class="van-field__control"
|
||||
placeholder="Address"
|
||||
aria-labelledby="van-field-label"
|
||||
style="height: auto;"
|
||||
>
|
||||
</textarea>
|
||||
</div>
|
||||
<div class="van-field__error-message">
|
||||
Address can not be empty
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`should valid area code and render error message correctly 1`] = `
|
||||
<div class="van-cell van-cell--clickable van-field"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="van-cell__title van-field__label">
|
||||
<label id="van-field-label"
|
||||
for="van-field-input"
|
||||
>
|
||||
Area
|
||||
</label>
|
||||
</div>
|
||||
<div class="van-cell__value van-field__value">
|
||||
<div class="van-field__body">
|
||||
<input type="text"
|
||||
id="van-field-input"
|
||||
class="van-field__control"
|
||||
readonly
|
||||
placeholder="Area"
|
||||
aria-labelledby="van-field-label"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<i class="van-badge__wrapper van-icon van-icon-arrow van-cell__right-icon">
|
||||
</i>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`should valid name and render error message correctly 1`] = `
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label">
|
||||
<label id="van-field-label"
|
||||
for="van-field-input"
|
||||
>
|
||||
Name
|
||||
</label>
|
||||
</div>
|
||||
<div class="van-cell__value van-field__value">
|
||||
<div class="van-field__body">
|
||||
<input type="text"
|
||||
id="van-field-input"
|
||||
class="van-field__control"
|
||||
placeholder="Name"
|
||||
aria-labelledby="van-field-label"
|
||||
>
|
||||
</div>
|
||||
<div class="van-field__error-message">
|
||||
Please fill in the name
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`should valid postal code and render error message correctly 1`] = `
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label">
|
||||
<label id="van-field-label"
|
||||
for="van-field-input"
|
||||
>
|
||||
Postal
|
||||
</label>
|
||||
</div>
|
||||
<div class="van-cell__value van-field__value">
|
||||
<div class="van-field__body">
|
||||
<input type="tel"
|
||||
id="van-field-input"
|
||||
class="van-field__control"
|
||||
placeholder="Postal"
|
||||
aria-labelledby="van-field-label"
|
||||
>
|
||||
</div>
|
||||
<div class="van-field__error-message">
|
||||
Wrong postal code
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`should valid tel and render error message correctly 1`] = `
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label">
|
||||
<label id="van-field-label"
|
||||
for="van-field-input"
|
||||
>
|
||||
Phone
|
||||
</label>
|
||||
</div>
|
||||
<div class="van-cell__value van-field__value">
|
||||
<div class="van-field__body">
|
||||
<input type="tel"
|
||||
id="van-field-input"
|
||||
class="van-field__control"
|
||||
placeholder="Phone"
|
||||
aria-labelledby="van-field-label"
|
||||
>
|
||||
</div>
|
||||
<div class="van-field__error-message">
|
||||
Malformed phone number
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -121,32 +121,6 @@ test('should emit changeDetail event after changing address detail', () => {
|
||||
expect(wrapper.emitted('changeDetail')[0][0]).toEqual('123');
|
||||
});
|
||||
|
||||
test('should return current areas after calling getArea method', () => {
|
||||
const wrapper = mount(AddressEdit, {
|
||||
props: { areaList },
|
||||
});
|
||||
|
||||
expect(wrapper.vm.getArea()).toEqual([
|
||||
{ code: '110000', name: '北京市' },
|
||||
{ code: '110100', name: '北京市' },
|
||||
{ code: '110101', name: '东城区' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('should update current areas after calling setAreaCode method', async () => {
|
||||
const wrapper = mount(AddressEdit, {
|
||||
props: { areaList },
|
||||
});
|
||||
|
||||
wrapper.vm.setAreaCode('110102');
|
||||
await later();
|
||||
expect(wrapper.vm.getArea()).toEqual([
|
||||
{ code: '110000', name: '北京市' },
|
||||
{ code: '110100', name: '北京市' },
|
||||
{ code: '110102', name: '西城区' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('should show search result after focusing to address detail', async () => {
|
||||
const wrapper = mount(AddressEdit, {
|
||||
props: {
|
||||
@ -1,5 +1,4 @@
|
||||
import type { ComponentPublicInstance } from 'vue';
|
||||
import type { AreaColumnOption } from '../area';
|
||||
import type { AddressEditProps } from './AddressEdit';
|
||||
|
||||
export type AddressEditSearchItem = {
|
||||
@ -21,7 +20,6 @@ export type AddressEditInfo = {
|
||||
};
|
||||
|
||||
export type AddressEditExpose = {
|
||||
getArea: () => AreaColumnOption[];
|
||||
setAreaCode: (code?: string | undefined) => void;
|
||||
setAddressDetail: (value: string) => void;
|
||||
};
|
||||
|
||||
@ -18,11 +18,15 @@ import {
|
||||
import { pickerSharedProps } from '../picker/Picker';
|
||||
import { INHERIT_PROPS, INHERIT_SLOTS, formatDataForCascade } from './utils';
|
||||
|
||||
// Composables
|
||||
import { useExpose } from '../composables/use-expose';
|
||||
|
||||
// Components
|
||||
import { Picker } from '../picker';
|
||||
import { Picker, type PickerInstance } from '../picker';
|
||||
|
||||
// Types
|
||||
import type { AreaList } from './types';
|
||||
import type { PickerExpose } from '../picker/types';
|
||||
|
||||
const [name, bem] = createNamespace('area');
|
||||
|
||||
@ -47,6 +51,8 @@ export default defineComponent({
|
||||
|
||||
setup(props, { emit, slots }) {
|
||||
const codes = ref<string[]>([]);
|
||||
const picker = ref<PickerInstance>();
|
||||
|
||||
const columns = computed(() => formatDataForCascade(props));
|
||||
const onChange = (...args: unknown[]) => emit('change', ...args);
|
||||
const onCancel = (...args: unknown[]) => emit('cancel', ...args);
|
||||
@ -84,8 +90,14 @@ export default defineComponent({
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
useExpose<PickerExpose>({
|
||||
confirm: () => picker.value?.confirm(),
|
||||
getSelectedOptions: () => picker.value?.getSelectedOptions() || [],
|
||||
});
|
||||
|
||||
return () => (
|
||||
<Picker
|
||||
ref={picker}
|
||||
v-model={codes.value}
|
||||
v-slots={pick(slots, INHERIT_SLOTS)}
|
||||
class={bem()}
|
||||
|
||||
@ -152,6 +152,15 @@ export default {
|
||||
| columns-top | Custom content above columns | - |
|
||||
| columns-bottom | Custom content below columns | - |
|
||||
|
||||
### Methods
|
||||
|
||||
Use [ref](https://v3.vuejs.org/guide/component-template-refs.html) to get Area instance and call instance methods.
|
||||
|
||||
| Name | Description | Attribute | Return value |
|
||||
| --- | --- | --- | --- |
|
||||
| confirm | Stop scrolling and emit confirm event | - | - |
|
||||
| getSelectedOptions | Get current selected options | - | _PickerOption[]_ |
|
||||
|
||||
### Types
|
||||
|
||||
The component exports the following type definitions:
|
||||
@ -168,5 +177,5 @@ import type { AreaInstance } from 'vant';
|
||||
|
||||
const areaRef = ref<AreaInstance>();
|
||||
|
||||
areaRef.value?.reset();
|
||||
areaRef.value?.confirm();
|
||||
```
|
||||
|
||||
@ -154,6 +154,15 @@ export default {
|
||||
| columns-top | 自定义选项上方内容 | - |
|
||||
| columns-bottom | 自定义选项下方内容 | - |
|
||||
|
||||
### 方法
|
||||
|
||||
通过 ref 可以获取到 Area 实例并调用实例方法,详见[组件实例方法](#/zh-CN/advanced-usage#zu-jian-shi-li-fang-fa)。
|
||||
|
||||
| 方法名 | 说明 | 参数 | 返回值 |
|
||||
| --- | --- | --- | --- |
|
||||
| confirm | 停止惯性滚动并触发 `confirm` 事件 | - | - |
|
||||
| getSelectedOptions | 获取当前选中的选项 | - | _PickerOption[]_ |
|
||||
|
||||
### 类型定义
|
||||
|
||||
组件导出以下类型定义:
|
||||
@ -170,7 +179,7 @@ import type { AreaInstance } from 'vant';
|
||||
|
||||
const areaRef = ref<AreaInstance>();
|
||||
|
||||
areaRef.value?.reset();
|
||||
areaRef.value?.confirm();
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
/* eslint-disable camelcase */
|
||||
import type { ComponentPublicInstance } from 'vue';
|
||||
import { PickerExpose } from '../picker/types';
|
||||
import type { AreaProps } from './Area';
|
||||
|
||||
export type AreaList = {
|
||||
@ -8,4 +9,4 @@ export type AreaList = {
|
||||
province_list: Record<string, string>;
|
||||
};
|
||||
|
||||
export type AreaInstance = ComponentPublicInstance<AreaProps>;
|
||||
export type AreaInstance = ComponentPublicInstance<AreaProps, PickerExpose>;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { AreaProps } from '.';
|
||||
import type { PickerOption } from '../picker';
|
||||
|
||||
const EMPTY_CODE = '000000';
|
||||
export const AREA_EMPTY_CODE = '000000';
|
||||
|
||||
export const INHERIT_SLOTS = [
|
||||
'title',
|
||||
@ -24,8 +24,8 @@ export const INHERIT_PROPS = [
|
||||
|
||||
const makeOption = (
|
||||
text = '',
|
||||
value = EMPTY_CODE,
|
||||
children?: PickerOption[]
|
||||
value = AREA_EMPTY_CODE,
|
||||
children: PickerOption[] | undefined = undefined
|
||||
): PickerOption => ({
|
||||
text,
|
||||
value,
|
||||
@ -48,7 +48,13 @@ export function formatDataForCascade({
|
||||
const getProvinceChildren = () => {
|
||||
if (showCity) {
|
||||
return placeholder.length
|
||||
? [makeOption(placeholder[0], EMPTY_CODE, showCounty ? [] : undefined)]
|
||||
? [
|
||||
makeOption(
|
||||
placeholder[0],
|
||||
AREA_EMPTY_CODE,
|
||||
showCounty ? [] : undefined
|
||||
),
|
||||
]
|
||||
: [];
|
||||
}
|
||||
};
|
||||
@ -94,9 +100,9 @@ export function formatDataForCascade({
|
||||
if (placeholder.length) {
|
||||
const county = showCounty ? [makeOption(placeholder[2])] : undefined;
|
||||
const city = showCity
|
||||
? [makeOption(placeholder[1], EMPTY_CODE, county)]
|
||||
? [makeOption(placeholder[1], AREA_EMPTY_CODE, county)]
|
||||
: undefined;
|
||||
options.unshift(makeOption(placeholder[0], EMPTY_CODE, city));
|
||||
options.unshift(makeOption(placeholder[0], AREA_EMPTY_CODE, city));
|
||||
}
|
||||
|
||||
return options;
|
||||
|
||||
@ -15,7 +15,7 @@ import {
|
||||
createNamespace,
|
||||
makeRequiredProp,
|
||||
} from '../utils';
|
||||
import { getMonthEndDay } from '../datetime-picker/utils';
|
||||
import { getMonthEndDay } from '../date-picker/utils';
|
||||
import {
|
||||
t,
|
||||
bem,
|
||||
|
||||
@ -53,7 +53,7 @@ const themeVars = {
|
||||
<demo-block :title="t('darkMode')">
|
||||
<van-cell center :title="t('switchDarkMode')">
|
||||
<template #right-icon>
|
||||
<van-switch v-model="darkMode" size="24" />
|
||||
<van-switch v-model="darkMode" />
|
||||
</template>
|
||||
</van-cell>
|
||||
</demo-block>
|
||||
|
||||
@ -11,7 +11,6 @@ exports[`should render demo and match snapshot 1`] = `
|
||||
</div>
|
||||
<div role="switch"
|
||||
class="van-switch"
|
||||
style="font-size: 24px;"
|
||||
tabindex="0"
|
||||
aria-checked="false"
|
||||
>
|
||||
|
||||
@ -92,7 +92,6 @@ export default defineComponent({
|
||||
const renderSwitch = () => (
|
||||
<Switch
|
||||
v-model={contact.isDefault}
|
||||
size={24}
|
||||
onChange={(checked: boolean) => emit('changeDefault', checked)}
|
||||
/>
|
||||
);
|
||||
|
||||
@ -51,7 +51,6 @@ exports[`should render demo and match snapshot 1`] = `
|
||||
</div>
|
||||
<div role="switch"
|
||||
class="van-switch"
|
||||
style="font-size: 24px;"
|
||||
tabindex="0"
|
||||
aria-checked="false"
|
||||
>
|
||||
|
||||
163
packages/vant/src/date-picker/DatePicker.tsx
Normal file
163
packages/vant/src/date-picker/DatePicker.tsx
Normal file
@ -0,0 +1,163 @@
|
||||
import {
|
||||
ref,
|
||||
watch,
|
||||
computed,
|
||||
defineComponent,
|
||||
type PropType,
|
||||
type ExtractPropTypes,
|
||||
} from 'vue';
|
||||
|
||||
// Utils
|
||||
import { pick, extend, isDate, isSameValue, createNamespace } from '../utils';
|
||||
import {
|
||||
genOptions,
|
||||
sharedProps,
|
||||
getMonthEndDay,
|
||||
pickerInheritKeys,
|
||||
} from './utils';
|
||||
|
||||
// Components
|
||||
import { Picker } from '../picker';
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
const [name] = createNamespace('date-picker');
|
||||
|
||||
export type DatePickerColumnType = 'year' | 'month' | 'day';
|
||||
|
||||
const datePickerProps = extend({}, sharedProps, {
|
||||
columnsType: {
|
||||
type: Array as PropType<DatePickerColumnType[]>,
|
||||
default: () => ['year', 'month', 'day'],
|
||||
},
|
||||
minDate: {
|
||||
type: Date,
|
||||
default: () => new Date(currentYear - 10, 0, 1),
|
||||
validator: isDate,
|
||||
},
|
||||
maxDate: {
|
||||
type: Date,
|
||||
default: () => new Date(currentYear + 10, 11, 31),
|
||||
validator: isDate,
|
||||
},
|
||||
});
|
||||
|
||||
export type DatePickerProps = ExtractPropTypes<typeof datePickerProps>;
|
||||
|
||||
export default defineComponent({
|
||||
name,
|
||||
|
||||
props: datePickerProps,
|
||||
|
||||
emits: ['confirm', 'cancel', 'change', 'update:modelValue'],
|
||||
|
||||
setup(props, { emit, slots }) {
|
||||
const currentValues = ref<string[]>(props.modelValue);
|
||||
|
||||
const genYearOptions = () => {
|
||||
const minYear = props.minDate.getFullYear();
|
||||
const maxYear = props.maxDate.getFullYear();
|
||||
return genOptions(
|
||||
minYear,
|
||||
maxYear,
|
||||
'year',
|
||||
props.formatter,
|
||||
props.filter
|
||||
);
|
||||
};
|
||||
|
||||
const isMaxYear = (year: number) => year === props.maxDate.getFullYear();
|
||||
const isMaxMonth = (month: number) =>
|
||||
month === props.maxDate.getMonth() + 1;
|
||||
|
||||
const getValue = (type: DatePickerColumnType) => {
|
||||
const { minDate, columnsType } = props;
|
||||
const index = columnsType.indexOf(type);
|
||||
const value = currentValues.value[index];
|
||||
if (value) {
|
||||
return +value;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'year':
|
||||
return minDate.getFullYear();
|
||||
case 'month':
|
||||
return minDate.getMonth() + 1;
|
||||
case 'day':
|
||||
return minDate.getDate();
|
||||
}
|
||||
};
|
||||
|
||||
const genMonthOptions = () => {
|
||||
if (isMaxYear(getValue('year'))) {
|
||||
return genOptions(
|
||||
1,
|
||||
props.maxDate.getMonth() + 1,
|
||||
'month',
|
||||
props.formatter,
|
||||
props.filter
|
||||
);
|
||||
}
|
||||
return genOptions(1, 12, 'month', props.formatter, props.filter);
|
||||
};
|
||||
|
||||
const genDayOptions = () => {
|
||||
const year = getValue('year');
|
||||
const month = getValue('month');
|
||||
|
||||
let maxDate = getMonthEndDay(year, month);
|
||||
if (isMaxYear(year) && isMaxMonth(month)) {
|
||||
maxDate = props.maxDate.getDate();
|
||||
}
|
||||
|
||||
return genOptions(1, maxDate, 'day', props.formatter, props.filter);
|
||||
};
|
||||
|
||||
const columns = computed(() =>
|
||||
props.columnsType.map((type) => {
|
||||
switch (type) {
|
||||
case 'year':
|
||||
return genYearOptions();
|
||||
case 'month':
|
||||
return genMonthOptions();
|
||||
case 'day':
|
||||
return genDayOptions();
|
||||
default:
|
||||
throw new Error(
|
||||
`[Vant] DatePicker: unsupported columns type: ${type}`
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
watch(currentValues, (newValues) => {
|
||||
if (isSameValue(newValues, props.modelValue)) {
|
||||
emit('update:modelValue', newValues);
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newValues) => {
|
||||
if (isSameValue(newValues, currentValues.value)) {
|
||||
currentValues.value = newValues;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const onChange = (...args: unknown[]) => emit('change', ...args);
|
||||
const onCancel = (...args: unknown[]) => emit('cancel', ...args);
|
||||
const onConfirm = (...args: unknown[]) => emit('confirm', ...args);
|
||||
|
||||
return () => (
|
||||
<Picker
|
||||
v-slots={slots}
|
||||
v-model={currentValues.value}
|
||||
columns={columns.value}
|
||||
onChange={onChange}
|
||||
onCancel={onCancel}
|
||||
onConfirm={onConfirm}
|
||||
{...pick(props, pickerInheritKeys)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
208
packages/vant/src/date-picker/README.md
Normal file
208
packages/vant/src/date-picker/README.md
Normal file
@ -0,0 +1,208 @@
|
||||
# DatePicker
|
||||
|
||||
### Intro
|
||||
|
||||
Used to select date, usually used with the [Popup](#/en-US/popup) component.
|
||||
|
||||
### Install
|
||||
|
||||
Register component globally via `app.use`, refer to [Component Registration](#/en-US/advanced-usage#zu-jian-zhu-ce) for more registration ways.
|
||||
|
||||
```js
|
||||
import { createApp } from 'vue';
|
||||
import { DatePicker } from 'vant';
|
||||
|
||||
const app = createApp();
|
||||
app.use(DatePicker);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```html
|
||||
<van-date-picker v-model="currentDate" title="Choose Date" />
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentDate = ref(['2021', '01', '01']);
|
||||
return {
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 5, 1),
|
||||
currentDate,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Columns Type
|
||||
|
||||
Using `columns-type` prop to control the type of columns.
|
||||
|
||||
For example:
|
||||
|
||||
- Pass in `['year']` to select year.
|
||||
- Pass in `['month']` to select month.
|
||||
- Pass in `['year', 'month']` to select year and month.
|
||||
- Pass in `['month', 'day']` to select month and day.
|
||||
|
||||
```html
|
||||
<van-date-picker
|
||||
v-model="currentDate"
|
||||
title="Choose Year-Month"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:columns-type="columnsType"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentDate = ref(['2021', '01']);
|
||||
const columnsType = ['year', 'month'];
|
||||
return {
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 5, 1),
|
||||
currentDate,
|
||||
columnsType,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Options Formatter
|
||||
|
||||
```html
|
||||
<van-date-picker
|
||||
v-model="currentDate"
|
||||
title="Choose Year-Month"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:formatter="formatter"
|
||||
:columns-type="columnsType"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentDate = ref(['2021', '01']);
|
||||
const columnsType = ['year', 'month'];
|
||||
|
||||
const formatter = (type, val) => {
|
||||
if (type === 'year') {
|
||||
return `${val} Year`;
|
||||
}
|
||||
if (type === 'month') {
|
||||
return `${val} Month`;
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
return {
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 5, 1),
|
||||
formatter,
|
||||
currentDate,
|
||||
columnsType,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Options Filter
|
||||
|
||||
```html
|
||||
<van-date-picker
|
||||
v-model="currentDate"
|
||||
title="Choose Year-Month"
|
||||
:filter="filter"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:columns-type="columnsType"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentDate = ref(['2021', '01']);
|
||||
const columnsType = ['year', 'month'];
|
||||
const filter = (type, options) => {
|
||||
if (type === 'month') {
|
||||
return options.filter((option) => Number(option.value) % 6 === 0);
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
||||
return {
|
||||
filter,
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 5, 1),
|
||||
currentTime,
|
||||
columnsType,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| v-model | Current date | _string[]_ | `[]` |
|
||||
| columns-type | Columns type | _string[]_ | `['year', 'month', 'day']` |
|
||||
| min-date | Min date | _Date_ | Ten years ago on January 1 |
|
||||
| max-date | Max date | _Date_ | Ten years later on December 31 |
|
||||
| title | Toolbar title | _string_ | `''` |
|
||||
| confirm-button-text | Text of confirm button | _string_ | `Confirm` |
|
||||
| cancel-button-text | Text of cancel button | _string_ | `Cancel` |
|
||||
| show-toolbar | Whether to show toolbar | _boolean_ | `true` |
|
||||
| loading | Whether to show loading prompt | _boolean_ | `false` |
|
||||
| readonly | Whether to be readonly | _boolean_ | `false` |
|
||||
| filter | Option filter | _(type: string, options: PickerOption[]) => PickerOption[]_ | - |
|
||||
| formatter | Option formatter | _(type: string, option: PickerOption) => PickerOption_ | - |
|
||||
| option-height | Option height, supports `px` `vw` `vh` `rem` unit, default `px` | _number \| string_ | `44` |
|
||||
| visible-option-num | Count of visible columns | _number \| string_ | `6` |
|
||||
| swipe-duration | Duration of the momentum animation,unit `ms` | _number \| string_ | `1000` |
|
||||
|
||||
### Events
|
||||
|
||||
| Event | Description | Arguments |
|
||||
| --- | --- | --- |
|
||||
| confirm | Emitted when the confirm button is clicked | _{ selectedValues, selectedOptions }_ |
|
||||
| cancel | Emitted when the cancel button is clicked | _{ selectedValues, selectedOptions }_ |
|
||||
| change | Emitted when current option is changed | _{ selectedValues, selectedOptions, columnIndex }_ |
|
||||
|
||||
### Slots
|
||||
|
||||
| Name | Description | SlotProps |
|
||||
| -------------- | ---------------------------- | ---------------------- |
|
||||
| default | Custom toolbar content | - |
|
||||
| title | Custom title | - |
|
||||
| confirm | Custom confirm button text | - |
|
||||
| cancel | Custom cancel button text | - |
|
||||
| option | Custom option content | _option: PickerOption_ |
|
||||
| columns-top | Custom content above columns | - |
|
||||
| columns-bottom | Custom content below columns | - |
|
||||
|
||||
### Types
|
||||
|
||||
The component exports the following type definitions:
|
||||
|
||||
```ts
|
||||
import type { DatePickerProps, DatePickerColumnType } from 'vant';
|
||||
```
|
||||
232
packages/vant/src/date-picker/README.zh-CN.md
Normal file
232
packages/vant/src/date-picker/README.zh-CN.md
Normal file
@ -0,0 +1,232 @@
|
||||
# DatePicker 日期选择
|
||||
|
||||
### 介绍
|
||||
|
||||
日期选择器,用于选择年、月、日,通常与[弹出层](#/zh-CN/popup)组件配合使用。
|
||||
|
||||
### 引入
|
||||
|
||||
通过以下方式来全局注册组件,更多注册方式请参考[组件注册](#/zh-CN/advanced-usage#zu-jian-zhu-ce)。
|
||||
|
||||
```js
|
||||
import { createApp } from 'vue';
|
||||
import { DatePicker } from 'vant';
|
||||
|
||||
const app = createApp();
|
||||
app.use(DatePicker);
|
||||
```
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基础用法
|
||||
|
||||
通过 `v-model` 绑定当前选中的日期,通过 `min-date` 和 `max-date` 属性来设定可选的时间范围。
|
||||
|
||||
```html
|
||||
<van-date-picker v-model="currentDate" title="选择日期" />
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentDate = ref(['2021', '01', '01']);
|
||||
return {
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 5, 1),
|
||||
currentDate,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 选项类型
|
||||
|
||||
通过 `columns-type` 属性可以控制选项的类型,支持以任意顺序对 `year`、`month` 和 `day` 进行排列组合。
|
||||
|
||||
比如:
|
||||
|
||||
- 传入 `['year']` 来单独选择年份。
|
||||
- 传入 `['month']` 来单独选择月份。
|
||||
- 传入 `['year', 'month']` 来选择年份和月份。
|
||||
- 传入 `['month', 'day']` 来选择月份和日期。
|
||||
|
||||
```html
|
||||
<van-date-picker
|
||||
v-model="currentDate"
|
||||
title="选择年月"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:columns-type="columnsType"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentDate = ref(['2021', '01']);
|
||||
const columnsType = ['year', 'month'];
|
||||
return {
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 5, 1),
|
||||
currentDate,
|
||||
columnsType,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 格式化选项
|
||||
|
||||
通过传入 `formatter` 函数,可以对选项文字进行格式化处理。
|
||||
|
||||
```html
|
||||
<van-date-picker
|
||||
v-model="currentDate"
|
||||
title="选择年月"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:formatter="formatter"
|
||||
:columns-type="columnsType"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentDate = ref(['2021', '01']);
|
||||
const columnsType = ['year', 'month'];
|
||||
|
||||
const formatter = (type, val) => {
|
||||
if (type === 'year') {
|
||||
return `${val}年`;
|
||||
}
|
||||
if (type === 'month') {
|
||||
return `${val}月`;
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
return {
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 5, 1),
|
||||
formatter,
|
||||
currentDate,
|
||||
columnsType,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 过滤选项
|
||||
|
||||
通过传入 `filter` 函数,可以对选项数组进行过滤,实现自定义选项间隔。
|
||||
|
||||
```html
|
||||
<van-date-picker
|
||||
v-model="currentDate"
|
||||
title="选择年月"
|
||||
:filter="filter"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:columns-type="columnsType"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentDate = ref(['2021', '01']);
|
||||
const columnsType = ['year', 'month'];
|
||||
const filter = (type, options) => {
|
||||
if (type === 'month') {
|
||||
return options.filter((option) => Number(option.value) % 6 === 0);
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
||||
return {
|
||||
filter,
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 5, 1),
|
||||
currentTime,
|
||||
columnsType,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| v-model | 当前选中的日期 | _string[]_ | `[]` |
|
||||
| columns-type | 选项类型,由 `year`、`month` 和 `day` 组成的数组 | _string[]_ | `['year', 'month', 'day']` |
|
||||
| min-date | 可选的最小时间,精确到日 | _Date_ | 十年前 |
|
||||
| max-date | 可选的最大时间,精确到日 | _Date_ | 十年后 |
|
||||
| title | 顶部栏标题 | _string_ | `''` |
|
||||
| confirm-button-text | 确认按钮文字 | _string_ | `确认` |
|
||||
| cancel-button-text | 取消按钮文字 | _string_ | `取消` |
|
||||
| show-toolbar | 是否显示顶部栏 | _boolean_ | `true` |
|
||||
| loading | 是否显示加载状态 | _boolean_ | `false` |
|
||||
| readonly | 是否为只读状态,只读状态下无法切换选项 | _boolean_ | `false` |
|
||||
| filter | 选项过滤函数 | _(type: string, options: PickerOption[]) => PickerOption[]_ | - |
|
||||
| formatter | 选项格式化函数 | _(type: string, option: PickerOption) => PickerOption_ | - |
|
||||
| option-height | 选项高度,支持 `px` `vw` `vh` `rem` 单位,默认 `px` | _number \| string_ | `44` |
|
||||
| visible-option-num | 可见的选项个数 | _number \| string_ | `6` |
|
||||
| swipe-duration | 快速滑动时惯性滚动的时长,单位 `ms` | _number \| string_ | `1000` |
|
||||
|
||||
### Events
|
||||
|
||||
| 事件名 | 说明 | 回调参数 |
|
||||
| --- | --- | --- |
|
||||
| confirm | 点击完成按钮时触发 | _{ selectedValues, selectedOptions }_ |
|
||||
| cancel | 点击取消按钮时触发 | _{ selectedValues, selectedOptions }_ |
|
||||
| change | 选项改变时触发 | _{ selectedValues, selectedOptions, columnIndex }_ |
|
||||
|
||||
### Slots
|
||||
|
||||
| 名称 | 说明 | 参数 |
|
||||
| -------------- | ---------------------- | ---------------------- |
|
||||
| default | 自定义整个顶部栏的内容 | - |
|
||||
| title | 自定义标题内容 | - |
|
||||
| confirm | 自定义确认按钮内容 | - |
|
||||
| cancel | 自定义取消按钮内容 | - |
|
||||
| option | 自定义选项内容 | _option: PickerOption_ |
|
||||
| columns-top | 自定义选项上方内容 | - |
|
||||
| columns-bottom | 自定义选项下方内容 | - |
|
||||
|
||||
### 类型定义
|
||||
|
||||
组件导出以下类型定义:
|
||||
|
||||
```ts
|
||||
import type { DatePickerProps, DatePickerColumnType } from 'vant';
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 设置 min-date 或 max-date 后出现页面卡死的情况?
|
||||
|
||||
请注意不要在模板中直接使用类似 `min-date="new Date()"` 的写法,这样会导致每次渲染组件时传入一个新的 Date 对象,而传入新的数据会触发下一次渲染,从而陷入死循环。
|
||||
|
||||
正确的做法是将 `min-date` 作为一个数据定义在 `data` 函数或 `setup` 中。
|
||||
|
||||
### 在 iOS 系统上初始化组件失败?
|
||||
|
||||
如果你遇到了在 iOS 上无法渲染组件的问题,请确认在创建 Date 对象时没有使用 `new Date('2020-01-01')` 这样的写法,iOS 不支持以中划线分隔的日期格式,正确写法是 `new Date('2020/01/01')`。
|
||||
|
||||
对此问题的详细解释:[stackoverflow](https://stackoverflow.com/questions/13363673/javascript-date-is-invalid-on-ios)。
|
||||
|
||||
### 在桌面端无法操作组件?
|
||||
|
||||
参见[桌面端适配](#/zh-CN/advanced-usage#zhuo-mian-duan-gua-pei)。
|
||||
102
packages/vant/src/date-picker/demo/index.vue
Normal file
102
packages/vant/src/date-picker/demo/index.vue
Normal file
@ -0,0 +1,102 @@
|
||||
<script setup lang="ts">
|
||||
import VanDatePicker from '..';
|
||||
import { ref } from 'vue';
|
||||
import { useTranslate } from '../../../docs/site/use-translate';
|
||||
import type { PickerOption } from '../../picker';
|
||||
import { DatePickerColumnType } from '../DatePicker';
|
||||
|
||||
const t = useTranslate({
|
||||
'zh-CN': {
|
||||
day: '日',
|
||||
year: '年',
|
||||
month: '月',
|
||||
chooseDate: '选择日期',
|
||||
columnsType: '选项类型',
|
||||
optionsFilter: '过滤选项',
|
||||
chooseYearMonth: '选择年月',
|
||||
optionsFormatter: '格式化选项',
|
||||
},
|
||||
'en-US': {
|
||||
day: ' Day',
|
||||
year: ' Year',
|
||||
month: ' Month',
|
||||
chooseDate: 'Choose Date',
|
||||
columnsType: 'Columns Type',
|
||||
optionsFilter: 'Options Filter',
|
||||
chooseYearMonth: 'Choose Year-Month',
|
||||
optionsFormatter: 'Options Formatter',
|
||||
},
|
||||
});
|
||||
|
||||
const minDate = new Date(2020, 0, 1);
|
||||
const maxDate = new Date(2025, 5, 1);
|
||||
const basicDate = ref(['2021', '01', '01']);
|
||||
const yearMonthDate = ref(['2021', '01']);
|
||||
const formatterDate = ref(['2021', '01']);
|
||||
const filterDate = ref(['2021', '01']);
|
||||
|
||||
const columnsType: DatePickerColumnType[] = ['year', 'month'];
|
||||
|
||||
const filter = (type: string, options: PickerOption[]) => {
|
||||
if (type === 'month') {
|
||||
return options.filter((option) => Number(option.value) % 6 === 0);
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
||||
const formatter = (type: string, option: PickerOption) => {
|
||||
if (type === 'year') {
|
||||
option.text += t('year');
|
||||
}
|
||||
if (type === 'month') {
|
||||
option.text += t('month');
|
||||
}
|
||||
if (type === 'day') {
|
||||
option.text += t('day');
|
||||
}
|
||||
return option;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<demo-block card :title="t('basicUsage')">
|
||||
<van-date-picker
|
||||
v-model="basicDate"
|
||||
:title="t('chooseDate')"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block card :title="t('columnsType')">
|
||||
<van-date-picker
|
||||
v-model="yearMonthDate"
|
||||
:title="t('chooseYearMonth')"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:columns-type="columnsType"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block card :title="t('optionsFormatter')">
|
||||
<van-date-picker
|
||||
v-model="formatterDate"
|
||||
:title="t('chooseYearMonth')"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:formatter="formatter"
|
||||
:columns-type="columnsType"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block card :title="t('optionsFilter')">
|
||||
<van-date-picker
|
||||
v-model="filterDate"
|
||||
:title="t('optionFilter')"
|
||||
:filter="filter"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:columns-type="columnsType"
|
||||
/>
|
||||
</demo-block>
|
||||
</template>
|
||||
13
packages/vant/src/date-picker/index.ts
Normal file
13
packages/vant/src/date-picker/index.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { withInstall } from '../utils';
|
||||
import _DatePicker, { DatePickerProps } from './DatePicker';
|
||||
|
||||
export const DatePicker = withInstall(_DatePicker);
|
||||
export default DatePicker;
|
||||
export type { DatePickerProps };
|
||||
export type { DatePickerColumnType } from './DatePicker';
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
VanDatePicker: typeof DatePicker;
|
||||
}
|
||||
}
|
||||
1016
packages/vant/src/date-picker/test/__snapshots__/demo.spec.ts.snap
Normal file
1016
packages/vant/src/date-picker/test/__snapshots__/demo.spec.ts.snap
Normal file
File diff suppressed because it is too large
Load Diff
220
packages/vant/src/date-picker/test/date-picker.legacy.ts
Normal file
220
packages/vant/src/date-picker/test/date-picker.legacy.ts
Normal file
@ -0,0 +1,220 @@
|
||||
// import DatePicker from '../../datetime-picker/DatePicker';
|
||||
// import { mount, later, triggerDrag } from '../../../test';
|
||||
|
||||
// function filter(type: string, options: string[]): string[] {
|
||||
// const mod = type === 'year' ? 10 : 5;
|
||||
// return options.filter((option: string) => Number(option) % mod === 0);
|
||||
// }
|
||||
|
||||
// function formatter(type: string, value: string): string {
|
||||
// return `${value} ${type}`;
|
||||
// }
|
||||
|
||||
// test('filter prop', async () => {
|
||||
// const wrapper = mount(DatePicker, {
|
||||
// props: {
|
||||
// filter,
|
||||
// minDate: new Date(2020, 0, 1),
|
||||
// maxDate: new Date(2025, 10, 1),
|
||||
// modelValue: new Date(2020, 10, 1, 0, 0),
|
||||
// },
|
||||
// });
|
||||
|
||||
// expect(wrapper.html()).toMatchSnapshot();
|
||||
// });
|
||||
|
||||
// test('formatter prop', async () => {
|
||||
// const wrapper = mount(DatePicker, {
|
||||
// props: {
|
||||
// filter,
|
||||
// formatter,
|
||||
// minDate: new Date(2010, 0, 1),
|
||||
// maxDate: new Date(2025, 10, 1),
|
||||
// modelValue: new Date(2020, 10, 1, 0, 0),
|
||||
// },
|
||||
// });
|
||||
|
||||
// expect(wrapper.html()).toMatchSnapshot();
|
||||
|
||||
// triggerDrag(wrapper.find('.van-picker-column'), 0, -100);
|
||||
// wrapper.find('.van-picker-column ul').trigger('transitionend');
|
||||
// await later();
|
||||
|
||||
// expect((wrapper.vm as Record<string, any>).getPicker().getValues()).toEqual([
|
||||
// '2020 year',
|
||||
// '05 month',
|
||||
// '05 day',
|
||||
// '00 hour',
|
||||
// '00 minute',
|
||||
// ]);
|
||||
// });
|
||||
|
||||
// test('confirm event', () => {
|
||||
// const date = new Date(2020, 10, 1, 0, 0);
|
||||
|
||||
// const wrapper = mount(DatePicker, {
|
||||
// props: {
|
||||
// modelValue: date,
|
||||
// minDate: new Date(2020, 0, 1),
|
||||
// maxDate: new Date(2025, 10, 1),
|
||||
// },
|
||||
// });
|
||||
|
||||
// wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![0][0].getFullYear()).toEqual(2020);
|
||||
|
||||
// triggerDrag(wrapper.find('.van-picker-column'), 0, -100);
|
||||
// wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![1][0].getFullYear()).toEqual(2025);
|
||||
// });
|
||||
|
||||
// test('year-month type', async () => {
|
||||
// const date = new Date(2020, 10, 1, 0, 0);
|
||||
|
||||
// const wrapper = mount(DatePicker, {
|
||||
// props: {
|
||||
// type: 'year-month',
|
||||
// modelValue: date,
|
||||
// minDate: new Date(2020, 0, 1),
|
||||
// maxDate: new Date(2025, 10, 1),
|
||||
// },
|
||||
// });
|
||||
|
||||
// wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![0][0].getFullYear()).toEqual(2020);
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![0][0].getMonth()).toEqual(10);
|
||||
|
||||
// triggerDrag(wrapper.find('.van-picker-column'), 0, -100);
|
||||
// wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![1][0].getFullYear()).toEqual(2025);
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![1][0].getMonth()).toEqual(0);
|
||||
|
||||
// triggerDrag(wrapper.findAll('.van-picker-column')[0], 0, -100);
|
||||
// await later();
|
||||
// triggerDrag(wrapper.findAll('.van-picker-column')[1], 0, -100);
|
||||
// wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![2][0].getFullYear()).toEqual(2025);
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![2][0].getMonth()).toEqual(10);
|
||||
// });
|
||||
|
||||
// test('month-day type', async () => {
|
||||
// const date = new Date(2020, 10, 1, 0, 0);
|
||||
|
||||
// const wrapper = mount(DatePicker, {
|
||||
// props: {
|
||||
// type: 'month-day',
|
||||
// modelValue: date,
|
||||
// minDate: new Date(2020, 0, 1),
|
||||
// maxDate: new Date(2025, 10, 1),
|
||||
// },
|
||||
// });
|
||||
|
||||
// wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![0][0].getMonth()).toEqual(10);
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![0][0].getDate()).toEqual(1);
|
||||
|
||||
// triggerDrag(wrapper.find('.van-picker-column'), 0, -300);
|
||||
// wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![1][0].getMonth()).toEqual(11);
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![1][0].getDate()).toEqual(1);
|
||||
|
||||
// triggerDrag(wrapper.find('.van-picker-column'), 0, -300);
|
||||
// await later();
|
||||
// triggerDrag(wrapper.findAll('.van-picker-column')[1], 0, -300);
|
||||
// wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![2][0].getMonth()).toEqual(11);
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![2][0].getDate()).toEqual(31);
|
||||
// });
|
||||
|
||||
// test('datehour type', () => {
|
||||
// const wrapper = mount(DatePicker, {
|
||||
// props: {
|
||||
// minDate: new Date(2010, 0, 1),
|
||||
// maxDate: new Date(2025, 10, 1),
|
||||
// modelValue: new Date(2020, 10, 1, 0, 0),
|
||||
// },
|
||||
// });
|
||||
|
||||
// wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![0][0].getHours()).toEqual(0);
|
||||
|
||||
// triggerDrag(wrapper.findAll('.van-picker-column')[3], 0, -300);
|
||||
// wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![1][0].getHours()).toEqual(23);
|
||||
// });
|
||||
|
||||
// test('cancel event', () => {
|
||||
// const wrapper = mount(DatePicker);
|
||||
|
||||
// wrapper.find('.van-picker__cancel').trigger('click');
|
||||
// expect(wrapper.emitted('cancel')).toBeTruthy();
|
||||
// });
|
||||
|
||||
// test('max-date prop', () => {
|
||||
// const maxDate = new Date(2010, 5, 0, 0, 0);
|
||||
// const wrapper = mount(DatePicker, {
|
||||
// props: {
|
||||
// modelValue: new Date(2020, 10, 30, 30, 30),
|
||||
// maxDate,
|
||||
// },
|
||||
// });
|
||||
|
||||
// wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![0][0]).toEqual(maxDate);
|
||||
// });
|
||||
|
||||
// test('min-date prop', () => {
|
||||
// const minDate = new Date(2030, 0, 0, 0, 0);
|
||||
// const wrapper = mount(DatePicker, {
|
||||
// props: {
|
||||
// modelValue: new Date(2020, 0, 0, 0, 0),
|
||||
// minDate,
|
||||
// },
|
||||
// });
|
||||
|
||||
// wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![0][0]).toEqual(minDate);
|
||||
// });
|
||||
|
||||
// test('dynamic set value', async () => {
|
||||
// const wrapper = mount(DatePicker, {
|
||||
// props: {
|
||||
// modelValue: new Date(2019, 1, 1),
|
||||
// },
|
||||
// });
|
||||
|
||||
// await wrapper.setProps({ modelValue: new Date(2019, 1, 1) });
|
||||
// wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// await wrapper.setProps({ modelValue: new Date(2025, 1, 1) });
|
||||
// wrapper.find('.van-picker__confirm').trigger('click');
|
||||
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![0][0].getFullYear()).toEqual(2019);
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![1][0].getFullYear()).toEqual(2025);
|
||||
// });
|
||||
|
||||
// test('use min-date with filter', async () => {
|
||||
// const minDate = new Date(2030, 0, 0, 0, 3);
|
||||
// const maxDate = new Date(2040, 0, 0, 0, 0);
|
||||
|
||||
// const wrapper = mount(DatePicker, {
|
||||
// props: {
|
||||
// minDate,
|
||||
// maxDate,
|
||||
// modelValue: new Date(2020, 0, 0, 0, 0),
|
||||
// filter(type: string, values: string[]) {
|
||||
// if (type === 'minute') {
|
||||
// return values.filter((value) => Number(value) % 30 === 0);
|
||||
// }
|
||||
|
||||
// return values;
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
|
||||
// await later();
|
||||
|
||||
// wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![0][0]).toEqual(
|
||||
// new Date(2030, 0, 0, 0, 30)
|
||||
// );
|
||||
// });
|
||||
116
packages/vant/src/date-picker/test/datetime-picker.legacy.tsx
Normal file
116
packages/vant/src/date-picker/test/datetime-picker.legacy.tsx
Normal file
@ -0,0 +1,116 @@
|
||||
// import { DatetimePicker } from '../../datetime-picker';
|
||||
// import { mount, later } from '../../../test';
|
||||
// import { reactive } from 'vue';
|
||||
// import { useExpose } from '../../composables/use-expose';
|
||||
|
||||
// test('should emit confirm event after clicking the confirm button', () => {
|
||||
// const onConfirm = jest.fn();
|
||||
// const wrapper = mount(DatetimePicker, {
|
||||
// props: {
|
||||
// onConfirm,
|
||||
// },
|
||||
// });
|
||||
// wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// expect(onConfirm).toHaveBeenCalledTimes(1);
|
||||
// });
|
||||
|
||||
// test('should emit cancel event after clicking the confirm button', () => {
|
||||
// const onCancel = jest.fn();
|
||||
// const wrapper = mount(DatetimePicker, {
|
||||
// props: {
|
||||
// onCancel,
|
||||
// },
|
||||
// });
|
||||
// wrapper.find('.van-picker__cancel').trigger('click');
|
||||
// expect(onCancel).toHaveBeenCalledTimes(1);
|
||||
// });
|
||||
|
||||
// test('should allow to call getPicker method', () => {
|
||||
// const wrapper = mount(DatetimePicker);
|
||||
|
||||
// expect(wrapper.vm.getPicker()).toBeTruthy();
|
||||
// });
|
||||
|
||||
// test('should render title slot correctly', () => {
|
||||
// const wrapper = mount(DatetimePicker, {
|
||||
// props: {
|
||||
// showToolbar: true,
|
||||
// },
|
||||
// slots: {
|
||||
// title: () => 'Custom title',
|
||||
// },
|
||||
// });
|
||||
|
||||
// expect(wrapper.find('.van-picker__toolbar').html()).toMatchSnapshot();
|
||||
// });
|
||||
|
||||
// test('should emit value correctly when dynamic change min-date', async () => {
|
||||
// const defaultValue = new Date(2020, 10, 2, 10, 30);
|
||||
// const wrapper = mount({
|
||||
// emits: ['confirm'],
|
||||
// setup(_, { emit }) {
|
||||
// const state = reactive({
|
||||
// date: defaultValue,
|
||||
// minDate: new Date(2010, 0, 1, 10, 30),
|
||||
// });
|
||||
|
||||
// const onChange = () => {
|
||||
// state.minDate = state.date;
|
||||
// };
|
||||
|
||||
// useExpose({
|
||||
// onChange,
|
||||
// });
|
||||
|
||||
// return () => (
|
||||
// <DatetimePicker
|
||||
// v-model={state.date}
|
||||
// minDate={state.minDate}
|
||||
// onConfirm={(value: Date) => emit('confirm', value)}
|
||||
// />
|
||||
// );
|
||||
// },
|
||||
// });
|
||||
|
||||
// await later();
|
||||
// (wrapper.vm as Record<string, any>).onChange();
|
||||
// await later();
|
||||
// wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![0][0]).toEqual(defaultValue);
|
||||
// });
|
||||
|
||||
// test('should update value correctly after calling setColumnIndex method', async () => {
|
||||
// const onConfirm = jest.fn();
|
||||
// const defaultDate = new Date(2020, 0, 1);
|
||||
// const wrapper = mount(DatetimePicker, {
|
||||
// props: {
|
||||
// type: 'date',
|
||||
// minDate: defaultDate,
|
||||
// maxDate: new Date(2020, 0, 30),
|
||||
// modelValue: defaultDate,
|
||||
// onConfirm,
|
||||
// },
|
||||
// });
|
||||
|
||||
// wrapper.vm.getPicker().setColumnIndex(2, 14);
|
||||
// await wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// expect(onConfirm.mock.calls[0]).toEqual([new Date(2020, 0, 15)]);
|
||||
// });
|
||||
|
||||
// test('should update value correctly after calling setColumnValue method', async () => {
|
||||
// const onConfirm = jest.fn();
|
||||
// const defaultDate = new Date(2020, 0, 1);
|
||||
// const wrapper = mount(DatetimePicker, {
|
||||
// props: {
|
||||
// type: 'date',
|
||||
// minDate: defaultDate,
|
||||
// maxDate: new Date(2020, 0, 30),
|
||||
// modelValue: defaultDate,
|
||||
// onConfirm,
|
||||
// },
|
||||
// });
|
||||
|
||||
// wrapper.vm.getPicker().setColumnValue(2, '15');
|
||||
// await wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// expect(onConfirm.mock.calls[0]).toEqual([new Date(2020, 0, 15)]);
|
||||
// });
|
||||
59
packages/vant/src/date-picker/test/index.legacy.ts
Normal file
59
packages/vant/src/date-picker/test/index.legacy.ts
Normal file
@ -0,0 +1,59 @@
|
||||
// import { later, mount, triggerDrag } from '../../../test';
|
||||
// import DatePicker from '../../datetime-picker/DatePicker';
|
||||
|
||||
// test('month-day type', async () => {
|
||||
// const date = new Date(2020, 10, 1, 0, 0);
|
||||
|
||||
// const wrapper = mount(DatePicker, {
|
||||
// props: {
|
||||
// type: 'month-day',
|
||||
// modelValue: date,
|
||||
// minDate: new Date(2020, 0, 1),
|
||||
// maxDate: new Date(2025, 10, 1),
|
||||
// },
|
||||
// });
|
||||
|
||||
// await later();
|
||||
// wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![0][0].getMonth()).toEqual(10);
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![0][0].getDate()).toEqual(1);
|
||||
|
||||
// await later();
|
||||
// triggerDrag(wrapper.find('.van-picker-column'), 0, -300);
|
||||
// wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![1][0].getMonth()).toEqual(11);
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![1][0].getDate()).toEqual(1);
|
||||
|
||||
// await later();
|
||||
// triggerDrag(wrapper.findAll('.van-picker-column')[1], 0, -300);
|
||||
// wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![2][0].getMonth()).toEqual(11);
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![2][0].getDate()).toEqual(31);
|
||||
// });
|
||||
|
||||
// test('v-model', async () => {
|
||||
// const minDate = new Date(2030, 0, 0, 0, 3);
|
||||
|
||||
// const wrapper = mount(DatePicker, {
|
||||
// props: {
|
||||
// minDate,
|
||||
// },
|
||||
// });
|
||||
|
||||
// await later();
|
||||
// wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![0][0]).toEqual(minDate);
|
||||
// });
|
||||
|
||||
// test('value has an initial value', async () => {
|
||||
// const defaultValue = new Date(2020, 0, 0, 0, 0);
|
||||
// const wrapper = mount(DatePicker, {
|
||||
// propsData: {
|
||||
// modelValue: defaultValue,
|
||||
// },
|
||||
// });
|
||||
|
||||
// await later();
|
||||
// wrapper.find('.van-picker__confirm').trigger('click');
|
||||
// expect(wrapper.emitted<[Date]>('confirm')![0][0]).toEqual(defaultValue);
|
||||
// });
|
||||
55
packages/vant/src/date-picker/utils.ts
Normal file
55
packages/vant/src/date-picker/utils.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { extend, padZero, makeArrayProp } from '../utils';
|
||||
import { pickerSharedProps } from '../picker/Picker';
|
||||
import type { PropType } from 'vue';
|
||||
import type { PickerOption } from '../picker';
|
||||
|
||||
type Filter = (columnType: string, options: PickerOption[]) => PickerOption[];
|
||||
type Formatter = (type: string, option: PickerOption) => PickerOption;
|
||||
|
||||
export const sharedProps = extend({}, pickerSharedProps, {
|
||||
modelValue: makeArrayProp<string>(),
|
||||
filter: Function as PropType<Filter>,
|
||||
formatter: {
|
||||
type: Function as PropType<Formatter>,
|
||||
default: (type: string, option: PickerOption) => option,
|
||||
},
|
||||
});
|
||||
|
||||
export const pickerInheritKeys = Object.keys(pickerSharedProps) as Array<
|
||||
keyof typeof pickerSharedProps
|
||||
>;
|
||||
|
||||
export function times<T>(n: number, iteratee: (index: number) => T) {
|
||||
if (n < 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const result: T[] = Array(n);
|
||||
|
||||
let index = -1;
|
||||
while (++index < n) {
|
||||
result[index] = iteratee(index);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export const getMonthEndDay = (year: number, month: number): number =>
|
||||
32 - new Date(year, month - 1, 32).getDate();
|
||||
|
||||
export const genOptions = <T extends string>(
|
||||
min: number,
|
||||
max: number,
|
||||
type: T,
|
||||
formatter: Formatter,
|
||||
filter?: Filter
|
||||
) => {
|
||||
const options = times(max - min + 1, (index) => {
|
||||
const value = padZero(min + index);
|
||||
return formatter(type, {
|
||||
text: value,
|
||||
value,
|
||||
});
|
||||
});
|
||||
return filter ? filter(type, options) : options;
|
||||
};
|
||||
@ -1,338 +0,0 @@
|
||||
import {
|
||||
ref,
|
||||
watch,
|
||||
computed,
|
||||
nextTick,
|
||||
onMounted,
|
||||
defineComponent,
|
||||
type PropType,
|
||||
} from 'vue';
|
||||
|
||||
// Utils
|
||||
import {
|
||||
pick,
|
||||
clamp,
|
||||
extend,
|
||||
isDate,
|
||||
padZero,
|
||||
makeStringProp,
|
||||
createNamespace,
|
||||
} from '../utils';
|
||||
import {
|
||||
times,
|
||||
sharedProps,
|
||||
getTrueValue,
|
||||
getMonthEndDay,
|
||||
pickerInheritKeys,
|
||||
proxyPickerMethods,
|
||||
} from './utils';
|
||||
|
||||
// Composables
|
||||
import { useExpose } from '../composables/use-expose';
|
||||
|
||||
// Components
|
||||
import { Picker, PickerInstance } from '../picker';
|
||||
|
||||
// Types
|
||||
import { DatetimePickerColumnType, DatetimePickerType } from './types';
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
const [name] = createNamespace('date-picker');
|
||||
|
||||
export default defineComponent({
|
||||
name,
|
||||
|
||||
props: extend({}, sharedProps, {
|
||||
type: makeStringProp<DatetimePickerType>('datetime'),
|
||||
modelValue: Date,
|
||||
columnsOrder: Array as PropType<DatetimePickerColumnType[]>,
|
||||
minDate: {
|
||||
type: Date,
|
||||
default: () => new Date(currentYear - 10, 0, 1),
|
||||
validator: isDate,
|
||||
},
|
||||
maxDate: {
|
||||
type: Date,
|
||||
default: () => new Date(currentYear + 10, 11, 31),
|
||||
validator: isDate,
|
||||
},
|
||||
}),
|
||||
|
||||
emits: ['confirm', 'cancel', 'change', 'update:modelValue'],
|
||||
|
||||
setup(props, { emit, slots }) {
|
||||
const formatValue = (value?: Date) => {
|
||||
if (isDate(value)) {
|
||||
const timestamp = clamp(
|
||||
value.getTime(),
|
||||
props.minDate.getTime(),
|
||||
props.maxDate.getTime()
|
||||
);
|
||||
return new Date(timestamp);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const picker = ref<PickerInstance>();
|
||||
const currentDate = ref(formatValue(props.modelValue));
|
||||
|
||||
const getBoundary = (type: 'max' | 'min', value: Date) => {
|
||||
const boundary = props[`${type}Date` as const];
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
||||
const ranges = computed(() => {
|
||||
const { maxYear, maxDate, maxMonth, maxHour, maxMinute } = getBoundary(
|
||||
'max',
|
||||
currentDate.value || props.minDate
|
||||
);
|
||||
|
||||
const { minYear, minDate, minMonth, minHour, minMinute } = getBoundary(
|
||||
'min',
|
||||
currentDate.value || props.minDate
|
||||
);
|
||||
|
||||
let result: Array<{ type: DatetimePickerColumnType; range: number[] }> = [
|
||||
{
|
||||
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],
|
||||
},
|
||||
];
|
||||
|
||||
switch (props.type) {
|
||||
case 'date':
|
||||
result = result.slice(0, 3);
|
||||
break;
|
||||
case 'year-month':
|
||||
result = result.slice(0, 2);
|
||||
break;
|
||||
case 'month-day':
|
||||
result = result.slice(1, 3);
|
||||
break;
|
||||
case 'datehour':
|
||||
result = result.slice(0, 4);
|
||||
break;
|
||||
}
|
||||
|
||||
if (props.columnsOrder) {
|
||||
const columnsOrder = props.columnsOrder.concat(
|
||||
result.map((column) => column.type)
|
||||
);
|
||||
result.sort(
|
||||
(a, b) => columnsOrder.indexOf(a.type) - columnsOrder.indexOf(b.type)
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
const originColumns = computed(() =>
|
||||
ranges.value.map(({ type, range: rangeArr }) => {
|
||||
let values = times(rangeArr[1] - rangeArr[0] + 1, (index) =>
|
||||
padZero(rangeArr[0] + index)
|
||||
);
|
||||
|
||||
if (props.filter) {
|
||||
values = props.filter(type, values);
|
||||
}
|
||||
|
||||
return {
|
||||
type,
|
||||
values,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const columns = computed(() =>
|
||||
originColumns.value.map((column) => ({
|
||||
values: column.values.map((value) =>
|
||||
props.formatter(column.type, value)
|
||||
),
|
||||
}))
|
||||
);
|
||||
|
||||
const updateColumnValue = () => {
|
||||
const value = currentDate.value || props.minDate;
|
||||
const { formatter } = props;
|
||||
|
||||
const values = originColumns.value.map((column) => {
|
||||
switch (column.type) {
|
||||
case 'year':
|
||||
return formatter('year', `${value.getFullYear()}`);
|
||||
case 'month':
|
||||
return formatter('month', padZero(value.getMonth() + 1));
|
||||
case 'day':
|
||||
return formatter('day', padZero(value.getDate()));
|
||||
case 'hour':
|
||||
return formatter('hour', padZero(value.getHours()));
|
||||
case 'minute':
|
||||
return formatter('minute', padZero(value.getMinutes()));
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
});
|
||||
|
||||
nextTick(() => {
|
||||
picker.value?.setValues(values);
|
||||
});
|
||||
};
|
||||
|
||||
const updateInnerValue = () => {
|
||||
const { type } = props;
|
||||
const indexes = picker.value!.getIndexes();
|
||||
|
||||
const getValue = (type: DatetimePickerColumnType) => {
|
||||
let index = 0;
|
||||
originColumns.value.forEach((column, columnIndex) => {
|
||||
if (type === column.type) {
|
||||
index = columnIndex;
|
||||
}
|
||||
});
|
||||
const { values } = originColumns.value[index];
|
||||
return getTrueValue(values[indexes[index]]);
|
||||
};
|
||||
|
||||
let year;
|
||||
let month;
|
||||
let day;
|
||||
if (type === 'month-day') {
|
||||
year = (currentDate.value || props.minDate).getFullYear();
|
||||
month = getValue('month');
|
||||
day = getValue('day');
|
||||
} else {
|
||||
year = getValue('year');
|
||||
month = getValue('month');
|
||||
day = type === 'year-month' ? 1 : getValue('day');
|
||||
}
|
||||
|
||||
const maxDay = getMonthEndDay(year, month);
|
||||
day = day > maxDay ? maxDay : day;
|
||||
|
||||
let hour = 0;
|
||||
let minute = 0;
|
||||
|
||||
if (type === 'datehour') {
|
||||
hour = getValue('hour');
|
||||
}
|
||||
|
||||
if (type === 'datetime') {
|
||||
hour = getValue('hour');
|
||||
minute = getValue('minute');
|
||||
}
|
||||
|
||||
const value = new Date(year, month - 1, day, hour, minute);
|
||||
currentDate.value = formatValue(value);
|
||||
};
|
||||
|
||||
const onConfirm = () => {
|
||||
emit('update:modelValue', currentDate.value);
|
||||
emit('confirm', currentDate.value);
|
||||
};
|
||||
|
||||
const onCancel = () => emit('cancel');
|
||||
|
||||
const onChange = () => {
|
||||
updateInnerValue();
|
||||
nextTick(() => {
|
||||
nextTick(() => emit('change', currentDate.value));
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
updateColumnValue();
|
||||
nextTick(updateInnerValue);
|
||||
});
|
||||
|
||||
watch(columns, updateColumnValue);
|
||||
|
||||
watch(currentDate, (value, oldValue) =>
|
||||
emit('update:modelValue', oldValue ? value : null)
|
||||
);
|
||||
|
||||
watch(() => [props.filter, props.maxDate], updateInnerValue);
|
||||
|
||||
watch(
|
||||
() => props.minDate,
|
||||
() => {
|
||||
nextTick(updateInnerValue);
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(value) => {
|
||||
value = formatValue(value);
|
||||
|
||||
if (value && value.valueOf() !== currentDate.value?.valueOf()) {
|
||||
currentDate.value = value;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
useExpose({
|
||||
getPicker: () =>
|
||||
picker.value && proxyPickerMethods(picker.value, updateInnerValue),
|
||||
});
|
||||
|
||||
return () => (
|
||||
<Picker
|
||||
v-slots={slots}
|
||||
ref={picker}
|
||||
columns={columns.value}
|
||||
onChange={onChange}
|
||||
onCancel={onCancel}
|
||||
onConfirm={onConfirm}
|
||||
{...pick(props, pickerInheritKeys)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
@ -1,49 +0,0 @@
|
||||
import { ref, defineComponent, type ExtractPropTypes } from 'vue';
|
||||
import { pick, extend, createNamespace } from '../utils';
|
||||
import { useExpose } from '../composables/use-expose';
|
||||
import TimePicker from './TimePicker';
|
||||
import DatePicker from './DatePicker';
|
||||
import { DatetimePickerInstance } from './types';
|
||||
|
||||
const [name, bem] = createNamespace('datetime-picker');
|
||||
|
||||
const timePickerPropKeys = Object.keys(TimePicker.props);
|
||||
const datePickerPropKeys = Object.keys(DatePicker.props);
|
||||
const datetimePickerProps = extend({}, TimePicker.props, DatePicker.props, {
|
||||
modelValue: [String, Date],
|
||||
});
|
||||
|
||||
export type DatetimePickerProps = ExtractPropTypes<typeof datetimePickerProps>;
|
||||
|
||||
export default defineComponent({
|
||||
name,
|
||||
|
||||
props: datetimePickerProps,
|
||||
|
||||
setup(props, { attrs, slots }) {
|
||||
const root = ref<DatetimePickerInstance>();
|
||||
|
||||
useExpose({
|
||||
getPicker: () => root.value?.getPicker(),
|
||||
});
|
||||
|
||||
return () => {
|
||||
const isTimePicker = props.type === 'time';
|
||||
const Component = isTimePicker ? TimePicker : DatePicker;
|
||||
const inheritProps = pick(
|
||||
props,
|
||||
isTimePicker ? timePickerPropKeys : datePickerPropKeys
|
||||
);
|
||||
|
||||
return (
|
||||
<Component
|
||||
v-slots={slots}
|
||||
ref={root}
|
||||
class={bem()}
|
||||
{...inheritProps}
|
||||
{...attrs}
|
||||
/>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
@ -1,367 +0,0 @@
|
||||
# DatetimePicker
|
||||
|
||||
### Intro
|
||||
|
||||
Used to select time, support date and time dimensions, usually used with the [Popup](#/en-US/popup) component.
|
||||
|
||||
### Install
|
||||
|
||||
Register component globally via `app.use`, refer to [Component Registration](#/en-US/advanced-usage#zu-jian-zhu-ce) for more registration ways.
|
||||
|
||||
```js
|
||||
import { createApp } from 'vue';
|
||||
import { DatetimePicker } from 'vant';
|
||||
|
||||
const app = createApp();
|
||||
app.use(DatetimePicker);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Choose Date
|
||||
|
||||
```html
|
||||
<van-datetime-picker
|
||||
v-model="currentDate"
|
||||
type="date"
|
||||
title="Choose Date"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentDate = ref(new Date(2021, 0, 17));
|
||||
return {
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 10, 1),
|
||||
currentDate,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Choose Year-Month
|
||||
|
||||
```html
|
||||
<van-datetime-picker
|
||||
v-model="currentDate"
|
||||
type="year-month"
|
||||
title="Choose Year-Month"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:formatter="formatter"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentDate = ref(new Date());
|
||||
|
||||
const formatter = (type, val) => {
|
||||
if (type === 'year') {
|
||||
return `${val} Year`;
|
||||
}
|
||||
if (type === 'month') {
|
||||
return `${val} Month`;
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
return {
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 10, 1),
|
||||
formatter,
|
||||
currentDate,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Choose Month-Day
|
||||
|
||||
```html
|
||||
<van-datetime-picker
|
||||
v-model="currentDate"
|
||||
type="month-day"
|
||||
title="Choose Month-Day"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:formatter="formatter"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentDate = ref(new Date());
|
||||
|
||||
const formatter = (type, val) => {
|
||||
if (type === 'month') {
|
||||
return `${val} Month`;
|
||||
}
|
||||
if (type === 'day') {
|
||||
return `${val} Day`;
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
return {
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 10, 1),
|
||||
formatter,
|
||||
currentDate,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Choose Time
|
||||
|
||||
```html
|
||||
<van-datetime-picker
|
||||
v-model="currentTime"
|
||||
type="time"
|
||||
title="Choose Time"
|
||||
:min-hour="10"
|
||||
:max-hour="20"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentTime = ref('12:00');
|
||||
return { currentTime };
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Choose DateTime
|
||||
|
||||
```html
|
||||
<van-datetime-picker
|
||||
v-model="currentDate"
|
||||
type="datetime"
|
||||
title="Choose DateTime"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentDate = ref(new Date());
|
||||
return {
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 10, 1),
|
||||
currentDate,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Choose DateHour
|
||||
|
||||
```html
|
||||
<van-datetime-picker
|
||||
v-model="currentDate"
|
||||
type="datehour"
|
||||
title="Choose DateTime"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentDate = ref(new Date());
|
||||
return {
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 10, 1),
|
||||
currentDate,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Option Filter
|
||||
|
||||
```html
|
||||
<van-datetime-picker
|
||||
v-model="currentTime"
|
||||
type="time"
|
||||
title="Option Filter"
|
||||
:filter="filter"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentTime = ref('12:00');
|
||||
|
||||
const filter = (type, options) => {
|
||||
if (type === 'minute') {
|
||||
return options.filter((option) => Number(option) % 5 === 0);
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
||||
return {
|
||||
filter,
|
||||
currentTime,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Columns Order
|
||||
|
||||
```html
|
||||
<van-datetime-picker
|
||||
v-model="currentDate"
|
||||
type="date"
|
||||
title="Columns Order"
|
||||
:columns-order="['month', 'day', 'year']"
|
||||
:formatter="formatter"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentDate = ref(new Date());
|
||||
|
||||
const formatter = (type, val) => {
|
||||
if (type === 'year') {
|
||||
return val + ' Year';
|
||||
}
|
||||
if (type === 'month') {
|
||||
return val + ' Month';
|
||||
}
|
||||
if (type === 'day') {
|
||||
return val + ' Day';
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
return {
|
||||
formatter,
|
||||
currentDate,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| type | Can be set to `date` `time`<br> `year-month` `month-day` `datehour` | _string_ | `datetime` |
|
||||
| title | Toolbar title | _string_ | `''` |
|
||||
| confirm-button-text | Text of confirm button | _string_ | `Confirm` |
|
||||
| cancel-button-text | Text of cancel button | _string_ | `Cancel` |
|
||||
| show-toolbar | Whether to show toolbar | _boolean_ | `true` |
|
||||
| loading | Whether to show loading prompt | _boolean_ | `false` |
|
||||
| readonly | Whether to be readonly | _boolean_ | `false` |
|
||||
| filter | Option filter | _(type: string, values: string[]) => string[]_ | - |
|
||||
| formatter | Option text formatter | _(type: string, value: string) => string_ | - |
|
||||
| columns-order | Array for ordering columns, where item can be set to<br> `year`, `month`, `day`, `hour` and `minute` | _string[]_ | - |
|
||||
| option-height | Option height, supports `px` `vw` `vh` `rem` unit, default `px` | _number \| string_ | `44` |
|
||||
| visible-option-num | Count of visible columns | _number \| string_ | `6` |
|
||||
| swipe-duration | Duration of the momentum animation,unit `ms` | _number \| string_ | `1000` |
|
||||
|
||||
### DatePicker Props
|
||||
|
||||
Following props are supported when the type is date or datetime
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
| --------- | ----------- | ------ | ------------------------------ |
|
||||
| min-date | Min date | _Date_ | Ten years ago on January 1 |
|
||||
| max-date | Max date | _Date_ | Ten years later on December 31 |
|
||||
|
||||
### TimePicker Props
|
||||
|
||||
Following props are supported when the type is time
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
| ---------- | -------------------------- | ------------------ | ------- |
|
||||
| min-hour | Min hour for `time` type | _number \| string_ | `0` |
|
||||
| max-hour | Max hour for `time` type | _number \| string_ | `23` |
|
||||
| min-minute | Max minute for `time` type | _number \| string_ | `0` |
|
||||
| max-minute | Max minute for `time` type | _number \| string_ | `59` |
|
||||
|
||||
### Events
|
||||
|
||||
| Event | Description | Arguments |
|
||||
| ------- | ------------------------------------------ | -------------------- |
|
||||
| change | Emitted when value changed | value: current value |
|
||||
| confirm | Emitted when the confirm button is clicked | value: current value |
|
||||
| cancel | Emitted when the cancel button is clicked | - |
|
||||
|
||||
### Slots
|
||||
|
||||
| Name | Description | SlotProps |
|
||||
| -------------- | ---------------------------- | -------------------------- |
|
||||
| default | Custom toolbar content | - |
|
||||
| title | Custom title | - |
|
||||
| confirm | Custom confirm button text | - |
|
||||
| cancel | Custom cancel button text | - |
|
||||
| option | Custom option content | _option: string \| object_ |
|
||||
| columns-top | Custom content above columns | - |
|
||||
| columns-bottom | Custom content below columns | - |
|
||||
|
||||
### Methods
|
||||
|
||||
Use [ref](https://v3.vuejs.org/guide/component-template-refs.html) to get DatetimePicker instance and call instance methods.
|
||||
|
||||
| Name | Description | Attribute | Return value |
|
||||
| --------- | ------------------- | --------- | ------------ |
|
||||
| getPicker | get Picker instance | - | - |
|
||||
|
||||
### Types
|
||||
|
||||
The component exports the following type definitions:
|
||||
|
||||
```ts
|
||||
import type {
|
||||
DatetimePickerType,
|
||||
DatetimePickerProps,
|
||||
DatetimePickerInstance,
|
||||
} from 'vant';
|
||||
```
|
||||
|
||||
`DatetimePickerInstance` is the type of component instance:
|
||||
|
||||
```ts
|
||||
import { ref } from 'vue';
|
||||
import type { DatetimePickerInstance } from 'vant';
|
||||
|
||||
const datetimePickerRef = ref<DatetimePickerInstance>();
|
||||
|
||||
datetimePickerRef.value?.getPicker();
|
||||
```
|
||||
@ -1,398 +0,0 @@
|
||||
# DatetimePicker 时间选择
|
||||
|
||||
### 介绍
|
||||
|
||||
时间选择器,支持日期、年月、时分等维度,通常与[弹出层](#/zh-CN/popup)组件配合使用。
|
||||
|
||||
### 引入
|
||||
|
||||
通过以下方式来全局注册组件,更多注册方式请参考[组件注册](#/zh-CN/advanced-usage#zu-jian-zhu-ce)。
|
||||
|
||||
```js
|
||||
import { createApp } from 'vue';
|
||||
import { DatetimePicker } from 'vant';
|
||||
|
||||
const app = createApp();
|
||||
app.use(DatetimePicker);
|
||||
```
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 选择年月日
|
||||
|
||||
DatetimePicker 通过 type 属性来定义需要选择的时间类型,type 为 `date` 表示选择年月日。通过 min-date 和 max-date 属性可以确定可选的时间范围。
|
||||
|
||||
```html
|
||||
<van-datetime-picker
|
||||
v-model="currentDate"
|
||||
type="date"
|
||||
title="选择年月日"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentDate = ref(new Date(2021, 0, 17));
|
||||
return {
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 10, 1),
|
||||
currentDate,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 选择年月
|
||||
|
||||
将 type 设置为 `year-month` 即可选择年份和月份。通过传入 `formatter` 函数,可以对选项文字进行格式化处理。
|
||||
|
||||
```html
|
||||
<van-datetime-picker
|
||||
v-model="currentDate"
|
||||
type="year-month"
|
||||
title="选择年月"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:formatter="formatter"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentDate = ref(new Date());
|
||||
|
||||
const formatter = (type, val) => {
|
||||
if (type === 'year') {
|
||||
return `${val}年`;
|
||||
}
|
||||
if (type === 'month') {
|
||||
return `${val}月`;
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
return {
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 10, 1),
|
||||
formatter,
|
||||
currentDate,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 选择月日
|
||||
|
||||
将 type 设置为 `month-day` 即可选择月份和日期。
|
||||
|
||||
```html
|
||||
<van-datetime-picker
|
||||
v-model="currentDate"
|
||||
type="month-day"
|
||||
title="选择月日"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:formatter="formatter"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentDate = ref(new Date());
|
||||
|
||||
const formatter = (type, val) => {
|
||||
if (type === 'month') {
|
||||
return `${val}月`;
|
||||
}
|
||||
if (type === 'day') {
|
||||
return `${val}日`;
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
return {
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 10, 1),
|
||||
formatter,
|
||||
currentDate,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 选择时间
|
||||
|
||||
将 type 设置为 `time` 即可选择时间(小时和分钟)。
|
||||
|
||||
```html
|
||||
<van-datetime-picker
|
||||
v-model="currentTime"
|
||||
type="time"
|
||||
title="选择时间"
|
||||
:min-hour="10"
|
||||
:max-hour="20"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentTime = ref('12:00');
|
||||
return { currentTime };
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 选择完整时间
|
||||
|
||||
将 type 设置为 `datetime` 即可选择完整时间,包括年月日和小时、分钟。
|
||||
|
||||
```html
|
||||
<van-datetime-picker
|
||||
v-model="currentDate"
|
||||
type="datetime"
|
||||
title="选择完整时间"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentDate = ref(new Date());
|
||||
return {
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 10, 1),
|
||||
currentDate,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 选择年月日小时
|
||||
|
||||
将 type 设置为 `datehour` 即可选择日期和小时,包括年月日和小时。
|
||||
|
||||
```html
|
||||
<van-datetime-picker
|
||||
v-model="currentDate"
|
||||
type="datehour"
|
||||
title="选择年月日小时"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentDate = ref(new Date());
|
||||
return {
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 10, 1),
|
||||
currentDate,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 选项过滤器
|
||||
|
||||
通过传入 `filter` 函数,可以对选项数组进行过滤,实现自定义时间间隔。
|
||||
|
||||
```html
|
||||
<van-datetime-picker v-model="currentTime" type="time" :filter="filter" />
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentTime = ref('12:00');
|
||||
|
||||
const filter = (type, options) => {
|
||||
if (type === 'minute') {
|
||||
return options.filter((option) => Number(option) % 5 === 0);
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
||||
return {
|
||||
filter,
|
||||
currentTime,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 自定义列排序
|
||||
|
||||
```html
|
||||
<van-datetime-picker
|
||||
v-model="currentDate"
|
||||
type="date"
|
||||
title="自定义列排序"
|
||||
:columns-order="['month', 'day', 'year']"
|
||||
:formatter="formatter"
|
||||
/>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const currentDate = ref(new Date());
|
||||
|
||||
const formatter = (type, val) => {
|
||||
if (type === 'year') {
|
||||
return val + '年';
|
||||
}
|
||||
if (type === 'month') {
|
||||
return val + '月';
|
||||
}
|
||||
if (type === 'day') {
|
||||
return val + '日';
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
return {
|
||||
formatter,
|
||||
currentDate,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| type | 时间类型,可选值为 `date` `time` <br> `year-month` `month-day` `datehour` | _string_ | `datetime` |
|
||||
| title | 顶部栏标题 | _string_ | `''` |
|
||||
| confirm-button-text | 确认按钮文字 | _string_ | `确认` |
|
||||
| cancel-button-text | 取消按钮文字 | _string_ | `取消` |
|
||||
| show-toolbar | 是否显示顶部栏 | _boolean_ | `true` |
|
||||
| loading | 是否显示加载状态 | _boolean_ | `false` |
|
||||
| readonly | 是否为只读状态,只读状态下无法切换选项 | _boolean_ | `false` |
|
||||
| filter | 选项过滤函数 | _(type: string, values: string[]) => string[]_ | - |
|
||||
| formatter | 选项格式化函数 | _(type: string, value: string) => string_ | - |
|
||||
| columns-order | 自定义列排序数组, 子项可选值为<br> `year`、`month`、`day`、`hour`、`minute` | _string[]_ | - |
|
||||
| option-height | 选项高度,支持 `px` `vw` `vh` `rem` 单位,默认 `px` | _number \| string_ | `44` |
|
||||
| visible-option-num | 可见的选项个数 | _number \| string_ | `6` |
|
||||
| swipe-duration | 快速滑动时惯性滚动的时长,单位`ms` | _number \| string_ | `1000` |
|
||||
|
||||
### DatePicker Props
|
||||
|
||||
当时间选择器类型为 date 或 datetime 时,支持以下 props:
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| -------- | -------------------------- | ------ | ------ |
|
||||
| min-date | 可选的最小时间,精确到分钟 | _Date_ | 十年前 |
|
||||
| max-date | 可选的最大时间,精确到分钟 | _Date_ | 十年后 |
|
||||
|
||||
### TimePicker Props
|
||||
|
||||
当时间选择器类型为 time 时,支持以下 props:
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| ---------- | -------------- | ------------------ | ------ |
|
||||
| min-hour | 可选的最小小时 | _number \| string_ | `0` |
|
||||
| max-hour | 可选的最大小时 | _number \| string_ | `23` |
|
||||
| min-minute | 可选的最小分钟 | _number \| string_ | `0` |
|
||||
| max-minute | 可选的最大分钟 | _number \| string_ | `59` |
|
||||
|
||||
### Events
|
||||
|
||||
| 事件名 | 说明 | 回调参数 |
|
||||
| ------- | ------------------------ | --------------------- |
|
||||
| change | 当值变化时触发的事件 | value: 当前选中的时间 |
|
||||
| confirm | 点击完成按钮时触发的事件 | value: 当前选中的时间 |
|
||||
| cancel | 点击取消按钮时触发的事件 | - |
|
||||
|
||||
### Slots
|
||||
|
||||
| 名称 | 说明 | 参数 |
|
||||
| -------------- | ---------------------- | -------------------------- |
|
||||
| default | 自定义整个顶部栏的内容 | - |
|
||||
| title | 自定义标题内容 | - |
|
||||
| confirm | 自定义确认按钮内容 | - |
|
||||
| cancel | 自定义取消按钮内容 | - |
|
||||
| option | 自定义选项内容 | _option: string \| object_ |
|
||||
| columns-top | 自定义选项上方内容 | - |
|
||||
| columns-bottom | 自定义选项下方内容 | - |
|
||||
|
||||
### 方法
|
||||
|
||||
通过 ref 可以获取到 DatetimePicker 实例并调用实例方法,详见[组件实例方法](#/zh-CN/advanced-usage#zu-jian-shi-li-fang-fa)。
|
||||
|
||||
| 方法名 | 说明 | 参数 | 返回值 |
|
||||
| --- | --- | --- | --- |
|
||||
| getPicker | 获取 Picker 实例,用于调用 Picker 的[实例方法](#/zh-CN/picker#fang-fa) | - | - |
|
||||
|
||||
### 类型定义
|
||||
|
||||
组件导出以下类型定义:
|
||||
|
||||
```ts
|
||||
import type {
|
||||
DatetimePickerType,
|
||||
DatetimePickerProps,
|
||||
DatetimePickerInstance,
|
||||
} from 'vant';
|
||||
```
|
||||
|
||||
`DatetimePickerInstance` 是组件实例的类型,用法如下:
|
||||
|
||||
```ts
|
||||
import { ref } from 'vue';
|
||||
import type { DatetimePickerInstance } from 'vant';
|
||||
|
||||
const datetimePickerRef = ref<DatetimePickerInstance>();
|
||||
|
||||
datetimePickerRef.value?.getPicker();
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 设置 min-date 或 max-date 后出现页面卡死的情况?
|
||||
|
||||
请注意不要在模板中直接使用类似`min-date="new Date()"`的写法,这样会导致每次渲染组件时传入一个新的 Date 对象,而传入新的数据会触发下一次渲染,从而陷入死循环。
|
||||
|
||||
正确的做法是将`min-date`作为一个数据定义在`data`函数中。
|
||||
|
||||
### 在 iOS 系统上初始化组件失败?
|
||||
|
||||
如果你遇到了在 iOS 上无法渲染组件的问题,请确认在创建 Date 对象时没有使用`new Date('2020-01-01')`这样的写法,iOS 不支持以中划线分隔的日期格式,正确写法是`new Date('2020/01/01')`。
|
||||
|
||||
对此问题的详细解释:[stackoverflow](https://stackoverflow.com/questions/13363673/javascript-date-is-invalid-on-ios)。
|
||||
|
||||
### 在桌面端无法操作组件?
|
||||
|
||||
参见[桌面端适配](#/zh-CN/advanced-usage#zhuo-mian-duan-gua-pei)。
|
||||
|
||||
### 是否有年份或月份选择器?
|
||||
|
||||
如果仅需要选择年份或者月份,建议直接使用 [Picker](#/zh-CN/picker) 组件。
|
||||
@ -1,184 +0,0 @@
|
||||
import {
|
||||
ref,
|
||||
watch,
|
||||
computed,
|
||||
nextTick,
|
||||
onMounted,
|
||||
defineComponent,
|
||||
} from 'vue';
|
||||
|
||||
// Utils
|
||||
import {
|
||||
pick,
|
||||
clamp,
|
||||
extend,
|
||||
padZero,
|
||||
createNamespace,
|
||||
makeNumericProp,
|
||||
} from '../utils';
|
||||
import {
|
||||
times,
|
||||
sharedProps,
|
||||
pickerInheritKeys,
|
||||
proxyPickerMethods,
|
||||
} from './utils';
|
||||
|
||||
// Composables
|
||||
import { useExpose } from '../composables/use-expose';
|
||||
|
||||
// Components
|
||||
import { Picker, PickerInstance } from '../picker';
|
||||
|
||||
const [name] = createNamespace('time-picker');
|
||||
|
||||
export default defineComponent({
|
||||
name,
|
||||
|
||||
props: extend({}, sharedProps, {
|
||||
minHour: makeNumericProp(0),
|
||||
maxHour: makeNumericProp(23),
|
||||
minMinute: makeNumericProp(0),
|
||||
maxMinute: makeNumericProp(59),
|
||||
modelValue: String,
|
||||
}),
|
||||
|
||||
emits: ['confirm', 'cancel', 'change', 'update:modelValue'],
|
||||
|
||||
setup(props, { emit, slots }) {
|
||||
const formatValue = (value?: string) => {
|
||||
const { minHour, maxHour, maxMinute, minMinute } = props;
|
||||
|
||||
if (!value) {
|
||||
value = `${padZero(minHour)}:${padZero(minMinute)}`;
|
||||
}
|
||||
|
||||
let [hour, minute] = value.split(':');
|
||||
hour = padZero(clamp(+hour, +minHour, +maxHour));
|
||||
minute = padZero(clamp(+minute, +minMinute, +maxMinute));
|
||||
|
||||
return `${hour}:${minute}`;
|
||||
};
|
||||
|
||||
const picker = ref<PickerInstance>();
|
||||
const currentDate = ref(formatValue(props.modelValue));
|
||||
|
||||
const ranges = computed(() => [
|
||||
{
|
||||
type: 'hour',
|
||||
range: [+props.minHour, +props.maxHour],
|
||||
},
|
||||
{
|
||||
type: 'minute',
|
||||
range: [+props.minMinute, +props.maxMinute],
|
||||
},
|
||||
]);
|
||||
|
||||
const originColumns = computed(() =>
|
||||
ranges.value.map(({ type, range: rangeArr }) => {
|
||||
let values = times(rangeArr[1] - rangeArr[0] + 1, (index) =>
|
||||
padZero(rangeArr[0] + index)
|
||||
);
|
||||
|
||||
if (props.filter) {
|
||||
values = props.filter(type, values);
|
||||
}
|
||||
|
||||
return {
|
||||
type,
|
||||
values,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const columns = computed(() =>
|
||||
originColumns.value.map((column) => ({
|
||||
values: column.values.map((value) =>
|
||||
props.formatter(column.type, value)
|
||||
),
|
||||
}))
|
||||
);
|
||||
|
||||
const updateColumnValue = () => {
|
||||
const pair = currentDate.value.split(':');
|
||||
const values = [
|
||||
props.formatter('hour', pair[0]),
|
||||
props.formatter('minute', pair[1]),
|
||||
];
|
||||
|
||||
nextTick(() => {
|
||||
picker.value?.setValues(values);
|
||||
});
|
||||
};
|
||||
|
||||
const updateInnerValue = () => {
|
||||
const [hourIndex, minuteIndex] = picker.value!.getIndexes();
|
||||
const [hourColumn, minuteColumn] = originColumns.value;
|
||||
|
||||
const hour = hourColumn.values[hourIndex] || hourColumn.values[0];
|
||||
const minute = minuteColumn.values[minuteIndex] || minuteColumn.values[0];
|
||||
|
||||
currentDate.value = formatValue(`${hour}:${minute}`);
|
||||
updateColumnValue();
|
||||
};
|
||||
|
||||
const onConfirm = () => emit('confirm', currentDate.value);
|
||||
const onCancel = () => emit('cancel');
|
||||
|
||||
const onChange = () => {
|
||||
updateInnerValue();
|
||||
nextTick(() => {
|
||||
nextTick(() => emit('change', currentDate.value));
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
updateColumnValue();
|
||||
nextTick(updateInnerValue);
|
||||
});
|
||||
|
||||
watch(columns, updateColumnValue);
|
||||
|
||||
watch(
|
||||
() => [props.filter, props.maxHour, props.minMinute, props.maxMinute],
|
||||
updateInnerValue
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.minHour,
|
||||
() => {
|
||||
nextTick(updateInnerValue);
|
||||
}
|
||||
);
|
||||
|
||||
watch(currentDate, (value) => emit('update:modelValue', value));
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(value) => {
|
||||
value = formatValue(value);
|
||||
|
||||
if (value !== currentDate.value) {
|
||||
currentDate.value = value;
|
||||
updateColumnValue();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
useExpose({
|
||||
getPicker: () =>
|
||||
picker.value && proxyPickerMethods(picker.value, updateInnerValue),
|
||||
});
|
||||
|
||||
return () => (
|
||||
<Picker
|
||||
v-slots={slots}
|
||||
ref={picker}
|
||||
columns={columns.value}
|
||||
onChange={onChange}
|
||||
onCancel={onCancel}
|
||||
onConfirm={onConfirm}
|
||||
{...pick(props, pickerInheritKeys)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
@ -1,153 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import VanDatetimePicker from '..';
|
||||
import { reactive } from 'vue';
|
||||
import { useTranslate } from '../../../docs/site/use-translate';
|
||||
|
||||
const t = useTranslate({
|
||||
'zh-CN': {
|
||||
day: '日',
|
||||
year: '年',
|
||||
month: '月',
|
||||
timeType: '选择时间',
|
||||
dateType: '选择年月日',
|
||||
datetimeType: '选择完整时间',
|
||||
datehourType: '选择年月日小时',
|
||||
monthDayType: '选择月日',
|
||||
yearMonthType: '选择年月',
|
||||
optionFilter: '选项过滤器',
|
||||
sortColumns: '自定义列排序',
|
||||
},
|
||||
'en-US': {
|
||||
day: ' Day',
|
||||
year: ' Year',
|
||||
month: ' Month',
|
||||
timeType: 'Choose Time',
|
||||
dateType: 'Choose Date',
|
||||
datetimeType: 'Choose DateTime',
|
||||
datehourType: 'Choose DateHour',
|
||||
monthDayType: 'Choose Month-Day',
|
||||
yearMonthType: 'Choose Year-Month',
|
||||
optionFilter: 'Option Filter',
|
||||
sortColumns: 'Columns Order',
|
||||
},
|
||||
});
|
||||
|
||||
const value = reactive({
|
||||
date: new Date(2021, 0, 17),
|
||||
time: '12:00',
|
||||
datetime: new Date(2020, 0, 1),
|
||||
datehour: new Date(2020, 0, 1),
|
||||
monthDay: new Date(2020, 0, 1),
|
||||
yearMonth: new Date(2020, 0, 1),
|
||||
optionFilter: '12:00',
|
||||
sortColumnsDate: new Date(2020, 0, 1),
|
||||
});
|
||||
|
||||
const minDate = new Date(2020, 0, 1);
|
||||
const maxDate = new Date(2025, 10, 1);
|
||||
|
||||
const filter = (type: string, values: string[]) => {
|
||||
if (type === 'minute') {
|
||||
return values.filter((value) => Number(value) % 5 === 0);
|
||||
}
|
||||
return values;
|
||||
};
|
||||
|
||||
const formatter = (type: string, value: string) => {
|
||||
if (type === 'year') {
|
||||
return value + t('year');
|
||||
}
|
||||
if (type === 'month') {
|
||||
return value + t('month');
|
||||
}
|
||||
if (type === 'day') {
|
||||
return value + t('day');
|
||||
}
|
||||
return value;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<demo-block card :title="t('dateType')">
|
||||
<van-datetime-picker
|
||||
v-model="value.date"
|
||||
type="date"
|
||||
:title="t('dateType')"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block card :title="t('yearMonthType')">
|
||||
<van-datetime-picker
|
||||
v-model="value.yearMonth"
|
||||
type="year-month"
|
||||
:title="t('yearMonthType')"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:formatter="formatter"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block card :title="t('monthDayType')">
|
||||
<van-datetime-picker
|
||||
v-model="value.monthDay"
|
||||
type="month-day"
|
||||
:title="t('monthDayType')"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:formatter="formatter"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block card :title="t('timeType')">
|
||||
<van-datetime-picker
|
||||
v-model="value.time"
|
||||
type="time"
|
||||
:title="t('timeType')"
|
||||
:min-hour="10"
|
||||
:max-hour="20"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block card :title="t('datetimeType')">
|
||||
<van-datetime-picker
|
||||
v-model="value.datetime"
|
||||
type="datetime"
|
||||
:title="t('datetimeType')"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block card :title="t('datehourType')">
|
||||
<van-datetime-picker
|
||||
v-model="value.datehour"
|
||||
type="datehour"
|
||||
:title="t('datehourType')"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block card :title="t('optionFilter')">
|
||||
<van-datetime-picker
|
||||
v-model="value.optionFilter"
|
||||
type="time"
|
||||
:title="t('optionFilter')"
|
||||
:filter="filter"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block card :title="t('sortColumns')">
|
||||
<van-datetime-picker
|
||||
v-model="value.sortColumnsDate"
|
||||
type="date"
|
||||
:title="t('sortColumns')"
|
||||
:columns-order="['month', 'day', 'year']"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:formatter="formatter"
|
||||
/>
|
||||
</demo-block>
|
||||
</template>
|
||||
@ -1,13 +0,0 @@
|
||||
import { withInstall } from '../utils';
|
||||
import _DatetimePicker, { DatetimePickerProps } from './DatetimePicker';
|
||||
|
||||
export const DatetimePicker = withInstall(_DatetimePicker);
|
||||
export default DatetimePicker;
|
||||
export type { DatetimePickerProps };
|
||||
export type { DatetimePickerType, DatetimePickerInstance } from './types';
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
VanDatetimePicker: typeof DatetimePicker;
|
||||
}
|
||||
}
|
||||
@ -1,220 +0,0 @@
|
||||
import DatePicker from '../DatePicker';
|
||||
import { mount, later, triggerDrag } from '../../../test';
|
||||
|
||||
function filter(type: string, options: string[]): string[] {
|
||||
const mod = type === 'year' ? 10 : 5;
|
||||
return options.filter((option: string) => Number(option) % mod === 0);
|
||||
}
|
||||
|
||||
function formatter(type: string, value: string): string {
|
||||
return `${value} ${type}`;
|
||||
}
|
||||
|
||||
test('filter prop', async () => {
|
||||
const wrapper = mount(DatePicker, {
|
||||
props: {
|
||||
filter,
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 10, 1),
|
||||
modelValue: new Date(2020, 10, 1, 0, 0),
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('formatter prop', async () => {
|
||||
const wrapper = mount(DatePicker, {
|
||||
props: {
|
||||
filter,
|
||||
formatter,
|
||||
minDate: new Date(2010, 0, 1),
|
||||
maxDate: new Date(2025, 10, 1),
|
||||
modelValue: new Date(2020, 10, 1, 0, 0),
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
|
||||
triggerDrag(wrapper.find('.van-picker-column'), 0, -100);
|
||||
wrapper.find('.van-picker-column ul').trigger('transitionend');
|
||||
await later();
|
||||
|
||||
expect((wrapper.vm as Record<string, any>).getPicker().getValues()).toEqual([
|
||||
'2020 year',
|
||||
'05 month',
|
||||
'05 day',
|
||||
'00 hour',
|
||||
'00 minute',
|
||||
]);
|
||||
});
|
||||
|
||||
test('confirm event', () => {
|
||||
const date = new Date(2020, 10, 1, 0, 0);
|
||||
|
||||
const wrapper = mount(DatePicker, {
|
||||
props: {
|
||||
modelValue: date,
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 10, 1),
|
||||
},
|
||||
});
|
||||
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(wrapper.emitted<[Date]>('confirm')![0][0].getFullYear()).toEqual(2020);
|
||||
|
||||
triggerDrag(wrapper.find('.van-picker-column'), 0, -100);
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(wrapper.emitted<[Date]>('confirm')![1][0].getFullYear()).toEqual(2025);
|
||||
});
|
||||
|
||||
test('year-month type', async () => {
|
||||
const date = new Date(2020, 10, 1, 0, 0);
|
||||
|
||||
const wrapper = mount(DatePicker, {
|
||||
props: {
|
||||
type: 'year-month',
|
||||
modelValue: date,
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 10, 1),
|
||||
},
|
||||
});
|
||||
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(wrapper.emitted<[Date]>('confirm')![0][0].getFullYear()).toEqual(2020);
|
||||
expect(wrapper.emitted<[Date]>('confirm')![0][0].getMonth()).toEqual(10);
|
||||
|
||||
triggerDrag(wrapper.find('.van-picker-column'), 0, -100);
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(wrapper.emitted<[Date]>('confirm')![1][0].getFullYear()).toEqual(2025);
|
||||
expect(wrapper.emitted<[Date]>('confirm')![1][0].getMonth()).toEqual(0);
|
||||
|
||||
triggerDrag(wrapper.findAll('.van-picker-column')[0], 0, -100);
|
||||
await later();
|
||||
triggerDrag(wrapper.findAll('.van-picker-column')[1], 0, -100);
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(wrapper.emitted<[Date]>('confirm')![2][0].getFullYear()).toEqual(2025);
|
||||
expect(wrapper.emitted<[Date]>('confirm')![2][0].getMonth()).toEqual(10);
|
||||
});
|
||||
|
||||
test('month-day type', async () => {
|
||||
const date = new Date(2020, 10, 1, 0, 0);
|
||||
|
||||
const wrapper = mount(DatePicker, {
|
||||
props: {
|
||||
type: 'month-day',
|
||||
modelValue: date,
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 10, 1),
|
||||
},
|
||||
});
|
||||
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(wrapper.emitted<[Date]>('confirm')![0][0].getMonth()).toEqual(10);
|
||||
expect(wrapper.emitted<[Date]>('confirm')![0][0].getDate()).toEqual(1);
|
||||
|
||||
triggerDrag(wrapper.find('.van-picker-column'), 0, -300);
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(wrapper.emitted<[Date]>('confirm')![1][0].getMonth()).toEqual(11);
|
||||
expect(wrapper.emitted<[Date]>('confirm')![1][0].getDate()).toEqual(1);
|
||||
|
||||
triggerDrag(wrapper.find('.van-picker-column'), 0, -300);
|
||||
await later();
|
||||
triggerDrag(wrapper.findAll('.van-picker-column')[1], 0, -300);
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(wrapper.emitted<[Date]>('confirm')![2][0].getMonth()).toEqual(11);
|
||||
expect(wrapper.emitted<[Date]>('confirm')![2][0].getDate()).toEqual(31);
|
||||
});
|
||||
|
||||
test('datehour type', () => {
|
||||
const wrapper = mount(DatePicker, {
|
||||
props: {
|
||||
minDate: new Date(2010, 0, 1),
|
||||
maxDate: new Date(2025, 10, 1),
|
||||
modelValue: new Date(2020, 10, 1, 0, 0),
|
||||
},
|
||||
});
|
||||
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(wrapper.emitted<[Date]>('confirm')![0][0].getHours()).toEqual(0);
|
||||
|
||||
triggerDrag(wrapper.findAll('.van-picker-column')[3], 0, -300);
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(wrapper.emitted<[Date]>('confirm')![1][0].getHours()).toEqual(23);
|
||||
});
|
||||
|
||||
test('cancel event', () => {
|
||||
const wrapper = mount(DatePicker);
|
||||
|
||||
wrapper.find('.van-picker__cancel').trigger('click');
|
||||
expect(wrapper.emitted('cancel')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('max-date prop', () => {
|
||||
const maxDate = new Date(2010, 5, 0, 0, 0);
|
||||
const wrapper = mount(DatePicker, {
|
||||
props: {
|
||||
modelValue: new Date(2020, 10, 30, 30, 30),
|
||||
maxDate,
|
||||
},
|
||||
});
|
||||
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(wrapper.emitted<[Date]>('confirm')![0][0]).toEqual(maxDate);
|
||||
});
|
||||
|
||||
test('min-date prop', () => {
|
||||
const minDate = new Date(2030, 0, 0, 0, 0);
|
||||
const wrapper = mount(DatePicker, {
|
||||
props: {
|
||||
modelValue: new Date(2020, 0, 0, 0, 0),
|
||||
minDate,
|
||||
},
|
||||
});
|
||||
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(wrapper.emitted<[Date]>('confirm')![0][0]).toEqual(minDate);
|
||||
});
|
||||
|
||||
test('dynamic set value', async () => {
|
||||
const wrapper = mount(DatePicker, {
|
||||
props: {
|
||||
modelValue: new Date(2019, 1, 1),
|
||||
},
|
||||
});
|
||||
|
||||
await wrapper.setProps({ modelValue: new Date(2019, 1, 1) });
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
await wrapper.setProps({ modelValue: new Date(2025, 1, 1) });
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
|
||||
expect(wrapper.emitted<[Date]>('confirm')![0][0].getFullYear()).toEqual(2019);
|
||||
expect(wrapper.emitted<[Date]>('confirm')![1][0].getFullYear()).toEqual(2025);
|
||||
});
|
||||
|
||||
test('use min-date with filter', async () => {
|
||||
const minDate = new Date(2030, 0, 0, 0, 3);
|
||||
const maxDate = new Date(2040, 0, 0, 0, 0);
|
||||
|
||||
const wrapper = mount(DatePicker, {
|
||||
props: {
|
||||
minDate,
|
||||
maxDate,
|
||||
modelValue: new Date(2020, 0, 0, 0, 0),
|
||||
filter(type: string, values: string[]) {
|
||||
if (type === 'minute') {
|
||||
return values.filter((value) => Number(value) % 30 === 0);
|
||||
}
|
||||
|
||||
return values;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await later();
|
||||
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(wrapper.emitted<[Date]>('confirm')![0][0]).toEqual(
|
||||
new Date(2030, 0, 0, 0, 30)
|
||||
);
|
||||
});
|
||||
@ -1,116 +0,0 @@
|
||||
import { DatetimePicker } from '..';
|
||||
import { mount, later } from '../../../test';
|
||||
import { reactive } from 'vue';
|
||||
import { useExpose } from '../../composables/use-expose';
|
||||
|
||||
test('should emit confirm event after clicking the confirm button', () => {
|
||||
const onConfirm = jest.fn();
|
||||
const wrapper = mount(DatetimePicker, {
|
||||
props: {
|
||||
onConfirm,
|
||||
},
|
||||
});
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(onConfirm).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should emit cancel event after clicking the confirm button', () => {
|
||||
const onCancel = jest.fn();
|
||||
const wrapper = mount(DatetimePicker, {
|
||||
props: {
|
||||
onCancel,
|
||||
},
|
||||
});
|
||||
wrapper.find('.van-picker__cancel').trigger('click');
|
||||
expect(onCancel).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('should allow to call getPicker method', () => {
|
||||
const wrapper = mount(DatetimePicker);
|
||||
|
||||
expect(wrapper.vm.getPicker()).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should render title slot correctly', () => {
|
||||
const wrapper = mount(DatetimePicker, {
|
||||
props: {
|
||||
showToolbar: true,
|
||||
},
|
||||
slots: {
|
||||
title: () => 'Custom title',
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.find('.van-picker__toolbar').html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should emit value correctly when dynamic change min-date', async () => {
|
||||
const defaultValue = new Date(2020, 10, 2, 10, 30);
|
||||
const wrapper = mount({
|
||||
emits: ['confirm'],
|
||||
setup(_, { emit }) {
|
||||
const state = reactive({
|
||||
date: defaultValue,
|
||||
minDate: new Date(2010, 0, 1, 10, 30),
|
||||
});
|
||||
|
||||
const onChange = () => {
|
||||
state.minDate = state.date;
|
||||
};
|
||||
|
||||
useExpose({
|
||||
onChange,
|
||||
});
|
||||
|
||||
return () => (
|
||||
<DatetimePicker
|
||||
v-model={state.date}
|
||||
minDate={state.minDate}
|
||||
onConfirm={(value: Date) => emit('confirm', value)}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
await later();
|
||||
(wrapper.vm as Record<string, any>).onChange();
|
||||
await later();
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(wrapper.emitted<[Date]>('confirm')![0][0]).toEqual(defaultValue);
|
||||
});
|
||||
|
||||
test('should update value correctly after calling setColumnIndex method', async () => {
|
||||
const onConfirm = jest.fn();
|
||||
const defaultDate = new Date(2020, 0, 1);
|
||||
const wrapper = mount(DatetimePicker, {
|
||||
props: {
|
||||
type: 'date',
|
||||
minDate: defaultDate,
|
||||
maxDate: new Date(2020, 0, 30),
|
||||
modelValue: defaultDate,
|
||||
onConfirm,
|
||||
},
|
||||
});
|
||||
|
||||
wrapper.vm.getPicker().setColumnIndex(2, 14);
|
||||
await wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(onConfirm.mock.calls[0]).toEqual([new Date(2020, 0, 15)]);
|
||||
});
|
||||
|
||||
test('should update value correctly after calling setColumnValue method', async () => {
|
||||
const onConfirm = jest.fn();
|
||||
const defaultDate = new Date(2020, 0, 1);
|
||||
const wrapper = mount(DatetimePicker, {
|
||||
props: {
|
||||
type: 'date',
|
||||
minDate: defaultDate,
|
||||
maxDate: new Date(2020, 0, 30),
|
||||
modelValue: defaultDate,
|
||||
onConfirm,
|
||||
},
|
||||
});
|
||||
|
||||
wrapper.vm.getPicker().setColumnValue(2, '15');
|
||||
await wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(onConfirm.mock.calls[0]).toEqual([new Date(2020, 0, 15)]);
|
||||
});
|
||||
@ -1,59 +0,0 @@
|
||||
import { later, mount, triggerDrag } from '../../../test';
|
||||
import DatePicker from '../DatePicker';
|
||||
|
||||
test('month-day type', async () => {
|
||||
const date = new Date(2020, 10, 1, 0, 0);
|
||||
|
||||
const wrapper = mount(DatePicker, {
|
||||
props: {
|
||||
type: 'month-day',
|
||||
modelValue: date,
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 10, 1),
|
||||
},
|
||||
});
|
||||
|
||||
await later();
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(wrapper.emitted<[Date]>('confirm')![0][0].getMonth()).toEqual(10);
|
||||
expect(wrapper.emitted<[Date]>('confirm')![0][0].getDate()).toEqual(1);
|
||||
|
||||
await later();
|
||||
triggerDrag(wrapper.find('.van-picker-column'), 0, -300);
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(wrapper.emitted<[Date]>('confirm')![1][0].getMonth()).toEqual(11);
|
||||
expect(wrapper.emitted<[Date]>('confirm')![1][0].getDate()).toEqual(1);
|
||||
|
||||
await later();
|
||||
triggerDrag(wrapper.findAll('.van-picker-column')[1], 0, -300);
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(wrapper.emitted<[Date]>('confirm')![2][0].getMonth()).toEqual(11);
|
||||
expect(wrapper.emitted<[Date]>('confirm')![2][0].getDate()).toEqual(31);
|
||||
});
|
||||
|
||||
test('v-model', async () => {
|
||||
const minDate = new Date(2030, 0, 0, 0, 3);
|
||||
|
||||
const wrapper = mount(DatePicker, {
|
||||
props: {
|
||||
minDate,
|
||||
},
|
||||
});
|
||||
|
||||
await later();
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(wrapper.emitted<[Date]>('confirm')![0][0]).toEqual(minDate);
|
||||
});
|
||||
|
||||
test('value has an initial value', async () => {
|
||||
const defaultValue = new Date(2020, 0, 0, 0, 0);
|
||||
const wrapper = mount(DatePicker, {
|
||||
propsData: {
|
||||
modelValue: defaultValue,
|
||||
},
|
||||
});
|
||||
|
||||
await later();
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(wrapper.emitted<[Date]>('confirm')![0][0]).toEqual(defaultValue);
|
||||
});
|
||||
@ -1,27 +0,0 @@
|
||||
import { ComponentPublicInstance } from 'vue';
|
||||
import type { PickerInstance } from '../picker';
|
||||
import { DatetimePickerProps } from './DatetimePicker';
|
||||
|
||||
export type DatetimePickerColumnType =
|
||||
| 'year'
|
||||
| 'month'
|
||||
| 'day'
|
||||
| 'hour'
|
||||
| 'minute';
|
||||
|
||||
export type DatetimePickerType =
|
||||
| 'date'
|
||||
| 'time'
|
||||
| 'datetime'
|
||||
| 'datehour'
|
||||
| 'month-day'
|
||||
| 'year-month';
|
||||
|
||||
export type DatetimePickerExpose = {
|
||||
getPicker: () => PickerInstance;
|
||||
};
|
||||
|
||||
export type DatetimePickerInstance = ComponentPublicInstance<
|
||||
DatetimePickerProps,
|
||||
DatetimePickerExpose
|
||||
>;
|
||||
@ -1,78 +0,0 @@
|
||||
import { extend } from '../utils';
|
||||
import { pickerSharedProps } from '../picker/Picker';
|
||||
import type { PropType } from 'vue';
|
||||
import type { PickerInstance, PickerOption } from '../picker';
|
||||
|
||||
export const sharedProps = extend({}, pickerSharedProps, {
|
||||
filter: Function as PropType<
|
||||
(columnType: string, options: PickerOption[]) => PickerOption[]
|
||||
>,
|
||||
formatter: {
|
||||
type: Function as PropType<
|
||||
(type: string, option: PickerOption) => PickerOption
|
||||
>,
|
||||
default: (type: string, option: PickerOption) => option,
|
||||
},
|
||||
});
|
||||
|
||||
export const pickerInheritKeys = Object.keys(pickerSharedProps) as Array<
|
||||
keyof typeof pickerSharedProps
|
||||
>;
|
||||
|
||||
export function times<T>(n: number, iteratee: (index: number) => T) {
|
||||
if (n < 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const result: T[] = Array(n);
|
||||
|
||||
let index = -1;
|
||||
while (++index < n) {
|
||||
result[index] = iteratee(index);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getTrueValue(value: string | undefined): number {
|
||||
if (!value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (Number.isNaN(parseInt(value, 10))) {
|
||||
if (value.length > 1) {
|
||||
value = value.slice(1);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return parseInt(value, 10);
|
||||
}
|
||||
|
||||
export const getMonthEndDay = (year: number, month: number): number =>
|
||||
32 - new Date(year, month - 1, 32).getDate();
|
||||
|
||||
// https://github.com/youzan/vant/issues/10013
|
||||
export const proxyPickerMethods = (
|
||||
picker: PickerInstance,
|
||||
callback: () => void
|
||||
) => {
|
||||
const methods = [
|
||||
'setValues',
|
||||
'setIndexes',
|
||||
'setColumnIndex',
|
||||
'setColumnValue',
|
||||
];
|
||||
return new Proxy(picker, {
|
||||
get: (target, prop: keyof PickerInstance) => {
|
||||
if (methods.includes(prop)) {
|
||||
return (...args: unknown[]) => {
|
||||
target[prop](...args);
|
||||
callback();
|
||||
};
|
||||
}
|
||||
return target[prop];
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -1,5 +1,7 @@
|
||||
import {
|
||||
ref,
|
||||
reactive,
|
||||
withKeys,
|
||||
defineComponent,
|
||||
type PropType,
|
||||
type ExtractPropTypes,
|
||||
@ -7,6 +9,7 @@ import {
|
||||
|
||||
// Utils
|
||||
import {
|
||||
noop,
|
||||
pick,
|
||||
extend,
|
||||
addUnit,
|
||||
@ -19,6 +22,7 @@ import {
|
||||
makeStringProp,
|
||||
callInterceptor,
|
||||
createNamespace,
|
||||
type ComponentInstance,
|
||||
} from '../utils';
|
||||
import { popupSharedProps, popupSharedPropKeys } from '../popup/shared';
|
||||
|
||||
@ -71,9 +75,10 @@ export default defineComponent({
|
||||
|
||||
props: dialogProps,
|
||||
|
||||
emits: ['confirm', 'cancel', 'update:show'],
|
||||
emits: ['confirm', 'cancel', 'keydown', 'update:show'],
|
||||
|
||||
setup(props, { emit, slots }) {
|
||||
const root = ref<ComponentInstance>();
|
||||
const loading = reactive({
|
||||
confirm: false,
|
||||
cancel: false,
|
||||
@ -113,6 +118,23 @@ export default defineComponent({
|
||||
|
||||
const onCancel = getActionHandler('cancel');
|
||||
const onConfirm = getActionHandler('confirm');
|
||||
const onKeydown = withKeys(
|
||||
(event: KeyboardEvent) => {
|
||||
// skip keyboard events of child elements
|
||||
if (event.target !== root.value?.popupRef?.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const onEventType: Record<string, () => void> = {
|
||||
Enter: props.showConfirmButton ? onConfirm : noop,
|
||||
Escape: props.showCancelButton ? onCancel : noop,
|
||||
};
|
||||
|
||||
onEventType[event.key]();
|
||||
emit('keydown', event);
|
||||
},
|
||||
['enter', 'esc']
|
||||
);
|
||||
|
||||
const renderTitle = () => {
|
||||
const title = slots.title ? slots.title() : props.title;
|
||||
@ -229,10 +251,13 @@ export default defineComponent({
|
||||
const { width, title, theme, message, className } = props;
|
||||
return (
|
||||
<Popup
|
||||
ref={root}
|
||||
role="dialog"
|
||||
class={[bem([theme]), className]}
|
||||
style={{ width: addUnit(width) }}
|
||||
tabindex={0}
|
||||
aria-labelledby={title || message}
|
||||
onKeydown={onKeydown}
|
||||
onUpdate:show={updateShow}
|
||||
{...pick(props, popupInheritKeys)}
|
||||
>
|
||||
|
||||
@ -97,6 +97,7 @@ exports[`should render demo and match snapshot 1`] = `
|
||||
<transition-stub>
|
||||
<div class="van-popup van-popup--center van-dialog"
|
||||
role="dialog"
|
||||
tabindex="0"
|
||||
aria-labelledby="Title"
|
||||
style="display: none;"
|
||||
>
|
||||
|
||||
@ -32,6 +32,7 @@ exports[`should render default slot correctly 1`] = `
|
||||
exports[`should render footer slot correctly 1`] = `
|
||||
<div class="van-popup van-popup--center van-dialog"
|
||||
role="dialog"
|
||||
tabindex="0"
|
||||
aria-labelledby="message"
|
||||
>
|
||||
<div class="van-dialog__content van-dialog__content--isolated">
|
||||
|
||||
@ -64,12 +64,12 @@ export default {
|
||||
<van-dropdown-item title="Title" ref="item">
|
||||
<van-cell center title="Title">
|
||||
<template #right-icon>
|
||||
<van-switch v-model="switch1" size="24" />
|
||||
<van-switch v-model="switch1" />
|
||||
</template>
|
||||
</van-cell>
|
||||
<van-cell center title="Title">
|
||||
<template #right-icon>
|
||||
<van-switch v-model="switch2" size="24" />
|
||||
<van-switch v-model="switch2" />
|
||||
</template>
|
||||
</van-cell>
|
||||
<div style="padding: 5px 16px;">
|
||||
|
||||
@ -66,12 +66,12 @@ export default {
|
||||
<van-dropdown-item title="筛选" ref="item">
|
||||
<van-cell center title="包邮">
|
||||
<template #right-icon>
|
||||
<van-switch v-model="switch1" size="24" />
|
||||
<van-switch v-model="switch1" />
|
||||
</template>
|
||||
</van-cell>
|
||||
<van-cell center title="团购">
|
||||
<template #right-icon>
|
||||
<van-switch v-model="switch2" size="24" />
|
||||
<van-switch v-model="switch2" />
|
||||
</template>
|
||||
</van-cell>
|
||||
<div style="padding: 5px 16px;">
|
||||
|
||||
@ -78,12 +78,12 @@ const onConfirm = () => {
|
||||
<van-dropdown-item :title="t('itemTitle')" ref="item">
|
||||
<van-cell center :title="t('switchTitle1')">
|
||||
<template #right-icon>
|
||||
<van-switch v-model="switch1" size="24" />
|
||||
<van-switch v-model="switch1" />
|
||||
</template>
|
||||
</van-cell>
|
||||
<van-cell center :title="t('switchTitle2')">
|
||||
<template #right-icon>
|
||||
<van-switch v-model="switch2" size="24" />
|
||||
<van-switch v-model="switch2" />
|
||||
</template>
|
||||
</van-cell>
|
||||
<div style="padding: 5px 16px">
|
||||
|
||||
@ -157,7 +157,7 @@ export default {
|
||||
```html
|
||||
<van-field name="switch" label="Switch">
|
||||
<template #input>
|
||||
<van-switch v-model="checked" size="20" />
|
||||
<van-switch v-model="checked" />
|
||||
</template>
|
||||
</van-field>
|
||||
```
|
||||
@ -359,24 +359,20 @@ export default {
|
||||
};
|
||||
```
|
||||
|
||||
### Field Type - DatetimePicker
|
||||
### Field Type - DatePicker
|
||||
|
||||
```html
|
||||
<van-field
|
||||
v-model="result"
|
||||
is-link
|
||||
readonly
|
||||
name="datetimePicker"
|
||||
label="Datetime Picker"
|
||||
placeholder="Select time"
|
||||
name="datePicker"
|
||||
label="Date Picker"
|
||||
placeholder="Select date"
|
||||
@click="showPicker = true"
|
||||
/>
|
||||
<van-popup v-model:show="showPicker" position="bottom">
|
||||
<van-datetime-picker
|
||||
type="time"
|
||||
@confirm="onConfirm"
|
||||
@cancel="showPicker = false"
|
||||
/>
|
||||
<van-date-picker @confirm="onConfirm" @cancel="showPicker = false" />
|
||||
</van-popup>
|
||||
```
|
||||
|
||||
@ -387,9 +383,8 @@ export default {
|
||||
setup() {
|
||||
const result = ref('');
|
||||
const showPicker = ref(false);
|
||||
|
||||
const onConfirm = (value) => {
|
||||
result.value = value;
|
||||
const onConfirm = ({ selectedValues }) => {
|
||||
result.value = selectedValues.join('/');
|
||||
showPicker.value = false;
|
||||
};
|
||||
|
||||
@ -431,12 +426,9 @@ export default {
|
||||
setup() {
|
||||
const result = ref('');
|
||||
const showArea = ref(false);
|
||||
const onConfirm = (areaValues) => {
|
||||
const onConfirm = ({ selectedOptions }) => {
|
||||
showArea.value = false;
|
||||
result.value = areaValues
|
||||
.filter((item) => !!item)
|
||||
.map((item) => item.name)
|
||||
.join('/');
|
||||
areaCode.value = selectedOptions.map((item) => item.text).join('/');
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@ -169,7 +169,7 @@ export default {
|
||||
```html
|
||||
<van-field name="switch" label="开关">
|
||||
<template #input>
|
||||
<van-switch v-model="checked" size="20" />
|
||||
<van-switch v-model="checked" />
|
||||
</template>
|
||||
</van-field>
|
||||
```
|
||||
@ -385,26 +385,22 @@ export default {
|
||||
};
|
||||
```
|
||||
|
||||
### 表单项类型 - 时间选择器
|
||||
### 表单项类型 - 日期选择器
|
||||
|
||||
在表单中使用 [DatetimePicker 组件](#/zh-CN/datetime-picker)。
|
||||
在表单中使用 [DatePicker 组件](#/zh-CN/date-picker)。
|
||||
|
||||
```html
|
||||
<van-field
|
||||
v-model="result"
|
||||
is-link
|
||||
readonly
|
||||
name="datetimePicker"
|
||||
name="datePicker"
|
||||
label="时间选择"
|
||||
placeholder="点击选择时间"
|
||||
@click="showPicker = true"
|
||||
/>
|
||||
<van-popup v-model:show="showPicker" position="bottom">
|
||||
<van-datetime-picker
|
||||
type="time"
|
||||
@confirm="onConfirm"
|
||||
@cancel="showPicker = false"
|
||||
/>
|
||||
<van-date-picker @confirm="onConfirm" @cancel="showPicker = false" />
|
||||
</van-popup>
|
||||
```
|
||||
|
||||
@ -415,9 +411,8 @@ export default {
|
||||
setup() {
|
||||
const result = ref('');
|
||||
const showPicker = ref(false);
|
||||
|
||||
const onConfirm = (value) => {
|
||||
result.value = value;
|
||||
result.value = selectedValues.join('/');
|
||||
showPicker.value = false;
|
||||
};
|
||||
|
||||
@ -461,12 +456,9 @@ export default {
|
||||
setup() {
|
||||
const result = ref('');
|
||||
const showArea = ref(false);
|
||||
const onConfirm = (areaValues) => {
|
||||
const onConfirm = ({ selectedOptions }) => {
|
||||
showArea.value = false;
|
||||
result.value = areaValues
|
||||
.filter((item) => !!item)
|
||||
.map((item) => item.name)
|
||||
.join('/');
|
||||
areaCode.value = selectedOptions.map((item) => item.text).join('/');
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@ -17,7 +17,7 @@ import VanCheckboxGroup from '../../checkbox-group';
|
||||
import FieldTypeArea from './FieldTypeArea.vue';
|
||||
import FieldTypePicker from './FieldTypePicker.vue';
|
||||
import FieldTypeCalendar from './FieldTypeCalendar.vue';
|
||||
import FieldTypeDatetimePicker from './FieldTypeDatetimePicker.vue';
|
||||
import FieldTypeDatePicker from './FieldTypeDatePicker.vue';
|
||||
|
||||
const t = useTranslate({
|
||||
'zh-CN': {
|
||||
@ -70,7 +70,7 @@ const onSubmit = (values: Record<string, string>) => {
|
||||
<van-cell-group inset>
|
||||
<van-field name="switch" :label="t('switch')">
|
||||
<template #input>
|
||||
<van-switch v-model="switchChecked" size="20" />
|
||||
<van-switch v-model="switchChecked" />
|
||||
</template>
|
||||
</van-field>
|
||||
|
||||
@ -127,7 +127,7 @@ const onSubmit = (values: Record<string, string>) => {
|
||||
</van-field>
|
||||
|
||||
<field-type-picker />
|
||||
<field-type-datetime-picker />
|
||||
<field-type-date-picker />
|
||||
<field-type-area />
|
||||
<field-type-calendar />
|
||||
</van-cell-group>
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import VanArea, { AreaColumnOption } from '../../area';
|
||||
import VanArea from '../../area';
|
||||
import VanField from '../../field';
|
||||
import VanPopup from '../../popup';
|
||||
import { ref } from 'vue';
|
||||
import { areaList } from '@vant/area-data';
|
||||
import { useTranslate } from '../../../docs/site/use-translate';
|
||||
import { areaListEn } from '../../area/demo/area-en';
|
||||
import type { PickerConfirmEventParams } from '../../picker';
|
||||
|
||||
const t = useTranslate({
|
||||
'zh-CN': {
|
||||
@ -23,11 +24,8 @@ const t = useTranslate({
|
||||
const areaCode = ref('');
|
||||
const showArea = ref(false);
|
||||
|
||||
const onConfirm = (areaValues: AreaColumnOption[]) => {
|
||||
areaCode.value = areaValues
|
||||
.filter((item) => !!item)
|
||||
.map((item) => item.name)
|
||||
.join('/');
|
||||
const onConfirm = ({ selectedOptions }: PickerConfirmEventParams) => {
|
||||
areaCode.value = selectedOptions.map((item) => item.text).join('/');
|
||||
showArea.value = false;
|
||||
};
|
||||
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import VanField from '../../field';
|
||||
import VanPopup from '../../popup';
|
||||
import VanDatetimePicker from '../../datetime-picker';
|
||||
import VanDatePicker from '../../date-picker';
|
||||
import { ref } from 'vue';
|
||||
import { useTranslate } from '../../../docs/site/use-translate';
|
||||
import type { PickerConfirmEventParams } from '../../picker';
|
||||
|
||||
const t = useTranslate({
|
||||
'zh-CN': {
|
||||
@ -19,8 +20,8 @@ const t = useTranslate({
|
||||
const result = ref('');
|
||||
const showPicker = ref(false);
|
||||
|
||||
const onConfirm = (time: string) => {
|
||||
result.value = time;
|
||||
const onConfirm = ({ selectedValues }: PickerConfirmEventParams) => {
|
||||
result.value = selectedValues.join('/');
|
||||
showPicker.value = false;
|
||||
};
|
||||
|
||||
@ -34,12 +35,12 @@ const onCancel = () => {
|
||||
v-model="result"
|
||||
is-link
|
||||
readonly
|
||||
name="datetimePicker"
|
||||
name="datePicker"
|
||||
:label="t('label')"
|
||||
:placeholder="t('placeholder')"
|
||||
@click="showPicker = true"
|
||||
/>
|
||||
<van-popup v-model:show="showPicker" round position="bottom" teleport="body">
|
||||
<van-datetime-picker type="time" @confirm="onConfirm" @cancel="onCancel" />
|
||||
<van-date-picker @confirm="onConfirm" @cancel="onCancel" />
|
||||
</van-popup>
|
||||
</template>
|
||||
@ -171,7 +171,6 @@ exports[`should render demo and match snapshot 1`] = `
|
||||
<div class="van-field__control van-field__control--custom">
|
||||
<div role="switch"
|
||||
class="van-switch"
|
||||
style="font-size: 20px;"
|
||||
tabindex="0"
|
||||
aria-checked="false"
|
||||
>
|
||||
@ -523,7 +522,7 @@ exports[`should render demo and match snapshot 1`] = `
|
||||
<div class="van-field__body">
|
||||
<input type="text"
|
||||
id="van-field-input"
|
||||
name="datetimePicker"
|
||||
name="datePicker"
|
||||
class="van-field__control"
|
||||
readonly
|
||||
placeholder="Select time"
|
||||
|
||||
@ -39,25 +39,30 @@ Locale.add(messages);
|
||||
|
||||
Current supported languages:
|
||||
|
||||
| Language | Filename |
|
||||
| ------------------------ | ------------ |
|
||||
| German | de-DE |
|
||||
| German (formal) | de-DE-formal |
|
||||
| English | en-US |
|
||||
| Spanish (Spain) | es-ES |
|
||||
| French | fr-FR |
|
||||
| Hindi | hi-IN |
|
||||
| Japanese | ja-JP |
|
||||
| Korean | ko-KR |
|
||||
| Norwegian | nb-NO |
|
||||
| Portuguese (Brazil) | pt-BR |
|
||||
| Romanian | ro-RO |
|
||||
| Russian | ru-RU |
|
||||
| Turkish | tr-TR |
|
||||
| Thai | th-TH |
|
||||
| Chinese | zh-CN |
|
||||
| Traditional Chinese (HK) | zh-HK |
|
||||
| Traditional Chinese (TW) | zh-TW |
|
||||
| Language | Filename | Version |
|
||||
| ------------------------ | ------------ | -------- |
|
||||
| Bangla (Bangladesh) | bn-BD | `v3.4.5` |
|
||||
| German | de-DE | - |
|
||||
| German (formal) | de-DE-formal | - |
|
||||
| English | en-US | - |
|
||||
| Spanish (Spain) | es-ES | - |
|
||||
| French | fr-FR | - |
|
||||
| Hindi | hi-IN | `v3.4.3` |
|
||||
| Indonesian | id-ID | `v3.4.5` |
|
||||
| Italian | it-IT | `v3.4.5` |
|
||||
| Japanese | ja-JP | - |
|
||||
| Korean | ko-KR | `v3.4.3` |
|
||||
| Norwegian | nb-NO | - |
|
||||
| Portuguese (Brazil) | pt-BR | `v3.3.3` |
|
||||
| Romanian | ro-RO | - |
|
||||
| Russian | ru-RU | `v3.1.5` |
|
||||
| Turkish | tr-TR | - |
|
||||
| Thai | th-TH | - |
|
||||
| Ukrainian | uk-UA | `v3.4.5` |
|
||||
| Vietnamese | vi-VN | `v3.4.5` |
|
||||
| Chinese | zh-CN | - |
|
||||
| Traditional Chinese (HK) | zh-HK | - |
|
||||
| Traditional Chinese (TW) | zh-TW | - |
|
||||
|
||||
> View all language configs [Here](https://github.com/youzan/vant/tree/dev/packages/vant/src/locale/lang).
|
||||
|
||||
|
||||
@ -40,25 +40,30 @@ Locale.add(messages);
|
||||
|
||||
目前支持的语言:
|
||||
|
||||
| 语言 | 文件名 |
|
||||
| --------------- | ------------ |
|
||||
| 德语 | de-DE |
|
||||
| 德语 (正式) | de-DE-formal |
|
||||
| 英语 | en-US |
|
||||
| 西班牙语 | es-ES |
|
||||
| 法语 | fr-FR |
|
||||
| 印地语 | hi-IN |
|
||||
| 日语 | ja-JP |
|
||||
| 韩语/朝鲜语 | ko-KR.ts |
|
||||
| 挪威语 | nb-NO |
|
||||
| 葡萄牙语 (巴西) | pt-BR |
|
||||
| 罗马尼亚语 | ro-RO |
|
||||
| 俄罗斯语 | ru-RU |
|
||||
| 土耳其语 | tr-TR |
|
||||
| 泰语 | th-TH |
|
||||
| 简体中文 | zh-CN |
|
||||
| 繁體中文(港) | zh-HK |
|
||||
| 繁體中文(台) | zh-TW |
|
||||
| 语言 | 文件名 | 版本 |
|
||||
| -------------------- | ------------ | -------- |
|
||||
| 孟加拉语(孟加拉国) | bn-BD | `v3.4.5` |
|
||||
| 德语 | de-DE | - |
|
||||
| 德语(正式) | de-DE-formal | - |
|
||||
| 英语 | en-US | - |
|
||||
| 西班牙语 | es-ES | - |
|
||||
| 法语 | fr-FR | - |
|
||||
| 印地语 | hi-IN | `v3.4.3` |
|
||||
| 印度尼西亚语 | id-ID | `v3.4.5` |
|
||||
| 意大利语 | it-IT | `v3.4.5` |
|
||||
| 日语 | ja-JP | - |
|
||||
| 韩语/朝鲜语 | ko-KR | `v3.4.3` |
|
||||
| 挪威语 | nb-NO | - |
|
||||
| 葡萄牙语(巴西) | pt-BR | `v3.3.3` |
|
||||
| 罗马尼亚语 | ro-RO | - |
|
||||
| 俄罗斯语 | ru-RU | `v3.1.5` |
|
||||
| 土耳其语 | tr-TR | - |
|
||||
| 泰语 | th-TH | - |
|
||||
| 乌克兰语 | uk-UA | `v3.4.5` |
|
||||
| 越南语 | vi-VN | `v3.4.5` |
|
||||
| 简体中文 | zh-CN | - |
|
||||
| 繁體中文(港) | zh-HK | - |
|
||||
| 繁體中文(台) | zh-TW | - |
|
||||
|
||||
> 在 [这里](https://github.com/youzan/vant/tree/dev/packages/vant/src/locale/lang) 查看所有的语言包源文件。
|
||||
|
||||
|
||||
72
packages/vant/src/locale/lang/bn-BD.ts
Normal file
72
packages/vant/src/locale/lang/bn-BD.ts
Normal file
@ -0,0 +1,72 @@
|
||||
export default {
|
||||
name: 'নাম',
|
||||
tel: 'ফোন',
|
||||
save: 'সংরক্ষণ করুন',
|
||||
confirm: 'নিশ্চিত করুন',
|
||||
cancel: 'বাতিল',
|
||||
delete: 'মুছুন',
|
||||
loading: 'লোড হচ্ছে...',
|
||||
noCoupon: 'কোন কুপন নেই',
|
||||
nameEmpty: 'অনুগ্রহ করে নামটি পূরণ করুন',
|
||||
addContact: 'যোগাযোগ যোগ করুন',
|
||||
telInvalid: 'বিকৃত ফোন নম্বর',
|
||||
vanCalendar: {
|
||||
end: 'শেষ',
|
||||
start: 'শুরু',
|
||||
title: 'ক্যালেন্ডার',
|
||||
weekdays: [
|
||||
'রবিবার',
|
||||
'সোমবার',
|
||||
'মঙ্গলবার',
|
||||
'বুধবার',
|
||||
'বৃহস্পতিবার',
|
||||
'শুক্রবার',
|
||||
'শনিবার',
|
||||
],
|
||||
monthTitle: (year: number, month: number) => `${year}/${month}`,
|
||||
rangePrompt: (maxRange: number) =>
|
||||
`${maxRange} দিনের বেশি নির্বাচন করবেন না`,
|
||||
},
|
||||
vanCascader: {
|
||||
select: 'নির্বাচন',
|
||||
},
|
||||
vanPagination: {
|
||||
prev: 'পূর্ববর্তী',
|
||||
next: 'পরবর্তী',
|
||||
},
|
||||
vanPullRefresh: {
|
||||
pulling: 'রিফ্রেশ করতে টানুন...',
|
||||
loosing: 'রিফ্রেশ করতে আলগা...',
|
||||
},
|
||||
vanSubmitBar: {
|
||||
label: 'মোট:',
|
||||
},
|
||||
vanCoupon: {
|
||||
unlimited: 'আনলিমিটেড',
|
||||
discount: (discount: number) => `${discount * 10}% ছাড়`,
|
||||
condition: (condition: number) => `অন্তত ${condition}`,
|
||||
},
|
||||
vanCouponCell: {
|
||||
title: 'কুপন',
|
||||
count: (count: number) => `আপনার ${count} কুপন আছে`,
|
||||
},
|
||||
vanCouponList: {
|
||||
exchange: 'বিনিময়',
|
||||
close: 'বন্ধ',
|
||||
enable: 'উপলভ্য',
|
||||
disabled: 'অনুপলব্ধ',
|
||||
placeholder: 'কুপন কোড',
|
||||
},
|
||||
vanAddressEdit: {
|
||||
area: 'এরিয়া',
|
||||
postal: 'ডাক',
|
||||
areaEmpty: 'অনুগ্রহ করে একটি রিসিভিং এলাকা নির্বাচন করুন',
|
||||
addressEmpty: 'ঠিকানা খালি হতে পারে না',
|
||||
postalEmpty: 'ভুল পোস্টাল কোড',
|
||||
addressDetail: 'ঠিকানা',
|
||||
defaultAddress: 'ডিফল্ট ঠিকানা হিসাবে সেট করুন',
|
||||
},
|
||||
vanAddressList: {
|
||||
add: 'নতুন ঠিকানা যোগ করুন',
|
||||
},
|
||||
};
|
||||
@ -30,7 +30,7 @@ export default {
|
||||
loosing: 'Loslassen zum Aktualisieren...',
|
||||
},
|
||||
vanSubmitBar: {
|
||||
label: 'Total:',
|
||||
label: 'Total:',
|
||||
},
|
||||
vanCoupon: {
|
||||
unlimited: 'Unbegrenzt',
|
||||
|
||||
@ -30,7 +30,7 @@ export default {
|
||||
loosing: 'Loslassen zum Aktualisieren...',
|
||||
},
|
||||
vanSubmitBar: {
|
||||
label: 'Total:',
|
||||
label: 'Total:',
|
||||
},
|
||||
vanCoupon: {
|
||||
unlimited: 'Unbegrenzt',
|
||||
|
||||
@ -30,7 +30,7 @@ export default {
|
||||
loosing: 'Loose to refresh...',
|
||||
},
|
||||
vanSubmitBar: {
|
||||
label: 'Total:',
|
||||
label: 'Total:',
|
||||
},
|
||||
vanCoupon: {
|
||||
unlimited: 'Unlimited',
|
||||
|
||||
@ -30,7 +30,7 @@ export default {
|
||||
loosing: 'Suelta para recargar...',
|
||||
},
|
||||
vanSubmitBar: {
|
||||
label: 'Total:',
|
||||
label: 'Total:',
|
||||
},
|
||||
vanCoupon: {
|
||||
unlimited: 'Ilimitado',
|
||||
|
||||
@ -30,7 +30,7 @@ export default {
|
||||
loosing: 'Relâchez pour actualiser...',
|
||||
},
|
||||
vanSubmitBar: {
|
||||
label: 'Total:',
|
||||
label: 'Total:',
|
||||
},
|
||||
vanCoupon: {
|
||||
unlimited: 'Illimité',
|
||||
|
||||
@ -38,7 +38,7 @@ export default {
|
||||
loosing: 'ताज़ा करने के लिए ढीला...',
|
||||
},
|
||||
vanSubmitBar: {
|
||||
label: 'कुल:',
|
||||
label: 'कुल:',
|
||||
},
|
||||
vanCoupon: {
|
||||
unlimited: 'असीमित',
|
||||
|
||||
64
packages/vant/src/locale/lang/id-ID.ts
Normal file
64
packages/vant/src/locale/lang/id-ID.ts
Normal file
@ -0,0 +1,64 @@
|
||||
export default {
|
||||
name: 'Nama',
|
||||
tel: 'Telepon',
|
||||
save: 'Simpan',
|
||||
confirm: 'Konfirmasi',
|
||||
cancel: 'Batal',
|
||||
delete: 'Hapus',
|
||||
loading: 'Memuat...',
|
||||
noCoupon: 'Tidak ada kupon',
|
||||
nameEmpty: 'Silakan isi nama',
|
||||
addContact: 'Tambahkan kontak',
|
||||
telInvalid: 'Nomor telepon salah format',
|
||||
vanCalendar: {
|
||||
end: 'Akhir',
|
||||
start: 'Mulai',
|
||||
title: 'Kalender',
|
||||
weekdays: ['minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu'],
|
||||
monthTitle: (year: number, month: number) => `${year}/${month}`,
|
||||
rangePrompt: (maxRange: number) =>
|
||||
`Pilih tidak lebih dari ${maxRange} hari`,
|
||||
},
|
||||
vanCascader: {
|
||||
select: 'Pilih',
|
||||
},
|
||||
vanPagination: {
|
||||
prev: 'Sebelumnya',
|
||||
next: 'Selanjutnya',
|
||||
},
|
||||
vanPullRefresh: {
|
||||
pulling: 'Tarik untuk menyegarkan...',
|
||||
loosing: 'Loose untuk menyegarkan...',
|
||||
},
|
||||
vanSubmitBar: {
|
||||
label: 'Jumlah:',
|
||||
},
|
||||
vanCoupon: {
|
||||
unlimited: 'Tidak terbatas',
|
||||
discount: (discount: number) => `${discount * 10}% off`,
|
||||
condition: (condition: number) => `Setidaknya ${condition}`,
|
||||
},
|
||||
vanCouponCell: {
|
||||
title: 'Kupon',
|
||||
count: (count: number) => `Anda memiliki kupon ${count}`,
|
||||
},
|
||||
vanCouponList: {
|
||||
exchange: 'Pertukaran',
|
||||
close: 'Tutup',
|
||||
enable: 'Tersedia',
|
||||
disabled: 'Tidak tersedia',
|
||||
placeholder: 'Kode kupon',
|
||||
},
|
||||
vanAddressEdit: {
|
||||
area: 'Daerah',
|
||||
postal: 'pos',
|
||||
areaEmpty: 'Silakan pilih area penerima',
|
||||
addressEmpty: 'Alamat tidak boleh kosong',
|
||||
postalEmpty: 'Kode pos salah',
|
||||
addressDetail: 'Alamat',
|
||||
defaultAddress: 'Tetapkan sebagai alamat default',
|
||||
},
|
||||
vanAddressList: {
|
||||
add: 'Tambahkan alamat baru',
|
||||
},
|
||||
};
|
||||
71
packages/vant/src/locale/lang/it-IT.ts
Normal file
71
packages/vant/src/locale/lang/it-IT.ts
Normal file
@ -0,0 +1,71 @@
|
||||
export default {
|
||||
name: 'Nome',
|
||||
tel: 'Telefono',
|
||||
save: 'Salva',
|
||||
confirm: 'Conferma',
|
||||
cancel: 'Annulla',
|
||||
delete: 'Elimina',
|
||||
loading: 'Caricamento in corso...',
|
||||
noCoupon: 'Nessun coupon',
|
||||
nameEmpty: 'Inserisci il nome',
|
||||
addContact: 'Aggiungi contatto',
|
||||
telInvalid: 'Numero di telefono errato',
|
||||
vanCalendar: {
|
||||
end: 'Fine',
|
||||
start: 'Inizio',
|
||||
title: 'Calendario',
|
||||
weekdays: [
|
||||
'domenica',
|
||||
'Lunedi',
|
||||
'Martedì',
|
||||
'mercoledì',
|
||||
'giovedì',
|
||||
'venerdì',
|
||||
'Sabato',
|
||||
],
|
||||
monthTitle: (year: number, month: number) => `${year}/${month}`,
|
||||
rangePrompt: (maxRange: number) => `Scegli non più di ${maxRange} giorni`,
|
||||
},
|
||||
vanCascader: {
|
||||
select: 'Seleziona',
|
||||
},
|
||||
vanPagination: {
|
||||
prev: 'Precedente',
|
||||
next: 'Avanti',
|
||||
},
|
||||
vanPullRefresh: {
|
||||
pulling: 'Tiri per aggiornare...',
|
||||
loosing: 'Largo per rinfrescare...',
|
||||
},
|
||||
vanSubmitBar: {
|
||||
label: 'Totale:',
|
||||
},
|
||||
vanCoupon: {
|
||||
unlimited: 'Illimitato',
|
||||
discount: (discount: number) => `${discount * 10}% di sconto`,
|
||||
condition: (condition: number) => `Almeno ${condition}`,
|
||||
},
|
||||
vanCouponCell: {
|
||||
title: 'Buono',
|
||||
count: (count: number) => `Hai ${count} coupon`,
|
||||
},
|
||||
vanCouponList: {
|
||||
exchange: 'Scambio',
|
||||
close: 'Chiudi',
|
||||
enable: 'Disponibile',
|
||||
disabled: 'Non disponibile',
|
||||
placeholder: 'Codice coupon',
|
||||
},
|
||||
vanAddressEdit: {
|
||||
area: 'Area',
|
||||
postal: 'Postale',
|
||||
areaEmpty: "Seleziona un'area di ricezione",
|
||||
addressEmpty: "L'indirizzo non può essere vuoto",
|
||||
postalEmpty: 'Codice postale errato',
|
||||
addressDetail: 'Indirizzo',
|
||||
defaultAddress: 'Imposta come indirizzo predefinito',
|
||||
},
|
||||
vanAddressList: {
|
||||
add: 'Aggiungi nuovo indirizzo',
|
||||
},
|
||||
};
|
||||
@ -30,7 +30,7 @@ export default {
|
||||
loosing: 'リリース時に更新...',
|
||||
},
|
||||
vanSubmitBar: {
|
||||
label: '合計:',
|
||||
label: '合計:',
|
||||
},
|
||||
vanCoupon: {
|
||||
unlimited: '無制限',
|
||||
|
||||
@ -38,7 +38,7 @@ export default {
|
||||
loosing: '새로 고침...',
|
||||
},
|
||||
vanSubmitBar: {
|
||||
label: '총:',
|
||||
label: '총:',
|
||||
},
|
||||
vanCoupon: {
|
||||
unlimited: '제한 없는',
|
||||
|
||||
@ -30,7 +30,7 @@ export default {
|
||||
loosing: 'Mist for oppdatering...',
|
||||
},
|
||||
vanSubmitBar: {
|
||||
label: 'Totalt:',
|
||||
label: 'Totalt:',
|
||||
},
|
||||
vanCoupon: {
|
||||
unlimited: 'Uendelig',
|
||||
|
||||
@ -30,7 +30,7 @@ export default {
|
||||
loosing: 'Solte para atualizar...',
|
||||
},
|
||||
vanSubmitBar: {
|
||||
label: 'Total:',
|
||||
label: 'Total:',
|
||||
},
|
||||
vanCoupon: {
|
||||
unlimited: 'Ilimitado',
|
||||
|
||||
@ -30,7 +30,7 @@ export default {
|
||||
loosing: 'Eliberează pentru a împrospăta...',
|
||||
},
|
||||
vanSubmitBar: {
|
||||
label: 'Total:',
|
||||
label: 'Total:',
|
||||
},
|
||||
vanCoupon: {
|
||||
unlimited: 'Nelimitat',
|
||||
|
||||
@ -30,7 +30,7 @@ export default {
|
||||
loosing: 'Отпустите для обновления...',
|
||||
},
|
||||
vanSubmitBar: {
|
||||
label: 'Всего:',
|
||||
label: 'Всего:',
|
||||
},
|
||||
vanCoupon: {
|
||||
unlimited: 'Безлимитный',
|
||||
|
||||
@ -31,7 +31,7 @@ export default {
|
||||
loosing: 'ปล่อยเพื่อรีเฟรช...',
|
||||
},
|
||||
vanSubmitBar: {
|
||||
label: 'รวม:',
|
||||
label: 'รวม:',
|
||||
},
|
||||
vanCoupon: {
|
||||
unlimited: 'ไม่ จำกัด',
|
||||
|
||||
72
packages/vant/src/locale/lang/uk-UA.ts
Normal file
72
packages/vant/src/locale/lang/uk-UA.ts
Normal file
@ -0,0 +1,72 @@
|
||||
export default {
|
||||
name: "Ім'я",
|
||||
tel: 'Телефон',
|
||||
save: 'Зберегти',
|
||||
confirm: 'підтвердити',
|
||||
cancel: 'Скасувати',
|
||||
delete: 'Видалити',
|
||||
loading: 'Завантаження...',
|
||||
noCoupon: 'Без купонів',
|
||||
nameEmpty: "Будь ласка, введіть ім'я",
|
||||
addContact: 'Додати контакт',
|
||||
telInvalid: 'Неправильний номер телефону',
|
||||
vanCalendar: {
|
||||
end: 'Кінець',
|
||||
start: 'Почати',
|
||||
title: 'Календар',
|
||||
weekdays: [
|
||||
'неділя',
|
||||
'понеділок',
|
||||
'вівторок',
|
||||
'середа',
|
||||
'четвер',
|
||||
"п'ятниця",
|
||||
'субота',
|
||||
],
|
||||
monthTitle: (year: number, month: number) => `${year}/${month}`,
|
||||
rangePrompt: (maxRange: number) =>
|
||||
`Виберіть не більше ніж ${maxRange} днів`,
|
||||
},
|
||||
vanCascader: {
|
||||
select: 'Вибрати',
|
||||
},
|
||||
vanPagination: {
|
||||
prev: 'Попередній',
|
||||
next: 'Далі',
|
||||
},
|
||||
vanPullRefresh: {
|
||||
pulling: 'Потягніть, щоб оновити...',
|
||||
loosing: 'Погано оновити...',
|
||||
},
|
||||
vanSubmitBar: {
|
||||
label: 'Усього:',
|
||||
},
|
||||
vanCoupon: {
|
||||
unlimited: 'Необмежено',
|
||||
discount: (discount: number) => `${discount * 10}% off`,
|
||||
condition: (condition: number) => `Принаймні ${condition}`,
|
||||
},
|
||||
vanCouponCell: {
|
||||
title: 'Купон',
|
||||
count: (count: number) => `У вас є ${count} купонів`,
|
||||
},
|
||||
vanCouponList: {
|
||||
exchange: 'Обмін',
|
||||
close: 'Закрити',
|
||||
enable: 'Доступно',
|
||||
disabled: 'Недоступно',
|
||||
placeholder: 'Код купона',
|
||||
},
|
||||
vanAddressEdit: {
|
||||
area: 'Область',
|
||||
postal: 'Поштовий',
|
||||
areaEmpty: 'Будь ласка, виберіть зону прийому',
|
||||
addressEmpty: 'Адреса не може бути порожньою',
|
||||
postalEmpty: 'Неправильний поштовий індекс',
|
||||
addressDetail: 'Адреса',
|
||||
defaultAddress: 'Встановити як адресу за замовчуванням',
|
||||
},
|
||||
vanAddressList: {
|
||||
add: 'Додати нову адресу',
|
||||
},
|
||||
};
|
||||
71
packages/vant/src/locale/lang/vi-VN.ts
Normal file
71
packages/vant/src/locale/lang/vi-VN.ts
Normal file
@ -0,0 +1,71 @@
|
||||
export default {
|
||||
name: 'tên',
|
||||
tel: 'Điện thoại',
|
||||
save: 'Cứu',
|
||||
confirm: 'Xác nhận',
|
||||
cancel: 'Hủy bỏ',
|
||||
delete: 'Xóa',
|
||||
loading: 'Đang tải ...',
|
||||
noCoupon: 'Không có phiếu giảm giá',
|
||||
nameEmpty: 'Vui lòng điền vào tên',
|
||||
addContact: 'Thêm liên hệ',
|
||||
telInvalid: 'Số điện thoại không đúng định dạng',
|
||||
vanCalendar: {
|
||||
end: 'Kết thúc',
|
||||
start: 'Bắt đầu',
|
||||
title: 'Lịch',
|
||||
weekdays: [
|
||||
'chủ nhật',
|
||||
'thứ hai',
|
||||
'thứ ba',
|
||||
'Thứ Tư',
|
||||
'Thứ năm',
|
||||
'Thứ sáu',
|
||||
'thứ bảy',
|
||||
],
|
||||
monthTitle: (year: number, month: number) => `${year}/${month}`,
|
||||
rangePrompt: (maxRange: number) => `Chọn không quá ${maxRange} ngày`,
|
||||
},
|
||||
vanCascader: {
|
||||
select: 'Chọn',
|
||||
},
|
||||
vanPagination: {
|
||||
prev: 'Trước đó',
|
||||
next: 'Tiếp theo',
|
||||
},
|
||||
vanPullRefresh: {
|
||||
pulling: 'Kéo để làm mới ...',
|
||||
loosing: 'Loose để làm mới ...',
|
||||
},
|
||||
vanSubmitBar: {
|
||||
label: 'Tổng:',
|
||||
},
|
||||
vanCoupon: {
|
||||
unlimited: 'Không giới hạn',
|
||||
discount: (discount: number) => `Chiết khấu ${discount * 10}%`,
|
||||
condition: (condition: number) => `Ít nhất ${condition}`,
|
||||
},
|
||||
vanCouponCell: {
|
||||
title: 'Phiếu giảm giá',
|
||||
count: (count: number) => `Bạn có ${count} phiếu giảm giá`,
|
||||
},
|
||||
vanCouponList: {
|
||||
exchange: 'Trao đổi',
|
||||
close: 'Đóng',
|
||||
enable: 'Có sẵn',
|
||||
disabled: 'Không khả dụng',
|
||||
placeholder: 'Mã phiếu thưởng',
|
||||
},
|
||||
vanAddressEdit: {
|
||||
area: 'Khu vực',
|
||||
postal: 'Bưu điện',
|
||||
areaEmpty: 'Vui lòng chọn khu vực nhận hàng',
|
||||
addressEmpty: 'Địa chỉ không được để trống',
|
||||
postalEmpty: 'Mã bưu chính sai',
|
||||
addressDetail: 'Địa chỉ',
|
||||
defaultAddress: 'Đặt làm địa chỉ mặc định',
|
||||
},
|
||||
vanAddressList: {
|
||||
add: 'Thêm địa chỉ mới',
|
||||
},
|
||||
};
|
||||
@ -30,7 +30,7 @@ export default {
|
||||
loosing: '释放即可刷新...',
|
||||
},
|
||||
vanSubmitBar: {
|
||||
label: '合计:',
|
||||
label: '合计:',
|
||||
},
|
||||
vanCoupon: {
|
||||
unlimited: '无门槛',
|
||||
|
||||
@ -30,7 +30,7 @@ export default {
|
||||
loosing: '釋放即可刷新...',
|
||||
},
|
||||
vanSubmitBar: {
|
||||
label: '合計:',
|
||||
label: '合計:',
|
||||
},
|
||||
vanCoupon: {
|
||||
unlimited: '無門檻',
|
||||
|
||||
@ -30,7 +30,7 @@ export default {
|
||||
loosing: '釋放即可刷新...',
|
||||
},
|
||||
vanSubmitBar: {
|
||||
label: '合計:',
|
||||
label: '合計:',
|
||||
},
|
||||
vanCoupon: {
|
||||
unlimited: '無門檻',
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
extend,
|
||||
unitToPx,
|
||||
truthProp,
|
||||
isSameValue,
|
||||
makeArrayProp,
|
||||
preventDefault,
|
||||
makeStringProp,
|
||||
@ -22,7 +23,6 @@ import {
|
||||
} from '../utils';
|
||||
import {
|
||||
isOptionExist,
|
||||
isValuesEqual,
|
||||
getColumnsType,
|
||||
findOptionByValue,
|
||||
formatCascadeColumns,
|
||||
@ -112,17 +112,26 @@ export default defineComponent({
|
||||
)
|
||||
);
|
||||
|
||||
const setValue = (index: number, value: string | number) => {
|
||||
if (selectedValues.value[index] !== value) {
|
||||
const newValues = selectedValues.value.slice(0);
|
||||
newValues[index] = value;
|
||||
selectedValues.value = newValues;
|
||||
}
|
||||
};
|
||||
|
||||
const onChange = (value: number | string, columnIndex: number) => {
|
||||
selectedValues.value[columnIndex] = value;
|
||||
setValue(columnIndex, value);
|
||||
|
||||
if (columnsType.value === 'cascade') {
|
||||
// reset values after cascading
|
||||
selectedValues.value.forEach((value, index) => {
|
||||
const options = currentColumns.value[index];
|
||||
if (!isOptionExist(options, value, fields.value)) {
|
||||
selectedValues.value[index] = options.length
|
||||
? options[0][fields.value.value]
|
||||
: undefined;
|
||||
setValue(
|
||||
index,
|
||||
options.length ? options[0][fields.value.value] : undefined
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -250,8 +259,10 @@ export default defineComponent({
|
||||
options.length &&
|
||||
!isOptionExist(options, selectedValues.value[index], fields.value)
|
||||
) {
|
||||
selectedValues.value[index] =
|
||||
getFirstEnabledOption(options)[fields.value.value];
|
||||
setValue(
|
||||
index,
|
||||
getFirstEnabledOption(options)![fields.value.value]
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -261,7 +272,7 @@ export default defineComponent({
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newValues) => {
|
||||
if (!isValuesEqual(newValues, selectedValues.value)) {
|
||||
if (!isSameValue(newValues, selectedValues.value)) {
|
||||
selectedValues.value = newValues;
|
||||
}
|
||||
},
|
||||
@ -270,14 +281,16 @@ export default defineComponent({
|
||||
watch(
|
||||
selectedValues,
|
||||
(newValues) => {
|
||||
if (!isValuesEqual(newValues, props.modelValue)) {
|
||||
emit('update:modelValue', selectedValues.value);
|
||||
if (!isSameValue(newValues, props.modelValue)) {
|
||||
emit('update:modelValue', newValues);
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
useExpose<PickerExpose>({ confirm });
|
||||
const getSelectedOptions = () => selectedOptions.value;
|
||||
|
||||
useExpose<PickerExpose>({ confirm, getSelectedOptions });
|
||||
|
||||
return () => (
|
||||
<div class={bem()}>
|
||||
|
||||
@ -355,9 +355,10 @@ export default {
|
||||
|
||||
Use [ref](https://v3.vuejs.org/guide/component-template-refs.html) to get Picker instance and call instance methods.
|
||||
|
||||
| Name | Description | Attribute | Return value |
|
||||
| ------- | ------------------------------------- | --------- | ------------ |
|
||||
| confirm | Stop scrolling and emit confirm event | - | - |
|
||||
| Name | Description | Attribute | Return value |
|
||||
| --- | --- | --- | --- |
|
||||
| confirm | Stop scrolling and emit confirm event | - | - |
|
||||
| getSelectedOptions | Get current selected options | - | _(PickerOption \| undefined)[]_ |
|
||||
|
||||
### Types
|
||||
|
||||
|
||||
@ -374,9 +374,10 @@ export default {
|
||||
|
||||
通过 ref 可以获取到 Picker 实例并调用实例方法,详见[组件实例方法](#/zh-CN/advanced-usage#zu-jian-shi-li-fang-fa)。
|
||||
|
||||
| 方法名 | 说明 | 参数 | 返回值 |
|
||||
| ------- | --------------------------------- | ---- | ------ |
|
||||
| confirm | 停止惯性滚动并触发 `confirm` 事件 | - | - |
|
||||
| 方法名 | 说明 | 参数 | 返回值 |
|
||||
| --- | --- | --- | --- |
|
||||
| confirm | 停止惯性滚动并触发 `confirm` 事件 | - | - |
|
||||
| getSelectedOptions | 获取当前选中的选项 | - | _(PickerOption \| undefined)[]_ |
|
||||
|
||||
### 类型定义
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ export type PickerColumn = PickerOption[];
|
||||
|
||||
export type PickerExpose = {
|
||||
confirm: () => void;
|
||||
getSelectedOptions: () => Array<PickerOption | undefined>;
|
||||
};
|
||||
|
||||
export type PickerColumnProvide = {
|
||||
@ -44,7 +45,7 @@ export type PickerInstance = ComponentPublicInstance<PickerProps, PickerExpose>;
|
||||
|
||||
export type PickerConfirmEventParams = {
|
||||
selectedValues: Array<number | string>;
|
||||
selectedOptions: PickerOption[];
|
||||
selectedOptions: Array<PickerOption | undefined>;
|
||||
};
|
||||
|
||||
export type PickerCancelEventParams = PickerConfirmEventParams;
|
||||
|
||||
@ -2,9 +2,10 @@ import { isDef, clamp, extend } from '../utils';
|
||||
import type { Ref } from 'vue';
|
||||
import type { PickerOption, PickerColumn, PickerFieldNames } from './types';
|
||||
|
||||
export function getFirstEnabledOption(options: PickerOption[]) {
|
||||
return options.find((option) => !option.disabled) || options[0];
|
||||
}
|
||||
export const getFirstEnabledOption = (
|
||||
options: PickerOption[]
|
||||
): PickerOption | undefined =>
|
||||
options.find((option) => !option.disabled) || options[0];
|
||||
|
||||
export function getColumnsType(
|
||||
columns: PickerColumn | PickerColumn[],
|
||||
@ -50,7 +51,7 @@ export function findOptionByValue(
|
||||
options: PickerOption[],
|
||||
value: number | string,
|
||||
fields: Required<PickerFieldNames>
|
||||
) {
|
||||
): PickerOption | undefined {
|
||||
const index = options.findIndex((option) => option[fields.value] === value);
|
||||
const enabledIndex = findIndexOfEnabledOption(options, index);
|
||||
return options[enabledIndex];
|
||||
@ -77,7 +78,7 @@ export function formatCascadeColumns(
|
||||
: undefined;
|
||||
|
||||
if (!cursor && options.length) {
|
||||
const firstValue = getFirstEnabledOption(options)[fields.value];
|
||||
const firstValue = getFirstEnabledOption(options)![fields.value];
|
||||
cursor = findOptionByValue(options, firstValue, fields);
|
||||
}
|
||||
|
||||
@ -94,16 +95,6 @@ export function getElementTranslateY(element: Element) {
|
||||
return Number(translateY);
|
||||
}
|
||||
|
||||
export function isValuesEqual(
|
||||
valuesA: Array<string | number>,
|
||||
valuesB: Array<string | number>
|
||||
) {
|
||||
return (
|
||||
valuesA.length === valuesB.length &&
|
||||
valuesA.every((value, index) => value === valuesB[index])
|
||||
);
|
||||
}
|
||||
|
||||
export function assignDefaultFields(
|
||||
fields: PickerFieldNames | undefined
|
||||
): Required<PickerFieldNames> {
|
||||
|
||||
@ -3,6 +3,7 @@ import {
|
||||
watch,
|
||||
provide,
|
||||
Teleport,
|
||||
nextTick,
|
||||
computed,
|
||||
onMounted,
|
||||
Transition,
|
||||
@ -68,6 +69,7 @@ export default defineComponent({
|
||||
'close',
|
||||
'opened',
|
||||
'closed',
|
||||
'keydown',
|
||||
'update:show',
|
||||
'clickOverlay',
|
||||
'clickCloseIcon',
|
||||
@ -172,9 +174,11 @@ export default defineComponent({
|
||||
|
||||
const onOpened = () => emit('opened');
|
||||
const onClosed = () => emit('closed');
|
||||
const onKeydown = (event: KeyboardEvent) => emit('keydown', event);
|
||||
|
||||
const renderPopup = lazyRender(() => {
|
||||
const { round, position, safeAreaInsetBottom } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
v-show={props.show}
|
||||
@ -187,6 +191,7 @@ export default defineComponent({
|
||||
}),
|
||||
{ 'van-safe-area-bottom': safeAreaInsetBottom },
|
||||
]}
|
||||
onKeydown={onKeydown}
|
||||
{...attrs}
|
||||
>
|
||||
{slots.default?.()}
|
||||
@ -216,6 +221,12 @@ export default defineComponent({
|
||||
(show) => {
|
||||
if (show && !opened) {
|
||||
open();
|
||||
|
||||
if (attrs.tabindex === 0) {
|
||||
nextTick(() => {
|
||||
popupRef.value?.focus();
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!show && opened) {
|
||||
opened = false;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ComponentPublicInstance } from 'vue';
|
||||
import type { Ref, ComponentPublicInstance } from 'vue';
|
||||
import type { PopupProps } from './Popup';
|
||||
|
||||
export type PopupPosition = 'top' | 'left' | 'bottom' | 'right' | 'center' | '';
|
||||
@ -9,4 +9,8 @@ export type PopupCloseIconPosition =
|
||||
| 'bottom-left'
|
||||
| 'bottom-right';
|
||||
|
||||
export type PopupInstance = ComponentPublicInstance<PopupProps>;
|
||||
export type PopupExpose = {
|
||||
popupRef: Ref<HTMLElement>;
|
||||
};
|
||||
|
||||
export type PopupInstance = ComponentPublicInstance<PopupProps, PopupExpose>;
|
||||
|
||||
@ -32,7 +32,7 @@ const show = ref(false);
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="t('showChildren')">
|
||||
<van-switch v-model="show" size="24px" />
|
||||
<van-switch v-model="show" />
|
||||
<van-skeleton title avatar :row="3" :loading="!show">
|
||||
<div class="demo-preview">
|
||||
<img src="https://img.yzcdn.cn/vant/logo.png" />
|
||||
|
||||
@ -46,7 +46,6 @@ exports[`should render demo and match snapshot 1`] = `
|
||||
<div>
|
||||
<div role="switch"
|
||||
class="van-switch"
|
||||
style="font-size: 24px;"
|
||||
tabindex="0"
|
||||
aria-checked="false"
|
||||
>
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
addUnit,
|
||||
addNumber,
|
||||
numericProp,
|
||||
isSameValue,
|
||||
getSizeStyle,
|
||||
preventDefault,
|
||||
stopPropagation,
|
||||
@ -131,9 +132,6 @@ export default defineComponent({
|
||||
return addNumber(min, diff);
|
||||
};
|
||||
|
||||
const isSameValue = (newValue: SliderValue, oldValue: SliderValue) =>
|
||||
JSON.stringify(newValue) === JSON.stringify(oldValue);
|
||||
|
||||
const handleRangeValue = (value: NumberRange) => {
|
||||
// 设置默认值
|
||||
const left = value[0] ?? Number(props.min);
|
||||
|
||||
@ -76,6 +76,7 @@ body {
|
||||
color: var(--van-submit-bar-price-color);
|
||||
font-weight: var(--van-font-bold);
|
||||
font-size: var(--van-submit-bar-price-font-size);
|
||||
margin-left: var(--van-padding-base);
|
||||
|
||||
&-integer {
|
||||
font-size: var(--van-submit-bar-price-integer-font-size);
|
||||
|
||||
@ -6,7 +6,7 @@ exports[`should render demo and match snapshot 1`] = `
|
||||
<div class="van-submit-bar__bar">
|
||||
<div class="van-submit-bar__text">
|
||||
<span>
|
||||
Total:
|
||||
Total:
|
||||
</span>
|
||||
<span class="van-submit-bar__price">
|
||||
¥
|
||||
@ -40,7 +40,7 @@ exports[`should render demo and match snapshot 1`] = `
|
||||
<div class="van-submit-bar__bar">
|
||||
<div class="van-submit-bar__text">
|
||||
<span>
|
||||
Total:
|
||||
Total:
|
||||
</span>
|
||||
<span class="van-submit-bar__price">
|
||||
¥
|
||||
@ -68,7 +68,7 @@ exports[`should render demo and match snapshot 1`] = `
|
||||
<div class="van-submit-bar__bar">
|
||||
<div class="van-submit-bar__text">
|
||||
<span>
|
||||
Total:
|
||||
Total:
|
||||
</span>
|
||||
<span class="van-submit-bar__price">
|
||||
¥
|
||||
@ -125,7 +125,7 @@ exports[`should render demo and match snapshot 1`] = `
|
||||
</div>
|
||||
<div class="van-submit-bar__text">
|
||||
<span>
|
||||
Total:
|
||||
Total:
|
||||
</span>
|
||||
<span class="van-submit-bar__price">
|
||||
¥
|
||||
|
||||
@ -50,7 +50,7 @@ export default {
|
||||
### Custom Size
|
||||
|
||||
```html
|
||||
<van-switch v-model="checked" size="24px" />
|
||||
<van-switch v-model="checked" size="22px" />
|
||||
```
|
||||
|
||||
### Custom Color
|
||||
@ -94,7 +94,7 @@ export default {
|
||||
```html
|
||||
<van-cell center title="Title">
|
||||
<template #right-icon>
|
||||
<van-switch v-model="checked" size="24" />
|
||||
<van-switch v-model="checked" />
|
||||
</template>
|
||||
</van-cell>
|
||||
```
|
||||
@ -108,9 +108,9 @@ export default {
|
||||
| v-model | Check status of Switch | _ActiveValue \| InactiveValue_ | `false` |
|
||||
| loading | Whether to show loading icon | _boolean_ | `false` |
|
||||
| disabled | Whether to disable switch | _boolean_ | `false` |
|
||||
| size | Size of switch | _number \| string_ | `30px` |
|
||||
| size | Size of switch button | _number \| string_ | `26px` |
|
||||
| active-color | Background color when active | _string_ | `#1989fa` |
|
||||
| inactive-color | Background color when inactive | _string_ | `white` |
|
||||
| inactive-color | Background color when inactive | _string_ | `rgba(120, 120, 128, 0.16)` |
|
||||
| active-value | Value when active | _any_ | `true` |
|
||||
| inactive-value | Value when inactive | _any_ | `false` |
|
||||
|
||||
@ -137,14 +137,13 @@ The component provides the following CSS variables, which can be used to customi
|
||||
|
||||
| Name | Default Value | Description |
|
||||
| --- | --- | --- |
|
||||
| --van-switch-size | _30px_ | - |
|
||||
| --van-switch-width | _2em_ | - |
|
||||
| --van-switch-height | _1em_ | - |
|
||||
| --van-switch-size | _26px_ | - |
|
||||
| --van-switch-width | _calc(1.8em + 4px)_ | - |
|
||||
| --van-switch-height | _calc(1em + 4px)_ | - |
|
||||
| --van-switch-node-size | _1em_ | - |
|
||||
| --van-switch-node-background | _var(--van-white)_ | - |
|
||||
| --van-switch-node-shadow | _0 3px 1px 0 rgba(0, 0, 0, 0.05)_ | - |
|
||||
| --van-switch-background | _var(--van-background-2)_ | - |
|
||||
| --van-switch-background | _rgba(120, 120, 128, 0.16)_ | - |
|
||||
| --van-switch-on-background | _var(--van-primary-color)_ | - |
|
||||
| --van-switch-duration | _var(--van-duration-base)_ | - |
|
||||
| --van-switch-disabled-opacity | _var(--van-disabled-opacity)_ | - |
|
||||
| --van-switch-border | _var(--van-border-width) solid rgba(0, 0, 0, 0.1)_ | - |
|
||||
|
||||
@ -58,7 +58,7 @@ export default {
|
||||
通过 `size` 属性自定义开关的大小。
|
||||
|
||||
```html
|
||||
<van-switch v-model="checked" size="24px" />
|
||||
<van-switch v-model="checked" size="22px" />
|
||||
```
|
||||
|
||||
### 自定义颜色
|
||||
@ -106,7 +106,7 @@ export default {
|
||||
```html
|
||||
<van-cell center title="标题">
|
||||
<template #right-icon>
|
||||
<van-switch v-model="checked" size="24" />
|
||||
<van-switch v-model="checked" />
|
||||
</template>
|
||||
</van-cell>
|
||||
```
|
||||
@ -115,16 +115,16 @@ export default {
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| -------------- | ------------------------ | ------------------ | --------- |
|
||||
| v-model | 开关选中状态 | _any_ | `false` |
|
||||
| loading | 是否为加载状态 | _boolean_ | `false` |
|
||||
| disabled | 是否为禁用状态 | _boolean_ | `false` |
|
||||
| size | 开关尺寸,默认单位为`px` | _number \| string_ | `30px` |
|
||||
| active-color | 打开时的背景色 | _string_ | `#1989fa` |
|
||||
| inactive-color | 关闭时的背景色 | _string_ | `white` |
|
||||
| active-value | 打开时对应的值 | _any_ | `true` |
|
||||
| inactive-value | 关闭时对应的值 | _any_ | `false` |
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| v-model | 开关选中状态 | _any_ | `false` |
|
||||
| loading | 是否为加载状态 | _boolean_ | `false` |
|
||||
| disabled | 是否为禁用状态 | _boolean_ | `false` |
|
||||
| size | 开关按钮的尺寸,默认单位为 `px` | _number \| string_ | `26px` |
|
||||
| active-color | 打开时的背景色 | _string_ | `#1989fa` |
|
||||
| inactive-color | 关闭时的背景色 | _string_ | `rgba(120, 120, 128, 0.16)` |
|
||||
| active-value | 打开时对应的值 | _any_ | `true` |
|
||||
| inactive-value | 关闭时对应的值 | _any_ | `false` |
|
||||
|
||||
### Events
|
||||
|
||||
@ -147,16 +147,15 @@ import type { SwitchProps } from 'vant';
|
||||
|
||||
组件提供了下列 CSS 变量,可用于自定义样式,使用方法请参考 [ConfigProvider 组件](#/zh-CN/config-provider)。
|
||||
|
||||
| 名称 | 默认值 | 描述 |
|
||||
| --- | --- | --- |
|
||||
| --van-switch-size | _30px_ | - |
|
||||
| --van-switch-width | _2em_ | - |
|
||||
| --van-switch-height | _1em_ | - |
|
||||
| --van-switch-node-size | _1em_ | - |
|
||||
| --van-switch-node-background | _var(--van-white)_ | - |
|
||||
| --van-switch-node-shadow | _0 3px 1px 0 rgba(0, 0, 0, 0.05)_ | - |
|
||||
| --van-switch-background | _var(--van-background-2)_ | - |
|
||||
| --van-switch-on-background | _var(--van-primary-color)_ | - |
|
||||
| --van-switch-duration | _var(--van-duration-base)_ | - |
|
||||
| --van-switch-disabled-opacity | _var(--van-disabled-opacity)_ | - |
|
||||
| --van-switch-border | _var(--van-border-width) solid rgba(0, 0, 0, 0.1)_ | - |
|
||||
| 名称 | 默认值 | 描述 |
|
||||
| ----------------------------- | --------------------------------- | ---- |
|
||||
| --van-switch-size | _26px_ | - |
|
||||
| --van-switch-width | _calc(1.8em + 4px)_ | - |
|
||||
| --van-switch-height | _calc(1em + 4px)_ | - |
|
||||
| --van-switch-node-size | _1em_ | - |
|
||||
| --van-switch-node-background | _var(--van-white)_ | - |
|
||||
| --van-switch-node-shadow | _0 3px 1px 0 rgba(0, 0, 0, 0.05)_ | - |
|
||||
| --van-switch-background | _rgba(120, 120, 128, 0.16)_ | - |
|
||||
| --van-switch-on-background | _var(--van-primary-color)_ | - |
|
||||
| --van-switch-duration | _var(--van-duration-base)_ | - |
|
||||
| --van-switch-disabled-opacity | _var(--van-disabled-opacity)_ | - |
|
||||
|
||||
@ -56,7 +56,7 @@ const onUpdateValue = (checked: boolean) => {
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="t('customSize')">
|
||||
<van-switch v-model="checked2" size="24px" />
|
||||
<van-switch v-model="checked2" size="22px" />
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="t('customColor')">
|
||||
@ -74,7 +74,7 @@ const onUpdateValue = (checked: boolean) => {
|
||||
<demo-block :title="t('withCell')">
|
||||
<van-cell center :title="t('title')">
|
||||
<template #right-icon>
|
||||
<van-switch v-model="checked5" size="24" />
|
||||
<van-switch v-model="checked5" />
|
||||
</template>
|
||||
</van-cell>
|
||||
</demo-block>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user