feat(Sku): add properties support (#5525)

This commit is contained in:
Waiter 2020-01-09 16:49:55 +08:00 committed by neverland
parent c6489f4006
commit 589d66d68a
9 changed files with 261 additions and 28 deletions

View File

@ -6,11 +6,12 @@ import SkuHeader from './components/SkuHeader';
import SkuHeaderItem from './components/SkuHeaderItem'; import SkuHeaderItem from './components/SkuHeaderItem';
import SkuRow from './components/SkuRow'; import SkuRow from './components/SkuRow';
import SkuRowItem from './components/SkuRowItem'; import SkuRowItem from './components/SkuRowItem';
import SkuRowPropItem from './components/SkuRowPropItem';
import SkuStepper from './components/SkuStepper'; import SkuStepper from './components/SkuStepper';
import SkuMessages from './components/SkuMessages'; import SkuMessages from './components/SkuMessages';
import SkuActions from './components/SkuActions'; import SkuActions from './components/SkuActions';
import { createNamespace, isDef } from '../utils'; import { createNamespace, isDef } from '../utils';
import { isAllSelected, isSkuChoosable, getSkuComb, getSelectedSkuValues } from './utils/skuHelper'; import { isAllSelected, isSkuChoosable, getSkuComb, getSelectedSkuValues, getSelectedPropValues } from './utils/skuHelper';
import { LIMIT_TYPE, UNSELECTED_SKU_VALUE_ID } from './constants'; import { LIMIT_TYPE, UNSELECTED_SKU_VALUE_ID } from './constants';
const namespace = createNamespace('sku'); const namespace = createNamespace('sku');
@ -86,6 +87,7 @@ export default createComponent({
data() { data() {
return { return {
selectedSku: {}, selectedSku: {},
selectedProp: {},
selectedNum: 1, selectedNum: 1,
show: this.value show: this.value
}; };
@ -147,7 +149,15 @@ export default createComponent({
}, },
isSkuCombSelected() { isSkuCombSelected() {
return isAllSelected(this.sku.tree, this.selectedSku); // SKU 未选完
if (this.hasSku && !isAllSelected(this.skuTree, this.selectedSku)) {
return false;
}
// 属性未全选
if (this.propList.some(it => (this.selectedProp[it.k_id] || []).length < 1)) {
return false;
}
return true;
}, },
isSkuEmpty() { isSkuEmpty() {
@ -158,27 +168,41 @@ export default createComponent({
return !this.sku.none_sku; return !this.sku.none_sku;
}, },
hasSkuOrAttr() {
return this.hasSku || this.propList.length > 0;
},
selectedSkuComb() { selectedSkuComb() {
if (!this.hasSku) { let skuComb = null;
return {
id: this.sku.collection_id,
price: Math.round(this.sku.price * 100),
stock_num: this.sku.stock_num
};
}
if (this.isSkuCombSelected) { if (this.isSkuCombSelected) {
return getSkuComb(this.sku.list, this.selectedSku); if (this.hasSku) {
skuComb = getSkuComb(this.sku.list, this.selectedSku);
} else {
skuComb = {
id: this.sku.collection_id,
price: Math.round(this.sku.price * 100),
stock_num: this.sku.stock_num
};
}
if (skuComb) {
skuComb.properties = this.selectedPropValues;
skuComb.property_price = this.selectedPropValues.reduce((acc, cur) => acc + (cur.price || 0), 0);
}
} }
return null; return skuComb;
}, },
selectedSkuValues() { selectedSkuValues() {
return getSelectedSkuValues(this.skuTree, this.selectedSku); return getSelectedSkuValues(this.skuTree, this.selectedSku);
}, },
selectedPropValues() {
return getSelectedPropValues(this.propList, this.selectedProp);
},
price() { price() {
if (this.selectedSkuComb) { if (this.selectedSkuComb) {
return (this.selectedSkuComb.price / 100).toFixed(2); return ((this.selectedSkuComb.price + this.selectedSkuComb.property_price) / 100).toFixed(2);
} }
// sku.price是一个格式化好的价格区间 // sku.price是一个格式化好的价格区间
return this.sku.price; return this.sku.price;
@ -186,7 +210,7 @@ export default createComponent({
originPrice() { originPrice() {
if (this.selectedSkuComb && this.selectedSkuComb.origin_price) { if (this.selectedSkuComb && this.selectedSkuComb.origin_price) {
return (this.selectedSkuComb.origin_price / 100).toFixed(2); return ((this.selectedSkuComb.origin_price + this.selectedSkuComb.property_price) / 100).toFixed(2);
} }
return this.sku.origin_price; return this.sku.origin_price;
}, },
@ -195,6 +219,10 @@ export default createComponent({
return this.sku.tree || []; return this.sku.tree || [];
}, },
propList() {
return this.sku.properties || [];
},
imageList() { imageList() {
const imageList = [this.goods.picture]; const imageList = [this.goods.picture];
@ -242,15 +270,18 @@ export default createComponent({
selectedText() { selectedText() {
if (this.selectedSkuComb) { if (this.selectedSkuComb) {
return `${t('selected')} ${this.selectedSkuValues.map(item => item.name).join('')}`; const values = this.selectedSkuValues.concat(this.selectedPropValues);
return `${t('selected')} ${values.map(item => item.name).join('')}`;
} }
const unselected = this.skuTree const unselectedSku = this.skuTree
.filter(item => this.selectedSku[item.k_s] === UNSELECTED_SKU_VALUE_ID) .filter(item => this.selectedSku[item.k_s] === UNSELECTED_SKU_VALUE_ID)
.map(item => item.k) .map(item => item.k);
.join(''); const unselectedProp = this.propList
.filter(item => (this.selectedProp[item.k_id] || []).length < 1)
.map(item => item.k);
return `${t('select')} ${unselected}`; return `${t('select')} ${unselectedSku.concat(unselectedProp).join('')}`;
} }
}, },
@ -259,6 +290,7 @@ export default createComponent({
this.skuEventBus = skuEventBus; this.skuEventBus = skuEventBus;
skuEventBus.$on('sku:select', this.onSelect); skuEventBus.$on('sku:select', this.onSelect);
skuEventBus.$on('sku:propSelect', this.onPropSelect);
skuEventBus.$on('sku:numChange', this.onNumChange); skuEventBus.$on('sku:numChange', this.onNumChange);
skuEventBus.$on('sku:previewImage', this.onPreviewImage); skuEventBus.$on('sku:previewImage', this.onPreviewImage);
skuEventBus.$on('sku:overLimit', this.onOverLimit); skuEventBus.$on('sku:overLimit', this.onOverLimit);
@ -321,6 +353,27 @@ export default createComponent({
}); });
}); });
} }
// 重置商品属性
this.selectedProp = {};
const { selectedProp = {} } = this.initialSku;
// 只有一个属性值时,默认选中,且选中外部传入信息
this.propList.forEach(item => {
if (item.v && item.v.length === 1) {
this.selectedProp[item.k_id] = [item.v[0].id];
} else if (selectedProp[item.k_id]) {
this.selectedProp[item.k_id] = selectedProp[item.k_id];
}
});
const propValues = this.selectedPropValues;
if (propValues.length > 0) {
this.$emit('sku-prop-selected', {
propValue: propValues[propValues.length - 1],
selectedProp: this.selectedProp,
selectedSkuComb: this.selectedSkuComb,
});
}
}, },
getSkuMessages() { getSkuMessages() {
@ -363,7 +416,28 @@ export default createComponent({
this.$emit('sku-selected', { this.$emit('sku-selected', {
skuValue, skuValue,
selectedSku: this.selectedSku, selectedSku: this.selectedSku,
selectedSkuComb: this.selectedSkuComb selectedSkuComb: this.selectedSkuComb,
});
},
onPropSelect(propValue) {
const arr = this.selectedProp[propValue.skuKeyStr] || [];
const pos = arr.indexOf(propValue.id);
if (pos > -1) {
arr.splice(pos, 1);
} else if (propValue.multiple) {
arr.push(propValue.id);
} else {
arr.splice(0, 1, propValue.id);
}
this.selectedProp = {
...this.selectedProp,
[propValue.skuKeyStr]: arr,
};
this.$emit('sku-prop-selected', {
propValue,
selectedProp: this.selectedProp,
selectedSkuComb: this.selectedSkuComb,
}); });
}, },
@ -476,6 +550,7 @@ export default createComponent({
originPrice, originPrice,
skuEventBus, skuEventBus,
selectedSku, selectedSku,
selectedProp,
selectedNum, selectedNum,
stepperTitle, stepperTitle,
selectedSkuComb selectedSkuComb
@ -510,7 +585,7 @@ export default createComponent({
<span class="van-sku__stock">{this.stockText}</span> <span class="van-sku__stock">{this.stockText}</span>
</SkuHeaderItem> </SkuHeaderItem>
)} )}
{this.hasSku && !this.hideSelectedText && ( {this.hasSkuOrAttr && !this.hideSelectedText && (
<SkuHeaderItem>{this.selectedText}</SkuHeaderItem> <SkuHeaderItem>{this.selectedText}</SkuHeaderItem>
)} )}
{slots('sku-header-extra')} {slots('sku-header-extra')}
@ -519,7 +594,7 @@ export default createComponent({
const Group = const Group =
slots('sku-group') || slots('sku-group') ||
(this.hasSku && ( (this.hasSkuOrAttr && (
<div class={this.skuGroupClass}> <div class={this.skuGroupClass}>
{this.skuTree.map(skuTreeItem => ( {this.skuTree.map(skuTreeItem => (
<SkuRow skuRow={skuTreeItem}> <SkuRow skuRow={skuTreeItem}>
@ -534,6 +609,19 @@ export default createComponent({
))} ))}
</SkuRow> </SkuRow>
))} ))}
{this.propList.map(skuTreeItem => (
<SkuRow skuRow={skuTreeItem}>
{skuTreeItem.v.map(skuValue => (
<SkuRowPropItem
skuValue={skuValue}
skuKeyStr={skuTreeItem.k_id + ''}
selectedProp={selectedProp}
skuEventBus={skuEventBus}
multiple={skuTreeItem.is_multiple}
/>
))}
</SkuRow>
))}
</div> </div>
)); ));

View File

@ -11,7 +11,7 @@ export type SkuRowProps = {
skuRow: SkuTreeItemData; skuRow: SkuTreeItemData;
}; };
const [createComponent, bem] = createNamespace('sku-row'); const [createComponent, bem, t] = createNamespace('sku-row');
function SkuRow( function SkuRow(
h: CreateElement, h: CreateElement,
@ -19,9 +19,12 @@ function SkuRow(
slots: DefaultSlots, slots: DefaultSlots,
ctx: RenderContext<SkuRowProps> ctx: RenderContext<SkuRowProps>
) { ) {
const multipleNode = props.skuRow.is_multiple && (
<span class={bem('title-multiple')}>{t('multiple')}</span>
);
return ( return (
<div class={[bem(), BORDER_BOTTOM]} {...inherit(ctx)}> <div class={[bem(), BORDER_BOTTOM]} {...inherit(ctx)}>
<div class={bem('title')}>{props.skuRow.k}</div> <div class={bem('title')}>{props.skuRow.k}{multipleNode}</div>
{slots.default && slots.default()} {slots.default && slots.default()}
</div> </div>
); );

View File

@ -0,0 +1,40 @@
import { createNamespace } from '../../utils';
const [createComponent] = createNamespace('sku-row-prop-item');
export default createComponent({
props: {
skuValue: Object,
skuKeyStr: String,
skuEventBus: Object,
selectedProp: Object,
multiple: Boolean,
},
methods: {
onSelect() {
this.skuEventBus.$emit('sku:propSelect', {
...this.skuValue,
skuKeyStr: this.skuKeyStr,
multiple: this.multiple,
});
},
},
render() {
const choosed = this.selectedProp && (this.selectedProp[this.skuKeyStr] || []).indexOf(this.skuValue.id) > -1;
return (
<span
class={[
'van-sku-row__item',
{
'van-sku-row__item--active': choosed,
}
]}
onClick={this.onSelect}
>
<span class="van-sku-row__item-name">{this.skuValue.name}</span>
</span>
);
}
});

View File

@ -178,12 +178,68 @@ export const skuData = {
type: 'time', type: 'time',
required: 0 required: 0
} }
] ],
} properties: [
{
k_id: 123,
k: '加冰',
v: [
{
id: 1222,
name: '少冰',
price: 1,
},
{
id: 1223,
name: '去冰',
price: 1,
}
],
},
{
k_id: 133,
k: '不知道',
v: [
{
id: 1244,
name: '什么',
price: 9,
}
],
},
{
k_id: 124,
k: '加料',
is_multiple: true,
v: [
{
id: 1224,
name: '布丁',
price: 3,
},
{
id: 1225,
name: '波霸',
price: 4,
},
{
id: 1226,
name: '珍珠',
price: 5,
}
],
}
],
},
}; };
export const initialSku = { export const initialSku = {
s1: '30349', s1: '30349',
s2: '1193', s2: '1193',
selectedNum: 3 selectedNum: 3,
selectedProp: {
123: [1222],
133: [1244],
124: [1225, 1226],
},
}; };

