Browse Source

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

shenqistart 5 years ago
parent
commit
ef1a07012f

+ 2 - 1
package.json

@@ -55,6 +55,8 @@
         "os": "^0.1.1",
         "progress-bar-webpack-plugin": "^1.12.1",
         "vue-qr": "^2.2.1",
+        "gsap": "^3.2.6",
+        "vue-lazyload": "^1.3.3",
         "webpack-build-notifier": "^1.1.1"
     },
     "peerDependencies": {
@@ -139,7 +141,6 @@
         "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",

File diff suppressed because it is too large
+ 17 - 0
src/assets/svg/address-location.svg


File diff suppressed because it is too large
+ 13 - 0
src/assets/svg/arrows-back.svg


File diff suppressed because it is too large
+ 13 - 0
src/assets/svg/hook-red.svg


File diff suppressed because it is too large
+ 15 - 0
src/assets/svg/tick-red.svg


+ 10 - 0
src/config.json

@@ -622,6 +622,16 @@
       "sort": "5",
       "showDemo": true,
       "author": "张宇"
+    },
+    {
+      "version": "1.0.0",
+      "name": "Address",
+      "chnName": "地址选择",
+      "desc": "业务功能-地址选择",
+      "type": "component",
+      "sort": "6",
+      "showDemo": true,
+      "author": "yangxiaolu"
     }
   ]
 }

+ 5 - 1
src/nutui.js

