├── .gitattributes ├── README.md ├── demo ├── alert.png ├── confirm.png ├── custom-content-layer.png ├── footer.png └── msg.png ├── index.js ├── package.json └── src ├── layer.css ├── layer.js └── layer.vue /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue2.0-layer-mobile移动端弹层 2 | 3 | > 本次组件升级支持slot内容分发功能,实现高定制内容风格的弹层 4 | 5 | ## 安装方法 6 | 7 | ```javascript 8 | npm install vue2-layer-mobile -S 9 | ``` 10 | 11 | ## 初始化 12 | 13 | ```javascript 14 | import layer from 'vue2-layer-mobile' 15 | Vue.use(layer) 16 | ``` 17 | 18 | > 该组件是基于开源插件[layer-mobile](http://layer.layui.com/mobile/)用vue重新改写的,并且扩展了一些便捷方法 19 | > 具体的API与layer-mobile高度保持一值,大家可以放心使用 20 | 21 | ## 组件使用 22 | 23 | ```javascript 24 | // 普通使用 25 | 26 | 27 | // 利用 slot,自定义风格各异的弹层 28 | // 扩展支持 slot 是为了解决以 plugin 形式时,通过 content 属性传入生成的内容不支持 vue 特性的问题 29 | 30 |
31 |

请输入支付密码

