From 8986da5ba66b6b3cf8aa267ae8c1b712955fafbf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=99=88=E5=98=89=E6=B6=B5?= <chenjiahan@meituan.com>
Date: Thu, 31 Aug 2017 19:36:37 +0800
Subject: [PATCH] add GoodsAction component

---
 docs/examples-docs/goods-action.md       | 88 ++++++++++++++++++++++++
 docs/src/doc.config.js                   |  4 ++
 packages/goods-action-big-btn/index.vue  | 32 +++++++++
 packages/goods-action-mini-btn/index.vue | 30 ++++++++
 packages/goods-action/index.vue          | 11 +++
 packages/index.js                        |  9 +++
 packages/vant-css/src/button.css         |  1 +
 packages/vant-css/src/common/var.css     |  1 +
 packages/vant-css/src/goods-action.css   | 40 +++++++++++
 packages/vant-css/src/index.css          |  1 +
 packages/vant-css/src/invalid-goods.css  |  2 +-
 packages/vant-css/src/order-goods.css    |  2 +-
 test/unit/components/goods-action.vue    | 37 ++++++++++
 test/unit/specs/goods-action.spec.js     | 47 +++++++++++++
 14 files changed, 303 insertions(+), 2 deletions(-)
 create mode 100644 docs/examples-docs/goods-action.md
 create mode 100644 packages/goods-action-big-btn/index.vue
 create mode 100644 packages/goods-action-mini-btn/index.vue
 create mode 100644 packages/goods-action/index.vue
 create mode 100644 packages/vant-css/src/goods-action.css
 create mode 100644 test/unit/components/goods-action.vue
 create mode 100644 test/unit/specs/goods-action.spec.js

diff --git a/docs/examples-docs/goods-action.md b/docs/examples-docs/goods-action.md
new file mode 100644
index 000000000..328a48951
--- /dev/null
+++ b/docs/examples-docs/goods-action.md
@@ -0,0 +1,88 @@
+<script>
+import { Toast } from 'packages';
+
+export default {
+  methods: {
+    onClickMiniBtn() {
+      Toast('点击图标');
+    },
+    onClickBigBtn() {
+      Toast('点击按钮');
+    }
+  }
+}
+</script>
+
+<style>
+.demo-goods-action {
+  .van-goods-action {
+    position: relative;
+  }
+}
+</style>
+
+## GoodsAction 商品操作组件
+
+### 使用指南
+``` javascript
+import {
+  GoodsAction,
+  GoodsActionBigBtn,
+  GoodsActionMiniBtn
+} from 'vant';
+
+Vue.component(GoodsAction.name, GoodsAction);
+Vue.component(GoodsActionBigBtn.name, GoodsActionBigBtn);
+Vue.component(GoodsActionMiniBtn.name, GoodsActionMiniBtn);
+```
+
+### 代码演示
+
+:::demo 
+```html
+<van-goods-action>
+  <van-goods-action-mini-btn icon="chat" @click="onClickMiniBtn">
+    客服
+  </van-goods-action-mini-btn>
+  <van-goods-action-mini-btn icon="cart" @click="onClickMiniBtn">
+    购物车
+  </van-goods-action-mini-btn>
+  <van-goods-action-big-btn @click="onClickBigBtn">
+    加入购物车
+  </van-goods-action-big-btn>
+  <van-goods-action-big-btn @click="onClickBigBtn" primary>
+    立即购买
+  </van-goods-action-big-btn>
+</van-goods-action>
+```
+
+```javascript
+export default {
+  methods: {
+    onClickMiniBtn() {
+      Toast('点击图标');
+    },
+    onClickBigBtn() {
+      Toast('点击按钮');
+    }
+  }
+}
+```
+:::
+
+### API
+
+#### GoodsActionMiniBtn
+
+| 参数 | 说明 | 类型 | 默认值 | 可选值 |
+|-----------|-----------|-----------|-------------|-------------|
+| icon | 图标 | `String`  | - | Icon 组件支持的所有图标 |
+| iconClass | 图标额外类名 | `String`  | `''` | - |
+| url | 跳转链接 | `String`  | `javascript:;` | - |
+
+#### GoodsActionBigBtn
+
+| 参数 | 说明 | 类型 | 默认值 | 可选值 |
+|-----------|-----------|-----------|-------------|-------------|
+| url | 跳转链接 | `String`  | `javascript:;` | - |
+| primary | 是否主行动按钮,主行动按钮默认为红色 | `Boolean` | `false` | - |
diff --git a/docs/src/doc.config.js b/docs/src/doc.config.js
index 53912fd7d..d73a34ac3 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": "/goods-action",
+                "title": "GoodsAction 商品操作"
+              },
               {
                 "path": "/invalid-goods",
                 "title": "InvalidGoods 不可用商品列表"
diff --git a/packages/goods-action-big-btn/index.vue b/packages/goods-action-big-btn/index.vue
new file mode 100644
index 000000000..58bf06b07
--- /dev/null
+++ b/packages/goods-action-big-btn/index.vue
@@ -0,0 +1,32 @@
+<template>
+  <van-button
+    tag="a"
+    :href="url"
+    class="van-goods-action__big-btn"
+    :type="primary ? 'primary' : 'default'"
+    @click="$emit('click', $event)"
+    bottomAction
+  >
+    <slot></slot>
+  </van-button>
+</template>
+
+<script>
+import Button from '../button';
+
+export default {
+  name: 'van-goods-action-big-btn',
+
+  components: {
+    [Button.name]: Button
+  },
+
+  props: {
+    primary: Boolean,
+    url: {
+      type: String,
+      default: 'javascript:;'
+    }
+  }
+};
+</script>
diff --git a/packages/goods-action-mini-btn/index.vue b/packages/goods-action-mini-btn/index.vue
new file mode 100644
index 000000000..4f91fd443
--- /dev/null
+++ b/packages/goods-action-mini-btn/index.vue
@@ -0,0 +1,30 @@
+<template>
+  <a :href="url" class="van-goods-action__mini-btn" @click="$emit('click', $event);">
+    <van-icon :class="['van-goods-action__mini-btn-icon', iconClass]" :name="icon" />
+    <slot></slot>
+  </a>
+</template>
+
+<script>
+import Icon from '../icon';
+
+export default {
+  name: 'van-goods-action-mini-btn',
+
+  components: {
+    [Icon.name]: Icon
+  },
+
+  props: {
+    icon: String,
+    iconClass: {
+      type: String,
+      default: ''
+    },
+    url: {
+      type: String,
+      default: 'javascript:;'
+    }
+  }
+};
+</script>
diff --git a/packages/goods-action/index.vue b/packages/goods-action/index.vue
new file mode 100644
index 000000000..8ab06d10a
--- /dev/null
+++ b/packages/goods-action/index.vue
@@ -0,0 +1,11 @@
+<template>
+  <div class="van-goods-action">
+    <slot></slot>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'van-goods-action'
+};
+</script>
diff --git a/packages/index.js b/packages/index.js
index bd8f2a69a..56cff9ada 100644
--- a/packages/index.js
+++ b/packages/index.js
@@ -13,6 +13,9 @@ import DatetimePicker from './datetime-picker';
 import Dialog from './dialog';
 import ExpressWay from './express-way';
 import Field from './field';