View File

@ -8,6 +8,7 @@ import SkuMessages from './components/SkuMessages';
import SkuStepper from './components/SkuStepper'; import SkuStepper from './components/SkuStepper';
import SkuRow from './components/SkuRow'; import SkuRow from './components/SkuRow';
import SkuRowItem from './components/SkuRowItem'; import SkuRowItem from './components/SkuRowItem';
import SkuRowPropItem from './components/SkuRowPropItem';
import skuHelper from './utils/skuHelper'; import skuHelper from './utils/skuHelper';
import constants from './constants'; import constants from './constants';
@ -20,6 +21,7 @@ Sku.SkuMessages = SkuMessages;
Sku.SkuStepper = SkuStepper; Sku.SkuStepper = SkuStepper;
Sku.SkuRow = SkuRow; Sku.SkuRow = SkuRow;
Sku.SkuRowItem = SkuRowItem; Sku.SkuRowItem = SkuRowItem;
Sku.SkuRowPropItem = SkuRowPropItem;
Sku.skuHelper = skuHelper; Sku.skuHelper = skuHelper;
Sku.skuConstants = constants; Sku.skuConstants = constants;

View File

@ -130,6 +130,10 @@
padding-bottom: @padding-sm; padding-bottom: @padding-sm;
} }
&__title-multiple {
color: @gray-6;
}
&__item { &__item {
position: relative; position: relative;
display: inline-flex; display: inline-flex;

View File

@ -52,6 +52,9 @@ export default {
textarea: '点击填写段落文本', textarea: '点击填写段落文本',
mobile: '输入手机号码' mobile: '输入手机号码'
} }
} },
vanSkuRow: {
multiple: '可多选',
},
} }
}; };

