diff --git a/docs/demos/views/sku.vue b/docs/demos/views/sku.vue index 97bc35520..6a956a76c 100644 --- a/docs/demos/views/sku.vue +++ b/docs/demos/views/sku.vue @@ -20,6 +20,24 @@ + + + + {{ $t('title2') }} + + + import data from '../mock/sku'; +import { LIMIT_TYPE } from '../../../packages/sku/constants'; export default { i18n: { 'zh-CN': { sku: data['zh-CN'], + title2: '自定义步进器相关配置', stepperTitle: '我要买', button1: '积分兑换', button2: '买买买' }, 'en-US': { sku: data['en-US'], + title2: 'Custom Stepper Related Config', stepperTitle: 'Stepper title', button1: 'Button', button2: 'Button' @@ -76,6 +97,22 @@ export default { initialSku: { s1: '30349', s2: '1193' + }, + customStepperConfig: { + quotaText: '单次限购100件', + handleOverLimit: (data) => { + const { action, limitType, quota } = data; + + if (action === 'minus') { + Toast('至少选择一件商品'); + } else if (action === 'plus') { + if (limitType === LIMIT_TYPE.QUOTA_LIMIT) { + Toast(`限购${quota}件`); + } else { + Toast('库存不够了~~'); + } + } + } } }; }, diff --git a/docs/demos/views/tab.vue b/docs/demos/views/tab.vue index 9585a7efc..63b120025 100644 --- a/docs/demos/views/tab.vue +++ b/docs/demos/views/tab.vue @@ -47,6 +47,17 @@ + + + + + + {{ $t('tab') }} + + {{ $t('content') }} {{ index }} + + + @@ -59,7 +70,8 @@ export default { title3: '禁用标签', title4: '样式风格', title5: '点击事件', - title6: '粘性布局' + title6: '粘性布局', + title7: '自定义标签' }, 'en-US': { tab: 'Tab ', @@ -68,7 +80,8 @@ export default { title3: 'Disabled Tab', title4: 'Card Style', title5: 'Click Event', - title6: 'Sticky' + title6: 'Sticky', + title7: 'Custom Tab' } }, @@ -79,12 +92,6 @@ export default { }; }, - mounted() { - setTimeout(() => { - this.active = 3; - }, 1000); - }, - methods: { onClickDisabled() { Toast('Disabled!'); @@ -101,6 +108,11 @@ export default { .demo-tab { margin-bottom: 700px; + .van-tab .van-icon { + margin-right: 5px; + vertical-align: -2px; + } + .van-tab__pane { background-color: #fff; padding: 20px; diff --git a/docs/markdown/en-US/sku.md b/docs/markdown/en-US/sku.md index bef63dc4d..3c35d5bf3 100644 --- a/docs/markdown/en-US/sku.md +++ b/docs/markdown/en-US/sku.md @@ -27,6 +27,23 @@ Vue.use(Sku); /> ``` +#### Custom Stepper Config + +```html + +``` + #### Advanced Usage ```html @@ -72,6 +89,7 @@ Vue.use(Sku); | reset-selected-sku-on-hide | Whether to reset selected sku when hide | `Boolean` | `false` | - | | disable-stepper-input | Whether to disable stepper input | `Boolean` | `false` | - | | stepper-title | Quantity title | `String` | `Quantity` | - | +| custom-stepper-config | Custom stepper related config | `Object` | `{}` | - | ### Event @@ -156,6 +174,32 @@ goods: { } ``` + +#### customStepperConfig Data Structure +```javascript +customStepperConfig: { + // custom quota text + quotaText: 'only 5 can buy', + // custom callback when over limit + handleOverLimit: (data) => { + const { action, limitType, quota, quotaUsed } = data; + + if (action === 'minus') { + Toast('at least select one'); + } else if (action === 'plus') { + // const { LIMIT_TYPE } = Sku.skuConstants; + if (limitType === LIMIT_TYPE.QUOTA_LIMIT) { + let msg = `Buy up to ${quota}`; + if (quotaUsed > 0) msg += `,you already buy ${quotaUsed}`; + Toast(msg); + } else { + Toast('not enough stock'); + } + } + } +} +``` + #### Event Params Data Structure ```javascript diff --git a/docs/markdown/en-US/tab.md b/docs/markdown/en-US/tab.md index 0d49dda37..f7d2a4e21 100644 --- a/docs/markdown/en-US/tab.md +++ b/docs/markdown/en-US/tab.md @@ -104,8 +104,22 @@ In sticky mode, the tab will be fixed to top when scroll to top ```html - - 内容 {{ index }} + + content {{ index }} + + +``` + +#### Custom title +Use title slot to custom tab title + +```html + + + + tab + + content {{ index }} ``` @@ -126,6 +140,13 @@ In sticky mode, the tab will be fixed to top when scroll to top | title | Tab title | `String` | - | - | | disabled | Whether disabled current tab | `Boolean` | `false` | - | +### Tab Slot + +| name | Description | +|-----------|-----------| +| - | Content | +| title | Custom tab | + ### Tabs Event | Event | Description | Arguments | diff --git a/docs/markdown/zh-CN/sku.md b/docs/markdown/zh-CN/sku.md index d8e555ec4..21b4f3a19 100644 --- a/docs/markdown/zh-CN/sku.md +++ b/docs/markdown/zh-CN/sku.md @@ -27,6 +27,23 @@ Vue.use(Sku); /> ``` +#### 自定义步进器相关配置 + +```html + +``` + #### 高级用法 ```html @@ -73,6 +90,7 @@ Vue.use(Sku); | reset-selected-sku-on-hide | 窗口隐藏时重置已选择的sku | `Boolean` | `false` | - | | disable-stepper-input | 是否禁用sku中stepper的input框 | `Boolean` | `false` | - | | stepper-title | 数量选择组件左侧文案 | `String` | `购买数量` | - | +| custom-stepper-config | 步进器相关自定义配置 | `Object` | `{}` | - | ### Event @@ -163,6 +181,31 @@ goods: { } ``` +#### customStepperConfig 对象结构 +```javascript +customStepperConfig: { + // 自定义限购文案 + quotaText: '每次限购xxx件', + // 自定义步进器超过限制时的回调 + handleOverLimit: (data) => { + const { action, limitType, quota, quotaUsed } = data; + + if (action === 'minus') { + Toast('至少选择一件商品'); + } else if (action === 'plus') { + // const { LIMIT_TYPE } = Sku.skuConstants; + if (limitType === LIMIT_TYPE.QUOTA_LIMIT) { + let msg = `单次限购${quota}件`; + if (quotaUsed > 0) msg += `,您已购买${quotaUsed}`; + Toast(msg); + } else { + Toast('库存不够了~~'); + } + } + } +} +``` + #### 添加购物车和点击购买回调函数接收的 skuData 对象结构 ```javascript skuData: { diff --git a/docs/markdown/zh-CN/tab.md b/docs/markdown/zh-CN/tab.md index e3e3a94bb..3ac38def3 100644 --- a/docs/markdown/zh-CN/tab.md +++ b/docs/markdown/zh-CN/tab.md @@ -110,6 +110,20 @@ export default { ``` +#### 自定义标签 +通过 title slot 可以自定义标签内容 + +```html + + + + 选项 + + 内容 {{ index }} + + +``` + ### Tabs API | 参数 | 说明 | 类型 | 默认值 | 可选 | @@ -127,6 +141,13 @@ export default { | title | tab的标题 | `String` | - | - | | disabled | 是否禁用这个tab | `Boolean` | `false` | - | +### Tab Slot + +| 名称 | 说明 | +|-----------|-----------| +| - | 标签页内容 | +| title | 自定义标签 | + ### Tabs Event | 事件名 | 说明 | 参数 | diff --git a/package.json b/package.json index e4ac4560d..47304bcdd 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "karma-spec-reporter": "^0.0.32", "karma-webpack": "^2.0.9", "mocha": "^4.0.1", - "postcss": "^6.0.16", + "postcss": "^6.0.17", "postcss-calc": "^6.0.0", "postcss-easy-import": "^3.0.0", "postcss-loader": "^2.0.10", @@ -97,10 +97,10 @@ "url-loader": "^0.6.2", "vant-doc": "1.0.2", "vue": "^2.5.13", - "vue-loader": "^13.7.0", + "vue-loader": "^14.1.0", "vue-router": "^3.0.1", "vue-sfc-compiler": "^0.0.8", - "vue-style-loader": "^3.1.1", + "vue-style-loader": "^3.1.2", "vue-template-compiler": "^2.5.13", "vue-template-es2015-compiler": "^1.6.0", "webpack": "^3.10.0", diff --git a/packages/sku/Sku.vue b/packages/sku/Sku.vue index 475c86de3..7d503a2ab 100644 --- a/packages/sku/Sku.vue +++ b/packages/sku/Sku.vue @@ -66,6 +66,7 @@ :quota-used="quotaUsed" :disable-stepper-input="disableStepperInput" :hide-stock="hideStock" + :custom-stepper-config="customStepperConfig" /> @@ -159,6 +160,10 @@ export default create({ messagePlaceholderMap: { type: Object, default: () => ({}) + }, + customStepperConfig: { + type: Object, + default: () => ({}) } }, @@ -328,7 +333,15 @@ export default create({ this.selectedNum = num; }, - onOverLimit({ action, limitType, quota, quotaUsed }) { + onOverLimit(data) { + const { action, limitType, quota, quotaUsed } = data; + const { handleOverLimit } = this.customStepperConfig; + + if (handleOverLimit) { + handleOverLimit(data); + return; + } + if (action === 'minus') { Toast(this.$t('least')); } else if (action === 'plus') { diff --git a/packages/sku/components/SkuStepper.vue b/packages/sku/components/SkuStepper.vue index 08f8f197b..4c705a41d 100644 --- a/packages/sku/components/SkuStepper.vue +++ b/packages/sku/components/SkuStepper.vue @@ -12,7 +12,7 @@ /> {{ $t('remain', stock) }} - {{ $t('quota', quota) }} + {{ quotaText }} @@ -40,7 +40,8 @@ export default create({ quota: Number, quotaUsed: Number, hideStock: Boolean, - disableStepperInput: Boolean + disableStepperInput: Boolean, + customStepperConfig: Object }, data() { @@ -69,6 +70,18 @@ export default create({ } return this.skuStockNum; }, + quotaText() { + const { quotaText } = this.customStepperConfig; + let text = ''; + + if (quotaText) { + text = quotaText; + } else if (this.quota > 0) { + text = this.$t('quota', this.quota); + } + + return text; + }, stepperLimit() { const quotaLimit = this.quota - this.quotaUsed; let limit; diff --git a/packages/sku/constants.js b/packages/sku/constants.js index 80b443ee6..0d83bd7ce 100644 --- a/packages/sku/constants.js +++ b/packages/sku/constants.js @@ -2,3 +2,7 @@ export const LIMIT_TYPE = { QUOTA_LIMIT: 0, STOCK_LIMIT: 1 }; + +export default { + LIMIT_TYPE +}; diff --git a/packages/sku/index.js b/packages/sku/index.js index 4c21c126c..76ad3e005 100644 --- a/packages/sku/index.js +++ b/packages/sku/index.js @@ -6,6 +6,7 @@ import SkuStepper from './components/SkuStepper'; import SkuRow from './components/SkuRow'; import SkuRowItem from './components/SkuRowItem'; import skuHelper from './utils/skuHelper'; +import constants from './constants'; Sku.SkuActions = SkuActions; Sku.SkuHeader = SkuHeader; @@ -14,5 +15,6 @@ Sku.SkuStepper = SkuStepper; Sku.SkuRow = SkuRow; Sku.SkuRowItem = SkuRowItem; Sku.skuHelper = skuHelper; +Sku.skuConstants = constants; export default Sku; diff --git a/packages/tab/index.vue b/packages/tab/index.vue index 92887be1a..0902398d7 100644 --- a/packages/tab/index.vue +++ b/packages/tab/index.vue @@ -14,10 +14,7 @@ export default create({ mixins: [findParent], props: { - title: { - type: String, - required: true - }, + title: String, disabled: Boolean }, diff --git a/packages/tabs/index.vue b/packages/tabs/index.vue index ecf715702..11eb46034 100644 --- a/packages/tabs/index.vue +++ b/packages/tabs/index.vue @@ -1,5 +1,5 @@ - + - {{ tab.title }} + + {{ tab.title }} @@ -35,11 +36,16 @@ import { create } from '../utils'; import { raf } from '../utils/raf'; import { on, off } from '../utils/event'; +import VanNode from '../utils/node'; import scrollUtils from '../utils/scroll'; export default create({ name: 'van-tabs', + components: { + VanNode + }, + props: { sticky: Boolean, active: { @@ -69,6 +75,13 @@ export default create({ }; }, + computed: { + // whether the nav is scrollable + scrollable() { + return this.tabs.length > this.swipeThreshold; + } + }, + watch: { active(val) { this.correctActive(val); @@ -113,13 +126,6 @@ export default create({ } }, - computed: { - // whether the nav is scrollable - scrollable() { - return this.tabs.length > this.swipeThreshold; - } - }, - methods: { // whether to bind sticky listener scrollHandler(init) { diff --git a/packages/utils/node.js b/packages/utils/node.js new file mode 100644 index 000000000..929c056ce --- /dev/null +++ b/packages/utils/node.js @@ -0,0 +1,10 @@ +export default { + name: 'van-node', + functional: true, + props: { + node: Array + }, + render(h, ctx) { + return ctx.props.node; + } +}; diff --git a/packages/vant-css/src/toast.css b/packages/vant-css/src/toast.css index c5525ed13..ec646313e 100644 --- a/packages/vant-css/src/toast.css +++ b/packages/vant-css/src/toast.css @@ -16,7 +16,7 @@ flex-direction: column; box-sizing: border-box; transform: translate3d(-50%, -50%, 0); - background-color: rgba(39, 39, 39, .7); + background-color: rgba(0, 0, 0, .7); &__overlay { position: fixed; diff --git a/test/unit/specs/sku.spec.js b/test/unit/specs/sku.spec.js index f78f6944f..499939444 100644 --- a/test/unit/specs/sku.spec.js +++ b/test/unit/specs/sku.spec.js @@ -1,4 +1,5 @@ import Sku from 'packages/sku'; +import Toast from 'packages/toast'; import { mount } from 'avoriaz'; import { DOMChecker } from '../utils'; import data from '../mock/sku'; @@ -155,6 +156,60 @@ describe('Sku', (done) => { }); }); + it('should toast custom error when change step value', (done) => { + wrapper = mount(Sku, { + attachToDocument: true, + propsData: { + value: true, + sku: data.sku, + goodsId: data.goods_id, + goods: goods, + quota: data.quota, + quotaUsed: data.quota_used, + customStepperConfig: { + quotaText: '单次限购100件', + handleOverLimit: (data) => { + const { action, limitType, quota } = data; + + if (action === 'minus') { + Toast('至少选择一件商品'); + } else if (action === 'plus') { + if (limitType === 0) { + Toast(`限购${quota}件`); + } else { + Toast('库存不够了~~'); + } + } + } + } + } + }); + + // 点击减号 + const minusBtn = wrapper.find('.van-stepper__minus')[0]; + minusBtn.trigger('click'); + wrapper.vm.$nextTick(() => { + const toastText = document.querySelector('.van-toast div'); + expect(toastText.textContent).to.equal('至少选择一件商品'); + + // 手动修改购买数量 + const stepperInput = wrapper.find('.van-stepper__input')[0]; + stepperInput.element.value = 20; + stepperInput.trigger('input'); + wrapper.vm.$nextTick(() => { + expect(+stepperInput.element.value).to.equal(data.quota - data.quota_used); + + // 达到购买上限时,点击加号 + const plusBtn = wrapper.find('.van-stepper__plus')[0]; + plusBtn.trigger('click'); + wrapper.vm.$nextTick(() => { + expect(toastText.textContent).to.equal(`限购${data.quota}件`); + done(); + }); + }); + }); + }); + it('should not render sku group when none_sku is true', (done) => { const newData = Object.assign({}, data); newData.sku.none_sku = true; // eslint-disable-line diff --git a/yarn.lock b/yarn.lock index 9c19d8ddc..9173ef502 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5525,6 +5525,14 @@ postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.11, postcss@^6.0.13, postcss@^6.0.1 source-map "^0.6.1" supports-color "^5.1.0" +postcss@^6.0.17: + version "6.0.17" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.17.tgz#e259a051ca513f81e9afd0c21f7f82eda50c65c5" + dependencies: + chalk "^2.3.0" + source-map "^0.6.1" + supports-color "^5.1.0" + precinct@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/precinct/-/precinct-4.0.0.tgz#ce67108123b8ae7c156180fb3836c5e5de24654f" @@ -7119,9 +7127,9 @@ vue-lazyload@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/vue-lazyload/-/vue-lazyload-1.1.4.tgz#94dbb3fcb047f147f37900c0e22ad4fd478e31c4" -vue-loader@^13.7.0: - version "13.7.0" - resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-13.7.0.tgz#4d6a35b169c2a0a488842fb95c85052105fa9729" +vue-loader@^14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-14.1.0.tgz#ae31d62a11421061fca8bac30cbc15875df886d3" dependencies: consolidate "^0.14.0" hash-sum "^1.0.2" @@ -7134,7 +7142,7 @@ vue-loader@^13.7.0: resolve "^1.4.0" source-map "^0.6.1" vue-hot-reload-api "^2.2.0" - vue-style-loader "^3.0.0" + vue-style-loader "^4.0.1" vue-template-es2015-compiler "^1.6.0" vue-router@^3.0.1: @@ -7147,16 +7155,16 @@ vue-sfc-compiler@^0.0.8: dependencies: babel-polyfill "^6.26.0" -vue-style-loader@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-3.0.3.tgz#623658f81506aef9d121cdc113a4f5c9cac32df7" +vue-style-loader@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-3.1.2.tgz#6b66ad34998fc9520c2f1e4d5fa4091641c1597a" dependencies: hash-sum "^1.0.2" loader-utils "^1.0.2" -vue-style-loader@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-3.1.1.tgz#74fdef91a81d38bc0125746a1b5505e62d69e32c" +vue-style-loader@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.0.1.tgz#252300d32eb97e83c1a1cb5b2029e2d8c3adcf9f" dependencies: hash-sum "^1.0.2" loader-utils "^1.0.2"