Vue 自己封装一个 actionsheet 全局插件

1,132 阅读5分钟

//移动端使用的是 @pax/pax-ui
npm install @pax/pax-ui

1. 如果使用 UI框架中的actionsheet 上拉菜单

在main.js 中:

//Mobile
import PAXUI from '@pax/pax-ui'
import '@pax/pax-ui/dist/paxui.rem.css'

//全局使用 dialog
import { Confirm, Alert, Toast, Notify, Loading } from '@pax/pax-ui/dist/lib.rem/dialog'
import '@pax/pax-ui/dist/paxui.base.css' //注意:按需引入单个组件,另需导入重置基础样式

Vue.prototype.$dialog = {
    confirm: Confirm,
    alert: Alert,
    toast: Toast,
    notify: Notify,
    loading: Loading
};

/* ===调用=== */
// this.$dialog.confirm({ /* 参数 */});
// this.$dialog.alert({/* 参数 */});
// this.$dialog.toast({ /* 参数 */ });
// this.$dialog.notify({ /* 参数 */ });
// this.$dialog.loading({ /* 参数 */ });

API示例:

<pax-button-group>
    <pax-button @click.native="show1 = true" size="large">带取消</pax-button>
    <pax-button @click.native="show2 = true" size="large" type="warning">不带取消</pax-button>
</pax-button-group>

<pax-actionsheet :items="myItems1" v-model="show1" cancel="取消"></pax-actionsheet>
<pax-actionsheet :items="myItems2" v-model="show2"></pax-actionsheet>

export default {
    data(){
        return {
            show1: false,
            show2: false,
            myItems1: [
                {
                    label: '拍照',
                    callback: ()=> {
                        this.$dialog.toast({mes: 'this is a toast', timeout: 1000});
                        //注意: callback: function (){} 和 callback(){}  这样是无法正常使用当前this的
                    }
                },
                {
                    label: '从相册中偷取',
                    callback: () => {
                        this.$dialog.toast({mes:'this is a toast', timeout: 1000});
                    }
                }
            ],
            myItems2: [
                {
                    label: '示例菜单一 - 我是不会关闭的',
                    callback: () => {
                        this.$dialog.toast({mes: 'Say: 我是不会关闭的!', timeout: 1000});
                    },
                    stay: true  // 不关闭
                },
                {
                    label: '示例菜单二 - 自动关闭',
                    callback: () => {
                        this.$dialog.toast({mes: 'Say: 我关闭啦啦啦!', timeout: 1000});
                    }
                },
                {label: '示例菜单三 - 自动关闭'},
                {label: '示例菜单四 - 自动关闭'}
            ]
        }
    }
}

API:

pax-actionsheet 一共3个参数:

items: [{    //类型: Array
    label: String,  //菜单项的文本
    callback: Function,  //点击该菜单项的回调函数
    stay: Boolean,  //是否关闭 actionsheet (默认false, 直接关闭)
}],
cancel: 取消按钮的文本,为空则不显示取消按钮   //类型: String
masker-opacity: 遮罩层透明度   //类型: Number 默认值:0.5

2. 在 order_details_tabs.vue 组件中调用:

2.1 (第一种)如果把参数写死

<template>
    <!-- 快捷支付 -->
    <!-- 通过点击事件 @click.native="show2=true" 去调取 actionsheet 上拉菜单 -->
    <pax-cell-item @click.native="show2=true"
        arrow
        type="div"
        href="javascript:;">
        <span slot="left">付款方式</span>
        <span slot="right">
            <i>1</i>
            中国银行(2762)
        </span>
    </pax-cell-item>
    
    <div>
        <!-- 上拉菜单 - 选择银行 -->
        <pax-actionsheet :items="myBankItems" v-model="show2"></pax-actionsheet>
    </div>
</template>

export default {
    data(){
        return {
            show2: false,  //显示上拉菜单
            myBankItems: [
                {
                    label: '示例菜单一 - 我是不会关闭的',
                    callback: () => {
                        this.$dialog.toast({mes: 'Say: 我是不会关闭的!', timeout: 1000});
                    },
                    stay: true     //不关闭
                },
                {
                    label: '示例菜单二 - 自动关闭',
                    callback: () => {
                        this.$dialog.toast({mes: 'Say: 我关闭了!', timeout: 1000});
                    }
                },
                {label: '示例菜单三 - 自动关闭'},
                {label: '添加银行卡'}
            ]
        }
    }
}

