ソースを参照

Merge branch 'v2' of https://github.com/jdf2e/nutui into v2

yangkaixuan 5 年 前
コミット
82834aad3c

+ 163 - 163
package.json

@@ -1,165 +1,165 @@
 {
-  "name": "@nutui/nutui",
-  "version": "2.1.9",
-  "description": "一套轻量级移动端Vue组件库",
-  "typings": "dist/types/index.d.ts",
-  "main": "dist/nutui.js",
-  "files": [
-    "dist",
-    "README.md",
-    "package.json",
-    "LICENSE",
-    "CHANGELOG.md"
-  ],
-  "scripts": {
-    "dev": "npm run dev:new",
-    "dev:new": "cross-env NODE_ENV=development DOC_TYPE=true webpack-dev-server -d --open  --config build/webpack.dev.conf.js",
-    "dev:carefree": "cross-env NODE_ENV=carefree carefree_env=dev webpack -w --colors --progress --config build/webpack.demo.dev.conf.js",
-    "dev:demo": "cross-env NODE_ENV=development webpack-dev-server -d --open  --config build/webpack.demo.dev.conf.js",
-    "dev:doc": "cross-env NODE_ENV=development DOC_TYPE=true webpack-dev-server -d --open -w --progress --config build/webpack.doc.dev.conf.js",
-    "build:demo": "cross-env NODE_ENV=production webpack --hide-modules --progress --config build/webpack.demo.build.conf.js",
-    "build:doc": "cross-env NODE_ENV=production DOC_TYPE=true webpack --hide-modules --progress --config build/webpack.doc.build.conf.js",
-    "build:site": "npm run build:demo && npm run build:doc",
-    "build:prod": "cross-env NODE_ENV=production webpack --hide-modules --progress --config build/webpack.prod.conf.js && node scripts/createIndexScss.js",
-    "build:prodmin": "cross-env NODE_ENV=production webpack --hide-modules --progress --config build/webpack.prod.mini.conf.js",
-    "build:disp": "cross-env NODE_ENV=production PROD_TYPE=disp webpack --hide-modules --progress --config build/webpack.prod.disperse.conf.js",
-    "build": "npm run build:prod && npm run build:prodmin && npm run build:disp",
-    "clear": "node scripts/clearCache.js",
-    "eslint": "eslint src/packages/**/*.{js,vue}",
-    "add": "node scripts/createCptTpl.js",
-    "test": "cross-env NODE_ENV=test nyc --reporter=lcov --reporter=text  mocha-webpack --webpack-config build/webpack.test.conf.js --require test/setup.js src/packages/*/__test__/**.spec.js",
-    "coveralls": "cat ./coverage/lcov.info | coveralls",
-    "test:watch": "npm run test --watch"
-  },
-  "repository": {
-    "type": "git",
-    "url": "https://github.com/jdf2e/nutui.git"
-  },
-  "keywords": [
-    "nutui",
-    "nutui2",
-    "vue",
-    "webpack",
-    "vue component",
-    "jdc",
-    "jdcfe"
-  ],
-  "author": "jdcfe",
-  "license": "MIT",
-  "dependencies": {
-    "@babel/polyfill": "7.0.0",
-    "@babel/runtime": "7.1.2",
-    "cache-loader": "^4.1.0",
-    "happypack": "^5.0.1",
-    "intersection-observer": "^0.5.1",
-    "os": "^0.1.1",
-    "progress-bar-webpack-plugin": "^1.12.1",
-    "webpack-build-notifier": "^1.1.1"
-  },
-  "peerDependencies": {
-    "vue": "^2.6.10"
-  },
-  "devDependencies": {
-    "@babel/cli": "7.1.2",
-    "@babel/core": "7.1.2",
-    "@babel/plugin-syntax-dynamic-import": "7.0.0",
-    "@babel/plugin-transform-runtime": "7.1.0",
-    "@babel/preset-env": "7.1.0",
-    "@nutui/carefree": "^0.4.0",
-    "@tweenjs/tween.js": "17.2.0",
-    "@vue/test-utils": "1.0.0-beta.25",
-    "autoprefixer": "9.1.3",
-    "babel-eslint": "8.2.6",
-    "babel-helper-vue-jsx-merge-props": "2.0.3",
-    "babel-jest": "23.4.2",
-    "babel-loader": "8.0.4",
-    "babel-plugin-add-module-exports": "0.2.1",
-    "babel-plugin-dynamic-import-node": "2.0.0",
-    "babel-plugin-syntax-jsx": "6.18.0",
-    "babel-plugin-transform-es2015-modules-commonjs": "6.26.2",
-    "babel-preset-env": "1.7.0",
-    "babel-preset-stage-2": "6.24.1",
-    "chalk": "^2.4.2",
-    "chokidar": "^2.0.4",
-    "clipboard": "2.0.1",
-    "copy": "0.3.2",
-    "copy-webpack-plugin": "4.5.4",
-    "coveralls": "^3.0.2",
-    "cross-env": "^5.2.0",
-    "css-loader": "1.0.0",
-    "eslint": "4.19.1",
-    "eslint-loader": "2.1.1",
-    "eslint-plugin-vue": "4.7.1",
-    "expect": "23.6.0",
-    "file-loader": "1.1.11",
-    "folder-hash": "^2.1.2",
-    "friendly-errors-webpack-plugin": "1.7.0",
-    "google-code-prettify": "1.0.5",
-    "has": "1.0.3",
-    "highlight.js": "^9.13.1",
-    "html-webpack-plugin": "3.2.0",
-    "inquirer": "6.2.0",
-    "istanbul-instrumenter-loader": "3.0.1",
-    "jest": "23.5.0",
-    "jest-serializer-vue": "2.0.2",
-    "jsdom": "13.0.0",
-    "jsdom-global": "3.0.2",
-    "marked": "^0.6.1",
-    "mini-css-extract-plugin": "0.4.1",
-    "mocha": "5.2.0",
-    "mocha-webpack": "2.0.0-beta.0",
-    "moment": "2.22.2",
-    "node-filelist": "^1.0.0",
-    "node-notifier": "5.2.1",
-    "node-sass": "4.9.3",
-    "npm-run-all": "^4.1.5",
-    "nyc": "10.0.0",
-    "offline-plugin": "^5.0.6",
-    "optimize-css-assets-webpack-plugin": "5.0.0",
-    "ora": "3.0.0",
-    "path": "^0.12.7",
-    "portfinder": "1.0.17",
-    "postcss-import": "12.0.0",
-    "postcss-loader": "3.0.0",
-    "postcss-url": "8.0.0",
-    "qrcode": "1.3.2",
-    "raw-loader": "0.5.1",
-    "rimraf": "2.6.2",
-    "sass-loader": "7.1.0",
-    "sass-resources-loader": "1.3.3",
-    "semver": "5.5.1",
-    "shelljs": "^0.8.3",
-    "style-loader": "0.22.1",
-    "svg-sprite-loader": "3.9.2",
-    "three": "^0.99.0",
-    "uglifyjs-webpack-plugin": "1.3.0",
-    "url-loader": "1.1.1",
-    "vue": "^2.6.10",
-    "vue-i18n": "8.1.0",
-    "vue-jest": "2.6.0",
-    "vue-lazyload": "1.3.3",
-    "vue-loader": "15.4.0",
-    "vue-router": "^3.0.2",
-    "vue-style-loader": "^4.1.2",
-    "vue-template-compiler": "^2.6.10",
-    "vueg": "1.3.4",
-    "webpack": "4.25.0",
-    "webpack-bundle-analyzer": "^3.3.2",
-    "webpack-cli": "3.1.0",
-    "webpack-dev-server": "3.1.11",
-    "webpack-merge": "4.1.4",
-    "webpack-node-externals": "1.7.2"
-  },
-  "browserslist": [
-    "> 3%",
-    "Android >= 4",
-    "iOS >= 8"
-  ],
-  "nyc": {
-    "include": [
-      "src/packages/**/*.vue"
+    "name": "@nutui/nutui",
+    "version": "2.1.9",
+    "description": "一套轻量级移动端Vue组件库",
+    "typings": "dist/types/index.d.ts",
+    "main": "dist/nutui.js",
+    "files": [
+        "dist",
+        "README.md",
+        "package.json",
+        "LICENSE",
+        "CHANGELOG.md"
     ],
-    "instrument": false,
-    "sourceMap": false
-  }
-}
+    "scripts": {
+        "dev": "npm run dev:new",
+        "dev:new": "cross-env NODE_ENV=development DOC_TYPE=true webpack-dev-server -d --open  --config build/webpack.dev.conf.js",
+        "dev:carefree": "cross-env NODE_ENV=carefree carefree_env=dev webpack -w --colors --progress --config build/webpack.demo.dev.conf.js",
+        "dev:demo": "cross-env NODE_ENV=development webpack-dev-server -d --open  --config build/webpack.demo.dev.conf.js",
+        "dev:doc": "cross-env NODE_ENV=development DOC_TYPE=true webpack-dev-server -d --open -w --progress --config build/webpack.doc.dev.conf.js",
+        "build:demo": "cross-env NODE_ENV=production webpack --hide-modules --progress --config build/webpack.demo.build.conf.js",
+        "build:doc": "cross-env NODE_ENV=production DOC_TYPE=true webpack --hide-modules --progress --config build/webpack.doc.build.conf.js",
+        "build:site": "npm run build:demo && npm run build:doc",
+        "build:prod": "cross-env NODE_ENV=production webpack --hide-modules --progress --config build/webpack.prod.conf.js && node scripts/createIndexScss.js",
+        "build:prodmin": "cross-env NODE_ENV=production webpack --hide-modules --progress --config build/webpack.prod.mini.conf.js",
+        "build:disp": "cross-env NODE_ENV=production PROD_TYPE=disp webpack --hide-modules --progress --config build/webpack.prod.disperse.conf.js",
+        "build": "npm run build:prod && npm run build:prodmin && npm run build:disp",
+        "clear": "node scripts/clearCache.js",
+        "eslint": "eslint src/packages/**/*.{js,vue}",
+        "add": "node scripts/createCptTpl.js",
+        "test": "cross-env NODE_ENV=test nyc --reporter=lcov --reporter=text  mocha-webpack --webpack-config build/webpack.test.conf.js --require test/setup.js src/packages/*/__test__/**.spec.js",
+        "coveralls": "cat ./coverage/lcov.info | coveralls",
+        "test:watch": "npm run test --watch"
+    },
+    "repository": {
+        "type": "git",
+        "url": "https://github.com/jdf2e/nutui.git"
+    },
+    "keywords": [
+        "nutui",
+        "nutui2",
+        "vue",
+        "webpack",
+        "vue component",
+        "jdc",
+        "jdcfe"
+    ],
+    "author": "jdcfe",
+    "license": "MIT",
+    "dependencies": {
+        "@babel/polyfill": "7.0.0",
+        "@babel/runtime": "7.1.2",
+        "cache-loader": "^4.1.0",
+        "happypack": "^5.0.1",
+        "intersection-observer": "^0.5.1",
+        "os": "^0.1.1",
+        "progress-bar-webpack-plugin": "^1.12.1",
+        "webpack-build-notifier": "^1.1.1"
+    },
+    "peerDependencies": {
+        "vue": "^2.6.10"
+    },
+    "devDependencies": {
+        "@babel/cli": "7.1.2",
+        "@babel/core": "7.1.2",
+        "@babel/plugin-syntax-dynamic-import": "7.0.0",
+        "@babel/plugin-transform-runtime": "7.1.0",
+        "@babel/preset-env": "7.1.0",
+        "@nutui/carefree": "^0.4.0",
+        "@tweenjs/tween.js": "17.2.0",
+        "@vue/test-utils": "1.0.0-beta.25",
+        "autoprefixer": "9.1.3",
+        "babel-eslint": "8.2.6",
+        "babel-helper-vue-jsx-merge-props": "2.0.3",
+        "babel-jest": "23.4.2",
+        "babel-loader": "8.0.4",
+        "babel-plugin-add-module-exports": "0.2.1",
+        "babel-plugin-dynamic-import-node": "2.0.0",
+        "babel-plugin-syntax-jsx": "6.18.0",
+        "babel-plugin-transform-es2015-modules-commonjs": "6.26.2",
+        "babel-preset-env": "1.7.0",
+        "babel-preset-stage-2": "6.24.1",
+        "chalk": "^2.4.2",
+        "chokidar": "^2.0.4",
+        "clipboard": "2.0.1",
+        "copy": "0.3.2",
+        "copy-webpack-plugin": "4.5.4",
+        "coveralls": "^3.0.2",
+        "cross-env": "^5.2.0",
+        "css-loader": "1.0.0",
+        "eslint": "4.19.1",
+        "eslint-loader": "2.1.1",
+        "eslint-plugin-vue": "4.7.1",
+        "expect": "23.6.0",
+        "file-loader": "1.1.11",
+        "folder-hash": "^2.1.2",
+        "friendly-errors-webpack-plugin": "1.7.0",
+        "google-code-prettify": "1.0.5",
+        "has": "1.0.3",
+        "highlight.js": "^9.13.1",
+        "html-webpack-plugin": "3.2.0",
+        "inquirer": "6.2.0",
+        "istanbul-instrumenter-loader": "3.0.1",
+        "jest": "23.5.0",
+        "jest-serializer-vue": "2.0.2",
+        "jsdom": "13.0.0",
+        "jsdom-global": "3.0.2",
+        "marked": "^0.6.1",
+        "mini-css-extract-plugin": "0.4.1",
+        "mocha": "5.2.0",
+        "mocha-webpack": "2.0.0-beta.0",
+        "moment": "2.22.2",
+        "node-filelist": "^1.0.0",
+        "node-notifier": "5.2.1",
+        "node-sass": "4.9.3",
+        "npm-run-all": "^4.1.5",
+        "nyc": "10.0.0",
+        "offline-plugin": "^5.0.6",
+        "optimize-css-assets-webpack-plugin": "5.0.0",
+        "ora": "3.0.0",
+        "path": "^0.12.7",
+        "portfinder": "1.0.17",
+        "postcss-import": "12.0.0",
+        "postcss-loader": "3.0.0",
+        "postcss-url": "8.0.0",
+        "qrcode": "1.3.2",
+        "raw-loader": "0.5.1",
+        "rimraf": "2.6.2",
+        "sass-loader": "7.1.0",
+        "sass-resources-loader": "1.3.3",
+        "semver": "5.5.1",
+        "shelljs": "^0.8.3",
+        "style-loader": "0.22.1",
+        "svg-sprite-loader": "3.9.2",
+        "three": "^0.99.0",
+        "uglifyjs-webpack-plugin": "1.3.0",
+        "url-loader": "1.1.1",
+        "vue": "^2.6.10",
+        "vue-i18n": "8.1.0",
+        "vue-jest": "2.6.0",
+        "vue-lazyload": "1.3.3",
+        "vue-loader": "15.4.0",
+        "vue-router": "^3.0.2",
+        "vue-style-loader": "^4.1.2",
+        "vue-template-compiler": "^2.6.10",
+        "vueg": "1.3.4",
+        "webpack": "4.25.0",
+        "webpack-bundle-analyzer": "^3.3.2",
+        "webpack-cli": "3.1.0",
+        "webpack-dev-server": "3.1.11",
+        "webpack-merge": "4.1.4",
+        "webpack-node-externals": "1.7.2"
+    },
+    "browserslist": [
+        "> 3%",
+        "Android >= 4",
+        "iOS >= 8"
+    ],
+    "nyc": {
+        "include": [
+            "src/packages/**/*.vue"
+        ],
+        "instrument": false,
+        "sourceMap": false
+    }
+}

