├── .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 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
{{ aTitle[0] }}
8 |
9 |
10 |
14 |
15 |
16 |
17 |
21 |
22 |
23 | {{ btns[1] }}
24 | {{ btns[0] }}
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
219 |
220 |
223 |
--------------------------------------------------------------------------------