32 |
33 | 34 |
35 |
36 |
37 | 38 | 39 | export default { 40 | data() { 41 | return { 42 | showLayer: true 43 | } 44 | } 45 | } 46 | 47 | ``` 48 | 49 | ## plugin形式调用 50 | 51 | ```javascript 52 | 53 | // 询问层 54 | const index = this.$layer.open({ 55 | btn: ['确认', '取消'], 56 | content: 'hello word', 57 | className: 'good luck1', 58 | shade:true, 59 | success(layer) { 60 | console.log('layer id is:',layer.id) 61 | }, 62 | yes(index, $layer) { 63 | console.log(arguments) 64 | // 函数返回 false 可以阻止弹层自动关闭,需要手动关闭 65 | // return false; 66 | }, 67 | end() { 68 | console.log('end') 69 | } 70 | }) 71 | 72 | // 关闭层 73 | this.$layer.close(index) 74 | 75 | // loading层 76 | const index = this.$layer.open({ 77 | type:2, 78 | content: '加载中...', 79 | success(layer) { 80 | console.log('layer id is:',layer.id) 81 | }, 82 | end() { 83 | console.log('end') 84 | } 85 | }) 86 | 87 | // 底部对话框 88 | this.$layer.open({ 89 | content: '这是一个底部弹出的询问提示', 90 | btn: ['删除', '取消'], 91 | skin: 'footer', 92 | yes: (index) => { 93 | this.$layer.open({content: '执行删除操作'}) 94 | } 95 | }) 96 | 97 | // 页面层 98 | this.$layer.open({ 99 | type: 1, 100 | content: '可传入任何内容,支持html。一般用于手机页面中', 101 | anim: 'up', 102 | // 特别注意,这个styles属性跟 layer-mobile 有点区别多加了个s,因为style在vue中是保留关键词 103 | styles: 'position:fixed; bottom:0; left:0; width: 100%; height: 200px; padding:10px 0; border:none;' 104 | }) 105 | 106 | ``` 107 | 108 | ### 扩展方法 109 | 110 | __以下方法都可以通过 this.$layer.open 这个方法来实现.__ 111 | 112 | 提示层(msg) 113 | ```javascript 114 | 115 | this.$layer.msg('hello world', () => console.log('end!!!')) 116 | 117 | ``` 118 | 119 | 信息层(alert) 120 | ```javascript 121 | 122 | this.$layer.alert('您确定要刷新页面吗', () => window.location.reload()) 123 | 124 | ``` 125 | 126 | 询问层(confirm) 127 | ```javascript 128 | const index = this.$layer.confirm('您确定要删除吗?', () => alert('yes'), () => alert('no')) 129 | 130 | setTimeout(() => { 131 | this.$layer.close(index) 132 | }, 3000) 133 | 134 | ``` 135 | 136 | ## API 137 | 138 | ### Props 139 | 140 | | 参数 | 说明 | 类型 | 可选值 | 默认值 | 141 | | ---- | ---- | ---- | ------ | ------ | 142 | | type | 弹层的类型 | Number | 0表示信息框,1表示页面层,2表示加载层 | 0 | 143 | | content | 弹层内容 | String | 必选参数 | 无 | 144 | | title | 弹层标题 | String或Array | 值可以为字符串或者数组 | | 145 | | time | 控制自动关闭层所需秒数 | Number | 整数和浮点数 | 默认不开启 | 146 | | styles | 自定义层的样式 | String | 如'border:none; color:#fff;' | | 147 | | skin | 弹层显示风格 | String | footer(即底部对话框风格)、msg(普通提示)| | 148 | | className | 自定义一个css类 | String | custom-class | | 149 | | btn | 按钮 | String/Array | 字符串或者数组(目前最多支持两个) | | 150 | | anim | 动画类型 | String/Boolean | scale(默认)、up(从下往上弹出),如果不开启动画,设置false即可 | scale(默认) | 151 | | shade | 控制遮罩展现 | Boolean | true/false | true | 152 | | shadeClose | 是否点击遮罩时关闭层 | Boolean | true/false | true | 153 | | fixed | 是否固定层的位置 | Boolean | true/false | true | 154 | | top | 控制层的纵坐标 | Number | 整数和浮点数(一般情况下不需要设置,因为层会始终垂直水平居中,只有当fixed: false时top才有效) | 无 | 155 | | success | 层成功弹出层的回调(只要以插件形式使用才有效),该回调参数返回一个参数为当前层元素对象 | Function | Function/null | null | 156 | | yes | 点确定按钮触发的回调函数,返回一个参数为当前层的索引(只要以插件形式使用才有效) | Function | Function/null | null | 157 | | no | 点取消按钮触发的回调函数(只要以插件形式使用才有效) | Function | Function/null | null | 158 | | end | 层彻底销毁后的回调函数(只要以插件形式使用才有效) | Function | Function/null | null | 159 | 160 | ### Slots 161 | 162 | | name | 描述 | 163 | | ---- | ---- | 164 | | default | 弹出框的内容 | 165 | 166 | ### Events 167 | 168 | | name | 说明 | 回调参数 | 169 | | ---- | ---- | -------- | 170 | | sure | 点击确认按钮时触发 | 无 | 171 | | cancel | 点击取消按钮时触发 | 无 | 172 | | show | 弹窗可见时触发 | 无 | 173 | | close | 弹窗关闭时触发 | 无 | 174 | 175 | *`这些事件不适用于以插件形式调用的事件监听处理(它有自己的处理事件方法见以上api如yes、no等)`* 176 | 177 | 178 | ### 插件形式的内置方法/属性 179 | 180 | 返回当前使用的layer mobile版本号 181 | ```javascript 182 | this.$layer.v 183 | ``` 184 | 185 | 用于关闭特定层,index为该特定层的索引 186 | ```javascript 187 | layer.close(index) 188 | ``` 189 | 190 | 关闭页面所有layer的层 191 | ```javascript 192 | layer.closeAll() 193 | ``` 194 | 195 | 196 | ## 说明 197 | 198 | 1.参数(options) 199 | 200 | ~~style~~改成`styles` 201 | 202 | ~~shade~~不支持自定义透明度与背景色 203 | 204 | *特别注意,这个styles属性跟 layer-mobile 有点区别多加了个s,因为style在vue中是保留关键词* 205 | 206 | 2.扩展方法\[msg、alert、confirm\] 207 | *只有当你调用以上扩展方法时*,会自动给层添加一个css类'layui-m-'\+方法名\[msg、alert、confirm\] 208 | 209 | ## 效果图 210 | 211 |
212 |

利用 slot 自定义弹层

213 | 214 |
215 | 216 |
217 |

信息弹层

218 | 219 |
220 | 221 |
222 |

提示

223 | 224 |
225 | 226 |
227 |

底部提示弹层

228 | 229 |
230 | 231 |
232 |

询问弹层