@@ -122,6 +122,9 @@ import Drag from "./packages/drag/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";
+import "./packages/address/address.scss";
+
 const packages = {
   Cell,
   Dialog,
@@ -181,7 +184,8 @@ const packages = {
   SideNavBar: SideNavBar,
   SubSideNavBar: SubSideNavBar,
   SideNavBarItem: SideNavBarItem,
-  Drag: Drag
+  Drag: Drag,
+  Address: Address
 };
 
 const components = {};

+ 152 - 0
src/packages/address/address.scss

@@ -0,0 +1,152 @@
+.nut-address{
+    .title{
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        margin-top: 20px;
+        padding: 0 20px;
+        text-align: center;
+        font-size: 16px;
+        font-weight: bold;
+        color: #333;
+        line-height: 20px;
+        svg{
+            width: 20px;
+            height: 20px;
+        }
+        .arrow{
+            display: inline-block;
+            width: 20px;
+            height: 20px;
+            svg{
+                width: 20px;
+                height: 20px;
+            }
+        }
+    }
+    // 请选择
+    .custom-address{
+        .region-tab{
+            position: relative;
+            margin-top: 32px;
+            padding:0 20px;
+            display: flex;
+            font-size: 13px;
+            color: #1D1E1E;
+    
+            .tab-item{
+                margin-right: 30px;
+                
+                &.active{
+                    font-weight: bold;
+                }
+    
+                span{
+                    display: inline-block;
+                    max-width: 100px;
+                    white-space: nowrap;
+                    overflow: hidden;
+                    text-overflow: ellipsis;
+                }
+            }
+    
+            .region-tab-line{
+                position: absolute;
+                bottom: -10px;
+                left: 20px;
+                display: inline-block;
+                margin-top: 5px;
+                width: 26px;
+                height: 3px;
+                background: linear-gradient(90deg, rgba(245,80,58,1) 0%,rgba(250,209,203,1) 100%);
+            }
+        }
+    
+        .region-con{
+            margin: 20px 20px 0;
+            .region-group{
+                padding-top: 15px;
+                height: 270px;
+                overflow-y: auto;
+    
+                .region-item{
+                    display: flex;
+                    align-items: center;
+                    margin-bottom: 20px;
+                    font-size: 12px;
+                    color: #333;
+                    &.active{
+                        font-weight: bold;
+                    }
+    
+                    .nut-icon{
+                        margin-right: 6px;
+                        width: 13px;
+                        height: 13px;
+                    }
+                }
+            }
+        }
+    }
+    
+    // 配送至
+    .exist-address{
+        margin-top: 15px;
+
+        .exist-address-group{
+            padding:15px 20px 0;
+            height: 279px;
+            overflow-y: scroll;
+
+            .exist-ul{
+                .exist-item{
+                    display: flex;
+                    align-items: center;
+                    margin-bottom: 20px;
+                    font-size: 12px;
+                    line-height: 14px;
+                    color: rgba(51,51,51,1);
+                    &.active{
+                        font-weight: bold;
+                    }
+                    svg{
+                        margin-right: 9px;
+                        width: 13px;
+                        height: 13px;
+                    }
+                    span{
+                        display: inline-block;
+                        flex: 1;
+                    }
+                }
+            }
+        }
+
+        .choose-other{
+            width: 100%;
+            height: 54px;
+            padding: 6px 0px 0;
+            border-top: 1px solid #F2F2F2;
+            .btn{
+                width: 90%;
+                height: 42px;
+                line-height: 42px;
+                margin: auto;
+                text-align: center;
+                background: linear-gradient(135deg, rgba(242,20,12,1) 0%,rgba(242,39,12,1) 70%,rgba(242,77,12,1) 100%);
+                border-radius: 21px;
+                font-size: 15px;
+                color: rgba(255,255,255,1);
+            }
+        }
+        
+    }
+
+    .nut-icon{
+        display: flex;
+        align-items: center;
+        svg{
+            margin-top: -1px;
+        }
+    }
+}

+ 335 - 0
src/packages/address/address.vue

@@ -0,0 +1,335 @@
+<template>
+    <div class="nut-address">
+        <nut-popup v-model="showPopup" round position="bottom" class="choose-address" @close="close" @click-overlay="clickOverlay" @open="closeWay = 'self'">
+            <div class="title">
+                <span class="arrow" @click="switchModule">
+                    <nut-icon v-if="showModule == 'custom' && type == 'exist'" type="self" :url="require('../../assets/svg/arrows-back.svg')"></nut-icon>
+                   
+                </span>
+
+                <span v-if="type == 'custom'">{{customAddressTitle}}</span>
+                <span v-if="type == 'exist'">{{existAddressTitle}}</span>
+
+                <span @click="handClose('hand')"><nut-icon type="circle-cross" size="18px"></nut-icon></span>
+            </div>
+
+            <!-- 请选择 -->
+            <div class="custom-address" v-if="showModule == 'custom' ">
+                <div class="region-tab">
+                    <div 
+                        class="tab-item" 
+                        :class="[index == tabIndex?'active':'']"
+                        v-for="(item,key,index) in selectedRegion" 
+                        :key="index"
+                        :ref="'tab-item-'+key"
+                        @click="changeRegionTab(item,key,index)"
+                    ><span>{{ getTabName(item,index)}}</span></div>
+
+                    <span class="region-tab-line" ref="regionLine"></span>
+                </div>
+
+                <div class="region-con">
+                    <ul class="region-group">
+                        <li 
+                            v-for="(item,index) in regionList[tabName[tabIndex]]" 
+                            :key="index"
+                            class="region-item"
+                            :class="[selectedRegion[tabName[tabIndex]].id == item.id?'active':'']"
+                            @click="nextAreaList(item)"
+                        >
+                        <nut-icon type="self" :url="require('../../assets/svg/hook-red.svg')" v-if="selectedRegion[tabName[tabIndex]].id == item.id"></nut-icon>{{item.name}}</li>
+                    </ul>
+                </div>
+            </div>
+            <!-- 配送至 -->
+            <div class="exist-address" v-if="showModule == 'exist'">
+                <div class="exist-address-group">
+                    <ul class="exist-ul">
+                        <li 
+                            class="exist-item" 
+                            :class="[item.selectedAddress ?'active':'']"
+                            v-for="(item,index) in existAddress" 
+                            :key='index'
+                            @click="selectedExist(item)"
+                        >
+                            <nut-icon  type="self" :url="item.selectedAddress?selectedIcon:defaultIcon"></nut-icon>
+                            
+                            <span>{{item.provinceName + item.cityName + item.countyName + item.townName + item.addressDetail }}</span>
+                        </li>
+                    </ul>
+                </div>
+            
+                <div class="choose-other" @click="switchModule" v-if="isShowCustomAddress && showModule=='exist'">
+                    <div class="btn">{{customAndExistTitle}}</div>
+                </div>
+            </div>
+        </nut-popup>
+    </div>
+</template>
+<script>
+import Popup from "./../popup/popup.vue";
+import Icon from "./../icon/icon.vue";
+import { TweenMax } from 'gsap'
+export default {
+    name:'nut-address',
+    props: {
+        value: {
+            type: Boolean,
+            default: false
+        },
+        type: {
+            type: String,
+            default: 'custom'
+        },
+        customAddressTitle:{
+            type: String,
+            default: '请选择所在地区'
+        },
+        province:{
+            type: Array,
+            default: () => []
+        }, // 省
+        city:{
+            type: Array,
+            default: () => []
+        },// 市
+        country:{
+            type: Array,
+            default: () => []
+        },// 县
+        town:{
+            type: Array,
+            default: () => []
+        }, // 镇
+        isShowCustomAddress:{
+            type: Boolean,
+            default: true
+        },// 是否显示‘选择其他地区’按钮 type=‘exist’ 生效
+        existAddress:{
+            type: Array,
+            default: () => []
+        }, // 现存地址列表
+        existAddressTitle:{
+            type: String,
+            default: '配送至'
+        },
+        customAndExistTitle:{
+            type: String,
+            default: '选择其他地址'
+        },
+        defaultIcon:{  // 地址选择列表前 - 默认的图标
+            type: String,
+            default: require("../../assets/svg/address-location.svg")
+        },
+        selectedIcon:{ // 地址选择列表前 - 选中的图标
+            type: String,
+            default: require("../../assets/svg/tick-red.svg")
+        }
+    },
+    data() {
+        return {
+            showPopup:false,
+            showModule:'exist', //展示 exist 还是 custom 主要用于‘选择其他地址’
+            tabIndex: 0 ,
+            tabName:['province', 'city', 'country', 'town'],
+            regionList:{
+                'province': this.province,
+                'city': this.city,
+                'country': this.country,
+                'town': this.town
+            }, //省、市、县、镇列表,地址id字符串,地址字符串
+            selectedRegion:{
+                'province': {},
+                'city': {},
+                'country': {},
+                'town': {}
+            }, //已选择的 省、市、县、镇
+            
+            selectedExistAddress:{}, // 当前选择的地址
+        }
+    },
+    components: {
+        "nut-popup": Popup,
+        "nut-icon":Icon
+    },
+    watch:{
+        value(newVal,oldVal){
+            this.showPopup = newVal
+            
+        },
+        showPopup(newVal,oldVal){
+            if (newVal == false) this.$emit('input', false)
+            if (newVal == true) {
+
+                this.showModule = this.type
+            }
+        },
+        province(newVal,oldVal){
+            this.regionList.province = newVal
+        },
+        city(newVal,oldVal){
+            this.regionList.city = newVal
+        },
+        country(newVal,oldVal){
+            this.regionList.country = newVal
+        },
+        town(newVal,oldVal){
+            this.regionList.town = newVal
+            
+        },
+
+        existAddress(newVal,oldVal){
+            this.existAddress = newVal
+
+            newVal.forEach((item,index)=>{
+                if(item.selectedAddress){
+                    this.selectedExistAddress = item
+                }
+            })
+        },
+       
+    },
+    mounted(){
+        
+    },
+    methods: {
+
+        //获取已选地区列表名称
+        getTabName(item, index) {
+
+            if (item.name) return item.name
+
+            if (this.tabIndex < index) {
+                return item.name
+            } else {
+                return '请选择'
+            }
+        },
+        // 切换下一级列表
+        nextAreaList(item) {
+            // onchange 接收的参数
+            const calBack = {
+                custom:this.tabName[this.tabIndex]
+            }
+
+            this.selectedRegion[this.tabName[this.tabIndex]] = item
+
+            for (let i = this.tabIndex; i < this.tabName.length - 1; i++) {
+                this.selectedRegion[this.tabName[i + 1]] = {}
+            }
+            
+            if (this.tabIndex < 3) {
+
+                this.tabIndex = this.tabIndex + 1
+                this.lineAnimation()
+
+                // 切换下一个
+                calBack.next = this.tabName[this.tabIndex]
+                calBack.value = item
+                this.$emit('onChange', calBack)
+
+            } else {
+                this.handClose()
+            }
+        },
+        //切换地区Tab
+        changeRegionTab(item, key, index) {
+            this.tabIndex = index
+            this.lineAnimation()
+        },
+        // 移动下面的红线
+        lineAnimation() {
+            const name = 'tab-item-' + this.tabName[this.tabIndex]    
+            this.$nextTick(() => {
+                if(this.$refs[name] && this.$refs[name][0]){
+                    const distance = this.$refs[name][0].offsetLeft
+                    TweenMax.to(this.$refs.regionLine, 0.5, { left: distance })
+                }
+            })
+        },
+
+        // 选择现有地址
+        selectedExist(item) {
+            let copyExistAdd = this.existAddress
+            let prevExistAdd = {}
+
+            copyExistAdd.forEach((list,index)=>{
+                if(list.selectedAddress){
+                    prevExistAdd = list
+                }
+                list.selectedAddress = false
+            })
+
+            item.selectedAddress = true
+
+            this.selectedExistAddress = item
+    
+            this.$emit('selected',prevExistAdd,item,copyExistAdd)
+
+            this.handClose()
+        },
+
+        // 关闭
+        close(){
+            const that = this
+
+            const resCopy = Object.assign({}, this.selectedRegion) 
+        
+            const res = {
+                type: this.showModule,
+                data: {}
+            }
+            
+            if (this.showModule == 'custom') {
+                const { province, city, country, town } = resCopy
+                resCopy.addressIdStr = [province.id || 0, city.id || 0, country.id || 0, town.id || 0].join('_')
+                resCopy.addressStr = [province.name, city.name, country.name, town.name].join('')
+                res.data = resCopy
+            } else {
+                res.data = this.selectedExistAddress
+            }
+            
+            
+            this.initAddress()
+
+            if(this.closeWay == 'self'){
+                this.$emit('close',res)
+            }else{
+                // this.$emit('close',{type:'hand'})
+            }
+            
+            setTimeout(()=>{
+                that.showModule = 'type'
+            },500)
+            
+        },
+        // 手动关闭 点击叉号,或者蒙层
+        handClose(type = 'self'){
+            if(type == 'hand'){
+                this.closeWay = 'hand'
+            }else{
+                this.closeWay = 'self'
+            }
+
+            this.showPopup = false
+        },
+        // 点击遮罩层关闭
+        clickOverlay(){
+            this.closeWay = 'hand'
+        },
+        // 初始化
+        initAddress(){
+            for (let i = 0; i < this.tabName.length; i++) {
+                this.selectedRegion[this.tabName[i]] = {}
+            }
+            this.tabIndex = 0
+            this.lineAnimation()
+        },
+        // 选择其他地址
+        switchModule() {
+            this.showModule = this.showModule == 'exist' ? 'custom' : 'exist'
+            this.initAddress()
+        }
+    }
+}
+</script>

+ 188 - 0
src/packages/address/demo.vue

@@ -0,0 +1,188 @@
+<template>
+    <div class="address-box">
+      <h4>选择自定义地址</h4>
+      <div class="address-list init" @click="showAddress">
+        <div class="titile">选择地址</div>
+        <div class="choose">{{text1}}</div>
+      </div>
+      <h4>选择已有地址</h4>
+      <div class="address-list other" @click="showAddressExist">
+        <div class="titile">选择地址</div>
+        <div class="choose">{{text2}}</div>
+      </div>
+
+      <h4>自定义图标</h4>
+      <div class="address-list other" @click="showCustomImg">
+        <div class="titile">选择地址</div>
+        <div class="choose">{{text3}}</div>
+      </div>
+
+      <h4>自定义地址与已有地址切换</h4>
+      <div class="address-list other" @click="showAddressOther">
+        <div class="titile">选择地址</div>
+        <div class="choose">{{text4}}</div>
+      </div>
+
+
+      <nut-address v-model="showPopup" :province="province" :city="city" :country="country" :town="town" @onChange="onChange1" @close="close1" customAddressTitle="请选择所在地区"></nut-address>
+
+      <nut-address v-model="showPopupExist" type="exist" :existAddress="existAddress" @onChange="onChange2" @close="close2" :isShowCustomAddress="false" @selected="selected2" existAddressTitle="配送至"></nut-address>
+
+      <nut-address v-model="showPopupCustomImg" type="exist" :existAddress="existAddress" @onChange="onChange3" @close="close3" :isShowCustomAddress="false" @selected="selected3" :defaultIcon="defaultIcon" :selectedIcon='selectedIcon'></nut-address>
+
+      <nut-address v-model="showPopupOther" type="exist" :existAddress="existAddress" :province="province" :city="city" :country="country" :town="town" @onChange="onChange4" @close="close4" @selected="selected4" customAndExistTitle="选择其他地址"></nut-address>
+    </div>
+</template>
+
+<script>
+
+export default {
+  data() {
+    return {
+      showPopup:false,
+      province:[{"id":1,"name":"北京"},{"id":2,"name":"广西"},{"id":3,"name":"江西"},{"id":4,"name":"四川"}], // 省
+      city:[{"id":7,"name":"朝阳区"},{"id":8,"name":"崇文区"},{"id":9,"name":"昌平区"},{"id":6,"name":"石景山区"}],// 市
+      country:[{"id":3,"name":"八里庄街道"},{"id":9,"name":"北苑"},{"id":4,"name":"常营乡"}],// 县
+      town:[], // 镇
+
+      showPopupExist:false,
+
+      showPopupCustomImg:false,
+      selectedIcon:require('../../assets/svg/checked.svg'),
+      defaultIcon:require('../../assets/svg/unchecked.svg'),
+
+      showPopupOther:false,
+      existAddress:[
+        {"id":1,"addressDetail":"th ","cityName":"石景山区","countyName":"城区","provinceName":"北京","selectedAddress":true,"townName":""},
+        {"id":2,"addressDetail":"12_ ","cityName":"电饭锅","countyName":"扶绥县","provinceName":"北京","selectedAddress":false,"townName":""},
+        {"id":3,"addressDetail":"发大水比 ","cityName":"放到","countyName":"广宁街道","provinceName":"钓鱼岛全区","selectedAddress":false,"townName":""},
+        {"id":4,"addressDetail":"还是想吧百度吧 ","cityName":"研发","countyName":"八里庄街道","provinceName":"北京","selectedAddress":false,"townName":""},
+      ],
+
+      text1:'请选择地址',
+      text2:'请选择地址',
+      text3:'请选择地址',
+      text4:'请选择地址'
+    };
+  },
+  methods: {
+      showAddress(){
+        this.showPopup = true
+      },
+
+      onChange1(cal){
+        console.log('change1',cal)
+        if(this[cal.next].length < 1){
+          this.showPopup = false
+        }
+      },
+      close1(val){
+        console.log(val)
+        this.text1 = val.data.addressStr
+      },
+
+      showAddressExist(){
+        this.showPopupExist = true
+      },
+
+      onChange2(cal){
+        console.log('change2',cal)
+        if(this[cal.next].length < 1){
+          this.showPopupOther = false
+        }
+      },
+      
+      close2(val){
+        console.log(val)
+        if(val.type == 'exist'){
+          this.text2 = val.data.provinceName+val.data.cityName+val.data.countyName+val.data.townName+val.data.addressDetail
+        }else{
+          this.text2 = val.data.addressStr
+        }
+      },
+      selected2(prevExistAdd,nowExistAdd,arr){
+        console.log(prevExistAdd)
+        console.log(nowExistAdd)
+      },
+
+      showAddressOther(){
+        this.showPopupOther = true
+      },
+      showCustomImg(){
+        this.showPopupCustomImg = true
+      },
+      onChange3(cal){
+        console.log('change3',cal)
+        if(this[cal.next].length < 1){
+          this.showPopupOther = false
+        }
+      },
+      
+      close3(val){
+        console.log(val)
+        if(val.type == 'exist'){
+          this.text3 = val.data.provinceName+val.data.cityName+val.data.countyName+val.data.townName+val.data.addressDetail
+        }else{
+          this.text3 = val.data.addressStr
+        }
+      },
+      selected3(prevExistAdd,nowExistAdd,arr){
+        console.log(prevExistAdd)
+        console.log(nowExistAdd)
+      },
+      
+      onChange4(cal){
+        console.log('change4',cal)
+        if(this[cal.next].length < 1){
+          this.showPopupOther = false
+        }
+      },
+      
+      close4(val){
+        console.log(val)
+        if(val.type == 'exist'){
+          this.text4 = val.data.provinceName+val.data.cityName+val.data.countyName+val.data.townName+val.data.addressDetail
+        }else{
+          this.text4 = val.data.addressStr
+        }
+      },
+      selected4(prevExistAdd,nowExistAdd,arr){
+        console.log(prevExistAdd)
+        console.log(nowExistAdd)
+      }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.address-box{
+  margin-top: 10px;
+
+  .address-list{
+    margin-bottom: 10px;
+    background: #fff;
+    border-radius: 4px;
+    font-size: 14px;
+    padding: 10px;
+
+    &.init{
+      display: flex;
+      .titile{
+        margin-right: 15px;
+      }
+      .choose{
+        color: #999;
+      }
+    }
+    &.other{
+      display: flex;
+      .titile{
+        margin-right: 15px;
+      }
+      .choose{
+        color: #999;
+      }
+    }
+  }
+}
+</style>

+ 308 - 0
src/packages/address/doc.md

@@ -0,0 +1,308 @@
+# Address 地址选择
+
+## 选择自定义地址
+
+```html
+<div class="address-list init" @click="showAddress">
+    <div class="titile">选择地址</div>
+    <div class="choose">{{text1}}</div>
+</div>
+
+<nut-address 
+    v-model="showPopup" 
+    :province="province" 
+    :city="city" 
+    :country="country" 
+    :town="town" 
+    customAddressTitle="请选择所在地区"
+    @onChange="onChange1" 
+    @close="close1">
+</nut-address>
+```
+```javascript
+
+<script>
+export default {
+  data() {
+    return {
+        showPopup:false,
+        province:[{"id":1,"name":"北京"},{"id":2,"name":"广西"},{"id":3,"name":"江西"},{"id":4,"name":"四川"}], // 省
+        city:[{"id":7,"name":"朝阳区"},{"id":8,"name":"崇文区"},{"id":9,"name":"昌平区"},{"id":6,"name":"石景山区"}],// 市
+        country:[{"id":3,"name":"八里庄街道"},{"id":9,"name":"北苑"},{"id":4,"name":"常营乡"}],// 县
+        town:[], // 镇 必传值:name
+    }
+  },
+  methods: {
+      showAddress(){
+        this.showPopup = true 
+      },
+      onChange1(cal){
+        // 判断下一个行政区列表的内容是否有值
+        if(this[cal.next].length < 1){
+          this.showPopup = false
+        }
+      },
+      close1(val){
+        this.text1 = val.data.addressStr 
+      }
+  }
+}
+</script>
+
+```
+## 选择已有地址
+```html
+<div class="address-list other" @click="showAddressExist">
+  <div class="titile">选择地址</div>
+  <div class="choose">{{text2}}</div>
+</div>
+
+<nut-address 
+  v-model="showPopupExist" 
+  type="exist" 
+  :existAddress="existAddress" 
+  @onChange="onChange2" 
+  @close="close2" 
+  :isShowCustomAddress="false" 
+  existAddressTitle="配送至"
+  @selected="selected2">
+</nut-address>
+
+```
+```javascript
+
+<script>
+export default {
+  data() {
+    return {
+        showPopupExist:false,
+        existAddress:[
+          {"id":1,"addressDetail":"th ","cityName":"石景山区","countyName":"城区","provinceName":"北京","selectedAddress":true,"townName":""},
+          {"id":2,"addressDetail":"12_ ","cityName":"电饭锅","countyName":"扶绥县","provinceName":"北京","selectedAddress":false,"townName":""},
+          {"id":3,"addressDetail":"发大水比 ","cityName":"放到","countyName":"广宁街道","provinceName":"钓鱼岛全区","selectedAddress":false,"townName":""},
+          {"id":4,"addressDetail":"还是想吧百度吧 ","cityName":"研发","countyName":"八里庄街道","provinceName":"北京","selectedAddress":false,"townName":""},
+        ],
+        text2:'请选择地址',
+    }
+  },
+  methods: {
+      showAddressExist(){
+        this.showPopupExist = true
+      },
+
+      onChange2(cal){
+        console.log('change2',cal)
+        if(this[cal.next].length < 1){
+          this.showPopupOther = false
+        }
+      },
+      
+      close2(val){
+        console.log(val)
+        if(val.type == 'exist'){
+          this.text2 = val.data.provinceName+val.data.cityName+val.data.countyName+val.data.townName+val.data.addressDetail
+        }else{
+          this.text2 = val.data.addressStr
+        }
+      },
+      selected2(prevExistAdd,nowExistAdd,arr){
+        console.log(prevExistAdd)
+        console.log(nowExistAdd)
+      },
+  }
+}
+</script>
+
+```
+
+
+## 自定义图标
+
+```html
+<div class="address-list other" @click="showCustomImg">
+    <div class="titile">选择地址</div>
+    <div class="choose">{{text3}}</div>
+</div>
+
+<nut-address 
+  v-model="showPopupCustomImg" 
+  type="exist" 
+  :existAddress="existAddress" 
+  @onChange="onChange3" 
+  @close="close3" 
+  :isShowCustomAddress="false" 
+  @selected="selected3" 
+  :defaultIcon="defaultIcon" 
+  :selectedIcon='selectedIcon'>
+</nut-address>
+
+```
+```javascript
+
+<script>
+export default {
+  data() {
+    return {
+        showPopupCustomImg:false,
+        selectedIcon:require('../../assets/svg/checked.svg'),
+        defaultIcon:require('../../assets/svg/unchecked.svg'),
+        existAddress:[
+          {"id":1,"addressDetail":"th ","cityName":"石景山区","countyName":"城区","provinceName":"北京","selectedAddress":true,"townName":""},
+          {"id":2,"addressDetail":"12_ ","cityName":"电饭锅","countyName":"扶绥县","provinceName":"北京","selectedAddress":false,"townName":""},
+          {"id":3,"addressDetail":"发大水比 ","cityName":"放到","countyName":"广宁街道","provinceName":"钓鱼岛全区","selectedAddress":false,"townName":""},
+          {"id":4,"addressDetail":"还是想吧百度吧 ","cityName":"研发","countyName":"八里庄街道","provinceName":"北京","selectedAddress":false,"townName":""},
+        ],
+        tex3:'请选择地址',
+    }
+  },
+  methods: {
+      showCustomImg(){
+        this.showPopupCustomImg = true
+      },
+
+      onChange3(cal){
+        console.log('change2',cal)
+        if(this[cal.next].length < 1){
+          this.showPopupOther = false
+        }
+      },
+      
+      close3(val){
+        console.log(val)
+        if(val.type == 'exist'){
+          this.text3 = val.data.provinceName+val.data.cityName+val.data.countyName+val.data.townName+val.data.addressDetail
+        }else{
+          this.text3 = val.data.addressStr
+        }
+      },
+      selected3(prevExistAdd,nowExistAdd,arr){
+        console.log(prevExistAdd)
+        console.log(nowExistAdd)
+      },
+  }
+}
+</script>
+
+```
+
+## 自定义地址与已有地址切换
+
+```html
+<div class="address-list other" @click="showAddressOther">
+    <div class="titile">选择地址</div>
+    <div class="choose">{{text2}}</div>
+</div>
+<nut-address 
+    v-model="showPopupOther" 
+    type="exist" 
+    :existAddress="existAddress" 
+    :province="province" 
+    :city="city" 
+    :country="country" 
+    :town="town" 
+    customAndExistTitle="选择其他地址"
+    @onChange="onChange4" 
+    @close="close4" 
+    @selected="selected4">
+</nut-address>
+
+
+```
+```javascript
+<script>
+export default {
+  data() {
+    return {
+      showPopupOther:false,
+      existAddress:[
+        {"id":1,"addressDetail":"th ","cityName":"石景山区","countyName":"城区","provinceName":"北京","selectedAddress":true,"townName":""},
+        {"id":2,"addressDetail":"12_ ","cityName":"电饭锅","countyName":"扶绥县","provinceName":"北京","selectedAddress":false,"townName":""},
+        {"id":3,"addressDetail":"发大水比 ","cityName":"放到","countyName":"广宁街道","provinceName":"钓鱼岛全区","selectedAddress":false,"townName":""},
+        {"id":4,"addressDetail":"还是想吧百度吧 ","cityName":"研发","countyName":"八里庄街道","provinceName":"北京","selectedAddress":false,"townName":""},
+      ], // 已有地址列表 必传值:provinceName、cityName、countyName、townName、addressDetail 、selectedAddress
+      province:[{"id":1,"name":"北京"},{"id":2,"name":"广西"},{"id":3,"name":"江西"},{"id":4,"name":"四川"}], // 省
+      city:[{"id":7,"name":"朝阳区"},{"id":8,"name":"崇文区"},{"id":9,"name":"昌平区"},{"id":6,"name":"石景山区"}],// 市
+      country:[{"id":3,"name":"八里庄街道"},{"id":9,"name":"北苑"},{"id":4,"name":"常营乡"}],// 县
+      town:[], // 镇 必传值:name
+    };
+  },
+  methods: {
+    showAddressOther(){
+        this.showPopupOther = true
+    },
+    onChange4(cal){
+        if(this[cal.next].length < 1){
+          this.showPopupOther = false
+        }
+    },
+    close4(val){
+        if(val.type == 'exist'){
+          this.text2 = val.data.provinceName+val.data.cityName+val.data.countyName+val.data.townName+val.data.addressDetail
+        }else{
+          this.text2 = val.data.addressStr
+        }
+    },
+  
+    selected4(prevExistAdd,nowExistAdd,arr){
+      console.log(prevExistAdd)
+      console.log(nowExistAdd)
+    }
+  }
+}
+</script>
+```
+
+
+## API
+| 字段 | 说明 | 类型 | 默认值
+|----- | ----- | ----- | ----- 
+| v-model | 是否打开地址选择 | String | ''
+| type | 地址选择类型 exist/custom | String | custom
+| province | 省,每个省的对象中,必须有 name 字段 | Array | []
+| city | 市,每个市的对象中,必须有 name 字段 | Array | []
+| country | 县,每个县的对象中,必须有 name 字段 | Array | []
+| town | 乡/镇,每个乡/镇的对象中,必须有 name 字段 | Array | []
+| existAddress | 已存在地址列表,每个地址对象中,必传值provinceName、cityName、countyName、townName、addressDetail、selectedAddress(字段解释见下) | Array | []
+| defaultIcon | 已有地址列表默认图标,type=‘exist’ 时生效 | string | ''
+| selectedIcon | 已有地址列表选中图标,type=‘exist’ 时生效 | string | ''
+| isShowCustomAddress | 是否可以切换自定义地址选择,type=‘exist’ 时生效 | Boolean | true
+| customAddressTitle  | 自定义地址选择文案,type='custom' 时生效 | string | '请选择所在地区'
+| existAddressTitle| 已有地址文案 ,type=‘exist’ 时生效| string | '配送至'
+| customAndExistTitle| 自定义地址与已有地址切换按钮文案 ,type=‘exist’ 时生效| string | '选择其他地址'
+
+
+  * provinceName 省的名字
+  * cityName 市的名字
+  * countyName 县的名字
+  * townName 乡/镇的名字
+  * addressDetail 具体地址
+  * selectedAddress 字段用于判断当前地址列表的选中项。
+
+## Event
+| 字段 | 说明 | 回调参数 
+|----- | ----- | ----- 
+| onChange | 自定义选择地址时,选择地区时触发 |  参考 onChange
+| selected | 选择已有地址列表时触发 | 参考 selected
+| close | 地址选择弹框关闭时触发 | 参考 close
+
+## onChange 回调参数
+| 参数 | 说明 | 可能值 
+|----- | ----- | ----- 
+| custom | 当前点击的行政区域  |  province(省) / city(市) / country(县) / town(乡)
+| next | 当前点击的行政区域的下一级 | province(省) / city(市) / country(县) / town(乡)
+| value | 当前点击的行政区域的值(返回传入的值) | {}
+
+## selected 回调参数
+| 参数 | 说明 | 可能值 
+|----- | ----- | ----- 
+| 第一个参数(prevExistAdd) |  选择前选中的地址 |  {}
+| 第二个参数(nowExistAdd) |  当前选中的地址 |  {}
+| 第三个参数(arr) |  选择完之后的已有地址列表(selectedAddress 值发生改变) |  {}
+
+## close 回调参数
+| 参数 | 说明 | 可能值 
+|----- | ----- | ----- 
+| type | 地址选择类型 exist/custom  |  exist/custom
+| data | 选择地址的值,custom 时,addressStr 为选择的地址组合 | {} 
+
+注:点击叉号或者点击遮罩层关闭地址选择,不会触发 close 事件。

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

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

+ 5 - 0
src/packages/icon/icon.vue

@@ -31,6 +31,11 @@ export default {
       icon:null
     };
   },
+  watch:{
+    url(val){
+      this.icon=val
+    }
+  },
   created() {
     if(this.url){         
       this.icon =this.url;

+ 1 - 0
src/packages/stepper/stepper.vue

@@ -155,6 +155,7 @@ export default {
             }else{
                 this.num = this.tempNum;
             }
+            this.$emit('update:value', this.num);
             this.$emit('blur', e, this.num);
         },
         checknum(e) {

+ 1 - 0
types/nutui.d.ts

@@ -80,3 +80,4 @@ export declare class SubSideNavBar extends UIComponent { }
 export declare class SideNavBarItem extends UIComponent { }
 export declare class Qart extends UIComponent { }
 export declare class Drag extends UIComponent { }
+export declare class Address extends UIComponent {}