Browse Source

feat: 新增card卡片

xuhui86 5 years ago
parent
commit
bd8c7a9920

File diff suppressed because it is too large
+ 7 - 0
src/assets/svg/arrow-down.svg


File diff suppressed because it is too large
+ 7 - 0
src/assets/svg/arrow-up.svg


+ 10 - 0
src/config.json

@@ -417,6 +417,16 @@
           "sort": "0",
           "showDemo": true,
           "author": "undo"
+        },
+        {
+          "version": "1.0.0",
+          "name": "Card",
+          "chnName": "卡片",
+          "desc": "卡片组件",
+          "type": "component",
+          "sort": "0",
+          "showDemo": true,
+          "author": "xuhui"
         }
     ]
 }

+ 4 - 1
src/nutui.js

@@ -84,6 +84,8 @@ import Badge from './packages/badge/index.js';
 import './packages/badge/badge.scss';
 import Field from "./packages/field/index.js";
 import "./packages/field/field.scss";
+import Card from "./packages/card/index.js";
+import "./packages/card/card.scss";
 
 const packages = {
   Cell,
@@ -128,7 +130,8 @@ const packages = {
   Swiper,
   ImagePreview,
   Badge,
-  Field: Field
+  Field: Field,
+  Card
 };
 
 const components = {};

+ 50 - 0
src/packages/card/__test__/card.spec.js

@@ -0,0 +1,50 @@
+import { shallowMount, mount } from '@vue/test-utils'
+import Card from '../card.vue';
+import Vue from 'vue';
+
+describe('Card.vue', () => {
+  const wrapper = shallowMount(Card, {});
+  it('不是通栏设置', () => {
+    wrapper.setProps({ isFull: true });
+    return Vue.nextTick().then(function () {
+      expect(wrapper.find('.nut-card-container').contains('full-content')).toBe(true);
+    })
+  })
+  it('设置卡片标题最大限度', () => {
+    wrapper.setProps({ title: '哈哈哈哈哈我是标题啦啦啦啦啦' });
+    return Vue.nextTick().then(function () {
+      expect(wrapper.find('.nut-card-title').text()).toBe('哈哈哈哈哈我是标题啦啦啦啦啦');
+    })
+  })
+  it('设置卡片内容', () => {
+    wrapper.setProps({ content: '哈哈哈哈哈我是内容啦啦啦啦啦哈哈哈哈哈我是内容啦啦啦啦啦哈哈哈哈哈我是内容啦啦啦啦啦' });
+    return Vue.nextTick().then(function () {
+      expect(wrapper.find('.mut-card-content').text()).toBe('哈哈哈哈哈我是内容啦啦啦啦啦哈哈哈哈哈我是内容啦啦啦啦啦哈哈哈哈哈我是内容啦啦啦啦啦');
+    })
+  })
+  it('设置卡片底部按钮', () => {
+    wrapper.setProps({
+      footerButtons: [
+        { event: 'click1', clickName: '操作一' },
+        { event: 'click2', clickName: '操作二' },
+      ] });
+    return Vue.nextTick().then(function () {
+      expect(wrapper.find('.mut-card-bottom')).toBe([
+        { event: 'click1', clickName: '操作一' },
+        { event: 'click2', clickName: '操作二' },
+      ]);
+    })
+  })
+  it('设置自动配置content', () => {
+    wrapper.setProps({ hasContent: true });
+    return Vue.nextTick().then(function () {
+      expect(wrapper.find('.mut-card-content')).toBe(true);
+    })
+  })
+  it('设置自动配置footer', () => {
+    wrapper.setProps({ hasFooter: true });
+    return Vue.nextTick().then(function () {
+      expect(wrapper.find('.mut-card-bottom')).toBe(true);
+    })
+  })
+});

+ 45 - 0
src/packages/card/card.scss

@@ -0,0 +1,45 @@
+.nut-card-container{
+  background: #fff;
+  border-radius: 4px;
+  margin: 15px;
+  .nut-card-header{
+    padding: 12px;
+    color: $text-black-1;
+    font-size: $font-size-body-small;
+    line-height: 24px;
+    .nut-card-title{
+      flex: 1;
+    }
+    .nut-card-icon{
+      flex-basis: 17px;
+      .nut-icon-self{
+        width: 100%
+      }
+    }
+  }
+  .switch-icon-class{
+    display: flex;
+    justify-content: space-between
+  }
+  .mut-card-content{
+    padding: 12px;
+    color: $text-black-2;
+    font-size: $font-size-body-small;
+    @include nut-cell-border;
+  }
+  .mut-card-bottom{
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    flex-wrap: wrap;
+    min-height: 52px;
+    padding: 0 12px;
+    @include nut-cell-border;
+    button{
+      margin: 0 5px;
+    }
+  }
+}
+.full-content{
+  margin: 0px;
+}

+ 92 - 0
src/packages/card/card.vue

@@ -0,0 +1,92 @@
+<template>
+  <div class="nut-card-container" :class="isFull ? 'full-content': ''">
+    <div
+      class="nut-card-header"
+      :class="openIcon ? 'switch-icon-class' : ''"
+      @click="headerClickHandler"
+    >
+      <slot name="title">
+        <div class="nut-card-title">{{title}}</div>
+        <div class="nut-card-icon" v-if="openIcon">
+          <nut-icon
+            type="self"
+            :url="isOpen ? require('../../assets/svg/arrow-up.svg') : require('../../assets/svg/arrow-down.svg')"
+          ></nut-icon>
+        </div>
+      </slot>
+    </div>
+    <template v-if="isOpen">
+      <div class="mut-card-content" v-if="content || hasContent">
+        <slot name="content">{{content}}</slot>
+      </div>
+      <div class="mut-card-bottom" v-if="(footerButtons && footerButtons.length>0)||hasFooter">
+        <slot name="footer">
+          <template v-for="(item,index) in footerButtons">
+            <nut-button
+              :key="index"
+              size="small"
+              type="bottom"
+              @click="clickHandler(item.event)"
+            >{{item.clickName}}</nut-button>
+          </template>
+        </slot>
+      </div>
+    </template>
+  </div>
+</template>
+<script>
+import locale from '../../mixins/locale';
+export default {
+  name: 'nut-card',
+  props: {
+    title: {
+      // 卡片标题
+      type: String,
+      default: ''
+    },
+    openIcon: {
+      // switch icon是否展示
+      type: Boolean,
+      default: false
+    },
+    content: {
+      // 卡片内容
+      type: String,
+      default: ''
+    },
+    hasContent: {
+      // 使用content solt
+      type: Boolean,
+      default: false
+    },
+    hasFooter: {
+      // 使用footer solt
+      type: Boolean,
+      default: false
+    },
+    isOpen: {
+      // 是否展开 默认都展示
+      type: Boolean,
+      default: true
+    },
+    footerButtons: {
+      type: Array, // 默认传入的点击事件
+      default: () => []
+    },
+    isFull: {
+      // 是否通栏
+      type: Boolean,
+      default: false
+    }
+  },
+  computed: {},
+  methods: {
+    clickHandler(event) {
+      this.$emit(event);
+    },
+    headerClickHandler() {
+      this.$emit('switchClick');
+    }
+  }
+};
+</script>

+ 80 - 0
src/packages/card/demo.vue

@@ -0,0 +1,80 @@
+<template>
+  <div class="demo-list">
+    <h4>基础样式</h4>
+    <nut-card
+      title="这里是默认卡片内容"
+      :footerButtons="footerButtons"
+      @click1="clickHandler1"
+      @click2="clickHandler2"
+    ></nut-card>
+
+    <h4>可添加标题的卡片</h4>
+    <nut-card title="卡片标题" content="这里是卡片内容区域"></nut-card>
+
+    <h4>带标题/操作</h4>
+    <nut-card
+      title="卡片标题"
+      content="这里是卡片内容区域"
+      :footerButtons="footerButtons"
+      @click1="clickHandler1"
+      @click2="clickHandler2"
+    ></nut-card>
+
+    <h4>可展开/收起的卡片</h4>
+    <nut-card title="卡片标题" content="这里是卡片内容区域" openIcon :isOpen="isOpen"  @switchClick="switchClick"></nut-card>
+
+    <h4>通栏卡片</h4>
+    <nut-card title="卡片标题" content="这里是卡片内容区域" isFull></nut-card>
+
+    <h4>通过Slot插槽分发内容</h4>
+    <nut-card hasContent hasFooter>
+      <div slot="title" class="card-title">
+        <div class="right">我是标题</div>
+        <div class="left">一小时</div>
+      </div>
+      <div slot="content">这里是卡片内容区域</div>
+      <div slot="footer">
+        <nut-button size="small" type="bottom" @click="clickHandler1">操作一</nut-button>
+        <nut-button size="small" type="bottom" @click="clickHandler2">操作二</nut-button>
+      </div>
+    </nut-card>
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      isOpen: false,
+      footerButtons: [
+        { event: 'click1', clickName: '操作一' }, // event的值 对应组件的点击事件 clickName为按钮显示的文案
+        { event: 'click2', clickName: '操作二' }
+      ]
+    };
+  },
+  methods: {
+    clickHandler1() {
+      // 操作1
+      this.$toast.text('操作一', { duration: 1000 });
+    },
+    clickHandler2() {
+      // 操作2
+      this.$toast.text('操作二', { duration: 1000 });
+    },
+    switchClick() {
+      // 展开收起卡片
+      this.isOpen = !this.isOpen;
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.card-title {
+  display: flex;
+  justify-content: space-between;
+  .left {
+    color: $text-black-4;
+  }
+}
+</style>

+ 107 - 0
src/packages/card/doc.md

@@ -0,0 +1,107 @@
+# Card 卡片
+
+
+## 基本用法
+
+基础样式
+
+```html
+<nut-card
+  title="这里是默认卡片内容"
+  :footerButtons="footerButtons"
+  @click1="clickHandler1"
+  @click2="clickHandler2"
+></nut-card>
+```
+
+
+可添加标题的卡片
+
+```html
+<nut-card title="卡片标题" content="这里是卡片内容区域"></nut-card>
+```
+
+带标题/操作
+```html
+<nut-card
+  title="卡片标题"
+  content="这里是卡片内容区域"
+  :footerButtons="footerButtons"
+  @click1="clickHandler1"
+  @click2="clickHandler2"
+></nut-card>
+```
+
+
+可展开/收起的卡片
+
+```html
+<nut-card title="卡片标题" content="这里是卡片内容区域" openIcon :isOpen="isOpen"  @switchClick="switchClick"></nut-card>
+```
+
+通栏卡片
+
+```html
+<nut-card title="卡片标题" content="这里是卡片内容区域" isFull></nut-card>
+```
+## 自定义内容
+Card组件提供了多个插槽,可以灵活地自定义内容
+
+```html
+<nut-card hasContent hasFooter>
+  <div slot="title" class="card-title">
+    <div class="right">我是标题</div>
+    <div class="left">一小时</div>
+  </div>
+  <div slot="content">这里是卡片内容区域</div>
+  <div slot="footer">
+    <nut-button size="small" type="bottom" @click="clickHandler1">操作一</nut-button>
+    <nut-button size="small" type="bottom" @click="clickHandler2">操作二</nut-button>
+  </div>
+</nut-card>
+```
+
+以上操作对应的js部分
+```javascript
+export default {
+  data() {
+    return {
+      isOpen: false,
+      footerButtons: [
+        { event: 'click1', clickName: '操作一' }, // event的值对应组件的点击事件, clickName的值为按钮显示的文案
+        { event: 'click2', clickName: '操作二' }
+      ]
+    };
+  },
+  methods: {
+    clickHandler1() {
+      // 操作1
+      this.$toast.text('操作一', { duration: 1000 });
+    },
+    clickHandler2() {
+      // 操作2
+      this.$toast.text('操作二', { duration: 1000 });
+    },
+    switchClick() {
+      // 展开收起卡片
+      this.isOpen = !this.isOpen;
+    }
+  }
+};
+```
+
+
+## Prop
+
+### nut-card
+
+| 字段    | 说明                                  | 类型   | 默认值                                                   |
+|---------|---------------------------------------|--------|----------------------------------------------------------|
+| title     | 标题 | String | '' |
+| openIcon  | 展开收起图标是否展示 | Boolean | false |
+| content  | 内容 | String  | '' |
+| isOpen  | 是否展开卡片内容 | Boolean  | true |
+| footerButtons  | 底部按钮 | Array  | [] |
+| isFull  | 是否为通栏卡片 | Boolean  | false |
+| hasContent  | 使用slot自定义content内容 | Boolean  | false |
+| hasFooter  | 使用slot自定义底部按钮 | Boolean  | false |

+ 8 - 0
src/packages/card/index.js

@@ -0,0 +1,8 @@
+import Card from './card.vue';
+import './card.scss';
+
+Card.install = function (Vue) {
+  Vue.component(Card.name, Card);
+};
+
+export default Card;