+import GoodsAction from './goods-action';
+import GoodsActionBigBtn from './goods-action-big-btn';
+import GoodsActionMiniBtn from './goods-action-mini-btn';
 import Icon from './icon';
 import ImagePreview from './image-preview';
 import InvalidGoods from './invalid-goods';
@@ -59,6 +62,9 @@ const components = [
   DatetimePicker,
   ExpressWay,
   Field,
+  GoodsAction,
+  GoodsActionBigBtn,
+  GoodsActionMiniBtn,
   Icon,
   InvalidGoods,
   Loading,
@@ -117,6 +123,9 @@ export {
   Dialog,
   ExpressWay,
   Field,
+  GoodsAction,
+  GoodsActionBigBtn,
+  GoodsActionMiniBtn,
   Icon,
   ImagePreview,
   InvalidGoods,
diff --git a/packages/vant-css/src/button.css b/packages/vant-css/src/button.css
index a3fd5f146..81ce54d89 100644
--- a/packages/vant-css/src/button.css
+++ b/packages/vant-css/src/button.css
@@ -103,6 +103,7 @@
     line-height: 50px;
     border: 0;
     border-radius: 0;
+    font-size: 16px;
     color: $bottom-action-button-default-color;
     background-color: $bottom-action-button-default-background-color;
 
diff --git a/packages/vant-css/src/common/var.css b/packages/vant-css/src/common/var.css
index 2edb3809e..61de30818 100644
--- a/packages/vant-css/src/common/var.css
+++ b/packages/vant-css/src/common/var.css
@@ -13,6 +13,7 @@ $c-yellow-light: #fcff00;
 $c-orange: #f60;
 $c-orange-dark: #f15a0c;
 $c-blue: #38f;
+$c-active: #e8e8e8;
 
 $c-background: #f8f8f8;
 
diff --git a/packages/vant-css/src/goods-action.css b/packages/vant-css/src/goods-action.css
new file mode 100644
index 000000000..ca163bbdd
--- /dev/null
+++ b/packages/vant-css/src/goods-action.css
@@ -0,0 +1,40 @@
+@import './common/var.css';
+
+.van-goods-action {
+  left: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  position: fixed;
+
+  &__mini-btn {
+    color: #666;
+    display: flex;
+    height: 50px;
+    font-size: 10px;
+    min-width: 15%;
+    text-align: center;
+    background-color: #fff;
+    flex-direction: column;
+    justify-content: center;
+
+    &::after {
+      @mixin border-retina (top, right), #cacaca;
+    }
+
+    &:last-child {
+      &::after {
+        border-right-width: 0;
+      }
+    }
+
+    &:active {
+      background-color: $c-active;
+    }
+  }
+
+  &__mini-btn-icon {
+    font-size: 20px;
+    margin-bottom: 5px;
+  }
+}
diff --git a/packages/vant-css/src/index.css b/packages/vant-css/src/index.css
index 8731cbdb4..68b67d59c 100644
--- a/packages/vant-css/src/index.css
+++ b/packages/vant-css/src/index.css
@@ -36,3 +36,4 @@
 @import './pay-order.css';
 @import './order-goods.css';
 @import './invalid-goods.css';
+@import './goods-action.css';
diff --git a/packages/vant-css/src/invalid-goods.css b/packages/vant-css/src/invalid-goods.css
index 43c0480ce..83317b9d3 100644
--- a/packages/vant-css/src/invalid-goods.css
+++ b/packages/vant-css/src/invalid-goods.css
@@ -1,4 +1,4 @@
-@import "./mixins/ellipsis";
+@import "./mixins/ellipsis.css";
 
 $van-invalid-goods-photo-size: 90px;
 
diff --git a/packages/vant-css/src/order-goods.css b/packages/vant-css/src/order-goods.css
index fe80bfb3f..b40500e16 100644
--- a/packages/vant-css/src/order-goods.css
+++ b/packages/vant-css/src/order-goods.css
@@ -1,4 +1,4 @@
-@import "./mixins/border_retina";
+@import "./mixins/border_retina.css";
 
 .van-order-goods {
   background-color: #fff;
diff --git a/test/unit/components/goods-action.vue b/test/unit/components/goods-action.vue
new file mode 100644
index 000000000..bf867989c
--- /dev/null
+++ b/test/unit/components/goods-action.vue
@@ -0,0 +1,37 @@
+<template>
+  <van-goods-action>
+    <van-goods-action-mini-btn icon="chat" @click="onClickMiniBtn">
+      客服
+    </van-goods-action-mini-btn>
+    <van-goods-action-mini-btn icon="cart" url="http://www.youzan.com" @click="onClickMiniBtn">
+      购物车
+    </van-goods-action-mini-btn>
+    <van-goods-action-big-btn @click="onClickBigBtn">
+      加入购物车
+    </van-goods-action-big-btn>
+    <van-goods-action-big-btn @click="onClickBigBtn" primary url="http://www.youzan.com">
+      立即购买
+    </van-goods-action-big-btn>
+  </van-goods-action>
+</template>
+
+<script>
+import GoodsAction from 'packages/goods-action';
+import GoodsActionBigBtn from 'packages/goods-action-big-btn';
+import GoodsActionMiniBtn from 'packages/goods-action-mini-btn';
+
+export default {
+  components: {
+    [GoodsAction.name]: GoodsAction,
+    [GoodsActionBigBtn.name]: GoodsActionBigBtn,
+    [GoodsActionMiniBtn.name]: GoodsActionMiniBtn
+  },
+
+  methods: {
+    onClickMiniBtn() {
+    },
+    onClickBigBtn() {
+    }
+  }
+};
+</script>
diff --git a/test/unit/specs/goods-action.spec.js b/test/unit/specs/goods-action.spec.js
new file mode 100644
index 000000000..e86b69a08
--- /dev/null
+++ b/test/unit/specs/goods-action.spec.js
@@ -0,0 +1,47 @@
+import GoodsAction from '../components/goods-action';
+import GoodsActionBigBtn from 'packages/goods-action-big-btn';
+import GoodsActionMiniBtn from 'packages/goods-action-mini-btn';
+import { mount } from 'avoriaz';
+import { DOMChecker } from '../utils';
+
+describe('GoodsAction', () => {
+  let wrapper;
+
+  afterEach(() => {
+    wrapper && wrapper.destroy();
+  });
+
+  it('create a GoodsAction', () => {
+    wrapper = mount(GoodsAction, {});
+
+    DOMChecker(wrapper, {
+      count: {
+        '.van-goods-action__mini-btn': 2,
+        '.van-goods-action__big-btn': 2,
+        '.van-icon-chat': 1
+      }
+    });
+  });
+
+  it('click GoodsActionBigBtn', () => {
+    wrapper = mount(GoodsActionBigBtn, {});
+
+    const submitSpyFunc = sinon.spy();
+    wrapper.vm.$on('click', submitSpyFunc);
+    wrapper.trigger('click');
+    expect(submitSpyFunc.calledOnce).to.be.true;
+  });
+
+  it('click GoodsActionMiniBtn', () => {
+    wrapper = mount(GoodsActionMiniBtn, {
+      propsData: {
+        icon: 'card'
+      }
+    });
+
+    const submitSpyFunc = sinon.spy();
+    wrapper.vm.$on('click', submitSpyFunc);
+    wrapper.trigger('click');
+    expect(submitSpyFunc.calledOnce).to.be.true;
+  });
+});