diff --git a/docs/demos/views/sku.vue b/docs/demos/views/sku.vue
index 6a956a76c..b09bb9ae8 100644
--- a/docs/demos/views/sku.vue
+++ b/docs/demos/views/sku.vue
@@ -13,6 +13,7 @@
:reset-stepper-on-hide="true"
:reset-selected-sku-on-hide="true"
:disable-stepper-input="true"
+ :message-config="messageConfig"
@buy-clicked="onBuyClicked"
@add-cart="onAddCartClicked"
/>
@@ -23,7 +24,7 @@
- {{ $t('title2') }}
+ {{ $t('title2') }}
@@ -94,6 +95,7 @@ export default {
return {
showBase: false,
showCustom: false,
+ showStepper: false,
initialSku: {
s1: '30349',
s2: '1193'
@@ -113,6 +115,14 @@ export default {
}
}
}
+ },
+ messageConfig: {
+ uploadImg: () => {
+ return new Promise((resolve) => {
+ setTimeout(() => resolve('https://img.yzcdn.cn/upload_files/2017/02/21/FjKTOxjVgnUuPmHJRdunvYky9OHP.jpg!100x100.jpg'), 1000);
+ });
+ },
+ uploadMaxSize: 3
}
};
},
diff --git a/docs/markdown/en-US/sku.md b/docs/markdown/en-US/sku.md
index d2b99a8c8..e0706870c 100644
--- a/docs/markdown/en-US/sku.md
+++ b/docs/markdown/en-US/sku.md
@@ -22,6 +22,7 @@ Vue.use(Sku);
:reset-stepper-on-hide="resetStepperOnHide"
:reset-selected-sku-on-hide="resetSelectedSkuOnHide"
:disable-stepper-input="disableStepperInput"
+ :message-config="messageConfig"
@buy-clicked="onBuyClicked"
@add-cart="onAddCartClicked"
/>
@@ -90,6 +91,7 @@ Vue.use(Sku);
| disable-stepper-input | Whether to disable stepper input | `Boolean` | `false` | - |
| stepper-title | Quantity title | `String` | `Quantity` | - |
| custom-stepper-config | Custom stepper related config | `Object` | `{}` | - |
+| message-config | Message related config | `Object` | `{}` | - |
| get-container | Return the mount node for sku | `Function` | - | `() => HTMLElement` |
### Event
@@ -201,6 +203,26 @@ customStepperConfig: {
}
```
+#### messageConfig Data Structure
+```javascript
+messageConfig: {
+ // the upload image callback
+ uploadImg: () => {
+ return new Promise((resolve) => {
+ setTimeout(() => resolve('https://img.yzcdn.cn/upload_files/2017/02/21/FjKTOxjVgnUuPmHJRdunvYky9OHP.jpg!100x100.jpg'), 1000);
+ });
+ },
+ // max file size (MB)
+ uploadMaxSize: 3,
+ // placehold config
+ placeholderMap: {
+ text: 'xxx',
+ tel: 'xxx',
+ ...
+ }
+}
+```
+
#### Event Params Data Structure
```javascript
diff --git a/docs/markdown/zh-CN/sku.md b/docs/markdown/zh-CN/sku.md
index a2f616d9b..d5fec3c72 100644
--- a/docs/markdown/zh-CN/sku.md
+++ b/docs/markdown/zh-CN/sku.md
@@ -22,6 +22,7 @@ Vue.use(Sku);
:reset-stepper-on-hide="resetStepperOnHide"
:reset-selected-sku-on-hide="resetSelectedSkuOnHide"
:disable-stepper-input="disableStepperInput"
+ :message-config="messageConfig"
@buy-clicked="onBuyClicked"
@add-cart="onAddCartClicked"
/>
@@ -91,6 +92,7 @@ Vue.use(Sku);
| disable-stepper-input | 是否禁用sku中stepper的input框 | `Boolean` | `false` | - |
| stepper-title | 数量选择组件左侧文案 | `String` | `购买数量` | - |
| custom-stepper-config | 步进器相关自定义配置 | `Object` | `{}` | - |
+| message-config | 留言相关配置 | `Object` | `{}` | - |
| get-container | 指定挂载的 HTML 节点 | `Function` | - | `() => HTMLElement` |
### Event
@@ -207,6 +209,26 @@ customStepperConfig: {
}
```
+#### messageConfig Data Structure
+```javascript
+messageConfig: {
+ // 图片上传回调,需要返回一个promise,promise正确执行的结果需要是一个图片url
+ uploadImg: () => {
+ return new Promise((resolve) => {
+ setTimeout(() => resolve('https://img.yzcdn.cn/upload_files/2017/02/21/FjKTOxjVgnUuPmHJRdunvYky9OHP.jpg!100x100.jpg'), 1000);
+ });
+ },
+ // 最大上传体积 (MB)
+ uploadMaxSize: 3,
+ // placehold配置
+ placeholderMap: {
+ text: 'xxx',
+ tel: 'xxx',
+ ...
+ }
+}
+```
+
#### 添加购物车和点击购买回调函数接收的 skuData 对象结构
```javascript
skuData: {
diff --git a/packages/locale/lang/en-US.js b/packages/locale/lang/en-US.js
index 1523d74cd..d3fe13f4c 100644
--- a/packages/locale/lang/en-US.js
+++ b/packages/locale/lang/en-US.js
@@ -113,10 +113,12 @@ export default {
},
vanSkuMessages: {
fill: 'Please fill',
+ upload: 'Please upload',
number: 'Please fill in the correct number format message',
email: 'Please fill in the correct email message',
idcard: 'Please fill in the correct ID number message',
overlimit: 'not more than 200 words',
+ onePic: 'only one picture',
placeholder: {
'id_no': 'Idcard Number',
text: 'Text',
@@ -127,6 +129,15 @@ export default {
textarea: 'Text'
}
},
+ vanSkuImgUploader: {
+ or: 'Or',
+ uploading: 'Uploading...',
+ rephoto: 'Take Again',
+ photo: 'Take',
+ reselect: 'Reselect',
+ select: 'Select Photo',
+ maxSize: maxSize => `The upload limit is up to ${maxSize}MB,please try to compress the photo`
+ },
vanSkuStepper: {
title: 'Quantity',
remain: count => `Remain ${count} items`,
diff --git a/packages/locale/lang/zh-CN.js b/packages/locale/lang/zh-CN.js
index 074fa1f3c..582b3c79a 100644
--- a/packages/locale/lang/zh-CN.js
+++ b/packages/locale/lang/zh-CN.js
@@ -117,10 +117,12 @@ export default {
},
vanSkuMessages: {
fill: '请填写',
+ upload: '请上传',
number: '请填写正确的数字格式留言',
email: '请填写正确的邮箱',
'id_no': '请填写正确的身份证号码',
overlimit: '写的太多了,不要超过200字',
+ onePic: '仅限一张',
placeholder: {
'id_no': '输入18位身份证号码',
text: '输入文本',
@@ -131,6 +133,15 @@ export default {
textarea: '点击填写段落文本'
}
},
+ vanSkuImgUploader: {
+ or: '或',
+ uploading: '正在上传...',
+ rephoto: '重拍',
+ photo: '拍照',
+ reselect: '重新选择照片',
+ select: '选择照片',
+ maxSize: maxSize => `最大可上传图片为${maxSize}MB,请尝试压缩图片尺寸`
+ },
vanSkuStepper: {
title: '购买数量',
remain: count => `剩余${count}件`,
diff --git a/packages/sku/Sku.vue b/packages/sku/Sku.vue
index 828b46589..807ebfe77 100644
--- a/packages/sku/Sku.vue
+++ b/packages/sku/Sku.vue
@@ -81,7 +81,7 @@
@@ -165,9 +165,13 @@ export default create({
type: Number,
default: 200
},
- messagePlaceholderMap: {
+ messageConfig: {
type: Object,
- default: () => ({})
+ default: () => ({
+ placeholderMap: {},
+ uploadImg: () => Promise.resolve(),
+ uploadMaxSize: 5
+ })
},
customStepperConfig: {
type: Object,
diff --git a/packages/sku/components/SkuImgUploader.vue b/packages/sku/components/SkuImgUploader.vue
new file mode 100644
index 000000000..e2bc4d06b
--- /dev/null
+++ b/packages/sku/components/SkuImgUploader.vue
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![]()
+
+
+
+
![]()
+
+
+
+
+
+
+
diff --git a/packages/sku/components/SkuMessages.vue b/packages/sku/components/SkuMessages.vue
index a1839030c..ecf606467 100644
--- a/packages/sku/components/SkuMessages.vue
+++ b/packages/sku/components/SkuMessages.vue
@@ -1,14 +1,29 @@
-
+
+
+
+ |
+
+
@@ -16,30 +31,36 @@
import { create } from '../../utils';
import Field from '../../field';
import CellGroup from '../../cell-group';
+import Cell from '../../cell';
import validateEmail from '../../utils/validate/email';
import validateNumber from '../../utils/validate/number';
+import SkuImgUploader from './SkuImgUploader';
export default create({
name: 'van-sku-messages',
components: {
+ SkuImgUploader,
Field,
+ Cell,
CellGroup
},
props: {
messages: Array,
- messagePlaceholderMap: Object,
+ messageConfig: Object,
goodsId: [Number, String]
},
- computed: {
- internalMessages() {
- return Array.isArray(this.messages) ? this.messages.filter(message => message.type !== 'image') : [];
- },
+ data() {
+ return {
+ messageValues: this.messages.map(() => ({ value: '' }))
+ };
+ },
- messageValues() {
- return this.internalMessages.map(() => '');
+ computed: {
+ messagePlaceholderMap() {
+ return this.messageConfig.placeholderMap || {};
}
},
@@ -57,8 +78,9 @@ export default create({
getMessages() {
const messages = {};
- this.messageValues.forEach((value, index) => {
- if (this.internalMessages[index].datetime > 0) {
+ this.messageValues.forEach((item, index) => {
+ let value = item.value;
+ if (this.messages[index].datetime > 0) {
value = value.replace(/T/g, ' ');
}
messages[`message_${index}`] = value;
@@ -70,8 +92,9 @@ export default create({
getCartMessages() {
const messages = {};
- this.messageValues.forEach((value, index) => {
- const message = this.internalMessages[index];
+ this.messageValues.forEach((item, index) => {
+ let value = item.value;
+ const message = this.messages[index];
if (message.datetime > 0) {
value = value.replace(/T/g, ' ');
}
@@ -90,17 +113,16 @@ export default create({
const values = this.messageValues;
for (let i = 0; i < values.length; i++) {
- const value = values[i];
- const message = this.internalMessages[i];
+ const value = values[i].value;
+ const message = this.messages[i];
if (value === '') {
// 必填字段的校验
if (message.required == '1') { // eslint-disable-line
- if (message.type === 'image') {
- continue;
- } else {
- return this.$t('fill') + message.name;
- }
+ const textType = message.type === 'image'
+ ? 'upload'
+ : 'fill';
+ return this.$t(textType) + message.name;
}
} else {
if (message.type === 'tel' && !validateNumber(value)) {
diff --git a/packages/sku/components/SkuStepper.vue b/packages/sku/components/SkuStepper.vue
index 4c705a41d..d1f496706 100644
--- a/packages/sku/components/SkuStepper.vue
+++ b/packages/sku/components/SkuStepper.vue
@@ -9,6 +9,7 @@
:max="stepperLimit"
:disable-input="disableStepperInput"
@overlimit="onOverLimit"
+ @change="onChange"
/>
{{ $t('remain', stock) }}
@@ -104,7 +105,6 @@ export default create({
setCurrentNum(num) {
this.currentNum = num;
},
-
onOverLimit(action) {
this.skuEventBus.$emit('sku:overLimit', {
action,
@@ -112,6 +112,10 @@ export default create({
quota: this.quota,
quotaUsed: this.quotaUsed
});
+ },
+ onChange(currentValue) {
+ const { handleStepperChange } = this.customStepperConfig;
+ handleStepperChange && handleStepperChange(currentValue);
}
}
});
diff --git a/packages/vant-css/src/sku.css b/packages/vant-css/src/sku.css
index 4f8ea9497..f1d70af18 100644
--- a/packages/vant-css/src/sku.css
+++ b/packages/vant-css/src/sku.css
@@ -1,4 +1,5 @@
@import './common/var.css';
+@import './mixins/clearfix.css';
.van-sku {
&-container {
@@ -144,7 +145,7 @@
margin-right: 20px;
}
}
-
+
&__stepper {
top: 7px;
left: 4px;
@@ -163,13 +164,85 @@
color: $gray-dark;
font-size: 12px;
}
-
+
&__quota {
display: inline-block;
color: $red;
font-size: 12px;
}
+ &-messages {
+ &__image-cell {
+ .van-cell__title {
+ width: 90px;
+ }
+ .van-cell__value {
+ text-align: left;
+ }
+ }
+ }
+
+ &-img-uploader {
+ display: inline-block;
+
+ &__header {
+ padding: 0 10px;
+ border: 1px solid #e5e5e5;
+ line-height: 24px;
+ border-radius: 3px;
+ font-size: 12px;
+
+ .van-icon {
+ top: 3px;
+ margin-right: 5px;
+ font-size: 14px;
+ }
+ }
+
+ &__imglist {
+ @mixin clearfix;
+ }
+
+ &__img-container {
+ height: 60px;
+ width: 60px;
+ margin-top: 10px;
+ margin-right: 10px;
+ float: left;
+ position: relative;
+ border: #e5e5e5 1px solid;
+
+ img {
+ max-width: 100%;
+ max-height: 100%;
+ top: 50%;
+ position: relative;
+ transform: translateY(-50%);
+ }
+ }
+
+ &__delete-picture {
+ position: absolute;
+ color: $red;
+ top: -10px;
+ right: -17px;
+ z-index: 1;
+ width: 22px;
+ height: 22px;
+ }
+
+ &__uploading {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ margin: auto;
+ width: 20px;
+ height: 20px;
+ }
+ }
+
/* sku actions */
&-actions {
display: flex;
diff --git a/test/unit/specs/sku.spec.js b/test/unit/specs/sku.spec.js
index 499939444..6d2947dee 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 Uploader from 'packages/uploader';
import Toast from 'packages/toast';
import { mount } from 'avoriaz';
import { DOMChecker } from '../utils';
@@ -13,6 +14,13 @@ const initialSku = {
};
goods.picture = goods.picture[0];
+const File = function() {
+ this.name = 'test';
+ this.size = 10000;
+};
+
+const mockFile = new File([], '/Users');
+
describe('Sku', (done) => {
let wrapper;
afterEach(() => {
@@ -239,7 +247,15 @@ describe('Sku', (done) => {
value: true,
sku: data.sku,
goodsId: data.goods_id,
- goods: goods
+ goods: goods,
+ messageConfig: {
+ uploadImg: () => {
+ return new Promise((resolve) => {
+ setTimeout(() => resolve('https://img.yzcdn.cn/upload_files/2017/02/21/FjKTOxjVgnUuPmHJRdunvYky9OHP.jpg!100x100.jpg'), 1000);
+ });
+ },
+ uploadMaxSize: 3
+ }
}
});
@@ -247,12 +263,15 @@ describe('Sku', (done) => {
const skuMessages = wrapper.find('.van-sku-messages')[0];
const inputs = skuMessages.find('input');
const textarea = skuMessages.find('textarea')[0];
+ const uploader = wrapper.find(Uploader)[0];
// 修改留言内容
inputs[0].element.value = 123;
// 测试身份证号
inputs[1].element.value = 234;
inputs[0].trigger('input');
inputs[1].trigger('input');
+ // 测试图片
+ uploader.vm.onChange({ target: { files: [mockFile] }});
wrapper.vm.$nextTick(() => {
// 点击购买
@@ -276,9 +295,9 @@ describe('Sku', (done) => {
textarea.element.value = '';
// 测试数字留言
- inputs[2].element.value = 'abc';
+ inputs[3].element.value = 'abc';
textarea.trigger('input');
- inputs[2].trigger('input');
+ inputs[3].trigger('input');
wrapper.vm.$nextTick(() => {
buyBtn.trigger('click');
@@ -286,10 +305,10 @@ describe('Sku', (done) => {
wrapper.vm.$nextTick(() => {
expect(toastText.textContent).to.equal('请填写正确的数字格式留言');
- inputs[2].element.value = 0;
- inputs[3].element.value = 345;
- inputs[2].trigger('input');
+ inputs[3].element.value = 0;
+ inputs[4].element.value = 345;
inputs[3].trigger('input');
+ inputs[4].trigger('input');
wrapper.vm.$nextTick(() => {
buyBtn.trigger('click');