[improvement] rename packages dir to src (#3659)
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import { createNamespace } from '../../utils';
|
||||
import { inherit } from '../../utils/functional';
|
||||
import Button from '../../button';
|
||||
|
||||
// Types
|
||||
import Vue, { CreateElement, RenderContext } from 'vue/types';
|
||||
import { DefaultSlots } from '../../utils/types';
|
||||
|
||||
export type SkuActionsProps = {
|
||||
buyText?: string;
|
||||
skuEventBus: Vue;
|
||||
showAddCartBtn?: boolean;
|
||||
};
|
||||
|
||||
const [createComponent, bem] = createNamespace('sku-actions');
|
||||
|
||||
function SkuActions(
|
||||
h: CreateElement,
|
||||
props: SkuActionsProps,
|
||||
slots: DefaultSlots,
|
||||
ctx: RenderContext<SkuActionsProps>
|
||||
) {
|
||||
const createEmitter = (name: string) => () => {
|
||||
props.skuEventBus.$emit(name);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class={bem()} {...inherit(ctx)}>
|
||||
{props.showAddCartBtn && (
|
||||
<Button
|
||||
square
|
||||
size="large"
|
||||
type="warning"
|
||||
text="加入购物车"
|
||||
onClick={createEmitter('sku:addCart')}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
square
|
||||
size="large"
|
||||
type="danger"
|
||||
text={props.buyText || '立即购买'}
|
||||
onClick={createEmitter('sku:buy')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SkuActions.props = {
|
||||
buyText: String,
|
||||
skuEventBus: Object,
|
||||
showAddCartBtn: Boolean
|
||||
};
|
||||
|
||||
export default createComponent<SkuActionsProps>(SkuActions);
|
||||
@@ -0,0 +1,74 @@
|
||||
import { createNamespace } from '../../utils';
|
||||
import { inherit } from '../../utils/functional';
|
||||
import Icon from '../../icon';
|
||||
|
||||
// Types
|
||||
import Vue, { CreateElement, RenderContext } from 'vue/types';
|
||||
import { DefaultSlots } from '../../utils/types';
|
||||
import { SkuData, SkuGoodsData, SelectedSkuData } from '../type';
|
||||
|
||||
export type SkuHeaderProps = {
|
||||
sku: SkuData;
|
||||
goods: SkuGoodsData;
|
||||
skuEventBus: Vue;
|
||||
selectedSku: SelectedSkuData;
|
||||
};
|
||||
|
||||
const [createComponent, bem] = createNamespace('sku-header');
|
||||
|
||||
function getSkuImg(sku: SkuData, selectedSku: SelectedSkuData) {
|
||||
const id = selectedSku.s1;
|
||||
|
||||
if (id) {
|
||||
// skuImg 挂载在 skuTree 中 s1 上
|
||||
const treeItem = sku.tree.filter(item => item.k_s === 's1')[0] || {};
|
||||
if (treeItem.v) {
|
||||
const matchedSku = treeItem.v.filter(skuValue => skuValue.id === id)[0];
|
||||
if (matchedSku) {
|
||||
return matchedSku.imgUrl || matchedSku.img_url;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function SkuHeader(
|
||||
h: CreateElement,
|
||||
props: SkuHeaderProps,
|
||||
slots: DefaultSlots,
|
||||
ctx: RenderContext<SkuHeaderProps>
|
||||
) {
|
||||
const { sku, goods, skuEventBus, selectedSku } = props;
|
||||
const goodsImg = getSkuImg(sku, selectedSku) || goods.picture;
|
||||
|
||||
const previewImage = () => {
|
||||
skuEventBus.$emit('sku:previewImage', goodsImg);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class={[bem(), 'van-hairline--bottom']} {...inherit(ctx)}>
|
||||
<div class={bem('img-wrap')} onClick={previewImage}>
|
||||
<img src={goodsImg} />
|
||||
</div>
|
||||
<div class={bem('goods-info')}>
|
||||
<div class="van-sku__goods-name van-ellipsis">{goods.title}</div>
|
||||
{slots.default && slots.default()}
|
||||
<Icon
|
||||
name="close"
|
||||
class="van-sku__close-icon"
|
||||
onClick={() => {
|
||||
skuEventBus.$emit('sku:close');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SkuHeader.props = {
|
||||
sku: Object,
|
||||
goods: Object,
|
||||
skuEventBus: Object,
|
||||
selectedSku: Object
|
||||
};
|
||||
|
||||
export default createComponent<SkuHeaderProps>(SkuHeader);
|
||||
@@ -0,0 +1,103 @@
|
||||
import { createNamespace } from '../../utils';
|
||||
import Icon from '../../icon';
|
||||
import Loading from '../../loading';
|
||||
import Uploader from '../../uploader';
|
||||
|
||||
const [createComponent, bem] = createNamespace('sku-img-uploader');
|
||||
|
||||
export default createComponent({
|
||||
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>
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,155 @@
|
||||
import { createNamespace } from '../../utils';
|
||||
import Cell from '../../cell';
|
||||
import CellGroup from '../../cell-group';
|
||||
import Field from '../../field';
|
||||
import { isEmail } from '../../utils/validate/email';
|
||||
import { isNumber } from '../../utils/validate/number';
|
||||
import SkuImgUploader from './SkuImgUploader';
|
||||
|
||||
const [createComponent, bem] = createNamespace('sku-messages');
|
||||
|
||||
const PLACEHOLDER = {
|
||||
id_no: '输入身份证号码',
|
||||
text: '输入文本',
|
||||
tel: '输入数字',
|
||||
email: '输入邮箱',
|
||||
date: '点击选择日期',
|
||||
time: '点击选择时间',
|
||||
textarea: '点击填写段落文本',
|
||||
mobile: '输入手机号码'
|
||||
};
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
messages: Array,
|
||||
messageConfig: Object,
|
||||
goodsId: [Number, String]
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
messageValues: this.resetMessageValues(this.messages)
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
messages(val) {
|
||||
this.messageValues = this.resetMessageValues(val);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
resetMessageValues(messages) {
|
||||
return (messages || []).map(() => ({ value: '' }));
|
||||
},
|
||||
|
||||
getType(message) {
|
||||
if (+message.multiple === 1) {
|
||||
return 'textarea';
|
||||
}
|
||||
if (message.type === 'id_no') {
|
||||
return 'text';
|
||||
}
|
||||
return message.datetime > 0 ? 'datetime-local' : message.type;
|
||||
},
|
||||
|
||||
getMessages() {
|
||||
const messages = {};
|
||||
|
||||
this.messageValues.forEach((item, index) => {
|
||||
let { value } = item;
|
||||
if (this.messages[index].datetime > 0) {
|
||||
value = value.replace(/T/g, ' ');
|
||||
}
|
||||
messages[`message_${index}`] = value;
|
||||
});
|
||||
|
||||
return messages;
|
||||
},
|
||||
|
||||
getCartMessages() {
|
||||
const messages = {};
|
||||
|
||||
this.messageValues.forEach((item, index) => {
|
||||
let { value } = item;
|
||||
const message = this.messages[index];
|
||||
if (message.datetime > 0) {
|
||||
value = value.replace(/T/g, ' ');
|
||||
}
|
||||
messages[message.name] = value;
|
||||
});
|
||||
|
||||
return messages;
|
||||
},
|
||||
|
||||
getPlaceholder(message) {
|
||||
const type = +message.multiple === 1 ? 'textarea' : message.type;
|
||||
const map = this.messageConfig.placeholderMap || {};
|
||||
return map[type] || PLACEHOLDER[type];
|
||||
},
|
||||
|
||||
validateMessages() {
|
||||
const values = this.messageValues;
|
||||
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
const { value } = values[i];
|
||||
const message = this.messages[i];
|
||||
|
||||
if (value === '') {
|
||||
// 必填字段的校验
|
||||
if (String(message.required) === '1') { // eslint-disable-line
|
||||
const textType = message.type === 'image'
|
||||
? '请上传'
|
||||
: '请填写';
|
||||
return textType + message.name;
|
||||
}
|
||||
} else {
|
||||
if (message.type === 'tel' && !isNumber(value)) {
|
||||
return '请填写正确的数字格式留言';
|
||||
}
|
||||
if (message.type === 'mobile' && !/^\d{6,20}$/.test(value)) {
|
||||
return '手机号长度为6-20位数字';
|
||||
}
|
||||
if (message.type === 'email' && !isEmail(value)) {
|
||||
return '请填写正确的邮箱';
|
||||
}
|
||||
if (message.type === 'id_no' && (value.length < 15 || value.length > 18)) {
|
||||
return '请填写正确的身份证号码';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { createNamespace } from '../../utils';
|
||||
import { inherit } from '../../utils/functional';
|
||||
|
||||
// Types
|
||||
import { CreateElement, RenderContext } from 'vue/types';
|
||||
import { DefaultSlots } from '../../utils/types';
|
||||
import { SkuTreeItemData } from '../type';
|
||||
|
||||
export type SkuRowProps = {
|
||||
skuRow: SkuTreeItemData;
|
||||
};
|
||||
|
||||
const [createComponent, bem] = createNamespace('sku-row');
|
||||
|
||||
function SkuRow(
|
||||
h: CreateElement,
|
||||
props: SkuRowProps,
|
||||
slots: DefaultSlots,
|
||||
ctx: RenderContext<SkuRowProps>
|
||||
) {
|
||||
return (
|
||||
<div class={bem()} {...inherit(ctx)}>
|
||||
<div class={bem('title')}>{props.skuRow.k}:</div>
|
||||
{slots.default && slots.default()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SkuRow.props = {
|
||||
skuRow: Object
|
||||
};
|
||||
|
||||
export default createComponent<SkuRowProps>(SkuRow);
|
||||
@@ -0,0 +1,53 @@
|
||||
import { createNamespace } from '../../utils';
|
||||
import { isSkuChoosable } from '../utils/skuHelper';
|
||||
|
||||
const [createComponent] = createNamespace('sku-row-item');
|
||||
|
||||
export default createComponent({
|
||||
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>
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,139 @@
|
||||
import { createNamespace } from '../../utils';
|
||||
import Stepper from '../../stepper';
|
||||
import { LIMIT_TYPE } from '../constants';
|
||||
|
||||
const [createComponent] = createNamespace('sku-stepper');
|
||||
const { QUOTA_LIMIT, STOCK_LIMIT } = LIMIT_TYPE;
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
quota: Number,
|
||||
quotaUsed: Number,
|
||||
hideStock: Boolean,
|
||||
skuEventBus: Object,
|
||||
skuStockNum: Number,
|
||||
selectedSku: Object,
|
||||
selectedNum: Number,
|
||||
stepperTitle: String,
|
||||
hideQuotaText: Boolean,
|
||||
selectedSkuComb: Object,
|
||||
disableStepperInput: Boolean,
|
||||
customStepperConfig: Object
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
currentNum: this.selectedNum,
|
||||
// 购买限制类型: 限购/库存
|
||||
limitType: STOCK_LIMIT
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
currentNum(num) {
|
||||
this.skuEventBus.$emit('sku:numChange', num);
|
||||
},
|
||||
|
||||
stepperLimit(limit) {
|
||||
if (limit < this.currentNum) {
|
||||
this.currentNum = limit;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
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;
|
||||
|
||||
// 无限购时直接取库存,有限购时取限购数和库存数中小的那个
|
||||
if (this.quota > 0 && quotaLimit <= this.stock) {
|
||||
// 修正负的limit
|
||||
limit = quotaLimit < 0 ? 0 : quotaLimit;
|
||||
this.limitType = QUOTA_LIMIT;
|
||||
} else {
|
||||
limit = this.stock;
|
||||
this.limitType = STOCK_LIMIT;
|
||||
}
|
||||
|
||||
return limit;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
setCurrentNum(num) {
|
||||
this.currentNum = num;
|
||||
},
|
||||
|
||||
onOverLimit(action) {
|
||||
this.skuEventBus.$emit('sku:overLimit', {
|
||||
action,
|
||||
limitType: this.limitType,
|
||||
quota: this.quota,
|
||||
quotaUsed: this.quotaUsed
|
||||
});
|
||||
},
|
||||
|
||||
onChange(currentValue) {
|
||||
const { handleStepperChange } = this.customStepperConfig;
|
||||
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>
|
||||
);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user