Browse Source

feat: nutui-collapse 组件开发

yumingming11 5 years ago
parent
commit
98123873e3

+ 23 - 4
src/config.json

@@ -1,5 +1,13 @@
 {
-  "sorts": ["数据展示", "数据录入", "操作反馈", "导航组件", "布局组件", "基础组件", "业务组件"],
+  "sorts": [
+    "数据展示",
+    "数据录入",
+    "操作反馈",
+    "导航组件",
+    "布局组件",
+    "基础组件",
+    "业务组件"
+  ],
   "packages": [
     {
       "name": "Cell",
@@ -524,7 +532,7 @@
       "type": "component",
       "sort": "6",
       "showDemo": true,
-      "author": "ymm"
+      "author": "Ymm0008"
     },
     {
       "version": "1.0.0",
@@ -642,7 +650,7 @@
       "desc": "用于数据展示",
       "sort": "0",
       "showDemo": true,
-      "author": "yumingming"
+      "author": "Ymm0008"
     },
     {
       "name": "FixedNav",
@@ -652,6 +660,17 @@
       "sort": "3",
       "showDemo": true,
       "author": "richard1015"
+    },
+    {
+      "version": "1.0.0",
+      "name": "Collapse",
+      "type": "component",
+      "chnName": "折叠面板",
+      "desc": "可以折叠/展开的内容区域",
+      "sort": "0",
+      "showDemo": true,
+      "author": "Ymm0008",
+      "showTest": true
     }
   ]
-}
+}

+ 7 - 6
src/nutui.js

@@ -119,8 +119,7 @@ import './packages/subsidenavbar/subsidenavbar.scss';
 import SideNavBarItem from './packages/sidenavbaritem/index.js';
 import './packages/sidenavbaritem/sidenavbaritem.scss';
 import Drag from './packages/drag/index.js';
-import './packages/drag/drag.scss'; 
-// import VueQr from "./packages/qart/index.js";
+import './packages/drag/drag.scss'; // import VueQr from "./packages/qart/index.js";
 // import "./packages/qart/qart.scss";
 
 import Address from './packages/address/index.js';
@@ -130,10 +129,12 @@ import './packages/notify/notify.scss';
 import CountUp from './packages/countup/index.js';
 import './packages/countup/countup.scss';
 import FixedNav from './packages/fixednav/index.js';
-import './packages/fixednav/fixednav.scss';
-// import Gesture from './packages/gesture/index.js';
+import './packages/fixednav/fixednav.scss'; // import Gesture from './packages/gesture/index.js';
 // import './packages/gesture/gesture.scss';
 