View File

@ -42,6 +42,18 @@ export const normalizeSkuTree = skuTree => {
return normalizedTree; return normalizedTree;
}; };
export const normalizePropList = propList => {
const normalizedProp = {};
propList.forEach(item => {
const itemObj = {};
item.v.forEach(it => {
itemObj[it.id] = it;
});
normalizedProp[item.k_id] = itemObj;
});
return normalizedProp;
};
// 判断是否所有的sku都已经选中 // 判断是否所有的sku都已经选中
export const isAllSelected = (skuTree, selectedSku) => { export const isAllSelected = (skuTree, selectedSku) => {
// 筛选selectedSku对象中key值不为空的值 // 筛选selectedSku对象中key值不为空的值
@ -104,10 +116,21 @@ export const isSkuChoosable = (skuList, selectedSku, skuToChoose) => {
return stock > 0; return stock > 0;
}; };
export const getSelectedPropValues = (propList, selectedProp) => {
const normalizeProp = normalizePropList(propList);
return Object.keys(selectedProp).reduce((acc, cur) => {
selectedProp[cur].forEach(it => {
acc.push(normalizeProp[cur][it]);
});
return acc;
}, []);
};
export default { export default {
normalizeSkuTree, normalizeSkuTree,
getSkuComb, getSkuComb,
getSelectedSkuValues, getSelectedSkuValues,
isAllSelected, isAllSelected,
isSkuChoosable isSkuChoosable,
getSelectedPropValues,
}; };

14
types/sku.d.ts vendored
View File

@ -10,6 +10,7 @@ export type SkuData = {
tree: SkuTreeItemData[]; tree: SkuTreeItemData[];
list: SkuListItemData[]; list: SkuListItemData[];
messages: SkuMessageData[]; messages: SkuMessageData[];
properties: SkuPropItemData[];
}; };
export type SkuTreeItemData = { export type SkuTreeItemData = {
@ -26,6 +27,19 @@ export type SkuTreeItemValueData = {
previewImgUrl?: string; previewImgUrl?: string;
}; };
export type SkuPropItemData = {
k: string;
v: SkuPropItemValueData[];
k_id: number;
is_multiple?: boolean;
};
export type SkuPropItemValueData = {
id: string;
name: string;
price?: number;
};
export type SkuListItemData = { export type SkuListItemData = {
id: number; id: number;
s1: string; s1: string;