233 | 234 |
235 | 236 | 237 | 238 | -------------------------------------------------------------------------------- /demo/alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheng/vue2-layer-mobile/31d1a8c1856b0cb7dd85410a7b6fd4c9280a7823/demo/alert.png -------------------------------------------------------------------------------- /demo/confirm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheng/vue2-layer-mobile/31d1a8c1856b0cb7dd85410a7b6fd4c9280a7823/demo/confirm.png -------------------------------------------------------------------------------- /demo/custom-content-layer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheng/vue2-layer-mobile/31d1a8c1856b0cb7dd85410a7b6fd4c9280a7823/demo/custom-content-layer.png -------------------------------------------------------------------------------- /demo/footer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheng/vue2-layer-mobile/31d1a8c1856b0cb7dd85410a7b6fd4c9280a7823/demo/footer.png -------------------------------------------------------------------------------- /demo/msg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qiheng/vue2-layer-mobile/31d1a8c1856b0cb7dd85410a7b6fd4c9280a7823/demo/msg.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Administrator on 2017/11/23. 3 | */ 4 | 5 | export { default } from './src/layer' 6 | 7 | 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue2-layer-mobile", 3 | "version": "1.0.1", 4 | "description": "vue2.0移动端弹层", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "vue2.0", 11 | "layer", 12 | "layer-mobile", 13 | "vue-layer-mobile" 14 | ], 15 | "author": "qiheng", 16 | "license": "MIT" 17 | } 18 | -------------------------------------------------------------------------------- /src/layer.css: -------------------------------------------------------------------------------- 1 | .layui-m-layer{position:relative;z-index:19891014}.layui-m-layer *{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.layui-m-layermain,.layui-m-layershade{position:fixed;left:0;top:0;width:100%;height:100%}.layui-m-layershade{background-color:rgba(0,0,0,.7);pointer-events:auto}.layui-m-layermain{display:table;font-family:Helvetica,arial,sans-serif;pointer-events:none}.layui-m-layermain .layui-m-layersection{display:table-cell;vertical-align:middle;text-align:center}.layui-m-layerchild{position:relative;display:inline-block;text-align:left;background-color:#fff;font-size:14px;border-radius:5px;box-shadow:0 0 8px rgba(0,0,0,.1);pointer-events:auto;-webkit-overflow-scrolling:touch;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.2s;animation-duration:.2s}@-webkit-keyframes layui-m-anim-scale{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes layui-m-anim-scale{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}.layui-m-anim-scale{animation-name:layui-m-anim-scale;-webkit-animation-name:layui-m-anim-scale}@-webkit-keyframes layui-m-anim-up{0%{opacity:0;-webkit-transform:translateY(800px);transform:translateY(800px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes layui-m-anim-up{0%{opacity:0;-webkit-transform:translateY(800px);transform:translateY(800px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}.layui-m-anim-up{-webkit-animation-name:layui-m-anim-up;animation-name:layui-m-anim-up}.layui-m-layer0 .layui-m-layerchild{width:90%;max-width:640px}.layui-m-layer1 .layui-m-layerchild{border:none;border-radius:0}.layui-m-layer2 .layui-m-layerchild{width:auto;max-width:260px;min-width:40px;border:none;background:0 0;box-shadow:none;color:#fff}.layui-m-layerchild h3{padding:0 10px;height:50px;line-height:50px;font-size:16px;font-weight:400;border-radius:5px 5px 0 0;text-align:center;background:#F2F2F2}.layui-m-layerbtn span,.layui-m-layerchild h3{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.layui-m-layercont{padding:50px 30px;line-height:22px;text-align:center}.layui-m-layer1 .layui-m-layercont{padding:0;text-align:left}.layui-m-layer2 .layui-m-layercont{text-align:center;padding:0;line-height:0}.layui-m-layer2 .layui-m-layercont i{width:25px;height:25px;margin-left:8px;display:inline-block;background-color:#fff;border-radius:100%;-webkit-animation:layui-m-anim-loading 1.4s infinite ease-in-out;animation:layui-m-anim-loading 1.4s infinite ease-in-out;-webkit-animation-fill-mode:both;animation-fill-mode:both}.layui-m-layerbtn,.layui-m-layerbtn span{position:relative;text-align:center;border-radius:0 0 5px 5px}.layui-m-layer2 .layui-m-layercont p{margin-top:20px}@-webkit-keyframes layui-m-anim-loading{0%,100%,80%{transform:scale(0);-webkit-transform:scale(0)}40%{transform:scale(1);-webkit-transform:scale(1)}}@keyframes layui-m-anim-loading{0%,100%,80%{transform:scale(0);-webkit-transform:scale(0)}40%{transform:scale(1);-webkit-transform:scale(1)}}.layui-m-layer2 .layui-m-layercont i:first-child{margin-left:0;-webkit-animation-delay:-.32s;animation-delay:-.32s}.layui-m-layer2 .layui-m-layercont i.layui-m-layerload{-webkit-animation-delay:-.16s;animation-delay:-.16s}.layui-m-layer2 .layui-m-layercont>div{line-height:22px;padding-top:7px;margin-bottom:20px;font-size:14px}.layui-m-layerbtn{display:box;display:-moz-box;display:-webkit-box;width:100%;height:50px;line-height:50px;font-size:0;border-top:1px solid #D0D0D0;background-color:#F2F2F2}.layui-m-layerbtn span{display:block;-moz-box-flex:1;box-flex:1;-webkit-box-flex:1;font-size:14px;cursor:pointer}.layui-m-layerbtn span[yes]{color:#40AFFE}.layui-m-layerbtn span[no]{border-right:1px solid #D0D0D0;border-radius:0 0 0 5px}.layui-m-layerbtn span:active{background-color:#F6F6F6}.layui-m-layerend{position:absolute;right:7px;top:10px;width:30px;height:30px;border:0;font-weight:400;background:0 0;cursor:pointer;-webkit-appearance:none;font-size:30px}.layui-m-layerend::after,.layui-m-layerend::before{position:absolute;left:5px;top:15px;content:'';width:18px;height:1px;background-color:#999;transform:rotate(45deg);-webkit-transform:rotate(45deg);border-radius:3px}.layui-m-layerend::after{transform:rotate(-45deg);-webkit-transform:rotate(-45deg)}body .layui-m-layer .layui-m-layer-footer{position:fixed;width:95%;max-width:100%;margin:0 auto;left:0;right:0;bottom:10px;background:0 0}.layui-m-layer-footer .layui-m-layercont{padding:20px;border-radius:5px 5px 0 0;background-color:rgba(255,255,255,.8)}.layui-m-layer-footer .layui-m-layerbtn{display:block;height:auto;background:0 0;border-top:none}.layui-m-layer-footer .layui-m-layerbtn span{background-color:rgba(255,255,255,.8)}.layui-m-layer-footer .layui-m-layerbtn span[no]{color:#FD482C;border-top:1px solid #c2c2c2;border-radius:0 0 5px 5px}.layui-m-layer-footer .layui-m-layerbtn span[yes]{margin-top:10px;border-radius:5px}body .layui-m-layer .layui-m-layer-msg{width:auto;max-width:90%;margin:0 auto;bottom:-150px;background-color:rgba(0,0,0,.7);color:#fff}.layui-m-layer-msg .layui-m-layercont{padding:10px 20px} 2 | -------------------------------------------------------------------------------- /src/layer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by qiheng on 2017/11/23. 3 | * email qihengxiao@gmail.com 4 | */ 5 | 6 | import LayerComponent from './layer.vue' 7 | 8 | let uuid = 0; 9 | const ready = { 10 | extend: function(oldObj, targetObj){ 11 | var newobj = JSON.parse(JSON.stringify(oldObj)); 12 | for(var i in targetObj){ 13 | newobj[i] = targetObj[i]; 14 | } 15 | return newobj; 16 | }, 17 | timer: {}, end: {}, layerPool: {} 18 | }; 19 | 20 | // 合并默认参数与传入参数 21 | const mergeOptions = function ($vm, options) { 22 | const defaults = {} 23 | for (let i in $vm.$options.props) { 24 | if (i !== 'value') { 25 | defaults[i] = $vm.$options.props[i].default 26 | } 27 | } 28 | const _options = ready.extend(defaults, options) 29 | for (let i in _options) { 30 | $vm[i] = _options[i] 31 | } 32 | } 33 | 34 | const plugin = { 35 | install(Vue, options = {}) { 36 | 37 | const doc = document 38 | const LayerCom = Vue.extend(LayerComponent) 39 | const layer = { 40 | version: '1.0.3', 41 | __init(options = {}) { 42 | const that = this 43 | options.uuid = uuid 44 | 45 | const $layer = that.$layer = new LayerCom({ 46 | el: doc.createElement('div') 47 | }); 48 | 49 | if (options.skin) { 50 | options.anim = 'up' 51 | 52 | options.skin === 'msg' && (options.shade = false) 53 | } 54 | 55 | if(!options.fixed){ 56 | options.styles = options.styles || ''; 57 | options.styles += ' top:'+ ( doc.body.scrollTop + options.top) + 'px'; 58 | } 59 | 60 | mergeOptions($layer, options) 61 | 62 | $layer.$nextTick(() => { 63 | document.body.appendChild($layer.$el) 64 | $layer.visible = true 65 | options.success && options.success($layer.$el) 66 | }) 67 | 68 | ready.layerPool[$layer.uuid] = $layer.$el 69 | that.__action(options, $layer) 70 | }, 71 | __action(options, $layer) { 72 | const that = this 73 | 74 | /*if (options.time) { 75 | ready.timer[$layer.uuid] = setTimeout(() => { 76 | that.close($layer.uuid) 77 | }, options.time * 1000) 78 | }*/ 79 | 80 | // 移除组件事件监听 81 | /*$layer.$off('close') 82 | $layer.$off('sure') 83 | $layer.$off('cancel')*/ 84 | 85 | // 添加组件事件监听 86 | $layer.$on('input', (val) => { 87 | $layer.visible = val 88 | }) 89 | 90 | $layer.$on('sure', () => { 91 | if (options.yes && options.yes($layer.uuid, $layer) === false) return 92 | console.log('重复') 93 | that.close($layer.uuid) 94 | }) 95 | 96 | $layer.$on('cancel', () => { 97 | if (options.no && options.no($layer.uuid, $layer) === false) return 98 | that.close($layer.uuid) 99 | }) 100 | 101 | $layer.$on('close', () => { 102 | console.log('close eent') 103 | setTimeout(() => { 104 | that.close($layer.uuid) 105 | }, 20) 106 | }) 107 | 108 | options.end && (ready.end[$layer.uuid] = options.end) 109 | }, 110 | // 核心函数 111 | open(options = {}) { 112 | const that = this 113 | that.__init(options) 114 | return uuid++ 115 | }, 116 | // 关闭层 117 | close(index) { 118 | const layerEl = ready.layerPool[index]; 119 | if (layerEl && layerEl.parentNode) { 120 | layerEl.parentNode.removeChild(layerEl) 121 | this.$layer.$destroy() 122 | delete ready.layerPool[index] 123 | clearTimeout(ready.timer[index]) 124 | delete ready.timer[index] 125 | typeof ready.end[index] === 'function' && ready.end[index]() 126 | delete ready.end[index] 127 | } 128 | }, 129 | // 关闭所有层 130 | closeAll() { 131 | for (const key in ready.layerPool) { 132 | this.close(key) 133 | } 134 | }, 135 | msg(content, options = {time: 3}, end) { 136 | if (typeof options === 'function') { 137 | end = options 138 | options = {time: 3} 139 | } 140 | options.className = 'layui-m-msg' + (options.className ? ' ' + options.className : '') 141 | options = ready.extend(options, { 142 | content: content || '', 143 | skin: 'msg', 144 | end, 145 | }) 146 | 147 | return this.open(options) 148 | }, 149 | alert(content, options = {}, yes) { 150 | if (typeof options === 'function') { 151 | yes = options 152 | options = {} 153 | } 154 | 155 | options.btn = options.btn || ['确认'] 156 | options.className = 'layui-m-alert' + (options.className ? ' ' + options.className : '') 157 | options = ready.extend(options, { 158 | type: 0, 159 | content: content || '', 160 | yes, 161 | }) 162 | 163 | return this.open(options) 164 | }, 165 | confirm(content, options = {}, yes, no) { 166 | if (typeof options === 'function') { 167 | yes = options 168 | options = {} 169 | } 170 | 171 | options.btn = options.btn || ['确认', '取消'] 172 | options.className = 'layui-m-confirm' + (options.className ? ' ' + options.className : '') 173 | options = ready.extend(options, { 174 | type: 0, 175 | content: content || '', 176 | yes, 177 | no, 178 | }) 179 | 180 | return this.open(options) 181 | } 182 | } 183 | 184 | if (!Vue.layer) { 185 | Vue.layer = layer 186 | } 187 | 188 | Vue.mixin({ 189 | created() { 190 | this.$layer = Vue.layer; 191 | }, 192 | components: { 193 | [LayerComponent.name]: LayerComponent 194 | } 195 | }) 196 | } 197 | }; 198 | 199 | export default plugin 200 | -------------------------------------------------------------------------------- /src/layer.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 219 | 220 | 223 | --------------------------------------------------------------------------------