Merge branch 'dev' into next

This commit is contained in:
chenjiahan 2020-07-08 17:34:31 +08:00
commit 4eb36898e9
18 changed files with 303 additions and 180 deletions

View File

@ -2,14 +2,17 @@
<img alt="logo" src="https://img.yzcdn.cn/vant/logo.png" width="120" height="120" style="margin-bottom: 10px;">
</p>
<h3 align="center" style="margin: 30px 0 35px;">Mobile UI Components built on Vue</h3>
<h1 align="center">Vant</h1>
<p align="center">Mobile UI Components built on Vue</p>
<p align="center">
<img src="https://img.shields.io/npm/v/vant.svg?style=for-the-badge" alt="npm version" />
<img src="https://img.shields.io/github/workflow/status/youzan/vant/CI/dev?style=for-the-badge" alt="npm version" />
<img src="https://img.shields.io/codecov/c/github/youzan/vant/dev.svg?style=for-the-badge&color=#4fc08d" alt="Coverage Status" />
<img src="https://img.shields.io/npm/dm/vant.svg?style=for-the-badge&color=#4fc08d" alt="downloads" />
<img src="https://img.badgesize.io/https://unpkg.com/vant/lib/vant.min.js?compression=gzip&style=for-the-badge&label=gzip%20size&color=#4fc08d" alt="Gzip Size" />
<img src="https://img.shields.io/npm/v/vant.svg?style=flat-square" alt="npm version" />
<img src="https://img.shields.io/github/workflow/status/youzan/vant/CI/dev?style=flat-square" alt="npm version" />
<img src="https://img.shields.io/codecov/c/github/youzan/vant/dev.svg?style=flat-square&color=#4fc08d" alt="Coverage Status" />
<img src="https://img.shields.io/npm/dm/vant.svg?style=flat-square&color=#4fc08d" alt="downloads" />
<img src="https://img.shields.io/jsdelivr/npm/hm/vant?style=flat-square" alt="Jsdelivr Hits">
<img src="https://img.badgesize.io/https://unpkg.com/vant/lib/vant.min.js?compression=gzip&style=flat-square&label=gzip%20size&color=#4fc08d" alt="Gzip Size" />
</p>
<p align="center">

View File

@ -1,14 +1,18 @@
<p align="center">
<img alt="logo" src="https://img.yzcdn.cn/vant/logo.png" width="120" style="margin-bottom: 10px;">
</p>
<h3 align="center" style="margin: 30px 0 35px;">轻量、可靠的移动端 Vue 组件库</h3>
<h1 align="center">Vant</h1>
<p align="center">轻量、可靠的移动端 Vue 组件库</p>
<p align="center">
<img src="https://img.shields.io/npm/v/vant.svg?style=for-the-badge" alt="npm version" />
<img src="https://img.shields.io/github/workflow/status/youzan/vant/CI/dev?style=for-the-badge" alt="npm version" />
<img src="https://img.shields.io/codecov/c/github/youzan/vant/dev.svg?style=for-the-badge&color=#4fc08d" alt="Coverage Status" />
<img src="https://img.shields.io/npm/dm/vant.svg?style=for-the-badge&color=#4fc08d" alt="downloads" />
<img src="https://img.badgesize.io/https://unpkg.com/vant/lib/vant.min.js?compression=gzip&style=for-the-badge&label=gzip%20size&color=#4fc08d" alt="Gzip Size" />
<img src="https://img.shields.io/npm/v/vant.svg?style=flat-square" alt="npm version" />
<img src="https://img.shields.io/github/workflow/status/youzan/vant/CI/dev?style=flat-square" alt="npm version" />
<img src="https://img.shields.io/codecov/c/github/youzan/vant/dev.svg?style=flat-square&color=#4fc08d" alt="Coverage Status" />
<img src="https://img.shields.io/npm/dm/vant.svg?style=flat-square&color=#4fc08d" alt="downloads" />
<img src="https://img.shields.io/jsdelivr/npm/hm/vant?style=flat-square" alt="Jsdelivr Hits">
<img src="https://img.badgesize.io/https://unpkg.com/vant/lib/vant.min.js?compression=gzip&style=flat-square&label=gzip%20size&color=#4fc08d" alt="Gzip Size" />
</p>
<p align="center">