+ 11 - 1
src/config.json

@@ -505,6 +505,16 @@
             "sort": "2",
             "showDemo": true,
             "author": "vickyYE"
+        },
+        {
+          "version": "1.0.0",
+          "name": "TabSelect",
+          "chnName": "分类选择",
+          "desc": "两级分类选择",
+          "type": "component",
+          "sort": "3",
+          "showDemo": true,
+          "author": "dsj"
         }
     ]
-}
+}

+ 5 - 2
src/nutui.js

@@ -97,6 +97,8 @@ import './packages/elevator/elevator.scss';
 import Popup from './packages/popup/index.js';
 import LeftSlip from './packages/leftslip/index.js';
 import './packages/leftslip/leftslip.scss';
+import TabSelect from "./packages/tabselect/index.js";
+import "./packages/tabselect/tabselect.scss";
 
 import './packages/popup/popup.scss';
 const packages = {
@@ -147,7 +149,8 @@ const packages = {
     Avatar,
     Elevator,
     Popup,
-    LeftSlip
+    LeftSlip,
+    TabSelect: TabSelect
 };
 
 const components = {};
@@ -230,4 +233,4 @@ export default {
     ...filters,
     ...directives,
     ...methods
-};
+};

+ 18 - 0
src/packages/leftslip/__test__/leftslip.spec.js

