chore: merge branch "dev"

This commit is contained in:
陈嘉涵 2019-12-02 11:05:15 +08:00
commit 9302cae9ba
38 changed files with 502 additions and 116 deletions

View File

@ -1,5 +1,47 @@
# Changelog
### [v2.3.0-beta.1](https://github.com/youzan/vant/tree/v2.3.0-beta.1)
`2019-11-30`
**Style**
Upgrading the style of business components:
- AddressEdit
- Card
- ContactList
- ContactCard
- ContactEdit
- SubmitBar
**Features**
- Card: add price-top slot [\#5134](https://github.com/youzan/vant/pull/5134)
- Circle: add stroke-linecap prop [\#5087](https://github.com/youzan/vant/pull/5087)
- CountDown: support SS and S format [\#5154](https://github.com/youzan/vant/pull/5154)
- Sku: add new startSaleNum prop [\#5105](https://github.com/youzan/vant/pull/5105)
- SubmitBar: add text-align prop [\#5130](https://github.com/youzan/vant/pull/5130)
- AddressList: add default-tag-text prop [\#5106](https://github.com/youzan/vant/pull/5106)
- ContactList: add default-tag-text prop [\#5089](https://github.com/youzan/vant/pull/5089)
- ContactCard: add show-set-default prop [\#5083](https://github.com/youzan/vant/pull/5083)
- Toast: improve type definitions [\#5086](https://github.com/youzan/vant/pull/5086)
**Bug Fixes**
- fix TreeSelect should sync value before trigger click-item event [\#5153](https://github.com/youzan/vant/pull/5153)
- fix TouchEmulator compatibility issues on firefox [\#5118](https://github.com/youzan/vant/pull/5118)
- fix Card allow use bottom slot without price or num [\#5116](https://github.com/youzan/vant/pull/5116)
- fix NumberKeyboard should not trigger blur event when hidden [\#5110](https://github.com/youzan/vant/pull/5110)
### [v2.2.15](https://github.com/youzan/vant/tree/v2.2.15)
`2019-11-28`
**Bug Fixes**
- fix List incorrect list status in some cases
### [v2.2.14](https://github.com/youzan/vant/tree/v2.2.14)
`2019-11-22`

View File

@ -10,6 +10,49 @@ Vant 遵循 [Semver](https://semver.org/lang/zh-CN/) 语义化版本规范。
- 次版本号:每隔一至二个月发布,包含新特性和较大的功能更新,向下兼容。
- 主版本号:发布时间不定,包含不兼容更新,预计下一个主版本会与 Vue 3.0 同期发布。
### [v2.3.0-beta.1](https://github.com/youzan/vant/tree/v2.3.0-beta.1)
`2019-11-30`
**Style**
在 2.3.0 版本中,我们对业务组件的样式进行了全新升级,涉及以下组件:
- AddressEdit
- Card
- ContactList
- ContactCard
- ContactEdit
- SubmitBar
**Features**
- Card: 新增 price-top 插槽 [\#5134](https://github.com/youzan/vant/pull/5134)
- Circle: 新增 stroke-linecap 属性 [\#5087](https://github.com/youzan/vant/pull/5087)
- CountDown: 支持 SS 和 S 格式 [\#5154](https://github.com/youzan/vant/pull/5154)
- Sku: 新增 new startSaleNum 属性 [\#5105](https://github.com/youzan/vant/pull/5105)
- SubmitBar: 新增 text-align 属性 [\#5130](https://github.com/youzan/vant/pull/5130)
- AddressList: 新增 default-tag-text 属性 [\#5106](https://github.com/youzan/vant/pull/5106)
- ContactList: 新增 default-tag-text 属性 [\#5089](https://github.com/youzan/vant/pull/5089)
- ContactCard: 新增 show-set-default 属性 [\#5083](https://github.com/youzan/vant/pull/5083)
- Toast: 完善 TS 类型定义 [\#5086](https://github.com/youzan/vant/pull/5086)
**Bug Fixes**
- 修复 TreeSelect 事件触发顺序错误的问题 [\#5153](https://github.com/youzan/vant/pull/5153)
- 修复 TouchEmulator 在 Firefox 上的兼容性问题 [\#5118](https://github.com/youzan/vant/pull/5118)
- 修复 Card 在未使用 price 属性的情况下 bottom 插槽不生效的问题 [\#5116](https://github.com/youzan/vant/pull/5116)
- 修复 NumberKeyboard 在隐藏状态下也会触发 blur 事件的问题 [\#5110](https://github.com/youzan/vant/pull/5110)
### [v2.2.15](https://github.com/youzan/vant/tree/v2.2.15)
`2019-11-28`
**Bug Fixes**
- 修复 List 组件在部分情况下加载状态未重置的问题
### [v2.2.14](https://github.com/youzan/vant/tree/v2.2.14)
`2019-11-22`

View File

@ -1,6 +1,6 @@
{
"name": "vant",
"version": "2.2.14",
"version": "2.3.0-beta.1",
"description": "Mobile UI Components built on Vue",
"main": "lib/index.js",
"module": "es/index.js",

View File

@ -45,7 +45,7 @@ Use slot to custom content.
<van-card
num="2"
title="Title"
desc="Description"
desc="Description"
price="2.00"
thumb="https://img.yzcdn.cn/vant/t-thirt.jpg"
>
@ -94,6 +94,7 @@ Use slot to custom content.
| num | Custom num |
| price | Custom price |
| origin-price | Custom origin price |
| price-top | Custom price top |
| bottom | Custom price bottom |
| thumb | Custom thumb |
| tag | Custom thumb tag |

View File

@ -17,7 +17,7 @@ Vue.use(Card);
<van-card
num="2"
price="2.00"
desc="描述信息"
desc="描述信息"
title="商品标题"
thumb="https://img.yzcdn.cn/vant/t-thirt.jpg"
/>
@ -32,7 +32,7 @@ Vue.use(Card);
num="2"
tag="标签"
price="2.00"
desc="描述信息"
desc="描述信息"
title="商品标题"
thumb="https://img.yzcdn.cn/vant/t-thirt.jpg"
origin-price="10.00"
@ -47,7 +47,7 @@ Vue.use(Card);
<van-card
num="2"
price="2.00"
desc="描述信息"
desc="描述信息"
title="商品标题"
thumb="https://img.yzcdn.cn/vant/t-thirt.jpg"
>
@ -96,6 +96,7 @@ Vue.use(Card);
| num | 自定义数量 |
| price | 自定义价格 |
| origin-price | 自定义商品原价 |
| price-top | 自定义价格上方区域 |
| bottom | 自定义价格下方区域 |
| thumb | 自定义图片 |
| tag | 自定义图片角标 |

View File

@ -22,6 +22,7 @@
width: @card-thumb-size;
height: @card-thumb-size;
margin-right: @padding-xs;
border-radius: @card-thumb-border-radius;
}
&__content {
@ -29,6 +30,7 @@
display: flex;
flex: 1;
flex-direction: column;
justify-content: space-between;
min-width: 0; /* hack for flex box ellipsis */
min-height: @card-thumb-size;
@ -62,6 +64,11 @@
display: inline-block;
color: @card-price-color;
font-weight: @font-weight-bold;
&--integer {
font-size: @card-price-integer-font-size;
font-family: @card-price-font-family;
}
}
&__origin-price {
@ -74,6 +81,7 @@
&__num {
float: right;
color: @card-num-color;
}
&__tag {

View File

@ -32,6 +32,7 @@ export type CardSlots = DefaultSlots & {
bottom?: ScopedSlot;
footer?: ScopedSlot;
'origin-price'?: ScopedSlot;
'price-top'?: ScopedSlot;
};
export type CardEvents = {
@ -114,11 +115,22 @@ function Card(
}
}
function PriceContent() {
const priceArr = props.price!.toString().split('.');
return (
<div>
{props.currency}
<span class={bem('price', 'integer')}>{priceArr[0]}</span>.
{priceArr[1]}
</div>
);
}
function Price() {
if (showPrice) {
return (
<div class={bem('price')}>
{slots.price ? slots.price() : `${props.currency} ${props.price}`}
{slots.price ? slots.price() : PriceContent()}
</div>
);
}
@ -137,7 +149,7 @@ function Card(
function Num() {
if (showNum) {
return <div class={bem('num')}>{slots.num ? slots.num() : `x ${props.num}`}</div>;
return <div class={bem('num')}>{slots.num ? slots.num() : `x${props.num}`}</div>;
}
}
@ -152,11 +164,14 @@ function Card(
<div class={bem('header')}>
{Thumb()}
<div class={bem('content', { centered: props.centered })}>
{Title()}
{Desc()}
{slots.tags && slots.tags()}
<div>
{Title()}
{Desc()}
{slots.tags && slots.tags()}
</div>
{showBottom && (
<div class="van-card__bottom">
{slots['price-top'] && slots['price-top']()}
{Price()}
{OriginPrice()}
{Num()}

View File

@ -11,11 +11,15 @@ exports[`renders demo correctly 1`] = `
</div>
</a>
<div class="van-card__content">
<div class="van-card__title van-multi-ellipsis--l2">商品名称</div>
<div class="van-card__desc van-ellipsis">描述信息</div>
<div>
<div class="van-card__title van-multi-ellipsis--l2">商品名称</div>
<div class="van-card__desc van-ellipsis">描述信息</div>
</div>
<div class="van-card__bottom">
<div class="van-card__price">¥ 2.00</div>
<div class="van-card__num">x 2</div>
<div class="van-card__price">
<div>¥<span class="van-card__price van-card__price--integer">2</span>.00</div>
</div>
<div class="van-card__num">x2</div>
</div>
</div>
</div>
@ -31,12 +35,16 @@ exports[`renders demo correctly 1`] = `
<div class="van-card__tag"><span class="van-tag van-tag--mark van-tag--danger">标签</span></div>
</a>
<div class="van-card__content">
<div class="van-card__title van-multi-ellipsis--l2">商品名称</div>
<div class="van-card__desc van-ellipsis">描述信息</div>
<div>
<div class="van-card__title van-multi-ellipsis--l2">商品名称</div>
<div class="van-card__desc van-ellipsis">描述信息</div>
</div>
<div class="van-card__bottom">
<div class="van-card__price">¥ 2.00</div>
<div class="van-card__price">
<div>¥<span class="van-card__price van-card__price--integer">2</span>.00</div>
</div>
<div class="van-card__origin-price">¥ 10.00</div>
<div class="van-card__num">x 2</div>
<div class="van-card__num">x2</div>
</div>
</div>
</div>
@ -51,16 +59,20 @@ exports[`renders demo correctly 1`] = `
</div>
</a>
<div class="van-card__content">
<div class="van-card__title van-multi-ellipsis--l2">商品名称</div>
<div class="van-card__desc van-ellipsis">描述信息</div>
<div><span class="van-tag van-tag--plain van-tag--danger van-hairline--surround" style="margin-right: 5px;">
<div>
<div class="van-card__title van-multi-ellipsis--l2">商品名称</div>
<div class="van-card__desc van-ellipsis">描述信息</div>
<div><span class="van-tag van-tag--plain van-tag--danger van-hairline--surround" style="margin-right: 5px;">
标签
</span> <span class="van-tag van-tag--plain van-tag--danger van-hairline--surround">
标签
</span></div>
</div>
<div class="van-card__bottom">
<div class="van-card__price">¥ 2.00</div>
<div class="van-card__num">x 2</div>
<div class="van-card__price">
<div>¥<span class="van-card__price van-card__price--integer">2</span>.00</div>
</div>
<div class="van-card__num">x2</div>
</div>
</div>
</div>

View File

@ -4,6 +4,7 @@ exports[`render bottom slot 1`] = `
<div class="van-card">
<div class="van-card__header">
<div class="van-card__content">
<div></div>
<div class="van-card__bottom">Custom Bottom</div>
</div>
</div>
@ -14,6 +15,7 @@ exports[`render origin-price slot 1`] = `
<div class="van-card">
<div class="van-card__header">
<div class="van-card__content">
<div></div>
<div class="van-card__bottom">
<div class="van-card__origin-price">Custom Origin Price</div>
</div>
@ -26,6 +28,7 @@ exports[`render price & num slot 1`] = `
<div class="van-card">
<div class="van-card__header">
<div class="van-card__content">
<div></div>
<div class="van-card__bottom">
<div class="van-card__price">Custom Price</div>
<div class="van-card__num">Custom Num</div>
@ -38,7 +41,9 @@ exports[`render price & num slot 1`] = `
exports[`render thumb & tag slot 1`] = `
<div class="van-card">
<div class="van-card__header"><a class="van-card__thumb">Custom Thumb<div class="van-card__tag">Custom Tag</div></a>
<div class="van-card__content"></div>
<div class="van-card__content">
<div></div>
</div>
</div>
</div>
`;
@ -46,7 +51,21 @@ exports[`render thumb & tag slot 1`] = `
exports[`render title & desc slot 1`] = `
<div class="van-card">
<div class="van-card__header">
<div class="van-card__content">Custom TitleCustom desc</div>
<div class="van-card__content">
<div>Custom TitleCustom desc</div>
</div>
</div>
</div>
`;
exports[`render price & price-top slot 1`] = `
<div class="van-card">
<div class="van-card__header">
<div class="van-card__content">
<div></div>
<div class="van-card__bottom">Custom Price-top<div class="van-card__price">Custom Price</div>
</div>
</div>
</div>
</div>
`;

View File

@ -94,3 +94,14 @@ test('render title & desc slot', () => {
expect(wrapper).toMatchSnapshot();
});
test('render price & price-top slot', () => {
const wrapper = mount(Card, {
scopedSlots: {
price: () => 'Custom Price',
'price-top': () => 'Custom Price-top'
}
});
expect(wrapper).toMatchSnapshot();
});

View File

@ -42,7 +42,7 @@ export default {
<van-count-down
millisecond
:time="time"
format="HH:mm:ss:SSS"
format="HH:mm:ss:SS"
/>
```
@ -114,10 +114,22 @@ export default {
| Attribute | Description | Type | Default | Version |
|------|------|------|------|------|
| time | Total time | *number* | - | - |
| format | Time formatDD-dayHH-hourmm-minutess-secondSSS-millisecond | *string* | `HH:mm:ss` | - |
| format | Time format | *string* | `HH:mm:ss` | - |
| auto-start | Whether to auto start count down | *boolean* | `true` | - |
| millisecond | Whether to enable millisecond render | *boolean* | `false` | - |
### Available formats
| Format | Description |
|------|------|
| DD | Day |
| HH | Hour |
| mm | Minute |
| ss | Second |
| S | Millisecond, 1-digit |
| SS | Millisecond, 2-digits |
| SSS | Millisecond, 3-digits |
### Events
| Event | Description | Arguments |

View File

@ -48,7 +48,7 @@ export default {
<van-count-down
millisecond
:time="time"
format="HH:mm:ss:SSS"
format="HH:mm:ss:SS"
/>
```
@ -124,10 +124,22 @@ export default {
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|------|------|------|------|------|
| time | 倒计时时长,单位毫秒 | *number* | - | - |
| format | 时间格式DD-日HH-时mm-分ss-秒SSS-毫秒 | *string* | `HH:mm:ss` | - |
| format | 时间格式 | *string* | `HH:mm:ss` | - |
| auto-start | 是否自动开始倒计时 | *boolean* | `true` | - |
| millisecond | 是否开启毫秒级渲染 | *boolean* | `false` | - |
### format 格式
| 格式 | 说明 |
|------|------|
| DD | 天数 |
| HH | 小时 |
| mm | 分钟 |
| ss | 秒数 |
| S | 毫秒1 位) |
| SS | 毫秒2 位) |
| SSS | 毫秒3 位) |
### Events
| 事件名 | 说明 | 回调参数 |

View File

@ -15,7 +15,7 @@
<van-count-down
millisecond
:time="time"
format="HH:mm:ss:SSS"
format="HH:mm:ss:SS"
/>
</demo-block>

View File

@ -5,3 +5,7 @@ exports[`complete format prop 1`] = `<div class="van-count-down">01-05-59-59-999
exports[`disable auto-start prop 1`] = `<div class="van-count-down">100</div>`;
exports[`incomplate format prop 1`] = `<div class="van-count-down">29-59-59-999</div>`;
exports[`milliseconds format S 1`] = `<div class="van-count-down">01-5</div>`;
exports[`milliseconds format SS 1`] = `<div class="van-count-down">01-50</div>`;

View File

@ -141,6 +141,30 @@ test('complete format prop', () => {
expect(wrapper).toMatchSnapshot();
});
test('milliseconds format SS', () => {
const wrapper = mount(CountDown, {
propsData: {
time: 1500,
autoStart: false,
format: 'ss-SS'
}
});
expect(wrapper).toMatchSnapshot();
});
test('milliseconds format S', () => {
const wrapper = mount(CountDown, {
propsData: {
time: 1500,
autoStart: false,
format: 'ss-S'
}
});
expect(wrapper).toMatchSnapshot();
});
test('incomplate format prop', () => {
const wrapper = mount(CountDown, {
propsData: {

View File

@ -57,7 +57,19 @@ export function parseFormat(format: string, timeData: TimeData): string {
format = format.replace('ss', padZero(seconds));
}
return format.replace('SSS', padZero(milliseconds, 3));
if (format.indexOf('S') !== -1) {
const ms = padZero(milliseconds, 3);
if (format.indexOf('SSS') !== -1) {
format = format.replace('SSS', ms);
} else if (format.indexOf('SS') !== -1) {
format = format.replace('SS', ms.slice(0, 2));
} else {
format = format.replace('S', ms.charAt(0));
}
}
return format;
}
export function isSameSecond(time1: number, time2: number): boolean {

View File

@ -89,7 +89,7 @@ declare global {
}
}
const version = '2.2.14';
const version = '2.3.0-beta.1';
const components = [
ActionSheet,
AddressEdit,

View File

@ -49,6 +49,10 @@ export default createComponent({
};
},
updated() {
this.innerLoading = this.loading;
},
mounted() {
if (this.immediateCheck) {
this.check();
@ -56,11 +60,8 @@ export default createComponent({
},
watch: {
finished: 'check',
loading(val) {
this.innerLoading = val;
this.check();
}
loading: 'check',
finished: 'check'
},
methods: {
@ -112,7 +113,7 @@ export default createComponent({
},
genLoading() {
if (this.innerLoading) {
if (this.innerLoading && !this.finished) {
return (
<div class={bem('loading')} key="loading">
{this.slots('loading') || (

View File

@ -141,6 +141,7 @@ export default {
| message-config | Message related config | *object* | `{}` | - |
| get-container | Return the mount node for sku | *string \| () => Element* | - | - |
| safe-area-inset-bottom | Whether to enable bottom safe area adaptation | *boolean* | `false` | 2.2.1 |
| start-sale-num | Minimum quantity | *number* | `1` | 2.2.15 |
### Events
@ -258,10 +259,10 @@ customStepperConfig: {
quotaText: 'only 5 can buy',
// custom callback when over limit
handleOverLimit: (data) => {
const { action, limitType, quota, quotaUsed } = data;
const { action, limitType, quota, quotaUsed, startSaleNum } = data;
if (action === 'minus') {
Toast('at least select one');
Toast(`at least select ${startSaleNum > 1 ? startSaleNum : 'one'}`);
} else if (action === 'plus') {
// const { LIMIT_TYPE } = Sku.skuConstants;
if (limitType === LIMIT_TYPE.QUOTA_LIMIT) {

View File

@ -145,6 +145,7 @@ export default {
| initial-sku | 默认选中的 sku具体参考高级用法 | *object* | `{}` | - |
| show-soldout-sku | 是否展示售罄的 sku默认展示并置灰 | *boolean* | `true` | - |
| safe-area-inset-bottom | 是否开启底部安全区适配,[详细说明](#/zh-CN/quickstart#di-bu-an-quan-qu-gua-pei) | *boolean* | `false` | 2.2.1 |
| start-sale-num | 起售数量 | *number* | `1` | 2.2.15 |
### Events
@ -270,10 +271,10 @@ customStepperConfig: {
quotaText: '每次限购xxx件',
// 自定义步进器超过限制时的回调
handleOverLimit: (data) => {
const { action, limitType, quota, quotaUsed } = data;
const { action, limitType, quota, quotaUsed, startSaleNum } = data;
if (action === 'minus') {
Toast('至少选择一件商品');
Toast(startSaleNum > 1 ? `${startSaleNum}件起售` : '至少选择一件商品');
} else if (action === 'plus') {
// const { LIMIT_TYPE } = Sku.skuConstants;
if (limitType === LIMIT_TYPE.QUOTA_LIMIT) {

View File

@ -45,6 +45,10 @@ export default createComponent({
type: Number,
default: 0
},
startSaleNum: {
type: Number,
default: 1
},
initialSku: {
type: Object,
default: () => ({})
@ -236,22 +240,6 @@ export default createComponent({
];
},
quotaText() {
const { quotaText, hideQuotaText } = this.customStepperConfig;
if (hideQuotaText) return '';
let text = '';
if (quotaText) {
text = quotaText;
} else if (this.quota > 0) {
text = t('quotaLimit', this.quota);
}
return text;
},
selectedText() {
if (this.selectedSkuComb) {
return `${t('selected')} ${this.selectedSkuValues.map(item => item.name).join('')}`;
@ -274,6 +262,7 @@ export default createComponent({
skuEventBus.$on('sku:numChange', this.onNumChange);
skuEventBus.$on('sku:previewImage', this.onPreviewImage);
skuEventBus.$on('sku:overLimit', this.onOverLimit);
skuEventBus.$on('sku:stepperState', this.onStepperState);
skuEventBus.$on('sku:addCart', this.onAddCart);
skuEventBus.$on('sku:buy', this.onBuy);
@ -289,6 +278,8 @@ export default createComponent({
const { skuStepper } = this.$refs;
const { selectedNum } = this.initialSku;
const num = isDef(selectedNum) ? selectedNum : 1;
// 用来缓存不合法的情况
this.stepperError = null;
if (skuStepper) {
skuStepper.setCurrentNum(num);
@ -409,18 +400,35 @@ export default createComponent({
}
if (action === 'minus') {
Toast(t('minusTip'));
if (this.startSaleNum > 1) {
Toast(t('minusStartTip', this.startSaleNum));
} else {
Toast(t('minusTip'));
}
} else if (action === 'plus') {
if (limitType === QUOTA_LIMIT) {
let msg = t('quotaLimit', quota);
if (quotaUsed > 0) msg += `${t('quotaCount', quotaUsed)}`;
Toast(msg);
if (quotaUsed > 0) {
Toast(t('quotaUsedTip', quota, quotaUsed));
} else {
Toast(t('quotaTip', quota));
}
} else {
Toast(t('soldout'));
}
}
},
onStepperState(data) {
if (data.valid) {
this.stepperError = null;
} else {
this.stepperError = {
...data,
action: 'plus',
};
}
},
onAddCart() {
this.onBuyOrAddCart('add-cart');
},
@ -430,6 +438,10 @@ export default createComponent({
},
onBuyOrAddCart(type) {
// 有信息表示该sku根本不符合购买条件
if (this.stepperError) {
return this.onOverLimit(this.stepperError);
}
const error = this.validateSku();
if (error) {
Toast(error);
@ -463,7 +475,6 @@ export default createComponent({
selectedSku,
selectedNum,
stepperTitle,
hideQuotaText,
selectedSkuComb
} = this;
@ -494,7 +505,6 @@ export default createComponent({
{!this.hideStock && (
<SkuHeaderItem>
<span class="van-sku__stock">{this.stockText}</span>
{!hideQuotaText && this.quotaText && <span class="van-sku__quota">({this.quotaText})</span>}
</SkuHeaderItem>
)}
{this.hasSku && !this.hideSelectedText && (
@ -530,6 +540,7 @@ export default createComponent({
stock={this.stock}
quota={this.quota}
quotaUsed={this.quotaUsed}
startSaleNum={this.startSaleNum}
skuEventBus={skuEventBus}
selectedNum={selectedNum}
selectedSku={selectedSku}
@ -537,6 +548,7 @@ export default createComponent({
skuStockNum={sku.stock_num}
disableStepperInput={this.disableStepperInput}
customStepperConfig={this.customStepperConfig}
hideQuotaText={this.hideQuotaText}
onChange={event => {
this.$emit('stepper-change', event);
}}

View File

@ -16,6 +16,7 @@ export default createComponent({
stepperTitle: String,
disableStepperInput: Boolean,
customStepperConfig: Object,
hideQuotaText: Boolean,
quota: {
type: Number,
default: 0
@ -23,7 +24,11 @@ export default createComponent({
quotaUsed: {
type: Number,
default: 0
}
},
startSaleNum: {
type: Number,
default: 1,
},
},
data() {
@ -40,9 +45,17 @@ export default createComponent({
},
stepperLimit(limit) {
if (limit < this.currentNum) {
if (limit < this.currentNum && this.stepperMinLimit <= limit) {
this.currentNum = limit;
}
this.checkState(this.stepperMinLimit, limit);
},
stepperMinLimit(start) {
if (start > this.currentNum || start > this.stepperLimit) {
this.currentNum = start;
}
this.checkState(start, this.stepperLimit);
}
},
@ -62,7 +75,35 @@ export default createComponent({
}
return limit;
}
},
stepperMinLimit() {
return this.startSaleNum < 1 ? 1 : this.startSaleNum;
},
quotaText() {
const { quotaText, hideQuotaText } = this.customStepperConfig;
if (hideQuotaText) return '';
let text = '';
if (quotaText) {
text = quotaText;
} else {
const textArr = [];
if (this.startSaleNum > 1) {
textArr.push(t('quotaStart', this.startSaleNum));
}
if (this.quota > 0) {
textArr.push(t('quotaLimit', this.quota));
}
text = textArr.join(t('comma'));
}
return text;
},
},
created() {
this.checkState(this.stepperMinLimit, this.stepperLimit);
},
methods: {
@ -75,7 +116,8 @@ export default createComponent({
action,
limitType: this.limitType,
quota: this.quota,
quotaUsed: this.quotaUsed
quotaUsed: this.quotaUsed,
startSaleNum: this.startSaleNum,
});
},
@ -83,7 +125,27 @@ export default createComponent({
const { handleStepperChange } = this.customStepperConfig;
handleStepperChange && handleStepperChange(currentValue);
this.$emit('change', currentValue);
}
},
checkState(min, max) {
// 如果选择小于起售,则强制变为起售
if (this.currentNum < min || min > max) {
this.currentNum = min;
} else if (this.currentNum > max) {
// 当前选择数量大于最大可选时,需要重置已选数量
this.currentNum = max;
}
this.skuEventBus.$emit('sku:stepperState', {
valid: min <= max,
min,
max,
limitType: this.limitType,
quota: this.quota,
quotaUsed: this.quotaUsed,
startSaleNum: this.startSaleNum,
});
},
},
render() {
@ -94,11 +156,13 @@ export default createComponent({
<Stepper
vModel={this.currentNum}
class="van-sku__stepper"
min={this.stepperMinLimit}
max={this.stepperLimit}
disableInput={this.disableStepperInput}
onOverlimit={this.onOverLimit}
onChange={this.onChange}
/>
{!this.hideQuotaText && this.quotaText && <span class="van-sku__stepper-quota">({this.quotaText})</span>}
</div>
</div>
);

View File

@ -2,6 +2,7 @@ export default {
goods_id: '946755',
quota: 15,
quota_used: 0,
start_sale_num: 10,
goods_info: {
title: '测试商品',
picture:

View File

@ -11,6 +11,7 @@
:hide-stock="skuData.sku.hide_stock"
:quota="skuData.quota"
:quota-used="skuData.quota_used"
:start-sale-num="skuData.start_sale_num"
:close-on-click-overlay="closeOnClickOverlay"
:message-config="messageConfig"
:custom-sku-validator="customSkuValidator"
@ -42,6 +43,7 @@
:hide-stock="skuData.sku.hide_stock"
:quota="skuData.quota"
:quota-used="skuData.quota_used"
:start-sale-num="skuData.start_sale_num"
:custom-stepper-config="customStepperConfig"
:message-config="messageConfig"
hide-quota-text
@ -70,6 +72,7 @@
:hide-stock="skuData.sku.hide_stock"
:quota="skuData.quota"
:quota-used="skuData.quota_used"
:start-sale-num="skuData.start_sale_num"
:custom-stepper-config="customStepperConfig"
:message-config="messageConfig"
:show-soldout-sku="false"
@ -99,6 +102,7 @@
:hide-stock="skuData.sku.hide_stock"
:quota="skuData.quota"
:quota-used="skuData.quota_used"
:start-sale-num="skuData.start_sale_num"
show-add-cart-btn
reset-stepper-on-hide
safe-area-inset-bottom
@ -126,7 +130,7 @@
square
size="large"
type="danger"
@click="props.skuEventBus.$emit('sku:buy')"
@click="skuEventBus.$emit('sku:buy')"
>
{{ $t('button2') }}
</van-button>
@ -185,10 +189,10 @@ export default {
quotaText: '单次限购100件',
stockFormatter: (stock) => `剩余${stock}`,
handleOverLimit: (data) => {
const { action, limitType, quota } = data;
const { action, limitType, quota, startSaleNum = 1 } = data;
if (action === 'minus') {
this.$toast('至少选择一件商品');
this.$toast(startSaleNum > 1 ? `${startSaleNum}件起售` : '至少选择一件商品');
} else if (action === 'plus') {
if (limitType === LIMIT_TYPE.QUOTA_LIMIT) {
this.$toast(`限购${quota}`);

View File

@ -196,18 +196,28 @@
}
&-container {
height: 30px;
min-height: 30px;
margin-right: 20px;
overflow: hidden;
}
}
&__stepper {
float: right;
padding-left: @padding-base;
&-title {
float: left;
line-height: 30px;
}
&-quota {
display: inline-block;
float: right;
color: @red;
font-size: @font-size-sm;
line-height: 30px;
}
}
&__stock {
@ -221,12 +231,6 @@
}
}
&__quota {
display: inline-block;
color: @red;
font-size: @font-size-sm;
}
&-messages {
padding-bottom: @padding-xl;

View File

@ -11,11 +11,12 @@ export default {
soldout: '库存不足',
originPrice: '原价',
minusTip: '至少选择一件',
minusStartTip: (start: number) => `${start}件起售`,
unavailable: '商品已经无法购买啦',
stock: '剩余',
stockUnit: '件',
quotaLimit: (quota: number) => `每人限购${quota}`,
quotaCount: (count: number) => `你已购买${count}`
quotaTip: (quota: number) => `每人限购${quota}`,
quotaUsedTip: (quota: number, count: number) => `每人限购${quota}件,你已购买${count}`
},
vanSkuActions: {
buy: '立即购买',
@ -26,6 +27,9 @@ export default {
fail: '上传失败<br />重新上传'
},
vanSkuStepper: {
quotaLimit: (quota: number) => `限购${quota}`,
quotaStart: (start: number) => `${start}件起售`,
comma: '',
num: '购买数量'
},
vanSkuMessages: {

View File

@ -42,6 +42,7 @@
@font-size-md: 14px;
@font-size-lg: 16px;
@font-weight-bold: 500;
@price-integer-font-family: Avenir-Heavy PingFang SC, Helvetica Neue, Arial, sans-serif;
// Animation
@animation-duration-base: .3s;
@ -52,6 +53,7 @@
@border-width-base: 1px;
@border-radius-sm: 2px;
@border-radius-md: 4px;
@border-radius-lg: 8px;
@border-radius-max: 999px;
// ActionSheet
@ -137,13 +139,17 @@
@card-font-size: @font-size-sm;
@card-text-color: @text-color;
@card-background-color: @background-color-light;
@card-thumb-size: 90px;
@card-thumb-size: 88px;
@card-thumb-border-radius: @border-radius-lg;
@card-title-line-height: 16px;
@card-desc-color: @gray-7;
@card-desc-line-height: 20px;
@card-price-color: @red;
@card-origin-price-color: @gray-7;
@card-price-color: @gray-8;
@card-origin-price-color: @gray-6;
@card-num-color: @gray-6;
@card-origin-price-font-size: @font-size-xs;
@card-price-integer-font-size: @font-size-lg;
@card-price-font-family: @price-integer-font-family;
// Cell
@cell-font-size: @font-size-md;
@ -596,7 +602,7 @@
@submit-bar-background-color: @white;
@submit-bar-button-width: 110px;
@submit-bar-price-color: @red;
@submit-bar-price-font-size: 18px;
@submit-bar-price-font-size: @font-size-md;
@submit-bar-currency-font-size: @font-size-md;
@submit-bar-text-color: @text-color;
@submit-bar-text-font-size: @font-size-md;
@ -606,6 +612,10 @@
@submit-bar-tip-color: #f56723;
@submit-bar-tip-background-color: #fff7cc;
@submit-bar-tip-icon-size: 12px;
@submit-bar-button-height: 40px;
@submit-bar-padding: 0 @padding-md;
@submit-bar-price-integer-font-size: 20px;
@submit-bar-price-font-family: @price-integer-font-family;
// Swipe
@swipe-indicator-size: 6px;
@ -669,7 +679,7 @@
@tag-text-color: @white;
@tag-border-radius: .2em;
@tag-round-border-radius: @border-radius-max;
@tag-dander-color: @red;
@tag-danger-color: @red;
@tag-primary-color: @blue;
@tag-success-color: @green;
@tag-warning-color: @orange;
@ -683,7 +693,7 @@
@toast-loading-icon-color: @white;
@toast-line-height: 20px;
@toast-border-radius: @border-radius-md;
@toast-background-color: rgba(@text-color, .88);
@toast-background-color: fade(@text-color, 88%);
@toast-icon-size: 40px;
@toast-text-min-width: 96px;
@toast-text-padding: @padding-xs @padding-sm;

View File

@ -75,6 +75,7 @@ Use slot to add custom contents.
| price | Price | *number* | - | - |
| label | Price left label | *string* | `Total` | - |
| suffix-label | Price right label | *string* | - | - |
| text-align | Price label text align can be set to `right` `left` | *string* | `right` | - |
| button-text | Button text | *string* | - | - |
| button-type | Button type | *string* | `danger` | - |
| tip | Tip | *string* | - | - |

View File

@ -75,6 +75,7 @@ Vue.use(SubmitBar);
| price | 价格(单位分) | *number* | - | - |
| label | 价格左侧文案 | *string* | `合计:` | - |
| suffix-label | 价格右侧文案 | *string* | - | - |
| text-align | 价格文案对齐方向,可选值为 `right` `left` | *string* | `right` | - |
| button-text | 按钮文字 | *string* | - | - |
| button-type | 按钮类型 | *string* | `danger` | - |
| tip | 提示文案 | *string* | - | - |
@ -82,7 +83,7 @@ Vue.use(SubmitBar);
| disabled | 是否禁用按钮 | *boolean* | `false` | - |
| loading | 是否显示加载中的按钮 | *boolean* | `false` | - |
| currency | 货币符号 | *string* | `¥` | - |
| decimal-length | 价格小数点后位数 | *number* | `2` | - |
| decimal-length | 价格小数点后位数 | *number* | `2` | - |
| safe-area-inset-bottom | 是否开启底部安全区适配,[详细说明](#/zh-CN/quickstart#di-bu-an-quan-qu-gua-pei) | *boolean* | `false` | - |
### Events

View File

@ -102,7 +102,7 @@ export default {
}
.van-checkbox {
margin-left: @padding-sm;
margin-right: @padding-sm;
}
}
</style>

View File

@ -32,6 +32,7 @@
align-items: center;
justify-content: flex-end;
height: @submit-bar-height;
padding: @submit-bar-padding;
font-size: @submit-bar-text-font-size;
}
@ -39,7 +40,6 @@
flex: 1;
padding-right: @padding-sm;
color: @submit-bar-text-color;
font-weight: @font-weight-bold;
text-align: right;
span {
@ -49,19 +49,30 @@
&__suffix-label {
margin-left: 5px;
font-weight: @font-weight-bold;
}
&__price {
color: @submit-bar-price-color;
font-size: @submit-bar-price-font-size;
font-weight: @font-weight-bold;
font-size: @font-size-sm;
&::first-letter {
font-size: @submit-bar-currency-font-size;
&--integer {
font-size: @submit-bar-price-integer-font-size;
font-family: @submit-bar-price-font-family;
}
}
&__button {
width: @submit-bar-button-width;
height: @submit-bar-button-height;
font-weight: @font-weight-bold;
line-height: @submit-bar-button-height;
border: none;
&--danger {
background: @goods-action-button-danger-color;
}
}
&--safe-area-inset-bottom {

View File

@ -20,6 +20,7 @@ export type SubmitBarProps = {
suffixLabel?: string;
decimalLength: number;
safeAreaInsetBottom?: boolean;
textAlign?: 'right' | 'left';
};
export type SubmitBarSlots = DefaultSlots & {
@ -39,12 +40,14 @@ function SubmitBar(
function Text() {
if (typeof price === 'number') {
const priceText = `${props.currency} ${(price / 100).toFixed(props.decimalLength)}`;
const priceArr = (price / 100).toFixed(props.decimalLength).split('.');
return (
<div class={bem('text')}>
<div style={{ textAlign: props.textAlign ? props.textAlign : '' }} class={bem('text')}>
<span>{props.label || t('label')}</span>
<span class={bem('price')}>{priceText}</span>
<span class={bem('price')}>
{props.currency}
<span class={bem('price', 'integer')}>{priceArr[0]}</span>.{priceArr[1]}
</span>
{props.suffixLabel && (
<span class={bem('suffix-label')}>{props.suffixLabel}</span>
)}
@ -76,9 +79,8 @@ function SubmitBar(
{slots.default && slots.default()}
{Text()}
<Button
square
size="large"
class={bem('button')}
round
class={bem('button', props.buttonType)}
type={props.buttonType}
loading={props.loading}
disabled={props.disabled}
@ -113,7 +115,8 @@ SubmitBar.props = {
buttonType: {
type: String,
default: 'danger'
}
},
textAlign: String,
};
export default createComponent<SubmitBarProps, {}, SubmitBarSlots>(SubmitBar);

View File

@ -5,7 +5,7 @@ exports[`renders demo correctly 1`] = `
<div>
<div class="van-submit-bar">
<div class="van-submit-bar__bar">
<div class="van-submit-bar__text"><span>合计:</span><span class="van-submit-bar__price">¥ 30.50</span></div><button class="van-button van-button--danger van-button--large van-button--square van-submit-bar__button"><span class="van-button__text">提交订单</span></button>
<div class="van-submit-bar__text"><span>合计:</span><span class="van-submit-bar__price">¥<span class="van-submit-bar__price van-submit-bar__price--integer">30</span>.50</span></div><button class="van-button van-button--danger van-button--normal van-button--round van-submit-bar__button van-submit-bar__button--danger"><span class="van-button__text">提交订单</span></button>
</div>
</div>
</div>
@ -14,14 +14,14 @@ exports[`renders demo correctly 1`] = `
<div class="van-submit-bar__tip"><i class="van-icon van-icon-info-o van-submit-bar__tip-icon">
<!----></i><span class="van-submit-bar__tip-text">你的收货地址不支持同城送, 我们已为你推荐快递</span></div>
<div class="van-submit-bar__bar">
<div class="van-submit-bar__text"><span>合计:</span><span class="van-submit-bar__price">¥ 30.50</span></div><button disabled="disabled" class="van-button van-button--danger van-button--large van-button--disabled van-button--square van-submit-bar__button"><span class="van-button__text">提交订单</span></button>
<div class="van-submit-bar__text"><span>合计:</span><span class="van-submit-bar__price">¥<span class="van-submit-bar__price van-submit-bar__price--integer">30</span>.50</span></div><button disabled="disabled" class="van-button van-button--danger van-button--normal van-button--disabled van-button--round van-submit-bar__button van-submit-bar__button--danger"><span class="van-button__text">提交订单</span></button>
</div>
</div>
</div>
<div>
<div class="van-submit-bar">
<div class="van-submit-bar__bar">
<div class="van-submit-bar__text"><span>合计:</span><span class="van-submit-bar__price">¥ 30.50</span></div><button class="van-button van-button--danger van-button--large van-button--square van-submit-bar__button">
<div class="van-submit-bar__text"><span>合计:</span><span class="van-submit-bar__price">¥<span class="van-submit-bar__price van-submit-bar__price--integer">30</span>.50</span></div><button class="van-button van-button--danger van-button--normal van-button--round van-submit-bar__button van-submit-bar__button--danger">
<div class="van-loading van-loading--circular van-button__loading"><span class="van-loading__spinner van-loading__spinner--circular" style="color: currentColor; width: 20px; height: 20px;"><svg viewBox="25 25 50 50" class="van-loading__circular"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span></div>
</button>
</div>
@ -39,7 +39,7 @@ exports[`renders demo correctly 1`] = `
<div class="van-checkbox__icon van-checkbox__icon--round van-checkbox__icon--checked"><i class="van-icon van-icon-success">
<!----></i></div><span class="van-checkbox__label">全选</span>
</div>
<div class="van-submit-bar__text"><span>合计:</span><span class="van-submit-bar__price">¥ 30.50</span></div><button class="van-button van-button--danger van-button--large van-button--square van-submit-bar__button"><span class="van-button__text">提交订单</span></button>
<div class="van-submit-bar__text"><span>合计:</span><span class="van-submit-bar__price">¥<span class="van-submit-bar__price van-submit-bar__price--integer">30</span>.50</span></div><button class="van-button van-button--danger van-button--normal van-button--round van-submit-bar__button van-submit-bar__button--danger"><span class="van-button__text">提交订单</span></button>
</div>
</div>
</div>

View File

@ -3,7 +3,7 @@
exports[`decimal-length prop 1`] = `
<div class="van-submit-bar">
<div class="van-submit-bar__bar">
<div class="van-submit-bar__text"><span>合计:</span><span class="van-submit-bar__price">¥ 1.1</span></div><button class="van-button van-button--danger van-button--large van-button--square van-submit-bar__button"></button>
<div class="van-submit-bar__text"><span>合计:</span><span class="van-submit-bar__price">¥<span class="van-submit-bar__price van-submit-bar__price--integer">1</span>.1</span></div><button class="van-button van-button--danger van-button--normal van-button--round van-submit-bar__button van-submit-bar__button--danger"></button>
</div>
</div>
`;
@ -11,7 +11,7 @@ exports[`decimal-length prop 1`] = `
exports[`disable submit 1`] = `
<div class="van-submit-bar">
<div class="van-submit-bar__bar">
<div class="van-submit-bar__text"><span>合计:</span><span class="van-submit-bar__price">¥ 0.01</span></div><button disabled="disabled" class="van-button van-button--danger van-button--large van-button--disabled van-button--square van-submit-bar__button"></button>
<div class="van-submit-bar__text"><span>合计:</span><span class="van-submit-bar__price">¥<span class="van-submit-bar__price van-submit-bar__price--integer">0</span>.01</span></div><button disabled="disabled" class="van-button van-button--danger van-button--normal van-button--disabled van-button--round van-submit-bar__button van-submit-bar__button--danger"></button>
</div>
</div>
`;
@ -19,18 +19,26 @@ exports[`disable submit 1`] = `
exports[`suffix-label prop 1`] = `
<div class="van-submit-bar">
<div class="van-submit-bar__bar">
<div class="van-submit-bar__text"><span>Label</span><span class="van-submit-bar__price">¥ 1.11</span><span class="van-submit-bar__suffix-label">Suffix Label</span></div><button class="van-button van-button--danger van-button--large van-button--square van-submit-bar__button"></button>
<div class="van-submit-bar__text"><span>Label</span><span class="van-submit-bar__price">¥<span class="van-submit-bar__price van-submit-bar__price--integer">1</span>.11</span><span class="van-submit-bar__suffix-label">Suffix Label</span></div><button class="van-button van-button--danger van-button--normal van-button--round van-submit-bar__button van-submit-bar__button--danger"></button>
</div>
</div>
`;
exports[`text-align prop 1`] = `
<div class="van-submit-bar">
<div class="van-submit-bar__bar">
<div class="van-submit-bar__text" style="text-align: left;"><span>合计:</span><span class="van-submit-bar__price">¥<span class="van-submit-bar__price van-submit-bar__price--integer">1</span>.11</span></div><button class="van-button van-button--danger van-button--normal van-button--round van-submit-bar__button van-submit-bar__button--danger"></button>
</div>
</div>
`;
exports[`top slot 1`] = `
<div class="van-submit-bar">top<div class="van-submit-bar__bar"><button class="van-button van-button--danger van-button--large van-button--square van-submit-bar__button"></button></div>
<div class="van-submit-bar">top<div class="van-submit-bar__bar"><button class="van-button van-button--danger van-button--normal van-button--round van-submit-bar__button van-submit-bar__button--danger"></button></div>
</div>
`;
exports[`without price 1`] = `
<div class="van-submit-bar">
<div class="van-submit-bar__bar"><button class="van-button van-button--danger van-button--large van-button--square van-submit-bar__button"></button></div>
<div class="van-submit-bar__bar"><button class="van-button van-button--danger van-button--normal van-button--round van-submit-bar__button van-submit-bar__button--danger"></button></div>
</div>
`;

View File

@ -85,3 +85,15 @@ test('suffix-label prop', () => {
expect(wrapper).toMatchSnapshot();
});
test('text-align prop', () => {
const wrapper = mount(SubmitBar, {
context: {
props: {
price: 111,
textAlign: 'left'
}
}
});
expect(wrapper).toMatchSnapshot();
});

View File

@ -23,10 +23,10 @@
}
&--danger {
background-color: @tag-dander-color;
background-color: @tag-danger-color;
&.van-tag--plain {
color: @tag-dander-color;
color: @tag-danger-color;
}
}

View File

@ -97,9 +97,8 @@ function TreeSelect(
}
}
emit(ctx, 'click-item', item);
emit(ctx, 'update:active-id', newActiveId);
emit(ctx, 'click-item', item);
// compatible for old usage, should be removed in next major version
emit(ctx, 'itemclick', item);
}
@ -119,9 +118,8 @@ function TreeSelect(
class={bem('nav')}
activeKey={mainActiveIndex}
onChange={(index: number) => {
emit(ctx, 'click-nav', index);
emit(ctx, 'update:main-active-index', index);
emit(ctx, 'click-nav', index);
// compatible for old usage, should be removed in next major version
emit(ctx, 'navclick', index);
}}

View File

@ -295,3 +295,37 @@ test('className of nav', () => {
const items = wrapper.findAll('.van-tree-select__nav-item');
expect(items.at(0).element.classList.contains('my-class')).toBeTruthy();
});
test('should sync value before trigger click-item event', done => {
const wrapper = mount({
template: `
<van-tree-select
:items="items"
:main-active-index="0"
:active-id.sync="activeId"
@click-item="onClickItem"
/>
`,
data() {
return {
activeId: mockItem.id,
mainActiveIndex: 0,
items: [
{
text: 'group1',
children: [mockItem, mockItem2]
}
]
};
},
methods: {
onClickItem() {
expect(wrapper.vm.activeId).toEqual(mockItem2.id);
done();
}
}
});
const items = wrapper.findAll('.van-tree-select__item');
items.at(1).trigger('click');
});