View File

@ -10,6 +10,26 @@ Vant follows [Semantic Versioning 2.0.0](https://semver.org/lang/zh-CN/).
- Minor versionreleased every one to two months, including backwards compatible features.
- Major versionincluding breaking changes and new features.
### [v2.9.1-beta.1](https://github.com/youzan/vant/compare/v2.9.0...v2.9.1-beta.1)
`2020-07-07`
**Feature**
- Field: add clear-trigger prop [#6699](https://github.com/youzan/vant/issues/6699)
- Search: add clear-trigger prop [#6700](https://github.com/youzan/vant/issues/6700)
- Uploader: add preview-cover slot [#6707](https://github.com/youzan/vant/issues/6707)
- Sku: improve message datetime picker [8d29e5](https://github.com/youzan/vant/commit/8d29e5c8c6df278800865596f285c17029150963) [7343e5](https://github.com/youzan/vant/commit/7343e55409900635a0e39063edb9f67493048a54)
**Bug Fixes**
- Calendar: subtitle not updated in some cases [#6723](https://github.com/youzan/vant/issues/6723)
- Checkbox: dynamic bind group [#6730](https://github.com/youzan/vant/issues/6730)
- Image: memory leak during SSR [#6721](https://github.com/youzan/vant/issues/6721)
- ImagePreview: swipeTo type should be optional [#6727](https://github.com/youzan/vant/issues/6727)
- Picker: click during momentum case incorrect result [#6724](https://github.com/youzan/vant/issues/6724)
- Popup: lock-scroll not work in some cases [#6698](https://github.com/youzan/vant/issues/6698)
### [v2.9.0](https://github.com/youzan/vant/compare/v2.8.7...v2.9.0)
`2020-07-03`

View File

@ -10,6 +10,26 @@ Vant 遵循 [Semver](https://semver.org/lang/zh-CN/) 语义化版本规范。
- 次版本号:每隔一至二个月发布,包含新特性和较大的功能更新,向下兼容。
- 主版本号:发布时间不定,包含不兼容更新,预计下一个主版本会与 Vue 3.0 同期发布。
### [v2.9.1-beta.1](https://github.com/youzan/vant/compare/v2.9.0...v2.9.1-beta.1)
`2020-07-07`
**Feature**
- Field: 新增 clear-trigger 属性 [#6699](https://github.com/youzan/vant/issues/6699)
- Search: 新增 clear-trigger 属性 [#6700](https://github.com/youzan/vant/issues/6700)
- Uploader: 新增 preview-cover 插槽 [#6707](https://github.com/youzan/vant/issues/6707)
- Sku: 优化留言栏时间选择交互 [8d29e5](https://github.com/youzan/vant/commit/8d29e5c8c6df278800865596f285c17029150963) [7343e5](https://github.com/youzan/vant/commit/7343e55409900635a0e39063edb9f67493048a54)
**Bug Fixes**
- Calendar: 修复个别情况下日历标题不更新的问题 [#6723](https://github.com/youzan/vant/issues/6723)
- Checkbox: 修复动态设置 bind-group 时不生效的问题 [#6730](https://github.com/youzan/vant/issues/6730)
- Image: 修复 SSR 时 LazyLoad 属性存在内存泄露的问题 [#6721](https://github.com/youzan/vant/issues/6721)
- ImagePreview: 修复 swipeTo 方法类型定义错误 [#6727](https://github.com/youzan/vant/issues/6727)
- Picker: 修复惯性滚动过程中点击选项会导致选中结果错误的问题 [#6724](https://github.com/youzan/vant/issues/6724)
- Popup: 修复 lock-scroll 在个别场景下不生效的问题 [#6698](https://github.com/youzan/vant/issues/6698)
### [v2.9.0](https://github.com/youzan/vant/compare/v2.8.7...v2.9.0)
`2020-07-03`

View File

@ -1,6 +1,6 @@
{
"name": "vant",
"version": "2.9.0",
"version": "2.9.1-beta.1",
"description": "Mobile UI Components built on Vue",
"main": "lib/index.js",
"module": "es/index.js",
@ -40,8 +40,12 @@
"url": "git@github.com:youzan/vant.git"
},
"keywords": [
"ui",
"vue",
"component"
"frontend",
"mobile ui",
"component",
"components"
],
"author": "youzanfe",
"license": "MIT",

View File

@ -10,6 +10,7 @@
>
<van-popup
v-model="showArea"
round
slot="extra"
position="bottom"
get-container="body"

View File

@ -10,6 +10,7 @@
>
<van-calendar
v-model="showCalendar"
round
slot="extra"
get-container="body"
@confirm="onConfirm"

View File

@ -10,6 +10,7 @@
>
<van-popup
v-model="showPicker"
round
slot="extra"
position="bottom"
get-container="body"

View File

@ -10,6 +10,7 @@
>
<van-popup
v-model="showPicker"
round
slot="extra"
position="bottom"
get-container="body"

View File

@ -30,6 +30,14 @@ export function ChildrenMixin(parent, options = {}) {
},
},
watch: {
disableBindRelation(val) {
if (!val) {
this.bindRelation();
}
},
},
mounted() {
this.bindRelation();
},

View File

@ -0,0 +1,105 @@
// Utils
import { createNamespace } from '../../utils';
import { stringToDate, dateToString } from '../utils/time-helper';
// Components
import Popup from '../../popup';
import DateTimePicker from '../../datetime-picker';
import Field from '../../field';
const namespace = createNamespace('sku-datetime-field');
const createComponent = namespace[0];
const t = namespace[2];
export default createComponent({
props: {
value: String,
label: String,
required: Boolean,
placeholder: String,
type: {
type: String,
default: 'date',
},
},
data() {
return {
showDatePicker: false,
currentDate: this.type === 'time' ? '' : new Date(),
};
},
watch: {
value(val) {
switch (this.type) {
case 'time':
this.currentDate = val;
break;
case 'date':
case 'datetime':
this.currentDate = stringToDate(val) || new Date();
break;
}
},
},
computed: {
title() {
return t(`title.${this.type}`);
},
},
methods: {
onClick() {
this.showDatePicker = true;
},
onConfirm(val) {
let data = val;
if (this.type !== 'time') {
data = dateToString(val, this.type);
}
this.$emit('input', data);
this.showDatePicker = false;
},
onCancel() {
this.showDatePicker = false;
},
formatter(type, val) {
const word = t(`format.${type}`);
return `${val}${word}`;
},
},
render() {
return (
<Field
readonly
is-link
center
value={this.value}
label={this.label}
required={this.required}
placeholder={this.placeholder}
onClick={this.onClick}
>
<Popup
vModel={this.showDatePicker}
round
slot="extra"
position="bottom"
getContainer="body"
>
<DateTimePicker
type={this.type}
title={this.title}
value={this.currentDate}
formatter={this.formatter}
onCancel={this.onCancel}
onConfirm={this.onConfirm}
/>
</Popup>
</Field>
);
},
});

View File

@ -2,11 +2,11 @@
import { createNamespace } from '../../utils';
// Components
import Icon from '../../icon';
import Loading from '../../loading';
import Uploader from '../../uploader';
const [createComponent, bem, t] = createNamespace('sku-img-uploader');
const namespace = createNamespace('sku-img-uploader');
const createComponent = namespace[0];
const t = namespace[2];
export default createComponent({
props: {
@ -20,26 +20,32 @@ export default createComponent({
data() {
return {
// 正在上传的图片 base64
paddingImg: '',
uploadFail: false,
fileList: [],
};
},
watch: {
value(val) {
if (val) {
this.fileList = [{ url: val, isImage: true }];
} else {
this.fileList = [];
}
},
},
methods: {
afterReadFile(file) {
// 上传文件
this.paddingImg = file.content;
this.uploadFail = false;
file.status = 'uploading';
file.message = t('uploading');
this.uploadImg(file.file, file.content)
.then((img) => {
file.status = 'done';
this.$emit('input', img);
this.$nextTick(() => {
this.paddingImg = '';
});
})
.catch(() => {
this.uploadFail = true;
file.status = 'failed';
file.message = t('fail');
});
},
@ -47,68 +53,21 @@ export default createComponent({
this.$toast(t('oversize', this.maxSize));
},
genUploader(content, disabled = false) {
return (
<Uploader
class={bem('uploader')}
disabled={disabled}
afterRead={this.afterReadFile}
maxSize={this.maxSize * 1024 * 1024}
onOversize={this.onOversize}
>
<div class={bem('img')}>{content}</div>
</Uploader>
);
},
genMask() {
return (
<div class={bem('mask')}>
{this.uploadFail ? (
[
<Icon name="warning-o" size="20px" />,
<div class={bem('warn-text')} domPropsInnerHTML={t('fail')} />,
]
) : (
<Loading type="spinner" size="20px" color="white" />
)}
</div>
);
onDelete() {
this.$emit('input', '');
},
},
render() {
return (
<div class={bem()}>
{this.value &&
this.genUploader(
[
<img src={this.value} />,
<Icon
name="clear"
class={bem('delete')}
onClick={() => {
this.$emit('input', '');
}}
/>,
],
true
)}
{this.paddingImg &&
this.genUploader(
[<img src={this.paddingImg} />, this.genMask()],
!this.uploadFail
)}
{!this.value &&
!this.paddingImg &&
this.genUploader(
<div class={bem('trigger')}>
<Icon name="photograph" size="22px" />
</div>
)}
</div>
<Uploader
vModel={this.fileList}
maxCount={1}
afterRead={this.afterReadFile}
maxSize={this.maxSize * 1024 * 1024}
onOversize={this.onOversize}
onDelete={this.onDelete}
/>
);
},
});

View File

@ -7,6 +7,7 @@ import { isNumeric } from '../../utils/validate/number';
import Cell from '../../cell';
import Field from '../../field';
import SkuImgUploader from './SkuImgUploader';
import SkuDateTimeField from './SkuDateTimeField';
const [createComponent, bem, t] = createNamespace('sku-messages');
@ -48,18 +49,14 @@ export default createComponent({
if (message.type === 'id_no') {
return 'text';
}
return message.datetime > 0 ? 'datetime-local' : message.type;
return message.datetime > 0 ? 'datetime' : message.type;
},
getMessages() {
const messages = {};
this.messageValues.forEach((item, index) => {
let { value } = item;
if (this.messages[index].datetime > 0) {
value = value.replace(/T/g, ' ');
}
messages[`message_${index}`] = value;
messages[`message_${index}`] = item.value;
});
return messages;
@ -69,12 +66,8 @@ export default createComponent({
const messages = {};
this.messageValues.forEach((item, index) => {
let { value } = item;
const message = this.messages[index];
if (message.datetime > 0) {
value = value.replace(/T/g, ' ');
}
messages[message.name] = value;
messages[message.name] = item.value;
});
return messages;
@ -125,7 +118,6 @@ export default createComponent({
<Cell
key={`${this.goodsId}-${index}`}
title={message.name}
label={t('imageLabel')}
class={bem('image-cell')}
required={String(message.required) === '1'}
valueClass={bem('image-cell-value')}
@ -135,14 +127,31 @@ export default createComponent({
maxSize={this.messageConfig.uploadMaxSize}
uploadImg={this.messageConfig.uploadImg}
/>
<div class={bem('image-cell-label')}>{t('imageLabel')}</div>
</Cell>
);
}
// 时间和日期使用的vant选择器
const isDateOrTime = ['date', 'time'].indexOf(message.type) > -1;
if (isDateOrTime) {
return (
<SkuDateTimeField
vModel={this.messageValues[index].value}
label={message.name}
key={`${this.goodsId}-${index}`}
required={String(message.required) === '1'}
placeholder={this.getPlaceholder(message)}
type={this.getType(message)}
/>
);
}
return (
<Field
vModel={this.messageValues[index].value}
maxlength="200"
center={!message.multiple}
label={message.name}
key={`${this.goodsId}-${index}`}
required={String(message.required) === '1'}

View File

@ -314,12 +314,6 @@
&-messages {
padding-bottom: @padding-xl;
.van-cell::after {
top: 0;
right: @padding-md;
bottom: auto;
}
&__image-cell {
.van-cell__title {
max-width: @field-label-width;
@ -330,75 +324,13 @@
overflow: visible;
text-align: left;
}
}
}
&-img-uploader {
display: inline-block;
&__uploader {
vertical-align: middle;
}
&__img {
position: relative;
float: left;
width: 64px;
height: 64px;
margin-right: @padding-xs;
background: @sku-item-background-color;
border-radius: @border-radius-sm;
img {
width: 100%;
height: 100%;
object-fit: contain;
&-label {
color: @cell-label-color;
font-size: @cell-label-font-size;
line-height: @cell-label-line-height;
}
}
&__delete {
position: absolute;
top: -12px;
right: -14px;
z-index: 1;
padding: 6px;
color: @sku-upload-mask-color;
opacity: 0.8;
&::before {
background-color: @white;
border-radius: 14px;
}
}
&__mask {
position: absolute;
top: 0;
left: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
color: white;
background: @sku-upload-mask-color;
}
&__warn-text {
margin-top: 6px;
font-size: @font-size-sm;
line-height: 14px;
}
&__trigger {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
color: @sku-icon-gray-color;
}
}
&-actions {

View File

@ -26,7 +26,8 @@ export default {
vanSkuImgUploader: {
oversize: (maxSize: number) =>
`最大可上传图片为${maxSize}MB请尝试压缩图片尺寸`,
fail: '上传失败<br />重新上传',
fail: '上传失败',
uploading: '上传中...',
},
vanSkuStepper: {
quotaLimit: (quota: number) => `限购${quota}`,
@ -45,18 +46,32 @@ export default {
id_no: '请填写正确的身份证号码',
},
placeholder: {
id_no: '输入身份证号码',
text: '输入文本',
tel: '输入数字',
email: '输入邮箱',
date: '点击选择日期',
time: '点击选择时间',
textarea: '点击填写段落文本',
mobile: '输入手机号码',
id_no: '请填写身份证号',
text: '请填写留言',
tel: '请填写数字',
email: '请填写邮箱',
date: '选择日期',
time: '选择时间',
textarea: '请填写留言',
mobile: '请填写手机号',
},
},
vanSkuRow: {
multiple: '可多选',
},
vanSkuDatetimeField: {
title: {
date: '选择年月日',
time: '选择时间',
datetime: '选择日期时间',
},
format: {
year: '年',
month: '月',
day: '日',
hour: '时',
minute: '分',
},
},
},
};

View File

@ -1,6 +1,7 @@
import { mount } from '../../../test';
import Sku from '..';
import { getSkuData, initialSku } from '../demo/data';
import { stringToDate, dateToString } from '../utils/time-helper';
const skuData = getSkuData();
@ -30,3 +31,14 @@ test('resetSelectedSku method', () => {
wrapper.find('.van-button--danger').trigger('click');
expect(wrapper.emitted('buy-clicked').length).toEqual(1);
});
test('stringToDate', () => {
expect(dateToString(stringToDate(''))).toEqual('');
expect(dateToString(stringToDate('2020-07-01'))).toEqual('2020-07-01');
expect(dateToString(stringToDate('2020-07-01 22:44'), 'datetime')).toEqual(
'2020-07-01 22:44'
);
expect(dateToString(stringToDate('2020-12-31 23:59'), 'datetime')).toEqual(
'2020-12-31 23:59'
);
});

View File

@ -0,0 +1,28 @@
import { padZero } from '../../utils/format/string';
// 字符串转 Date
// 只处理 YYYY-MM-DD 或者 YYYY-MM-DD HH:MM 格式
export function stringToDate(timeString) {
if (!timeString) {
return null;
}
return new Date(timeString.replace(/-/g, '/'));
}
// Date 转字符串
// type: date or datetime
export function dateToString(date, type = 'date') {
if (!date) {
return '';
}
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
let timeString = `${year}-${padZero(month)}-${padZero(day)}`;
if (type === 'datetime') {
const hours = date.getHours();
const minute = date.getMinutes();
timeString += ` ${padZero(hours)}:${padZero(minute)}`;
}
return timeString;
}

View File

@ -20,9 +20,9 @@ export type ImagePreviewOptions =
closeOnPopstate?: boolean;
closeIconPosition?: string;
getContainer?: string | (() => Element);
onClose?: () => void;
onChange?: (index: number) => void;
swipeTo(index: number, options?: SwipeToOptions): void;
onClose?(): void;
onChange?(index: number): void;
swipeTo?(index: number, options?: SwipeToOptions): void;
};
export class VanImagePreview extends VanPopupMixin {