[improvement] Sku: jsx (#2666)

This commit is contained in:
neverland
2019-02-01 21:50:47 +08:00
committed by GitHub
parent 7ee383129c
commit 3958a42484
13 changed files with 458 additions and 526 deletions
+32
View File
@@ -0,0 +1,32 @@
import { use } from '../../utils';
import Button from '../../button';
const [sfc, bem] = use('sku-actions');
export default sfc({
props: {
buyText: String,
skuEventBus: Object,
showAddCartBtn: Boolean
},
render(h) {
const emit = name => () => {
this.skuEventBus.$emit('sku:addCart');
};
return (
<div class={bem()}>
{this.showAddCartBtn && (
<Button bottomAction text="加入购物车" onClick={emit('sku:addCart')} />
)}
<Button
type="primary"
bottomAction
text={this.buyText || '立即购买'}
onClick={emit('sku:buy')}
/>
</div>
);
}
});
-35
View File
@@ -1,35 +0,0 @@
<template>
<div :class="b()">
<van-button
v-if="showAddCartBtn"
bottom-action
text="加入购物车"
@click="skuEventBus.$emit('sku:addCart')"
/>
<van-button
type="primary"
bottom-action
:text="buyText || '立即购买'"
@click="skuEventBus.$emit('sku:buy')"
/>
</div>
</template>
<script>
import VanButton from '../../button';
import create from '../../utils/create';
export default create({
name: 'sku-actions',
components: {
VanButton
},
props: {
buyText: String,
skuEventBus: Object,
showAddCartBtn: Boolean
}
});
</script>
+61
View File
@@ -0,0 +1,61 @@
import { use } from '../../utils';
import Icon from '../../icon';
const [sfc, bem] = use('sku-header');
export default sfc({
props: {
sku: Object,
goods: Object,
skuEventBus: Object,
selectedSku: Object
},
computed: {
goodsImg() {
const s1Id = this.selectedSku.s1;
const skuImg = this.getSkuImg(s1Id);
// 优先使用选中 sku 的图片
return skuImg || this.goods.picture;
}
},
methods: {
getSkuImg(id) {
if (!id) return;
// skuImg 挂载在 skuTree 中 s1 上
const treeItem = this.sku.tree.filter(item => item.k_s === 's1')[0] || {};
if (!treeItem.v) return;
const matchedSku = treeItem.v.filter(skuValue => skuValue.id === id)[0];
if (matchedSku) return matchedSku.imgUrl || matchedSku.img_url;
},
previewImage() {
this.skuEventBus.$emit('sku:previewImage', this.goodsImg);
}
},
render(h) {
return (
<div class={[bem(), 'van-hairline--bottom']}>
<div class={bem('img-wrap')} onClick={this.previewImage}>
<img src={this.goodsImg} />
</div>
<div class={bem('goods-info')}>
<div class="van-sku__goods-name van-ellipsis">{this.goods.title}</div>
{this.$slots.default}
<Icon
name="close"
class="van-sku__close-icon"
onClick={() => {
this.skuEventBus.$emit('sku:close');
}}
/>
</div>
</div>
);
}
});
-68
View File
@@ -1,68 +0,0 @@
<template>
<div
:class="b()"
class="van-hairline--bottom"
>
<div
:class="b('img-wrap')"
@click="previewImage"
>
<img :src="goodsImg">
</div>
<div :class="b('goods-info')">
<div
v-text="goods.title"
class="van-sku__goods-name van-ellipsis"
/>
<!-- price display area -->
<slot />
<icon
name="close"
class="van-sku__close-icon"
@click="skuEventBus.$emit('sku:close')"
/>
</div>
</div>
</template>
<script>
import create from '../../utils/create';
export default create({
name: 'sku-header',
props: {
sku: Object,
goods: Object,
skuEventBus: Object,
selectedSku: Object
},
computed: {
goodsImg() {
const s1Id = this.selectedSku.s1;
const skuImg = this.getSkuImg(s1Id);
// 优先使用选中sku的图片
return skuImg || this.goods.picture;
}
},
methods: {
getSkuImg(id) {
if (!id) return;
// 目前skuImg都挂载在skuTree中s1那类sku上
const treeItem = this.sku.tree.filter(item => item.k_s === 's1')[0] || {};
if (!treeItem.v) return;
const matchedSku = treeItem.v.filter(skuValue => skuValue.id === id)[0];
if (matchedSku) return (matchedSku.imgUrl || matchedSku.img_url);
},
previewImage() {
this.skuEventBus.$emit('sku:previewImage', this.goodsImg);
}
}
});
</script>
+103
View File
@@ -0,0 +1,103 @@
import { use } from '../../utils';
import Icon from '../../icon';
import Loading from '../../loading';
import Uploader from '../../uploader';
const [sfc, bem] = use('sku-img-uploader');
export default sfc({
props: {
value: String,
uploadImg: Function,
maxSize: {
type: Number,
default: 6
}
},
data() {
return {
// 正在上传的图片 base64
paddingImg: ''
};
},
computed: {
imgList() {
return this.value && !this.paddingImg ? [this.value] : [];
}
},
methods: {
afterReadFile(file) {
// 上传文件
this.paddingImg = file.content;
this.uploadImg(file.file, file.content)
.then(img => {
this.$emit('input', img);
this.$nextTick(() => {
this.paddingImg = '';
});
})
.catch(() => {
this.paddingImg = '';
});
},
onOversize() {
this.$toast(`最大可上传图片为${this.maxSize}MB,请尝试压缩图片尺寸`);
}
},
render(h) {
const { imgList, paddingImg } = this;
const ImageList = (paddingImg || imgList.length > 0) && (
<div class="van-clearfix">
{imgList.map(img => (
<div class={bem('img')}>
<img src={img} />
<Icon
name="clear"
class={bem('delete')}
onClick={() => {
this.$emit('input', '');
}}
/>
</div>
))}
{paddingImg && (
<div class={bem('img')}>
<img src={paddingImg} />
<Loading type="spinner" class={bem('uploading')} />
</div>
)}
</div>
);
return (
<div class={bem()}>
<Uploader
disabled={!!paddingImg}
afterRead={this.afterReadFile}
maxSize={this.maxSize * 1024 * 1024}
onOversize={this.onOversize}
>
<div class={bem('header')}>
{paddingImg ? (
<div>正在上传...</div>
) : (
[
<Icon name="photograph" />,
<span class="label">{this.value ? '重拍' : '拍照'} </span>,
<Icon name="photo" />,
<span class="label">{this.value ? '重新选择照片' : '选择照片'}</span>
]
)}
</div>
</Uploader>
{ImageList}
</div>
);
}
});
-104
View File
@@ -1,104 +0,0 @@
<template>
<div :class="b()">
<!-- 头部 -->
<van-uploader
:disabled="!!paddingImg"
:after-read="afterReadFile"
:max-size="maxSize * 1024 * 1024"
@oversize="onOversize"
>
<div :class="b('header')">
<div v-if="paddingImg">正在上传...</div>
<template v-else>
<icon name="photograph" />
<span class="label">{{ value ? '重拍' : '拍照' }}</span>
<icon name="photo" />
<span class="label">{{ value ? '重新选择照片' : '选择照片' }}</span>
</template>
</div>
</van-uploader>
<!-- 图片列表区域 -->
<div
v-if="paddingImg || imgList.length > 0"
class="van-clearfix"
>
<!-- 已有的图片,图片右上角显示删除按钮 -->
<div
v-for="img in imgList"
:class="b('img')"
>
<img :src="img">
<icon
name="clear"
:class="b('delete')"
@click="$emit('input', '')"
/>
</div>
<!-- 正在上传的图片,有上传等待提示 -->
<div
v-if="paddingImg"
:class="b('img')"
>
<img :src="paddingImg">
<loading
type="spinner"
:class="b('uploading')"
/>
</div>
</div>
</div>
</template>
<script>
import VanUploader from '../../uploader';
import create from '../../utils/create';
export default create({
name: 'sku-img-uploader',
components: {
VanUploader
},
props: {
value: String,
uploadImg: Function,
maxSize: {
type: Number,
default: 6
}
},
data() {
return {
// 正在上传的图片base 64
paddingImg: ''
};
},
computed: {
imgList() {
return this.value && !this.paddingImg ? [this.value] : [];
}
},
methods: {
afterReadFile(file) {
// 上传文件
this.paddingImg = file.content;
this.uploadImg(file.file, file.content).then(img => {
this.$emit('input', img);
this.$nextTick(() => {
this.paddingImg = '';
});
}).catch(() => {
this.paddingImg = '';
});
},
onOversize() {
this.$toast(`最大可上传图片为${this.maxSize}MB,请尝试压缩图片尺寸`);
}
}
});
</script>
@@ -1,41 +1,13 @@
<template>
<cell-group :class="b()">
<template v-for="(message, index) in messages">
<cell
v-if="message.type === 'image'"
:class="b('image-cell')"
label="仅限一张"
:key="`${goodsId}-${index}`"
:required="message.required == '1'"
:title="message.name"
>
<sku-img-uploader
v-model="messageValues[index].value"
:upload-img="messageConfig.uploadImg"
:max-size="messageConfig.uploadMaxSize"
/>
</cell>
<field
v-else
v-model="messageValues[index].value"
maxlength="200"
:key="`${goodsId}-${index}`"
:required="message.required == '1'"
:label="message.name"
:placeholder="getPlaceholder(message)"
:type="getType(message)"
/>
</template>
</cell-group>
</template>
<script>
import create from '../../utils/create';
import { use } from '../../utils';
import Cell from '../../cell';
import CellGroup from '../../cell-group';
import Field from '../../field';
import validateEmail from '../../utils/validate/email';
import validateNumber from '../../utils/validate/number';
import SkuImgUploader from './SkuImgUploader';
const [sfc, bem] = use('sku-messages');
const PLACEHOLDER = {
id_no: '输入身份证号码',
text: '输入文本',
@@ -47,14 +19,7 @@ const PLACEHOLDER = {
mobile: '输入手机号码'
};
export default create({
name: 'sku-messages',
components: {
SkuImgUploader,
Field
},
export default sfc({
props: {
messages: Array,
messageConfig: Object,
@@ -73,12 +38,6 @@ export default create({
}
},
computed: {
messagePlaceholderMap() {
return this.messageConfig.placeholderMap || {};
}
},
methods: {
resetMessageValues(messages) {
return (messages || []).map(() => ({ value: '' }));
@@ -125,7 +84,8 @@ export default create({
getPlaceholder(message) {
const type = +message.multiple === 1 ? 'textarea' : message.type;
return this.messagePlaceholderMap[type] || PLACEHOLDER[type];
const map = this.messageConfig.placeholderMap || {};
return map[type] || PLACEHOLDER[type];
},
validateMessages() {
@@ -137,7 +97,7 @@ export default create({
if (value === '') {
// 必填字段的校验
if (message.required == '1') { // eslint-disable-line
if (String(message.required) === '1') { // eslint-disable-line
const textType = message.type === 'image'
? '请上传'
: '请填写';
@@ -159,6 +119,37 @@ export default create({
}
}
}
},
render(h) {
return (
<CellGroup class={bem()}>
{this.messages.map((message, index) => (message.type === 'image' ? (
<Cell
class={bem('image-cell')}
label="仅限一张"
title={message.name}
key={`${this.goodsId}-${index}`}
required={String(message.required) === '1'}
>
<SkuImgUploader
vModel={this.messageValues[index].value}
uploadImg={this.messageConfig.uploadImg}
maxSize={this.messageConfig.uploadMaxSize}
/>
</Cell>
) : (
<Field
vModel={this.messageValues[index].value}
maxlength="200"
label={message.name}
key={`${this.goodsId}-${index}`}
required={String(message.required) === '1'}
placeholder={this.getPlaceholder(message)}
type={this.getType(message)}
/>
)))}
</CellGroup>
);
}
});
</script>
+18
View File
@@ -0,0 +1,18 @@
import { use } from '../../utils';
const [sfc, bem] = use('sku-row');
export default sfc({
props: {
skuRow: Object
},
render(h) {
return (
<div class={bem()}>
<div class={bem('title')}>{this.skuRow.k}</div>
{this.$slots.default}
</div>
);
}
});
-18
View File
@@ -1,18 +0,0 @@
<template>
<div :class="b()">
<div :class="b('title')">{{ skuRow.k }}</div>
<slot />
</div>
</template>
<script>
import create from '../../utils/create';
export default create({
name: 'sku-row',
props: {
skuRow: Object
}
});
</script>
+53
View File
@@ -0,0 +1,53 @@
import { use } from '../../utils';
import { isSkuChoosable } from '../utils/skuHelper';
const [sfc] = use('sku-row-item');
export default sfc({
props: {
skuList: Array,
skuValue: Object,
skuKeyStr: String,
skuEventBus: Object,
selectedSku: Object
},
computed: {
choosable() {
return isSkuChoosable(this.skuList, this.selectedSku, {
key: this.skuKeyStr,
valueId: this.skuValue.id
});
}
},
methods: {
onSelect() {
if (this.choosable) {
this.skuEventBus.$emit('sku:select', {
...this.skuValue,
skuKeyStr: this.skuKeyStr
});
}
}
},
render(h) {
const choosed = this.skuValue.id === this.selectedSku[this.skuKeyStr];
return (
<span
class={[
'van-sku-row__item',
{
'van-sku-row__item--active': choosed,
'van-sku-row__item--disabled': !this.choosable
}
]}
onClick={this.onSelect}
>
{this.skuValue.name}
</span>
);
}
});
-52
View File
@@ -1,52 +0,0 @@
<template>
<span
v-text="skuValue.name"
class="van-sku-row__item"
:class="{
'van-sku-row__item--active': isChoosed,
'van-sku-row__item--disabled': !isChoosable
}"
@click="onSelect"
/>
</template>
<script>
import create from '../../utils/create';
import { isSkuChoosable } from '../utils/skuHelper';
export default create({
name: 'sku-row-item',
props: {
skuEventBus: Object,
skuValue: Object,
skuList: Array,
selectedSku: Object,
skuKeyStr: String
},
computed: {
isChoosed() {
return this.skuValue.id === this.selectedSku[this.skuKeyStr];
},
isChoosable() {
return isSkuChoosable(this.skuList, this.selectedSku, {
key: this.skuKeyStr,
valueId: this.skuValue.id
});
}
},
methods: {
onSelect() {
if (this.isChoosable) {
this.skuEventBus.$emit('sku:select', {
...this.skuValue,
skuKeyStr: this.skuKeyStr
});
}
}
}
});
</script>
@@ -1,55 +1,22 @@
<template>
<div class="van-sku-stepper-stock">
<div class="van-sku-stepper-container">
<div class="van-sku__stepper-title">{{ stepperTitle || '购买数量' }}</div>
<stepper
class="van-sku__stepper"
v-model="currentNum"
:min="1"
:max="stepperLimit"
:disable-input="disableStepperInput"
@overlimit="onOverLimit"
@change="onChange"
/>
</div>
<div
v-if="!hideStock"
v-text="stockText"
class="van-sku__stock"
/>
<div
v-if="!hideQuotaText && quotaText"
v-text="quotaText"
class="van-sku__quota"
/>
</div>
</template>
<script>
import create from '../../utils/create';
import { use } from '../../utils';
import Stepper from '../../stepper';
import { LIMIT_TYPE } from '../constants';
const [sfc] = use('sku-stepper');
const { QUOTA_LIMIT, STOCK_LIMIT } = LIMIT_TYPE;
export default create({
name: 'sku-stepper',
components: {
Stepper
},
export default sfc({
props: {
quota: Number,
hideQuotaText: Boolean,
quotaUsed: Number,
hideStock: Boolean,
skuEventBus: Object,
skuStockNum: Number,
selectedSku: Object,
selectedSkuComb: Object,
selectedNum: Number,
stepperTitle: String,
hideQuotaText: Boolean,
selectedSkuComb: Object,
disableStepperInput: Boolean,
customStepperConfig: Object
},
@@ -142,6 +109,27 @@ export default create({
handleStepperChange && handleStepperChange(currentValue);
this.$emit('change', currentValue);
}
},
render(h) {
return (
<div class="van-sku-stepper-stock">
<div class="van-sku-stepper-container">
<div class="van-sku__stepper-title">{this.stepperTitle || '购买数量'}</div>
<Stepper
vModel={this.currentNum}
class="van-sku__stepper"
max={this.stepperLimit}
disableInput={this.disableStepperInput}
onOverlimit={this.onOverLimit}
onChange={this.onChange}
/>
</div>
{!this.hideStock && <div class="van-sku__stock">{this.stockText}</div>}
{!this.hideQuotaText && this.quotaText && (
<div class="van-sku__quota">{this.quotaText}</div>
)}
</div>
);
}
});
</script>