diff --git a/docs/examples-docs/order-goods.md b/docs/examples-docs/order-goods.md new file mode 100644 index 000000000..f88a36717 --- /dev/null +++ b/docs/examples-docs/order-goods.md @@ -0,0 +1,229 @@ +## OrderGoods 下单页商品列表 + + + +### 使用指南 +``` javascript +import { OrderGoods } from 'vant'; + +Vue.component(OrderGoods.name, OrderGoods); +``` + +### 代码演示 + +#### 基础用法 + +:::demo 基础用法 +```html + + + + + +``` +::: + +#### 积分商品 + +:::demo 积分商品 +```html + + + + + +``` +::: + +#### 预售商品 + +:::demo 预售商品 +```html + + + + + +``` +::: + +#### 商品为空 + +:::demo 商品为空 +```html + +``` +::: + +#### 多个商品 + +:::demo 多个商品 +```html + +``` +::: + +### API + +| 参数 | 说明 | 类型 | 默认值 | +|-----------|-----------|-----------|-------------| +| shop-name | 店铺名称 | `String` | | +| shop-link | 店铺链接 | `String` | | +| header-icon | 店铺名称左侧的图标类型 | `String` | `shop` | +| header-badge | 店铺名称右侧的徽章链接 | `String` | | +| item-list | 商品列表 | `Array` | `[]` | +| empty-icon | 商品列表为空时的图标 | `String` | | +| empty-message | 商品列表为空时的提示文案 | `String` | `当前没有可购买的商品,请重新选择` | +| empty-button-text | 商品列表为空时的按钮文案 | `String` | `返回重新选择` | +| v-model | 买家留言 | `String` | `''` | +| show-total-price | 是否显示价格栏 | `Boolean` | `true` | +| show-message | 是否显示留言栏 | `Boolean` | `true` | +| message-editable | 留言是否可以编辑 | `Boolean` | `true` | +| price | 合计金额(单位分) | `Number` | | +| points | 合计积分 | `Number` | | + +### 数据格式 +#### itemList 中的配送方式字段说明 +| key | 说明 | 类型 | +|-----------|-----------|-----------| +| title | 商品名称 | `String` | +| img_url | 图片地址 | `String` | +| delivery_time | 发货时间 | `String` | +| num | 商品数量 | `Number` | +| points_price | 积分价格 | `Number` | +| pay_price(单位分) | 金额 | `Number` | +| sku | 商品 sku | `Array` | +| message | 商品留言 | `Array` | +| is_presale | 是否为预售 | `Boolean` | +| is_present | 是否为赠品 | `Boolean` | +| is_period_buy | 是否为周期购 | `Boolean` | +| show_delivery_time | 是否显示发货时间 | `Boolean` | + +### Slot +| name | 描述 | +|-----------|-----------| +| 默认 | 在商品列表和留言之间插入内容 | +| top | 在标题和商品列表之间插入内容 | +| bottom | 在合计价格下方插入内容 | diff --git a/docs/src/doc.config.js b/docs/src/doc.config.js index 64da013eb..792b893ce 100644 --- a/docs/src/doc.config.js +++ b/docs/src/doc.config.js @@ -189,6 +189,10 @@ module.exports = { "path": "/express-way", "title": "ExpressWay 配送方式" }, + { + "path": "/order-goods", + "title": "OrderGoods 下单页商品列表" + }, { "path": "/pay-order", "title": "PayOrder 提交订单栏" diff --git a/packages/index.js b/packages/index.js index c262f1834..86ed944e2 100644 --- a/packages/index.js +++ b/packages/index.js @@ -18,6 +18,7 @@ import ImagePreview from './image-preview'; import Lazyload from './lazyload'; import Loading from './loading'; import NoticeBar from './notice-bar'; +import OrderGoods from './order-goods'; import Panel from './panel'; import PayOrder from './pay-order'; import Picker from './picker'; @@ -60,6 +61,7 @@ const components = [ Icon, Loading, NoticeBar, + OrderGoods, Panel, PayOrder, Picker, @@ -118,6 +120,7 @@ export { Lazyload, Loading, NoticeBar, + OrderGoods, Panel, PayOrder, Picker, diff --git a/packages/order-goods/Card.vue b/packages/order-goods/Card.vue new file mode 100644 index 000000000..edfa63b90 --- /dev/null +++ b/packages/order-goods/Card.vue @@ -0,0 +1,102 @@ + + + + + + + + + {{ data.title }} + {{ price }} + + + + {{ desc }} + x {{ data.num }} + + + + 预售 + 周期购 + + + 查看留言 + + + + + + + 备注信息 + + + {{ key }} + + + + {{ value }} + + + + 查看订单详情 + + + + + + diff --git a/packages/order-goods/Empty.vue b/packages/order-goods/Empty.vue new file mode 100644 index 000000000..7e9779908 --- /dev/null +++ b/packages/order-goods/Empty.vue @@ -0,0 +1,31 @@ + + + + {{ message }} + {{ buttonText }} + + + + diff --git a/packages/order-goods/Header.vue b/packages/order-goods/Header.vue new file mode 100644 index 000000000..956229556 --- /dev/null +++ b/packages/order-goods/Header.vue @@ -0,0 +1,26 @@ + + + + {{ title }} + + + + + diff --git a/packages/order-goods/Message.vue b/packages/order-goods/Message.vue new file mode 100644 index 000000000..36a9af8e2 --- /dev/null +++ b/packages/order-goods/Message.vue @@ -0,0 +1,50 @@ + + + + {{ message }} + + + + diff --git a/packages/order-goods/Price.vue b/packages/order-goods/Price.vue new file mode 100644 index 000000000..ea1f68a5b --- /dev/null +++ b/packages/order-goods/Price.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/packages/order-goods/index.vue b/packages/order-goods/index.vue new file mode 100644 index 000000000..05dad6558 --- /dev/null +++ b/packages/order-goods/index.vue @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + diff --git a/packages/order-goods/utils.js b/packages/order-goods/utils.js new file mode 100644 index 000000000..50d57b953 --- /dev/null +++ b/packages/order-goods/utils.js @@ -0,0 +1,13 @@ +/** + * 拼接商品价格(金额和积分) + */ +export function getTotalPrice(price, points) { + const arr = []; + if (points) { + arr.push(points + '积分'); + } + if (price) { + arr.push('¥' + (price / 100).toFixed(2)); + } + return arr.join(' + '); +} diff --git a/packages/vant-css/src/order-goods.css b/packages/vant-css/src/order-goods.css new file mode 100644 index 000000000..fe80bfb3f --- /dev/null +++ b/packages/vant-css/src/order-goods.css @@ -0,0 +1,214 @@ +@import "./mixins/border_retina"; + +.van-order-goods { + background-color: #fff; + + &-card { + margin-left: -15px; + position: relative; + background-color: #fafafa; + + &:not(:first-child), + &__delivery { + &::after { + @mixin border-retina (top); + } + } + + &__tags { + flex: 1; + } + + &__tag-green, + &__tag-red { + font-size: 10px; + padding: 3px 5px; + margin-right: 5px; + border-radius: 2px; + display: inline-block; + } + + &__tag-green { + color: #fff; + background-color: #4b0; + } + + &__tag-red { + color: #ed5050; + padding: 5px 8px; + border: 1px solid #ed5050; + } + + &__delivery { + padding-left: 15px; + background-color: transparent; + + .van-cell__value { + color: #666; + } + } + + &__present { + top: 0; + left: 3px; + width: 18px; + height: 36px; + position: absolute; + background: url("https://b.yzcdn.cn/v2/image/wap/trade/confirm/present@2x.png") no-repeat; + background-size: 18px 36px; + } + + .van-button&__message-button { + height: 24px; + padding: 0 5px; + font-size: 10px; + line-height: 22px; + } + + &__button { + padding: 20px 15px 0; + .van-button { + height: 40px; + line-height: 38px; + } + } + + &__message { + width: 100%; + height: 100%; + background-color: #f8f8f8; + + h2 { + color: #999; + font-size: 12px; + line-height: 18px; + padding: 5px 0 5px 10px; + } + + ul { + background-color: #fff; + } + + p, + a, + label { + font-size: 14px; + padding: 14px 0; + line-height: 20px; + vertical-align: top; + display: inline-block; + } + + label { + color: #c9c9c9; + min-width: 90px; + } + + li { + display: flex; + padding: 0 10px; + position: relative; + + &:not(:last-child)::after { + @mixin border-retina (bottom); + } + + img { + width: 70px; + height: 70px; + } + } + } + } + + &-empty { + margin-left: -15px; + text-align: center; + position: relative; + + &::after { + @mixin border-retina (top); + } + + p { + color: #999; + padding: 0 10px; + font-size: 14px; + line-height: 20px; + } + + img { + width: 80px; + height: 84px; + padding: 15px 0; + } + + .van-button { + height: 41px; + margin: 15px 0; + padding: 0 10px; + line-height: 39px; + border-color: #e5e5e5; + } + } + + &-header { + line-height: 50px; + + a, + img, + .van-icon { + vertical-align: middle; + } + + .van-icon { + font-size: 18px; + margin-right: 5px; + } + + a { + color: #333; + font-size: 14px; + } + + img { + height: 14px; + margin-left: 5px; + } + } + + &-message { + textarea { + color: #666; + padding: 0; + width: 100%; + height: 22px; + border: none; + resize: none; + outline: none; + display: block; + font-size: 14px; + line-height: 22px; + transition: height .3s ease-in-out; + } + + textarea&-focused { + height: 48px; + } + + p { + color: #666; + text-align: left; + } + + .van-cell__title { + width: 75px; + } + } + + &-price { + .van-cell__value { + color: #f44; + } + } +} diff --git a/test/unit/specs/order-goods.spec.js b/test/unit/specs/order-goods.spec.js new file mode 100644 index 000000000..e950889f0 --- /dev/null +++ b/test/unit/specs/order-goods.spec.js @@ -0,0 +1,249 @@ +import OrderGoods from 'packages/order-goods'; +import { mount } from 'avoriaz'; +import { DOMChecker } from '../utils'; + +const item1 = { + img_url: 'https://img.yzcdn.cn/upload_files/2017/07/02/af5b9f44deaeb68000d7e4a711160c53.jpg', + pay_price: 1050, + title: '商品 A', + num: '1' +}; + +const item2 = { + points_price: 200, + pay_price: 50, + img_url: 'https://img.yzcdn.cn/upload_files/2017/07/02/e89d56cd92ad8ce3b9d8e1babc3758b6.jpg', + title: '商品 B', + num: '15', + sku: [{ v: '商品SKU1' }, { v: '商品SKU2' }] +}; + +const item3 = { + pay_price: 50, + img_url: 'https://img.yzcdn.cn/upload_files/2017/07/02/e89d56cd92ad8ce3b9d8e1babc3758b6.jpg', + title: '商品 C', + num: '15', + is_presale: true, + delivery_time: '三天后发货', + show_delivery_time: true, + is_presale: true, + is_present: true, + message: { + '留言1': '留言1内容', + '留言2': 'https://img.yzcdn.cn/upload_files/2017/07/02/e89d56cd92ad8ce3b9d8e1babc3758b6.jpg' + } +}; + +describe('OrderGoods', () => { + let wrapper; + afterEach(() => { + wrapper && wrapper.destroy(); + }); + + it('default', () => { + wrapper = mount(OrderGoods, { + attachToDocument: true, + propsData: { + shopName: '起码运动馆', + price: item1.pay_price, + itemList: [item1], + message: '留言留言' + } + }); + + DOMChecker(wrapper, { + text: { + '.van-order-goods-header a': '起码运动馆', + '.van-order-goods-price .van-cell__value span': '¥10.50', + '.van-card__title': item1.title, + '.van-card__num': 'x ' + item1.num, + '.van-card__price': '¥10.50' + }, + value: { + '.van-order-goods-message textarea': '留言留言' + }, + src: { + '.van-card__thumb img': item1.img_url + } + }); + }); + + it('empty list', () => { + wrapper = mount(OrderGoods, { + attachToDocument: true, + propsData: { + itemList: [] + } + }); + + DOMChecker(wrapper, { + text: { + '.van-order-goods-empty p': '当前没有可购买的商品,请重新选择', + '.van-order-goods-empty button': '返回重新选择' + }, + src: { + '.van-order-goods-empty img': 'http://b.yzcdn.cn/v2/image/wap/trade/new_order/empty@2x.png' + } + }); + }); + + it('empty list config', () => { + wrapper = mount(OrderGoods, { + attachToDocument: true, + propsData: { + itemList: [], + emptyIcon: 'https://img.yzcdn.cn/upload_files/2017/07/01/FlIeRrn5bMRoWhcwp4Dp1TmVAXKy.jpg', + emptyMessage: '测试', + emptyButtonText: '测试' + } + }); + + DOMChecker(wrapper, { + text: { + '.van-order-goods-empty p': '测试', + '.van-order-goods-empty button': '测试' + }, + src: { + '.van-order-goods-empty img': 'https://img.yzcdn.cn/upload_files/2017/07/01/FlIeRrn5bMRoWhcwp4Dp1TmVAXKy.jpg' + } + }); + }); + + it('message not editable', () => { + wrapper = mount(OrderGoods, { + attachToDocument: true, + propsData: { + itemList: [item1], + message: '留言留言', + messageEditable: false + } + }); + + DOMChecker(wrapper, { + text: { + '.van-order-goods-message p': '留言留言' + } + }); + }); + + it('points props', () => { + wrapper = mount(OrderGoods, { + attachToDocument: true, + propsData: { + itemList: [item1], + points: 100 + } + }); + + DOMChecker(wrapper, { + text: { + '.van-order-goods-price .van-cell__value span': '100积分' + } + }); + }); + + it('points prop and price prop', () => { + wrapper = mount(OrderGoods, { + attachToDocument: true, + propsData: { + itemList: [item1], + points: 100, + price: 1050 + } + }); + + DOMChecker(wrapper, { + text: { + '.van-order-goods-price .van-cell__value span': '100积分 + ¥10.50' + } + }); + }); + + it('shopLink prop', () => { + wrapper = mount(OrderGoods, { + attachToDocument: true, + propsData: { + itemList: [item1], + shopLink: 'http://www.youzan.com' + } + }); + + expect(wrapper.find('.van-order-goods-header a')[0].element.getAttribute('href')).to.equal('http://www.youzan.com'); + }); + + it('item with points', () => { + wrapper = mount(OrderGoods, { + attachToDocument: true, + propsData: { + itemList: [item2] + } + }); + + DOMChecker(wrapper, { + text: { + '.van-card__price': '200积分 + ¥0.50', + '.van-card__title': item2.title, + '.van-card__num': 'x ' + item2.num + }, + src: { + '.van-card__thumb img': item2.img_url + } + }); + }); + + it('presable item with deliveryTime', () => { + wrapper = mount(OrderGoods, { + attachToDocument: true, + propsData: { + itemList: [item3] + } + }); + + DOMChecker(wrapper, { + text: { + '.van-card__price': '¥0.50', + '.van-card__title': item3.title, + '.van-card__num': 'x ' + item3.num, + '.van-order-goods-card__delivery .van-cell__value span': item3.delivery_time + }, + count: { + '.van-order-goods-card__present': 1, + '.van-order-goods-card__tag-green': 1 + }, + src: { + '.van-card__thumb img': item3.img_url + } + }); + }); + + it('item with message', () => { + wrapper = mount(OrderGoods, { + attachToDocument: true, + propsData: { + itemList: [item3] + } + }); + + DOMChecker(wrapper, { + count: { + '.van-order-goods-card__message-button': 1, + '.van-order-goods-card__message li': 2 + } + }); + }); + + it('multi items', () => { + wrapper = mount(OrderGoods, { + attachToDocument: true, + propsData: { + itemList: [item1, item2, item3] + } + }); + + DOMChecker(wrapper, { + count: { + '.van-order-goods-card': 3 + } + }); + }); +});
{{ desc }}
{{ value }}
{{ message }}