ソースを参照

add:tabSelect组件

dushoujun 5 年 前
コミット
914aafe89f

+ 11 - 1
src/config.json

@@ -502,6 +502,16 @@
       "sort": "5",
       "showDemo": true,
       "author": "杨凯旋"
+    },
+    {
+      "version": "1.0.0",
+      "name": "TabSelect",
+      "chnName": "分类选择",
+      "desc": "",
+      "type": "component",
+      "sort": "3",
+      "showDemo": true,
+      "author": "dsj"
     }
   ]
-}
+}

+ 8 - 5
src/nutui.js

@@ -96,6 +96,9 @@ import Elevator from "./packages/elevator/index.js";
 import "./packages/elevator/elevator.scss";
 import Popup from "./packages/popup/index.js";
 import "./packages/popup/popup.scss";
+import TabSelect from "./packages/tabselect/index.js";
+import "./packages/tabselect/tabselect.scss";
+
 const packages = {
   Cell,
   Dialog,
@@ -143,7 +146,8 @@ const packages = {
   TextBox,
   Avatar,
   Elevator,
-  Popup
+  Popup,
+  TabSelect: TabSelect
 };
 
 const components = {};
@@ -171,7 +175,7 @@ pkgList.map(item => {
   }
 });
 
-const install = function(Vue, opts = {}) {
+const install = function (Vue, opts = {}) {
   if (install.installed) return;
 
   if (opts.locale) {
@@ -209,8 +213,7 @@ const install = function(Vue, opts = {}) {
 
   Vue.use(Lazyload, {
     lazyComponent: true,
-    loading:
-      "//img12.360buyimg.com/imagetools/jfs/t1/73967/28/14561/916/5dc142e4E0666555b/bf33454553c6035e.png"
+    loading: "//img12.360buyimg.com/imagetools/jfs/t1/73967/28/14561/916/5dc142e4E0666555b/bf33454553c6035e.png"
   });
 };
 
@@ -227,4 +230,4 @@ export default {
   ...filters,
   ...directives,
   ...methods
-};
+};

+ 127 - 0
src/packages/tabselect/__test__/tabselect.spec.js

@@ -0,0 +1,127 @@
+import { shallowMount, mount } from "@vue/test-utils";
+import TabSelect from "../tabselect.vue";
+import Vue from "vue";
+
+describe("TabSelect.vue", () => {
+  const wrapper = mount(TabSelect);
+
+  it("mainTitle标题", () => {
+    wrapper.setProps({ mainTitle: "配送" });
+    return Vue.nextTick().then(function() {
+      expect(
+        wrapper
+          .findAll(".nut-tabselect-main-title")
+          .at(0)
+          .text()
+      ).toBe("配送");
+    });
+  });
+
+  it("subTitle标题", () => {
+    wrapper.setProps({ subTitle: "送达时间" });
+    return Vue.nextTick().then(function() {
+      setTimeout(() => {
+        expect(
+          wrapper
+            .findAll(".nut-tabselect-sub-title")
+            .at(0)
+            .text()
+        ).toBe("送达时间");
+      }, 200);
+    });
+  });
+
+  it("是否支持多选", () => {
+    wrapper.setProps({ multiple: true });
+    return Vue.nextTick().then(function() {
+      setTimeout(() => {
+        wrapper
+          .findAll(".nut-tab-panel-list")
+          .at(1)
+          .trigger("click");
+        wrapper
+          .findAll(".nut-tab-panel-list")
+          .at(2)
+          .trigger("click");
+        expect(
+          wrapper
+            .findAll(".nut-tab-panel-list")
+            .at(1)
+            .is(".nut-tab-panel-list-active")
+        ).toBe(true);
+        expect(
+          wrapper
+            .findAll(".nut-tab-panel-list")
+            .at(2)
+            .is(".nut-tab-panel-list-active")
+        ).toBe(true);
+      }, 200);
+    });
+  });
+
+  it("是否支持单选", () => {
+    wrapper.setProps({ multiple: false });
+    return Vue.nextTick().then(function() {
+      setTimeout(() => {
+        wrapper
+          .findAll(".nut-tab-panel-list")
+          .at(1)
+          .trigger("click");
+        wrapper
+          .findAll(".nut-tab-panel-list")
+          .at(2)
+          .trigger("click");
+        expect(
+          wrapper
+            .findAll(".nut-tab-panel-list")
+            .at(1)
+            .is(".nut-tab-panel-list-active")
+        ).toBe(false);
+        expect(
+          wrapper
+            .findAll(".nut-tab-panel-list")
+            .at(2)
+            .is(".nut-tab-panel-list-active")
+        ).toBe(true);
+      }, 200);
+    });
+  });
+
+  it("设置max", () => {
+    wrapper.setProps({ max: 2, multiple: true });
+    return Vue.nextTick().then(function() {
+      setTimeout(() => {
+        wrapper
+          .findAll(".nut-tab-panel-list")
+          .at(1)
+          .trigger("click");
+        wrapper
+          .findAll(".nut-tab-panel-list")
+          .at(2)
+          .trigger("click");
+        wrapper
+          .findAll(".nut-tab-panel-list")
+          .at(3)
+          .trigger("click");
+        expect(
+          wrapper
+            .findAll(".nut-tab-panel-list")
+            .at(1)
+            .is(".nut-tab-panel-list-active")
+        ).toBe(true);
+        expect(
+          wrapper
+            .findAll(".nut-tab-panel-list")
+            .at(2)
+            .is(".nut-tab-panel-list-active")
+        ).toBe(true);
+        expect(
+          wrapper
+            .findAll(".nut-tab-panel-list")
+            .at(3)
+            .is(".nut-tab-panel-list-active")
+        ).toBe(false);
+      }, 200);
+    });
+  });
+});

+ 132 - 0
src/packages/tabselect/demo.vue

@@ -0,0 +1,132 @@
+<template>
+  <div>
+    <nut-cell
+      isLink
+      title="展示单选弹出层"
+      :showIcon="true"
+      @click.native="show = true"
+    >
+    </nut-cell>
+    <nut-tabselect
+      :mainTitle="mainTitle"
+      :subTitle="subTitle"
+      :defaultContent="defaultContent"
+      :tabList="tabList"
+      :show="show"
+      @close="show = false"
+      @choose="choose"
+      :multiple="false"
+    ></nut-tabselect>
+
+    <nut-cell
+      isLink
+      title="展示多选弹出层"
+      :showIcon="true"
+      @click.native="showMore = true"
+    >
+    </nut-cell>
+    <nut-tabselect
+      :mainTitle="mainTitle"
+      :subTitle="subTitle"
+      :defaultContent="defaultContent"
+      :tabList="tabList"
+      :show="showMore"
+      @close="showMore = false"
+      @choose="choose"
+      :multiple="true"
+      :max="3"
+    ></nut-tabselect>
+  </div>
+</template>
+
+<script>
+export default {
+  components: {},
+  data() {
+    return {
+      mainTitle: "配送",
+      subTitle: "送达时间",
+      defaultContent: [
+        "9:00——10:00",
+        "10:00——11:00",
+        "11:00——12:00",
+        "12:00——13:00",
+        "13:00——15:00",
+        "15:00——17:00",
+        "17:00——19:00"
+      ],
+      tabList: [
+        {
+          tabTitle: "京东快递",
+          children: [
+            {
+              tabTitle: "1月13日 (星期一)",
+              content: [
+                "11:00——12:00",
+                "12:00——13:00",
+                "13:00——15:00",
+                "15:00——17:00",
+                "17:00——19:00"
+              ]
+            },
+            {
+              tabTitle: "1月14日 (星期二)"
+            },
+            {
+              tabTitle: "1月15日 (星期三)"
+            },
+            {
+              tabTitle: "1月16日 (星期四)"
+            },
+            {
+              tabTitle: "1月17日 (星期五)"
+            },
+            {
+              tabTitle: "1月18日 (星期六)"
+            },
+            {
+              tabTitle: "1月19日 (星期天)"
+            }
+          ]
+        },
+        {
+          tabTitle: "上门自提",
+          children: [
+            {
+              tabTitle: "2月13日 (星期一)",
+              content: ["13:00——15:00", "15:00——17:00", "17:00——19:00"]
+            },
+            {
+              tabTitle: "2月14日 (星期二)"
+            },
+            {
+              tabTitle: "2月15日 (星期三)"
+            },
+            {
+              tabTitle: "2月16日 (星期四)"
+            },
+            {
+              tabTitle: "2月17日 (星期五)"
+            },
+            {
+              tabTitle: "2月18日 (星期六)"
+            },
+            {
+              tabTitle: "2月19日 (星期天)"
+            }
+          ]
+        }
+      ],
+      show: false,
+      showMore: false
+    };
+  },
+  methods: {
+    choose(title, item) {
+      console.log(title, item);
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped></style>

+ 142 - 0
src/packages/tabselect/doc.md

@@ -0,0 +1,142 @@
+# TabSelect 分类选择
+
+## 基本用法
+
+```html
+<nut-tabselect
+  :mainTitle="mainTitle"
+  :subTitle="subTitle"
+  :defaultContent="defaultContent"
+  :tabList="tabList"
+  :show="show"
+  @close="show = false"
+  @choose="choose"
+  :multiple="false"
+></nut-tabselect>
+```
+
+## 基本用法(多选模式)
+
+```html
+<nut-tabselect
+  :mainTitle="mainTitle"
+  :subTitle="subTitle"
+  :defaultContent="defaultContent"
+  :tabList="tabList"
+  :show="show"
+  @close="show = false"
+  @choose="choose"
+  :multiple="true"
+  :max="3"
+></nut-tabselect>
+```
+
+```javascript
+export default {
+  components: {},
+  data() {
+    return {
+      mainTitle: "配送",
+      subTitle: "送达时间",
+      defaultContent: [
+        "9:00——10:00",
+        "10:00——11:00",
+        "11:00——12:00",
+        "12:00——13:00",
+        "13:00——15:00",
+        "15:00——17:00",
+        "17:00——19:00"
+      ],
+      tabList: [
+        {
+          tabTitle: "京东快递", // 一级tab标题
+          children: [
+            // 一级tab内容
+            {
+              tabTitle: "1月13日 (星期一)", // 二级tab标题
+              content: [
+                // 二级tab内容,不传默认使用defaultContent字段
+                "11:00——12:00",
+                "12:00——13:00",
+                "13:00——15:00",
+                "15:00——17:00",
+                "17:00——19:00"
+              ]
+            },
+            {
+              tabTitle: "1月14日 (星期二)"
+            },
+            {
+              tabTitle: "1月15日 (星期三)"
+            },
+            {
+              tabTitle: "1月16日 (星期四)"
+            },
+            {
+              tabTitle: "1月17日 (星期五)"
+            },
+            {
+              tabTitle: "1月18日 (星期六)"
+            },
+            {
+              tabTitle: "1月19日 (星期天)"
+            }
+          ]
+        },
+        {
+          tabTitle: "上门自提",
+          children: [
+            {
+              tabTitle: "2月13日 (星期一)",
+              content: ["13:00——15:00", "15:00——17:00", "17:00——19:00"]
+            },
+            {
+              tabTitle: "2月14日 (星期二)"
+            },
+            {
+              tabTitle: "2月15日 (星期三)"
+            },
+            {
+              tabTitle: "2月16日 (星期四)"
+            },
+            {
+              tabTitle: "2月17日 (星期五)"
+            },
+            {
+              tabTitle: "2月18日 (星期六)"
+            },
+            {
+              tabTitle: "2月19日 (星期天)"
+            }
+          ]
+        }
+      ],
+      show: false
+    };
+  },
+  methods: {
+    choose(title, item) {
+      console.log(title, item);
+    }
+  }
+};
+```
+
+### Prop
+
+| 字段           | 说明                        | 类型    | 默认值   |
+| -------------- | --------------------------- | ------- | -------- |
+| mainTitle      | 一级 tab 标题               | String  | ''       |
+| subTitle       | 二级 tab 标题               | String  | ''       |
+| defaultContent | 二级 tab 下内容完全一致时传 | Array   | null     |
+| multiple       | 是否允许多选                | Boolean | false    |
+| tabList        | 整体数据                    | Array   | null     |
+| show           | 是否显示                    | Boolean | false    |
+| max            | 多选时最多可选个数          | Number  | Infinity |
+
+### Event
+
+| 事件名称 | 说明                 | 回调参数                               |
+| -------- | -------------------- | -------------------------------------- |
+| choose   | 切换页签或选中某一项 | 点击的一级 tab ,二级 tab ,选中项内容 |
+| close    | 组件隐藏时           | --                                     |

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

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

+ 129 - 0
src/packages/tabselect/tabselect.scss

@@ -0,0 +1,129 @@
+.nut-tabselect {
+  .nut-tab {
+    background: none;
+    border: none;
+    padding: 0;
+  }
+  .nav-bar {
+    display: none;
+  }
+  .nut-tab-item {
+    padding: 0;
+  }
+  .nut-tab-link {
+    font-size: 14px;
+  }
+  .nut-tab-title {
+    border: none;
+    height: auto;
+    line-height: auto;
+    padding-left: 18px;
+    .nut-title-nav-list {
+      flex: none;
+      padding: 0 13px;
+      height: 30px;
+      line-height: 30px;
+      border: 1px solid #333;
+      border-radius: 15px;
+      margin-right: 15px;
+      a {
+        color: #333;
+      }
+    }
+    .nut-tab-active {
+      border-color: #e2231a;
+      background: #fcedeb;
+      a {
+        color: #e2231a;
+      }
+    }
+  }
+  .nut-tab-inner {
+    .nut-tab-title-leftnav {
+      min-width: 158px;
+    }
+    .nut-title-nav {
+      height: 40px;
+      line-height: 40px;
+      background: #f4f4f4;
+      padding-left: 18px;
+    }
+    .nut-tab-active {
+      background: #fff;
+    }
+    .nut-tab-link {
+      line-height: inherit;
+      font-size: 14px;
+    }
+    .nut-tab-item {
+      padding: 0 10px 0 17px;
+    }
+    .nut-tab-panel {
+      max-height: 280px;
+      overflow-y: auto;
+    }
+  }
+  .nut-tab-title-leftnav {
+    border: none;
+    max-height: 280px;
+    overflow-y: auto;
+    .nut-title-nav {
+      border: none;
+    }
+  }
+  .nut-tabselect-main-title {
+    margin: 12px 0 8px 18px;
+    font-size: 18px;
+    line-height: 25px;
+    color: #000;
+    font-weight: bold;
+  }
+  .nut-tabselect-sub-title {
+    margin: 22px 0 11px 18px;
+    line-height: 20px;
+    color: #666;
+    font-size: 14px;
+  }
+
+  .nut-tab-panel li {
+    height: 29px;
+    line-height: 29px;
+    color: #333;
+    border: 1px solid #999999;
+    padding-left: 15px;
+    margin-bottom: 10px;
+    border-radius: 2px;
+    cursor: pointer;
+    &.nut-tab-panel-active {
+      color: #e2231a;
+      border: 1px solid #e2231a;
+      background: #fcedeb;
+    }
+  }
+  .popup-bottom.round {
+    border-radius: 12px 12px 0px 0px;
+  }
+  .nut-tabselect-btn {
+    display: flex;
+    background: #fff;
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    padding: 12px 18px;
+    a {
+      flex: 1;
+      height: 30px;
+      line-height: 30px;
+      background: linear-gradient(
+        135deg,
+        rgba(242, 20, 12, 1) 0%,
+        rgba(242, 39, 12, 1) 70%,
+        rgba(242, 77, 12, 1) 100%
+      );
+      border-radius: 15px;
+      color: #fff;
+      text-align: center;
+    }
+  }
+}

+ 193 - 0
src/packages/tabselect/tabselect.vue

@@ -0,0 +1,193 @@
+<template>
+  <div class="nut-tabselect">
+    <nut-popup
+      round
+      v-model="isShow"
+      position="bottom"
+      :style="{ height: '457px' }"
+    >
+      <div class="nut-tabselect-main-title" v-html="mainTitle"></div>
+      <nut-tab @tab-switch="tabSwitchOuter">
+        <nut-tab-panel
+          v-for="(value, idx) in tabList"
+          v-bind:key="value.tabTitle"
+          :tabTitle="value.tabTitle"
+        >
+          <div class="nut-tabselect-sub-title" v-html="subTitle"></div>
+          <nut-tab
+            @tab-switch="tabSwitchInner"
+            positionNav="left"
+            class="nut-tab-inner"
+          >
+            <nut-tab-panel
+              v-for="(item, index) in value.children"
+              v-bind:key="item.tabTitle"
+              :tabTitle="item.tabTitle"
+            >
+              <ul>
+                <template v-if="item.content">
+                  <li
+                    v-for="(sitem, sIndex) in item.content"
+                    v-bind:key="sitem"
+                    @click="choose(idx, index, sIndex, item, sitem)"
+                    class="nut-tab-panel-list"
+                    :class="{
+                      'nut-tab-panel-list-active': isActive(idx, index, sIndex)
+                    }"
+                  >
+                    {{ sitem }}
+                  </li>
+                </template>
+                <template v-else-if="defaultContent">
+                  <li
+                    v-for="(sitem, sIndex) in defaultContent"
+                    v-bind:key="sitem"
+                    @click="choose(idx, index, sIndex, item, sitem)"
+                    class="nut-tab-panel-list"
+                    :class="{
+                      'nut-tab-panel-list-active': isActive(idx, index, sIndex)
+                    }"
+                  >
+                    {{ sitem }}
+                  </li>
+                </template>
+              </ul>
+            </nut-tab-panel>
+          </nut-tab>
+        </nut-tab-panel>
+      </nut-tab>
+      <div class="nut-tabselect-btn">
+        <a href="javascript:;" @click="isShow = false">确定</a>
+      </div>
+    </nut-popup>
+  </div>
+</template>
+<script>
+import nuttab from "../tab/tab.vue";
+import "../tab/tab.scss";
+import nutpop from "../popup/popup.vue";
+import "../popup/popup.scss";
+export default {
+  name: "nut-tabselect",
+  props: {
+    mainTitle: {
+      type: String,
+      default: ""
+    },
+    subTitle: {
+      type: String,
+      default: ""
+    },
+    defaultContent: {
+      type: Array,
+      default: () => []
+    },
+    tabList: {
+      type: Array,
+      default: () => []
+    },
+    show: {
+      type: Boolean,
+      default: false
+    },
+    multiple: {
+      type: Boolean,
+      default: false
+    },
+    max: {
+      type: Number,
+      default: Infinity
+    }
+  },
+  data() {
+    return {
+      isShow: false,
+      level0: 0,
+      level1: new Set([0]),
+      level2: new Set(["0-0"]),
+      allChoose: new Set([this.getText(0, 0, 0)])
+    };
+  },
+  components: {
+    [nuttab.name]: nuttab,
+    [nutpop.name]: nutpop
+  },
+  watch: {
+    show(val) {
+      this.isShow = val;
+    },
+    isShow(val) {
+      if (!val) {
+        this.$emit("close");
+      }
+    }
+  },
+  mounted() {
+    this.emit();
+  },
+  methods: {
+    emit() {
+      this.$emit(
+        "choose",
+        (this.tabList[this.level0] && this.tabList[this.level0].tabTitle) || "",
+        [...this.allChoose]
+      );
+    },
+    getText(idx, index, sIndex) {
+      const tab =
+        (this.tabList[idx] && this.tabList[idx].children[index]) || {};
+      const subTit = tab.tabTitle;
+      const content =
+        (tab.content && tab.content[sIndex]) || this.defaultContent[sIndex];
+      return subTit + " " + content;
+    },
+    tabSwitchOuter: function(index, event) {
+      this.level0 = index;
+      this.level1 = new Set([0]);
+      this.level2 = new Set(["0-0"]);
+      this.allChoose = new Set([this.getText(index, 0, 0)]);
+      this.emit();
+    },
+    tabSwitchInner: function(index, event) {
+      if (!this.multiple) {
+        this.level1 = new Set([index]);
+      } else {
+        this.level1.add(index);
+      }
+    },
+    unChoose(index, sIndex) {
+      this.level2.delete(index + "-" + sIndex);
+      this.level2 = new Set(this.level2);
+    },
+    choose(idx, index, sIndex) {
+      if (this.multiple && this.isActive(idx, index, sIndex)) {
+        this.unChoose(index, sIndex);
+        this.allChoose.delete(this.getText(idx, index, sIndex));
+        this.emit();
+        return;
+      }
+      if (!this.multiple) {
+        this.level2 = new Set([index + "-" + sIndex]);
+        this.allChoose = new Set([this.getText(index, 0, 0)]);
+      } else {
+        if (this.max !== Infinity && this.max === this.level2.size) {
+          return;
+        }
+        this.level2 = new Set([...this.level2.add(index + "-" + sIndex)]);
+        this.allChoose.add(this.getText(idx, index, sIndex));
+      }
+      this.emit();
+    },
+    isActive(idx, index, sIndex) {
+      if (
+        idx === this.level0 &&
+        this.level1.has(index) &&
+        this.level2.has(index + "-" + sIndex)
+      ) {
+        return true;
+      }
+      return false;
+    }
+  }
+};
+</script>

+ 1 - 0
types/nutui.d.ts

@@ -64,3 +64,4 @@ export declare class Avatar extends UIComponent {}
 export declare class Infiniteloading extends UIComponent {}
 export declare class Lazyload extends UIComponent {}
 export declare class Elevator extends UIComponent {}
+export declare class TabSelect extends UIComponent {}