2.2 (第二种)如果调用 Vuex 中的公共状态, 使用 computed

<template>
    <div>
        <!-- 上拉菜单 - 选择银行 -->
        <pax-actionsheet :items="actionsheetData1"
            :cancel="actionsheetCancel1"
            v-model="show2">
        </pax-actionsheet>
    </div>
</template>

computed: {
    //得到Vuex中的公共状态
    ...mapState(['tradesCurrentObj', 'tradesBanksObj']),
    
    actionsheetData1(){ //对actionsheetData1变量进行计算
        let arr1 = [
            {
                label: '请选择银行卡',
                callback: () => {
                    alert('点击了选择银行卡')
                }
            }
        ];
        
        //对tradesBanksObj 数组的筛选,进行筛选
        //只能使用 for in 来遍历 this.tradesBanksObj 这个数组了
        for(let v in this.tradesBanksObj) {
            console.log(this.tradesBanksObj[v])
            
            arr1.push({
                label: this.tradesBanksObj[v],
                callback: () => {
                    alert(this.tradesBanksObj[v])
                }
            })
        }
        let str1 = '666';
        function str2(){
            return 777
        }
        arr1.push({
            label: `添加银行卡${str1}, ${str2()}`,
            callback: () => {
                alert('点击了添加银行卡')
            }
        })
        return arr1
    },
    actionsheetCancel1(){
        return '关闭'
    }
},

3. 如果不用组件,自己封装一个 actionsheet 全局插件

由于 @pax/pax-ui 这个mobile UI框架的 actionsheet 上拉菜单, 不能放置图标,所以想着自己封装一个 actionsheet 全局插件。

在 src/components 目录,新建 actionsheet/actionsheet.vue 目录及组件。

actionsheet.vue:

<template>
    <div>
        <transition name="fade">
            <div class="actionsheet_wrapper" v-show="open">
                <div class="actionsheet">
                    <div class="_header">
                        <span class="arrow" @click="cancelFunc">
                            <img src="/images/arrow.png" />
                        </span>
                        <span class="cart">请选择银行卡</span>
                        <span class="clear" @click="cancelFunc">关闭</span>
                    </div>
                    
                    <div class="_content">
                        <p class="item" v-for="(item, index) in tradesBanksData">
                            <span class="item_icon">
                                <img :src="('/images/bank-logo-map/' + item + '.png')" alt="" >
                            </span>
                            <span class="item_name"> {{item}} 中国建设银行(2766) </span>
                        </p>
                        <p class="item">
                            <span class="item_icon">
                                <img src="/images/bank-logo-map/CCB.png" alt="" >
                            </span>
                            
                            <span class="item_name">中国银行(6783)</span>
                        </p>
                        
                        <!-- 添加银行卡 -->
                        <p class="item">
                            <span class="item_icon">
                                <img src="/images/add-bank.png" alt="" />
                            </span>
                            <span class="item_name">添加银行卡</span>
                        </p>
                        
                        <!-- <p class="cancel" @click="cancelFunc">取消</p> -->
                        <p class="cancel"></p>
                    </div>
                </div>
            </div>
        </transition>
        <div class="actionsheet_mask" v-show="open" @click="cancelFunc"></div>
    </div>
</template>

<script>
    export default {
        name: "actionsheet.vue",
        data(){
            return {}
        },
        props: {
            open: false, //接收从父组件传过来的控制是否显示的属性
            tradesBanksData: {}  //接收从父组件传过来的, Vuex中的公共状态, 数据(先在父组件中通过mapState获得,然后通过 v-bind:tradesBanksData="tradesBanksObj" 传给自组件)
        },
        methods: {
            //在组件的模板中,给关闭按钮和取消按钮绑定了click事件,
            //功能为关闭操作表, 通过 this.$emit 发射该事件到父组件,
            //(第一个参数为父组件接收的方法属性名,如果想传递参数
            //也可以在后面加上第二个参数)
            cancelFunc(){
                this.$emit('show');
            }
        }
    }
</script>

