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');