@@ -0,0 +1,18 @@
+import { shallowMount } from '@vue/test-utils'
+import LeftSlip from "../leftslip.vue"
+import Vue from 'vue';
+
+describe('LeftSlip.vue', () => {
+    const wrapper = shallowMount(LeftSlip, {});
+    it('一键删除设置', () => {
+        wrapper.setProps({
+            onlyDel: true,
+        });
+
+        return Vue.nextTick().then(function() {
+            expect(wrapper.find('.delbtn').isVisible()).toBe(true);
+        })
+    })
+
+
+});

+ 7 - 7
src/packages/leftslip/leftslip.scss

@@ -1,7 +1,7 @@
 .nut-leftslip {
     position: relative;
     overflow: hidden;
-    &:first-child .slip-main {
+    &:first-child .nut-slip-main {
         border-top: 1px solid #d8d8d8;
     }
     .slip-main {
@@ -12,12 +12,12 @@
         border-bottom: 1px solid #d8d8d8;
         background: #fff;
     }
-    .leftslip-item {
+    .nut-leftslip-item {
         transition: all 0.6s ease;
     }
-    .delbtn{
+    .delbtn {
         position: absolute;
-        right: -50px;
+        right: -52px;
         top: 0;
         min-width: 40px;
         height: 100%;
@@ -29,7 +29,7 @@
         color: #fff;
         padding: 0 5px;
         font-size: 14px;
-        span{
+        span {
             // width: 40px;
             display: flex;
             position: absolute;
@@ -42,7 +42,7 @@
     }
     .slipbtns {
         position: absolute;
-        right: -0px;
+        right: 0;
         top: 0;
         // width: 80px;
         height: 100%;
@@ -63,4 +63,4 @@
             text-align: center;
         }
     }
-}
+}

+ 25 - 36
src/packages/leftslip/leftslip.vue

@@ -1,6 +1,6 @@
 <template>
     <div class="nut-leftslip">
-        <div class="leftslip-item" ref="slipItem" :style="deleteSlider">
+        <div class="nut-leftslip-item" ref="slipItem" :style="deleteSlider">
             <div @touchstart="touchStart($event)" @touchmove="touchMove($event)" @touchend="touchEnd($event)">
                 <slot name="slip-main"></slot>
             </div>
@@ -20,7 +20,7 @@ export default {
     name: 'nut-leftslip',
     props: {
         onlyDel: {
-            type: [Boolean],
+            type: Boolean,
             default: false
         }
     },
@@ -46,10 +46,6 @@ export default {
                 this.buttonWidth = this.buttonWidth + slot.elm.offsetWidth;
             }
         });
-        var that = this;
-        this.$on('bridge', () => {
-            that.restSlide();
-        }); //设置接收父组件的方法
 
         window.addEventListener('scroll', this.handleScroll, true);
     },
@@ -62,35 +58,32 @@ export default {
             if (this.disX) {
                 this.restSlide();
             }
-            // this.restSlide();
         },
         handleClick() {
             this.restSlide();
         },
         onlyDelClick() {
-            // this.$emit('onlyDelBtnClick', this.$refs.slipItem);
+            //一键删除模式点击删除
             this.$emit('oneDelete', this.$refs.slipItem);
             this.restSlide();
         },
         touchStart(e) {
             this.restSlide();
-            // console.log('rest', e.touches.length);
             e = e || event;
-
-            //tounches类数组,等于1时表示此时有只有一只手指在触摸屏幕
+            //等于1时表示此时有只有一只手指在触摸屏幕
             if (e.touches.length == 1) {
-                // 记录开始位置
                 this.startX = e.touches[0].clientX;
                 this.startY = e.touches[0].clientY;
             }
         },
         touchMove(e) {
             e = e || event;
-            // let parentElement = e.currentTarget.parentElement;
+            //获取当前滑动对象
+            let parentElement = e.currentTarget.parentElement;
             //获取删除按钮的宽度,此宽度为滑块左滑的最大距离
             let itemWd = this.$refs.slipItem.offsetWidth;
-            // console.log(this.$refs.delBtn.offsetWidth);
             let wd = this.onlyDel ? 40 : this.buttonWidth;
+
             if (e.touches.length == 1) {
                 // 滑动时距离浏览器左侧实时距离
                 this.moveY = e.touches[0].clientY;
@@ -103,22 +96,18 @@ export default {
                         //单一删除,左滑一键删除
                         if (this.disX < 0 || this.disX == 0) {
                             this.deleteSlider = 'transform:translateX(0px)';
-                            // parentElement.dataset.type = 0;
-                            // 大于0,表示左滑了,此时滑块开始滑动
                         }
                         this.deleteSlider = 'transform:translateX(-' + this.disX + 'px)';
                         this.delBtnStyle = 'width:' + this.disX + 'px';
+                        parentElement.dataset.type = 1;//设置滑动展开隐藏标志位,左滑展开为1,右滑或复位为0
                     } else {
                         // 如果是向右滑动或者不滑动,不改变滑块的位置
                         if (this.disX < wd / 4 || this.disX == 0) {
                             this.deleteSlider = 'transform:translateX(0px)';
-                            // parentElement.dataset.type = 0;
-                            // 大于0,表示左滑了,此时滑块开始滑动
+                            parentElement.dataset.type = 0;
                         } else if (this.disX > wd / 4) {
-                            //具体滑动距离我取的是 手指偏移距离*5。
-                            // parentElement.dataset.type = 1;
+                            parentElement.dataset.type = 1;
                             this.deleteSlider = 'transform:translateX(-' + this.disX + 'px)';
-
                             // 最大也只能等于删除按钮宽度
                             if (this.disX * 1.5 >= wd) {
                                 // parentElement.dataset.type = 1;
@@ -135,7 +124,7 @@ export default {
         },
         touchEnd(e) {
             e = e || event;
-            // let parentElement = e.currentTarget.parentElement;
+            let parentElement = e.currentTarget.parentElement;
             let itemWd = this.$refs.slipItem.offsetWidth;
             let wd = this.onlyDel ? 40 : this.buttonWidth;
             if (e.changedTouches.length == 1) {
@@ -148,28 +137,26 @@ export default {
                         //单一按钮,左滑一键删除
                         if (this.disX < 0 || this.disX == 0) {
                             this.deleteSlider = 'transform:translateX(0px)';
-                            // parentElement.dataset.type = 0;
-                            // 大于0,表示左滑了,此时滑块开始滑动
+                            parentElement.dataset.type = 0;
                         } else if (this.disX < itemWd - 20) {
-                            // parentElement.dataset.type = 0;
+                            parentElement.dataset.type = 1;
                             this.deleteSlider = 'transform:translateX(-50px);';
                             this.delBtnStyle = ' width:0px;';
                         } else {
                             this.deleteSlider = 'transform:translateX(-' + itemWd + 'px);';
                             this.delBtnStyle = ' width:' + itemWd + 'px;';
-                            // this.$emit('oneDelete', this.$refs.slipItem);
-                            // this.restSlide();
+                            parentElement.dataset.type = 1;
                             this.onlyDelClick();
                         }
                     } else {
                         //如果距离小于删除按钮的四分之一,强行回到起点
 
                         if (this.disX < wd / 4) {
-                            // parentElement.dataset.type = 0;
+                            parentElement.dataset.type = 0;
                             this.deleteSlider = 'transform:translateX(0px)';
                         } else {
                             //大于一半 滑动到最大值
-                            // parentElement.dataset.type = 1;
+                            parentElement.dataset.type = 1;
                             if (wd >= itemWd) {
                                 //按钮数不可超出整行宽度
                                 this.deleteSlider = 'transform:translateX(-' + (itemWd - 40) + 'px)';
@@ -184,18 +171,20 @@ export default {
             }
         },
         restSlide() {
-            let listItems = document.querySelectorAll('.leftslip-item');
-            let delBtns = document.querySelectorAll('.delbtn .trans');
-            // console.log(delBtns);
+            let listItems = document.querySelectorAll('.nut-leftslip-item');
+            
             // 复位
             for (let i = 0; i < listItems.length; i++) {
                 listItems[i].style = 'transform:translateX(0' + 'px)';
+                listItems[i].dataset.type = 0;//是否展开标志位默认0,左滑展开为1,右滑隐藏为0
             }
-            for (let j = 0; j < delBtns.length; j++) {
-                delBtns[j].style = '';
+            if(this.onlyDel){
+                let delBtns = document.querySelectorAll('.delbtn .trans');
+                for (let j = 0; j < delBtns.length; j++) {
+                    delBtns[j].style = '';
+                }
             }
-
-            // this.delBtnStyle = '';
+            
         }
     }
 };

+ 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-list-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;
+    }
+  }
+}

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

@@ -0,0 +1,194 @@
+<template>
+  <div class="nut-tabselect">
+    <nut-popup
+      round
+      closeable  
+      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 - 1
src/packages/textinput/doc.md

@@ -88,5 +88,5 @@ export default {
 | value | 当前input值,可使用 v-model 双向绑定数据 | String | ''
 | label | 文本框前面的标签 | String | ''
 | disabled | 是否禁用 | Boolean | false
-| clearBtn | 是否需要情况按钮 | Boolean | true
+| clearBtn | 是否需要清空按钮 | Boolean | true
 | hasBorder | 是否需要边框 | Boolean | true

+ 4 - 1
types/nutui.d.ts

@@ -64,4 +64,7 @@ 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 SlipesideBotdeslip[Cefttonutton extends UIComponent {}
+export declare class SlipesideBotdeslip extends UIComponent {}
+export declare class TabSelect extends UIComponent {}
+export declare class Popup extends UIComponent {}
+