+import Collapse from './packages/collapse/index.js';
+import './packages/collapse/collapse.scss';
+
 const packages = {
   Cell,
   Dialog,
@@ -197,8 +198,8 @@ const packages = {
   Address,
   Notify,
   CountUp,
-  FixedNav,
-  // Gesture: Gesture
+  FixedNav, // Gesture: Gesture
+  Collapse: Collapse
 };
 
 const components = {};

+ 5 - 0
src/packages/collapse/__test__/collapse.spec.js

@@ -0,0 +1,5 @@
+import { shallowMount, mount } from '@vue/test-utils';
+import Collapse from '../collapse.vue';
+import Vue from 'vue';
+
+describe('Collapse.vue', () => {});

+ 133 - 0
src/packages/collapse/collapse-item.vue

@@ -0,0 +1,133 @@
+<template>
+  <div :class="['nut-collapse-item', { 'nut-collapse-item-left': classDirection == 'left' }, { 'nut-collapse-item-icon': this.$parent.icon }]">
+    <div :class="['collapse-item', { 'item-expanded': openExpanded }, { 'nut-collapse-item-disabled': disabled }]" @click="toggleOpen">
+      <div class="collapse-title">
+        <span v-html="title"></span>
+      </div>
+      <span v-if="subTitle" v-html="subTitle" class="subTitle"></span>
+      <i
+        v-if="this.$parent.icon"
+        :class="['collapse-icon', { 'col-expanded': openExpanded }, { 'collapse-icon-disabled': disabled }]"
+        :style="iconStyle"
+      ></i>
+      <i v-else :class="['collapse-icon', { 'col-expanded': openExpanded }, { 'collapse-icon-disabled': disabled }]"></i>
+    </div>
+    <div :class="['collapse-wrapper']" ref="wrapper">
+      <div class="collapse-content" ref="content">
+        <slot></slot>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+export default {
+  name: 'nut-collapse-item',
+  props: {
+    title: {
+      type: String,
+      default: ''
+    },
+    subTitle: {
+      type: String,
+      default: ''
+    },
+    disabled: {
+      type: Boolean,
+      default: false
+    },
+    name: {
+      type: Number | String,
+      default: -1,
+      required: true
+    }
+  },
+  mounted() {
+    this.$nextTick(() => {
+      let active = this.$parent && this.$parent.value;
+      if (typeof active == 'number' || typeof active == 'string') {
+        if (this.name == active) {
+          this.defaultOpen();
+        }
+      }
+      if (active instanceof Array) {
+        let f = active.filter(item => item == this.name);
+        if (f.length > 0) {
+          this.defaultOpen();
+        }
+      }
+    });
+    this.classDirection = this.$parent.expandIconPosition;
+    if (this.$parent.icon) {
+      this.$set(this.iconStyle, 'background-image', 'url(' + this.$parent.icon + ')');
+    }
+  },
+  data() {
+    return {
+      openExpanded: false,
+      classDirection: 'right',
+      iconStyle: {
+        'background-image': 'url(https://img10.360buyimg.com/imagetools/jfs/t1/111306/10/17422/341/5f58aa0eEe9218dd6/28d76a42db334e31.png)',
+        'background-repeat': 'no-repeat',
+        'background-size': '100% 100%',
+        transform: 'rotate(0deg)'
+      }
+    };
+  },
+  methods: {
+    defaultOpen() {
+      this.$parent.accordion ? this.$parent.accordionFun(this.name) : this.open();
+      if (this.$parent.icon) {
+        this.$set(this.iconStyle, 'transform', 'rotate(' + this.$parent.rotate + 'deg)');
+      }
+    },
+    toggleOpen() {
+      this.$parent.changeEvt(this.name);
+      if (this.$parent.accordion) {
+        this.$parent.changeVal(this.name);
+        if (this.$parent.value == this.name) {
+          this.$parent.accordionFun(this.name);
+        }
+        this.animation();
+      } else {
+        this.$parent.changeValAry(this.name);
+        this.open();
+      }
+    },
+    open() {
+      this.openExpanded = !this.openExpanded;
+      this.animation();
+    },
+    // 手风琴模式
+    animation() {
+      this.$nextTick(() => {
+        const { content, wrapper } = this.$refs;
+        if (!content || !wrapper) {
+          return;
+        }
+        const { offsetHeight } = content;
+        if (offsetHeight) {
+          const contentHeight = `${offsetHeight}px`;
+          wrapper.style.willChange = 'height';
+          wrapper.style.height = !this.openExpanded ? 0 : contentHeight;
+          if (this.$parent.icon && !this.openExpanded) {
+            this.$set(this.iconStyle, 'transform', 'rotate(0deg)');
+          } else {
+            this.$set(this.iconStyle, 'transform', 'rotate(' + this.$parent.rotate + 'deg)');
+          }
+        }
+        if (!this.openExpanded) {
+          this.onTransitionEnd();
+        }
+      });
+    },
+    // 更改子组件展示
+    changeOpen(bol) {
+      this.openExpanded = bol;
+    },
+    // 清除 willChange 减少性能浪费
+    onTransitionEnd() {
+      this.$refs.wrapper.style.willChange = 'auto';
+    }
+  }
+};
+</script>

+ 94 - 0
src/packages/collapse/collapse.scss

@@ -0,0 +1,94 @@
+.nut-collapse {
+  position: relative;
+  .collapse-item,
+  .collapse-wrapper {
+    &::after {
+      position: absolute;
+      box-sizing: border-box;
+      content: ' ';
+      pointer-events: none;
+      right: 16px;
+      bottom: 0;
+      left: 16px;
+      border-bottom: 1px solid #ebedf0;
+      -webkit-transform: scaleY(0.5);
+      transform: scaleY(0.5);
+    }
+  }
+  .nut-collapse-item {
+    .collapse-item {
+      position: relative;
+      width: 100%;
+      overflow: hidden;
+      padding: 10px 16px;
+      color: #323233;
+      font-size: 14px;
+      line-height: 24px;
+      background-color: #fff;
+      box-sizing: border-box;
+      .collapse-icon {
+        display: block;
+        position: absolute;
+        top: 50%;
+        margin-top: -10px;
+        right: 16px;
+        width: 20px;
+        height: 20px;
+        line-height: 24px;
+        background-image: url(https://img10.360buyimg.com/imagetools/jfs/t1/111306/10/17422/341/5f58aa0eEe9218dd6/28d76a42db334e31.png);
+        background-repeat: no-repeat;
+        background-size: 100% 100%;
+        transition: transform 0.3s;
+      }
+      .col-expanded {
+        transform: rotate(-180deg);
+      }
+      .subTitle {
+        position: absolute;
+        top: 50%;
+        right: 40px;
+        margin-top: -12px;
+        color: #969799;
+      }
+    }
+    .collapse-wrapper {
+      position: relative;
+      height: 0;
+      overflow: hidden;
+      transition: height 0.3s ease-in-out;
+      .collapse-content {
+        padding: 12px 16px;
+        color: #969799;
+        font-size: 14px;
+        line-height: 1.5;
+        background-color: #fff;
+      }
+    }
+    .nut-collapse-item-disabled {
+      color: #c8c9cc;
+      cursor: not-allowed;
+      pointer-events: none;
+      .collapse-icon-disabled {
+        background-image: url(https://img12.360buyimg.com/imagetools/jfs/t1/150037/5/8088/344/5f5b0bf2E214aac54/ec3e64ce3fc46200.png);
+        background-repeat: no-repeat;
+        background-size: 100% 100%;
+      }
+    }
+  }
+  .nut-collapse-item-left {
+    .collapse-item {
+      padding: 10px 16px 10px 50px;
+      .collapse-icon {
+        left: 20px;
+      }
+      .subTitle {
+        right: 16px;
+      }
+    }
+  }
+  // .nut-collapse-item.nut-collapse-item-icon {
+  //     .collapse-icon {
+  //         transform: rotate(0deg);
+  //     }
+  // }
+}

+ 75 - 0
src/packages/collapse/collapse.vue

@@ -0,0 +1,75 @@
+<template>
+  <div class="nut-collapse" @changeEvt="changeEvt">
+    <slot></slot>
+  </div>
+</template>
+<script>
+import nutCollapseItem from './collapse-item';
+export default {
+  name: 'nut-collapse',
+  components: {
+    nutCollapseItem
+  },
+  model: {
+    prop: 'value',
+    event: 'changeActive'
+  },
+  props: {
+    value: {
+      type: String | Number
+    },
+    accordion: {
+      type: Boolean
+    },
+    expandIconPosition: {
+      type: String,
+      default: 'right'
+    },
+    icon: {
+      type: String,
+      default: ''
+    },
+    rotate: {
+      type: Number | String,
+      default: 180
+    }
+  },
+  watch: {
+    value(newVal, oldVal) {
+      this.accordionFun(newVal);
+    }
+  },
+  data() {
+    return {};
+  },
+  methods: {
+    changeEvt(name) {
+      this.$parent.change(name);
+    },
+    changeValAry(name) {
+      let index = this.value.indexOf(name);
+      let v = JSON.parse(JSON.stringify(this.value));
+      index > -1 ? v.splice(index, 1) : v.push(name);
+      this.$emit('changeActive', v);
+    },
+    changeVal(val) {
+      this.$emit('changeActive', val);
+    },
+    // 手风琴模式将所有的item收起,然后对应的展开(默认)
+    // 对于展开的再次点击的将其设置成收起,动画效果在item组件中执行
+    accordionFun(val) {
+      if (val instanceof Array) {
+      } else {
+        this.$children.forEach(item => {
+          if (item.name == val && item.openExpanded) {
+            item.changeOpen(false);
+          } else {
+            item.name == val ? item.changeOpen(true) : item.changeOpen(false);
+            item.animation();
+          }
+        });
+      }
+    }
+  }
+};
+</script>

+ 68 - 0
src/packages/collapse/demo.vue

@@ -0,0 +1,68 @@
+<template>
+  <div class="demo-list">
+    <h4>基本用法</h4>
+    <div class="show-demo">
+      <nut-collapse v-model="active1">
+        <nut-collapse-item :title="title1" :name="1">
+          京东“厂直优品计划”首推“政府优品馆” 3年覆盖80%镇级政府
+        </nut-collapse-item>
+        <nut-collapse-item :title="title2" :name="2">
+          京东到家:教师节期间 创意花束销量增长53倍
+        </nut-collapse-item>
+        <nut-collapse-item :title="title3" :name="3" disabled> </nut-collapse-item>
+      </nut-collapse>
+    </div>
+    <div class="show-demo">
+      <h4>手风琴</h4>
+      <nut-collapse v-model="active2" :accordion="true">
+        <nut-collapse-item :title="title1" :name="1">
+          华为终端操作系统EMUI 11发布,9月11日正式开启
+        </nut-collapse-item>
+        <nut-collapse-item :title="title2" :name="2" :subTitle="subTitle">
+          中国服务机器人市场已占全球市场超1/4
+        </nut-collapse-item>
+        <nut-collapse-item :title="title3" :name="3">
+          QuestMobile:90后互联网用户规模超越80后达3.62亿
+        </nut-collapse-item>
+      </nut-collapse>
+    </div>
+    <div class="show-demo">
+      <h4>图标展示</h4>
+      <nut-collapse v-model="active3" :accordion="true" :expandIconPosition="expandIconPosition" :icon="icon" :rotate="rotate">
+        <nut-collapse-item :title="title1" :name="1">
+          京东数科IPO将引入“绿鞋机制”
+        </nut-collapse-item>
+        <nut-collapse-item :title="title2" :name="2">
+          世界制造业大会开幕,阿里巴巴与安徽合作再升级
+        </nut-collapse-item>
+      </nut-collapse>
+    </div>
+  </div>
+</template>
+<script>
+import nutCollapseItem from './collapse-item';
+export default {
+  components: {
+    nutCollapseItem
+  },
+  data() {
+    return {
+      active1: [1, '2'],
+      active2: 1,
+      active3: 1,
+      expandIconPosition: 'left',
+      title1: '标题1',
+      title2: '标题2',
+      title3: '标题3',
+      subTitle: '副标题',
+      icon: 'https://img11.360buyimg.com/imagetools/jfs/t1/132849/8/9709/550/5f5f0d8aE802abee7/68bd02b3a52c3988.png',
+      rotate: 90
+    };
+  },
+  methods: {
+    change(name) {
+      console.log(`点击了name是${name}的面板`);
+    }
+  }
+};
+</script>

+ 111 - 0
src/packages/collapse/doc.md

@@ -0,0 +1,111 @@
+# Collapse 折叠面板
+
+## 基本用法
+
+通过`v-model`控制展开的面板列表,`activeNames`为数组格式
+
+```html
+<nut-collapse v-model="activeNames">
+    <nut-collapse-item title="标题1" :name="1">
+        京东“厂直优品计划”首推“政府优品馆” 3年覆盖80%镇级政府 
+    </nut-collapse-item>
+    <nut-collapse-item title="标题2" :name="2">
+        京东到家:教师节期间 创意花束销量增长53倍 
+    </nut-collapse-item>
+    <nut-collapse-item title="标题3" :name="3" disabled>
+    </nut-collapse-item>
+</nut-collapse>
+```
+
+``` javascript
+export default {
+  data() {
+    return {
+        activeNames: [1, 2]
+    };
+  }
+};
+```
+
+
+### 手风琴
+
+通过`accordion`可以设置为手风琴模式,最多展开一个面板,此时`activeName`为字符串格式;`subTitle`可以设置副标题的内容
+
+```html
+<nut-collapse v-model="activeName" :accordion="true">
+    <nut-collapse-item :title="title1" :name="1">
+        华为终端操作系统EMUI 11发布,9月11日正式开启 
+    </nut-collapse-item>
+    <nut-collapse-item :title="title2" :name="2" :subTitle="subTitle">
+        中国服务机器人市场已占全球市场超1/4 
+    </nut-collapse-item>
+    <nut-collapse-item :title="title3" :name="3">
+        QuestMobile:90后互联网用户规模超越80后达3.62亿 
+    </nut-collapse-item>
+</nut-collapse>
+```
+
+``` javascript
+export default {
+  data() {
+    return {
+      activeName: 1,
+      subTitle: '副标题'
+    };
+  }
+};
+```
+
+
+### 图标展示
+
+通过`expandIconPosition`可以设置图标的位置,
+
+```html
+<nut-collapse v-model="activeName" :accordion="true" :expandIconPosition="expandIconPosition" :icon="icon" :rotate="rotate">
+    <nut-collapse-item :title="title1" :name="1">
+        京东数科IPO将引入“绿鞋机制” 
+    </nut-collapse-item>
+    <nut-collapse-item :title="title2" :name="2">
+        世界制造业大会开幕,阿里巴巴与安徽合作再升级
+    </nut-collapse-item>
+</nut-collapse>
+```
+
+``` javascript
+export default {
+  data() {
+    return {
+      activeName: 1,
+      expandIconPosition: 'left',
+      icon: 'https://img11.360buyimg.com/imagetools/jfs/t1/132849/8/9709/550/5f5f0d8aE802abee7/68bd02b3a52c3988.png'
+      rotate: 180,
+    };
+  }
+};
+```
+
+## Collapse Prop
+
+| 字段 | 说明 | 类型 | 默认值
+|----- | ----- | ----- | ----- 
+| v-model | 当前展开面板的 name | 手风琴模式:string \| number<br>非手风琴模式:(string \| number)[] | - |
+| accordion | 是否开启手风琴模式 | boolean | false |
+| border | 是否显示外边框 | boolean | true |
+
+### Events
+
+| 事件名 | 说明 | 回调参数 |
+|------|------|------|
+| change | 切换面板时触发 | 类型与 v-model 绑定的值一致 |
+
+### CollapseItem Props
+| 参数 | 说明 | 类型 | 默认值 | 
+|------|------|------|------|
+| title | 标题栏左侧内容 | string | - |
+| name | 唯一标识符,必填 | string \ number | -1 |
+| expandIconPosition | 标题图标的位置 | string | right |
+| subTitle | 标题栏副标题 | string | - |
+| icon | 标题栏自定义图标链接 | string | - |
+| rotate | 点击折叠和展开的旋转角度,在自定义图标模式下生效 | string \ number | 180 |

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

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

+ 1 - 0
types/nutui.d.ts

@@ -84,3 +84,4 @@ export declare class Notify extends UIComponent {}
 export declare class CountUp extends UIComponent {}
 export declare class FixedNav extends UIComponent {}
 export declare class Gesture extends UIComponent {}
+export declare class Collapse extends UIComponent {}