<style scoped lang="less">
    * {
        box-sizing: border-box;
    }
    .actionsheet_wrapper {
        position: fixed;
        bottom:0;
        left: 0;
        width: 100%;
        z-index: 1501;
        .actionsheet {
            background-color:#fff;
            ._header{
                display:flex;
                width: 100%;
                height: 49px;
                padding: 0 8px;
                align-items: center;
                flex-direction: row;
                border-bottom: 1px solid #E6E6E6;
                
                //flex: 0 0 20% 表示占其中的 1/5, 其余的auto
                .arrow, .clear{
                    flex: 0 0 20%!important;
                }
                .arrow {
                    text-align:left;
                    img {
                        width: 18px;
                        height: 18px;
                        position: relative;
                        top: 2px;
                    }
                }
                
                // flex: 0 0 60%important;  表示占比 60%
                .cart {
                    flex: 0 0 60%!important;
                    
                    text-align:center;
                    margin-right:auto;
                    font-size: 14px;
                    color:#1111;
                }
                .clear {
                    text-align:right;
                    color: #0894ec;
                }
            }
            ._content {
                .cancel {
                    /* background-color: #39678c; */
                    color:#fff;
                    height: 49px;
                    line-height: 49px;
                    box-sizing: border-box;
                    /* padding-bottom: 105px; */
                }
                
                .item {
                    height: 49px;
                    /* border-bottom: 1px solid #e6e6e6; */
                    
                    display: flex;
                    align-items: center;
                    /* justify-content: center; */
                    padding: 0 8px;
                    
                    .item_icon {
                        flex:0 0 10%!important;
                        margin-right: 6px;
                        img {
                            width: 18px;
                            height: 18px;
                            position: relative;
                            top:1px;
                        }
                    }
                    .item_name {
                        flex: 0 0 90%!important;
                        
                        text-align: left;
                        display: inline-block;
                        height: 49px;
                        line-height: 49px;
                        border-bottom: 1px solid #e6e6e6;
                    }
                }
            }
        }
    }
    
    //过渡效果transition使用
    //定义transition的name属性值为 fade, 定义从底部向上淡入的滑入和滑出,
    //使用 translate3d,在移动端会开启硬件加速器,效果很流畅,接近原生效果
    .fade-enter-active, .fade-leave-active {
        transition: all .4s ease;
    }
    .fade-enter, .fade-leave-to {
        transform: translate3d(0, 100%, 0);
        opacity: 0;
    }
    
    //遮罩
    .actionsheet_mask {
        position: fixed;
        bottom:0;
        right:0;
        left:0;
        top:0;
        display: -webkit-box;
        display: -webkit-flex;
        display: -ms-flexbox;
        display: flex;
        -webkit-box-pack: center;
        -webkit-justify-content: center;
        -ms-flex-pack: center;
        justify-content: center;
        -webkit-box-align: center;
        -webkit-align-items: center;
        -ms-flex-align: center;
        align-items: center;
        pointer-events: none;
        -webkit-transition: opacity .2s ease-in;
        transition: opacity .2s ease-in;
        opacity: 0;
        
        z-index: 1500;
        background-color: rgb(0,0,0);
        opacity: 0.5;
        pointer-events: auto;
    }
</style>

在 components/vue-component.js 中进行 install

vue-component.js:

import VNavbar from './navbar/navbar.vue'
import VActionsheet from './actionsheet/actionsheet.vue'

export default {
    install(Vue) {
        Vue.component('VNavbar', VNavbar),
        Vue.component('VActionsheet', VActionsheet)
    }
}

4. 在组件中使用 actionsheet 全局插件

在组件中使用:

order_details_tabs.vue:

<button @click="toggleActionSheet">点击</button>
<VActionsheet :open="openActionsheet" v-on:show="func($event)"
    :tradesBanksData="tradesBanksObj"></VActionsheet>
    
export default {
    data(){
        return {
            openActionsheet: false
        },
        computed: {
            //得到Vuex中的公共状态
            ...mapState(['tradesCurrentObj', 'tradesBanksObj']),
        },
        methods: {
            //调用actions中的方法
            ...mapActions([
                types.DO_GET_TRADES_CURRENT,
                types.DO_GET_TRADES_BANKS
            ]),
            
            //该方法为按钮的点击事件, 把是否显示操作表的变量取反
            toggleActionSheet(){
                this.openActionsheet = !this.openActionsheet
            },
            //该方法为actionsheet组件通过 this.$emit 发射过来事件,
            //用于控制操作表显隐
            func(e) {
                this.openActionsheet = !this.openActionsheet;
                console.log(e);
            }
        }
    }
}

参考:

www.jianshu.com/p/09abce827…