diff --git a/src/sku/README.md b/src/sku/README.md index dee55190b..f7c6d500a 100644 --- a/src/sku/README.md +++ b/src/sku/README.md @@ -120,9 +120,11 @@ export default { | v-model | Whether to show sku | `boolean` | `false` | | sku | Sku data | `object` | - | | goods | Goods info | `object` | - | -| goods-id | Goods id | `string | number` | - | +| goods-id | Goods id | `string | `number` | - | +| price-tag | Tag behind the price | `string` | - | | hide-stock | Whether to hide stock | `boolean` | `false` | | hide-quota-text | Whether to hide quota text | `boolean` | `false` | +| hide-selected-text | Whether to hide selected text | `boolean` | `false` | | show-add-cart-btn | Whether to show cart button | `boolean` | `true` | | buy-text | Buy button text | `string` | - | - | | add-cart-text | Add cart button text | `string` | - | - | @@ -263,7 +265,13 @@ customStepperConfig: { Toast('not enough stock'); } } - } + }, + // custom callback when stepper value change + handleStepperChange: currentValue => {}, + // stock + stockNum: 1999, + // stock fomatter + stockFormatter: stockNum => {}, } ``` diff --git a/src/sku/README.zh-CN.md b/src/sku/README.zh-CN.md index 5e74779cc..9a906507f 100644 --- a/src/sku/README.zh-CN.md +++ b/src/sku/README.zh-CN.md @@ -123,8 +123,10 @@ export default { | sku | 商品sku数据 | `object` | - | - | | goods | 商品信息 | `object` | - | - | | goods-id | 商品 id | `string | number` | - | - | +| price-tag | 显示在价格后面的标签 | `string` | - | - | | hide-stock | 是否显示商品剩余库存 | `boolean` | `false` | - | | hide-quota-text | 是否显示限购提示 | `boolean` | `false` | 1.4.8 | +| hide-selected-text | 是否隐藏已选提示 | `boolean` | `false` | - | | show-add-cart-btn | 是否显示加入购物车按钮 | `boolean` | `true` | - | | buy-text | 购买按钮文字 | `string` | `立即购买` | - | | add-cart-text | 加入购物车按钮文字 | `string` | `加入购物车` | - | @@ -275,7 +277,13 @@ customStepperConfig: { Toast('库存不够了'); } } - } + }, + // 步进器变化的回调 + handleStepperChange: currentValue => {}, + // 库存 + stockNum: 1999, + // 格式化库存 + stockFormatter: stockNum => {}, } ``` diff --git a/src/sku/Sku.js b/src/sku/Sku.js index fef237af1..09e89b426 100644 --- a/src/sku/Sku.js +++ b/src/sku/Sku.js @@ -4,6 +4,7 @@ import Popup from '../popup'; import Toast from '../toast'; import ImagePreview from '../image-preview'; import SkuHeader from './components/SkuHeader'; +import SkuHeaderItem from './components/SkuHeaderItem'; import SkuRow from './components/SkuRow'; import SkuRowItem from './components/SkuRowItem'; import SkuStepper from './components/SkuStepper'; @@ -19,6 +20,7 @@ const { QUOTA_LIMIT } = LIMIT_TYPE; export default createComponent({ props: { sku: Object, + priceTag: String, goods: Object, value: Boolean, buyText: String, @@ -28,6 +30,7 @@ export default createComponent({ stepperTitle: String, getContainer: Function, hideQuotaText: Boolean, + hideSelectedText: Boolean, resetStepperOnHide: Boolean, customSkuValidator: Function, closeOnClickOverlay: Boolean, @@ -83,10 +86,8 @@ export default createComponent({ show(val) { this.$emit('input', val); if (!val) { - const selectedSkuValues = getSelectedSkuValues(this.sku.tree, this.selectedSku); - this.$emit('sku-close', { - selectedSkuValues, + selectedSkuValues: this.selectedSkuValues, selectedNum: this.selectedNum, selectedSkuComb: this.selectedSkuComb }); @@ -114,7 +115,6 @@ export default createComponent({ skuGroupClass() { return [ 'van-sku-group-container', - 'van-hairline--bottom', { 'van-sku-group-container--hide-soldout': !this.showSoldoutSku } @@ -160,6 +160,10 @@ export default createComponent({ return null; }, + selectedSkuValues() { + return getSelectedSkuValues(this.skuTree, this.selectedSku); + }, + price() { if (this.selectedSkuComb) { return (this.selectedSkuComb.price / 100).toFixed(2); @@ -168,6 +172,13 @@ export default createComponent({ return this.sku.price; }, + originPrice() { + if (this.selectedSkuComb && this.selectedSkuComb.origin_price) { + return (this.selectedSkuComb.origin_price / 100).toFixed(2); + } + return this.sku.origin_price; + }, + skuTree() { return this.sku.tree || []; }, @@ -191,6 +202,53 @@ export default createComponent({ } return imageList; + }, + + stock() { + const { stockNum } = this.customStepperConfig; + if (stockNum !== undefined) { + return stockNum; + } + if (this.selectedSkuComb) { + return this.selectedSkuComb.stock_num; + } + return this.sku.stock_num; + }, + + stockText() { + const { stockFormatter } = this.customStepperConfig; + if (stockFormatter) return stockFormatter(this.stock); + + return `剩余 ${this.stock}件`; + }, + + quotaText() { + const { quotaText, hideQuotaText } = this.customStepperConfig; + + if (hideQuotaText) return ''; + + let text = ''; + + if (quotaText) { + text = quotaText; + } else if (this.quota > 0) { + text = `每人限购${this.quota}件`; + } + + return text; + }, + + selectedText() { + if (this.selectedSkuComb) { + return `已选 ${this.selectedSkuValues.map(item => item.name).join(';')}`; + } + + const unselected = this.skuTree + .filter(item => this.selectedSku[item.k_s] === UNSELECTED_SKU_VALUE_ID) + .map(item => item.k) + .join(';'); + + return `选择 ${unselected}`; } }, @@ -378,6 +436,7 @@ export default createComponent({ sku, goods, price, + originPrice, skuEventBus, selectedSku, selectedNum, @@ -388,6 +447,7 @@ export default createComponent({ const slotsProps = { price, + originPrice, selectedNum, skuEventBus, selectedSku, @@ -398,9 +458,25 @@ export default createComponent({ const Header = slots('sku-header') || ( {slots('sku-header-price') || ( -
- - {price} +
+
+ + {price} + {this.priceTag && {this.priceTag}} +
+ {originPrice && ( + 原价 ¥{originPrice} + )} + {!this.hideStock && ( + + {this.stockText} + {!hideQuotaText && this.quotaText && ({this.quotaText})} + + )} + {this.hasSku && !this.hideSelectedText && ( + {this.selectedText} + )} + {slots('sku-header-extra')}
)} @@ -429,16 +505,14 @@ export default createComponent({ const Stepper = slots('sku-stepper') || ( { @@ -472,6 +546,7 @@ export default createComponent({ class="van-sku-container" getContainer={this.getContainer} closeOnClickOverlay={this.closeOnClickOverlay} + round > {Header}
diff --git a/src/sku/components/SkuHeader.tsx b/src/sku/components/SkuHeader.tsx index 7d3e24af6..1638b9f02 100644 --- a/src/sku/components/SkuHeader.tsx +++ b/src/sku/components/SkuHeader.tsx @@ -50,10 +50,9 @@ function SkuHeader(
-
{goods.title}
{slots.default && slots.default()} { skuEventBus.$emit('sku:close'); diff --git a/src/sku/components/SkuHeaderItem.tsx b/src/sku/components/SkuHeaderItem.tsx new file mode 100644 index 000000000..c72ed50a2 --- /dev/null +++ b/src/sku/components/SkuHeaderItem.tsx @@ -0,0 +1,25 @@ +import { createNamespace } from '../../utils'; +import { inherit } from '../../utils/functional'; + +// Types +import { CreateElement, RenderContext } from 'vue/types'; +import { DefaultSlots } from '../../utils/types'; + +export type SkuHeaderItemProps = {}; + +const [createComponent, bem] = createNamespace('sku-header-item'); + +function SkuHeader( + h: CreateElement, + props: SkuHeaderItemProps, + slots: DefaultSlots, + ctx: RenderContext +) { + return ( +
+ {slots.default && slots.default()} +
+ ); +} + +export default createComponent(SkuHeader); diff --git a/src/sku/components/SkuImgUploader.js b/src/sku/components/SkuImgUploader.js index f3759d37b..4034b4698 100644 --- a/src/sku/components/SkuImgUploader.js +++ b/src/sku/components/SkuImgUploader.js @@ -18,20 +18,16 @@ export default createComponent({ data() { return { // 正在上传的图片 base64 - paddingImg: '' + paddingImg: '', + uploadFail: false }; }, - computed: { - imgList() { - return this.value && !this.paddingImg ? [this.value] : []; - } - }, - methods: { afterReadFile(file) { // 上传文件 this.paddingImg = file.content; + this.uploadFail = false; this.uploadImg(file.file, file.content) .then(img => { this.$emit('input', img); @@ -40,23 +36,53 @@ export default createComponent({ }); }) .catch(() => { - this.paddingImg = ''; + this.uploadFail = true; }); }, onOversize() { this.$toast(`最大可上传图片为${this.maxSize}MB,请尝试压缩图片尺寸`); + }, + + renderUploader(content, disabled = false) { + return ( + +
+ {content} +
+
+ ); + }, + + renderMask() { + return ( +
+ {this.uploadFail + ? ( + [ + , +
上传失败
重新上传
+ ] + ) : ( + + )} +
+ ); } }, render(h) { - const { imgList, paddingImg } = this; - - const ImageList = (paddingImg || imgList.length > 0) && ( -
- {imgList.map(img => ( -
- + return ( +
+ {this.value && this.renderUploader( + [ + , -
- ))} - {paddingImg && ( -
- - + ], + true + )} + + {this.paddingImg && this.renderUploader( + [ + , + this.renderMask() + ], + !this.uploadFail + )} + + {!this.value && !this.paddingImg && this.renderUploader( +
+
)}
); - - return ( -
- -
- {paddingImg ? ( -
正在上传...
- ) : ( - [ - , - {this.value ? '重拍' : '拍照'} 或 , - , - {this.value ? '重新选择照片' : '选择照片'} - ] - )} -
-
- {ImageList} -
- ); } }); diff --git a/src/sku/components/SkuMessages.js b/src/sku/components/SkuMessages.js index 180897207..c1e22173d 100644 --- a/src/sku/components/SkuMessages.js +++ b/src/sku/components/SkuMessages.js @@ -127,6 +127,7 @@ export default createComponent({ {this.messages.map((message, index) => (message.type === 'image' ? ( ) { return ( -
-
{props.skuRow.k}:
+
+
{props.skuRow.k}
{slots.default && slots.default()}
); diff --git a/src/sku/components/SkuRowItem.js b/src/sku/components/SkuRowItem.js index cade376fa..6b464568e 100644 --- a/src/sku/components/SkuRowItem.js +++ b/src/sku/components/SkuRowItem.js @@ -37,6 +37,7 @@ export default createComponent({ render(h) { const choosed = this.skuValue.id === this.selectedSku[this.skuKeyStr]; + const imgUrl = this.skuValue.imgUrl || this.skuValue.img_url; return ( - {this.skuValue.name} + {imgUrl && } + {this.skuValue.name} ); } diff --git a/src/sku/components/SkuStepper.js b/src/sku/components/SkuStepper.js index 0f519194c..e2f1cbef8 100644 --- a/src/sku/components/SkuStepper.js +++ b/src/sku/components/SkuStepper.js @@ -7,14 +7,11 @@ const { QUOTA_LIMIT, STOCK_LIMIT } = LIMIT_TYPE; export default createComponent({ props: { - hideStock: Boolean, - selectedSku: Object, + stock: Number, skuEventBus: Object, skuStockNum: Number, selectedNum: Number, stepperTitle: String, - hideQuotaText: Boolean, - selectedSkuComb: Object, disableStepperInput: Boolean, customStepperConfig: Object, quota: { @@ -48,40 +45,6 @@ export default createComponent({ }, computed: { - stock() { - const { stockNum } = this.customStepperConfig; - if (stockNum !== undefined) { - return stockNum; - } - if (this.selectedSkuComb) { - return this.selectedSkuComb.stock_num; - } - return this.skuStockNum; - }, - - stockText() { - const { stockFormatter } = this.customStepperConfig; - if (stockFormatter) return stockFormatter(this.stock); - - return `剩余${this.stock}件`; - }, - - quotaText() { - const { quotaText, hideQuotaText } = this.customStepperConfig; - - if (hideQuotaText) return ''; - - let text = ''; - - if (quotaText) { - text = quotaText; - } else if (this.quota > 0) { - text = `每人限购${this.quota}件`; - } - - return text; - }, - stepperLimit() { const quotaLimit = this.quota - this.quotaUsed; let limit; @@ -125,7 +88,7 @@ export default createComponent({ return (
-
{this.stepperTitle || '购买数量'}:
+
{this.stepperTitle || '购买数量'}
- {!this.hideStock &&
{this.stockText}
} - {!this.hideQuotaText && this.quotaText && ( -
{this.quotaText}
- )}
); } diff --git a/src/sku/index.js b/src/sku/index.js index 76ad3e005..7e739e622 100644 --- a/src/sku/index.js +++ b/src/sku/index.js @@ -1,6 +1,7 @@ import Sku from './Sku'; import SkuActions from './components/SkuActions'; import SkuHeader from './components/SkuHeader'; +import SkuHeaderItem from './components/SkuHeaderItem'; import SkuMessages from './components/SkuMessages'; import SkuStepper from './components/SkuStepper'; import SkuRow from './components/SkuRow'; @@ -10,6 +11,7 @@ import constants from './constants'; Sku.SkuActions = SkuActions; Sku.SkuHeader = SkuHeader; +Sku.SkuHeaderItem = SkuHeaderItem; Sku.SkuMessages = SkuMessages; Sku.SkuStepper = SkuStepper; Sku.SkuRow = SkuRow; diff --git a/src/sku/index.less b/src/sku/index.less index 6baa0ce2d..43dd1b61a 100644 --- a/src/sku/index.less +++ b/src/sku/index.less @@ -3,6 +3,10 @@ .van-sku { &-container { + display: flex; + flex-direction: column; + align-items: stretch; + height: 70%; max-height: max-content; /* avoid androiod keyboard cover fields */ overflow-y: visible; font-size: 14px; @@ -10,6 +14,7 @@ } &-body { + flex: 1 1 auto; max-height: 350px; overflow-y: scroll; -webkit-overflow-scrolling: touch; @@ -26,11 +31,11 @@ &__img-wrap { position: relative; float: left; - width: 80px; - height: 80px; - margin-top: -10px; + width: 96px; + height: 96px; + margin: 12px 0 12px; background: @background-color; - border-radius: 2px; + border-radius: 4px; img { position: absolute; @@ -45,37 +50,51 @@ } &__goods-info { - box-sizing: border-box; - min-height: 82px; - padding: 10px 60px 10px 10px; + min-height: 96px; + padding: 12px 36px 12px 8px; overflow: hidden; } } - &__goods-name { + &-header-item { + margin-top: 8px; + color: @gray-dark; font-size: 12px; + line-height: 16px; } &__price-symbol { - vertical-align: middle; + font-size: 16px; + vertical-align: text-bottom; } &__price-num { - font-size: 16px; + font-weight: 500; + font-size: 22px; vertical-align: middle; } &__goods-price { - margin-top: 10px; color: @red; + } + + &__price-tag { + display: inline-block; + margin-left: 8px; + padding: 0 5px; + color: @red; + font-size: 12px; + line-height: 16px; vertical-align: middle; + background-color: @sku-price-tag-color; + border-radius: 8px; } &__close-icon { position: absolute; - top: 10px; + top: 12px; right: 15px; - color: @gray-dark; + color: @sku-icon-gray-color; font-size: 20px; text-align: center; } @@ -93,40 +112,53 @@ /* sku row */ &-row { - margin: 0 15px 10px 0; + margin: 0 3px 12px 0; &:last-child { margin-bottom: 0; } &__title { - padding-bottom: 10px; + padding-bottom: 12px; } &__item { - display: inline-block; - box-sizing: border-box; - min-width: 52px; - height: 28px; - margin: 0 10px 10px 0; - padding: 5px 9px; + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 40px; + margin: 0 12px 12px 0; color: @text-color; - font-size: 12px; + font-size: 13px; line-height: 16px; - text-align: center; - border: 1px solid @gray-dark; - border-radius: 3px; + vertical-align: middle; + background: @sku-item-background-color; + border-radius: 4px; + + &-img { + width: 24px; + height: 24px; + margin: 4px 0 4px 4px; + object-fit: cover; + border-radius: 2px; + } + + &-name { + padding: 8px; + } &--active { - color: @white; - background: @red; - border-color: @red; + color: @red; + background: @sku-item-active-background-color; } &--disabled { color: @gray; background: @active-color; - border-color: @border-color; + + .van-sku-row__item-img { + opacity: .3; + } } } } @@ -176,6 +208,7 @@ } .van-cell__value { + overflow: visible; text-align: left; } } @@ -184,35 +217,23 @@ &-img-uploader { display: inline-block; - &__header { - padding: 0 10px; - color: @text-color; - font-size: 12px; - line-height: 24px; - border: 1px solid @border-color; - border-radius: 3px; - - .van-icon { - top: 3px; - margin-right: 5px; - font-size: 14px; - } + &__uploader { + vertical-align: middle; } &__img { position: relative; float: left; - width: 60px; - height: 60px; - margin: 10px 10px 0 0; - border: 1px solid @border-color; + width: 64px; + height: 64px; + margin-right: 8px; + background: @sku-item-background-color; + border-radius: 2px; img { - position: relative; - top: 50%; - max-width: 100%; - max-height: 100%; - transform: translateY(-50%); + width: 100%; + height: 100%; + object-fit: contain; } } @@ -222,7 +243,8 @@ right: -14px; z-index: 1; padding: 6px; - color: @red; + color: @sku-upload-mask-color; + opacity: .8; &::before { background-color: @white; @@ -230,15 +252,33 @@ } } - &__uploading { + &__mask { position: absolute; top: 0; - right: 0; - bottom: 0; left: 0; - width: 20px; - height: 20px; - margin: auto; + 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: 12px; + line-height: 14px; + } + + &__trigger { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + color: @sku-icon-gray-color; } } diff --git a/src/style/var.less b/src/style/var.less index 3797fa412..6757ebde4 100644 --- a/src/style/var.less +++ b/src/style/var.less @@ -651,3 +651,10 @@ @uploader-file-icon-color: @gray-darker; @uploader-file-name-font-size: @font-size-sm; @uploader-file-name-text-color: @gray-darker; + +// Sku +@sku-price-tag-color: rgba(227, 20, 54, .1); +@sku-item-background-color: #f7f8fa; +@sku-item-active-background-color: rgba(227, 20, 54, .1); +@sku-icon-gray-color: #dcdde0; +@sku-upload-mask-color: rgba(50, 50, 51, .8);