feat:弹框
Showing
5 changed files
with
471 additions
and
1 deletions
... | @@ -2,11 +2,13 @@ import dialogBox from '@/components/DialogBox/dialogBox.vue' | ... | @@ -2,11 +2,13 @@ import dialogBox from '@/components/DialogBox/dialogBox.vue' |
2 | import LbTable from '@/components/LbTable/lb-table.vue' | 2 | import LbTable from '@/components/LbTable/lb-table.vue' |
3 | import Theme from '@/components/Theme/theme.vue' | 3 | import Theme from '@/components/Theme/theme.vue' |
4 | import Popup from '@/components/Popup/index' | 4 | import Popup from '@/components/Popup/index' |
5 | import MessageBox from '@/components/MessageBox/index.js' | ||
5 | export default { | 6 | export default { |
6 | install: (Vue) => { | 7 | install: (Vue) => { |
7 | Vue.component('dialogBox', dialogBox); | 8 | Vue.component('dialogBox', dialogBox); |
8 | Vue.component('lbTable', LbTable); | 9 | Vue.component('lbTable', LbTable); |
9 | Vue.component('Theme', Theme); | 10 | Vue.component('Theme', Theme); |
10 | Vue.prototype.$popup = Popup.install | 11 | Vue.prototype.$popup = Popup.install; |
12 | Vue.prototype.$alertMes = MessageBox.alert; | ||
11 | } | 13 | } |
12 | } | 14 | } |
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
src/components/MessageBox/index.js
0 → 100644
src/components/MessageBox/src/main.js
0 → 100644
1 | const defaults = { | ||
2 | title: null, | ||
3 | message: '', | ||
4 | type: '', | ||
5 | iconClass: '', | ||
6 | showInput: false, | ||
7 | showClose: true, | ||
8 | modalFade: true, | ||
9 | lockScroll: true, | ||
10 | closeOnClickModal: true, | ||
11 | closeOnPressEscape: true, | ||
12 | closeOnHashChange: true, | ||
13 | inputValue: null, | ||
14 | inputPlaceholder: '', | ||
15 | inputType: 'text', | ||
16 | inputPattern: null, | ||
17 | inputValidator: null, | ||
18 | inputErrorMessage: '', | ||
19 | showConfirmButton: true, | ||
20 | showCancelButton: false, | ||
21 | confirmButtonPosition: 'right', | ||
22 | confirmButtonHighlight: false, | ||
23 | cancelButtonHighlight: false, | ||
24 | confirmButtonText: '', | ||
25 | cancelButtonText: '', | ||
26 | confirmButtonClass: '', | ||
27 | cancelButtonClass: '', | ||
28 | customClass: '', | ||
29 | beforeClose: null, | ||
30 | dangerouslyUseHTMLString: false, | ||
31 | center: false, | ||
32 | roundButton: false, | ||
33 | distinguishCancelAndClose: false | ||
34 | }; | ||
35 | |||
36 | import Vue from 'vue'; | ||
37 | import msgboxVue from './main.vue'; | ||
38 | import merge from 'element-ui/src/utils/merge'; | ||
39 | import { isVNode } from 'element-ui/src/utils/vdom'; | ||
40 | |||
41 | const MessageBoxConstructor = Vue.extend(msgboxVue); | ||
42 | |||
43 | let currentMsg, instance; | ||
44 | let msgQueue = []; | ||
45 | |||
46 | const defaultCallback = action => { | ||
47 | if (currentMsg) { | ||
48 | let callback = currentMsg.callback; | ||
49 | if (typeof callback === 'function') { | ||
50 | if (instance.showInput) { | ||
51 | callback(instance.inputValue, action); | ||
52 | } else { | ||
53 | callback(action); | ||
54 | } | ||
55 | } | ||
56 | if (currentMsg.resolve) { | ||
57 | if (action === 'confirm') { | ||
58 | if (instance.showInput) { | ||
59 | currentMsg.resolve({ value: instance.inputValue, action }); | ||
60 | } else { | ||
61 | currentMsg.resolve(action); | ||
62 | } | ||
63 | } else if (currentMsg.reject && (action === 'cancel' || action === 'close')) { | ||
64 | currentMsg.reject(action); | ||
65 | } | ||
66 | } | ||
67 | } | ||
68 | }; | ||
69 | |||
70 | const initInstance = () => { | ||
71 | instance = new MessageBoxConstructor({ | ||
72 | el: document.createElement('div') | ||
73 | }); | ||
74 | |||
75 | instance.callback = defaultCallback; | ||
76 | }; | ||
77 | |||
78 | const showNextMsg = () => { | ||
79 | if (!instance) { | ||
80 | initInstance(); | ||
81 | } | ||
82 | instance.action = ''; | ||
83 | |||
84 | if (!instance.visible || instance.closeTimer) { | ||
85 | if (msgQueue.length > 0) { | ||
86 | currentMsg = msgQueue.shift(); | ||
87 | |||
88 | let options = currentMsg.options; | ||
89 | for (let prop in options) { | ||
90 | if (options.hasOwnProperty(prop)) { | ||
91 | instance[prop] = options[prop]; | ||
92 | } | ||
93 | } | ||
94 | if (options.callback === undefined) { | ||
95 | instance.callback = defaultCallback; | ||
96 | } | ||
97 | |||
98 | let oldCb = instance.callback; | ||
99 | instance.callback = (action, instance) => { | ||
100 | oldCb(action, instance); | ||
101 | showNextMsg(); | ||
102 | }; | ||
103 | if (isVNode(instance.message)) { | ||
104 | instance.$slots.default = [instance.message]; | ||
105 | instance.message = null; | ||
106 | } else { | ||
107 | delete instance.$slots.default; | ||
108 | } | ||
109 | ['modal', 'showClose', 'closeOnClickModal', 'closeOnPressEscape', 'closeOnHashChange'].forEach(prop => { | ||
110 | if (instance[prop] === undefined) { | ||
111 | instance[prop] = true; | ||
112 | } | ||
113 | }); | ||
114 | document.body.appendChild(instance.$el); | ||
115 | |||
116 | Vue.nextTick(() => { | ||
117 | instance.visible = true; | ||
118 | }); | ||
119 | } | ||
120 | } | ||
121 | }; | ||
122 | |||
123 | const MessageBox = function(options, callback) { | ||
124 | if (Vue.prototype.$isServer) return; | ||
125 | if (typeof options === 'string' || isVNode(options)) { | ||
126 | options = { | ||
127 | message: options | ||
128 | }; | ||
129 | if (typeof arguments[1] === 'string') { | ||
130 | options.title = arguments[1]; | ||
131 | } | ||
132 | } else if (options.callback && !callback) { | ||
133 | callback = options.callback; | ||
134 | } | ||
135 | |||
136 | if (typeof Promise !== 'undefined') { | ||
137 | return new Promise((resolve, reject) => { // eslint-disable-line | ||
138 | msgQueue.push({ | ||
139 | options: merge({}, defaults, MessageBox.defaults, options), | ||
140 | callback: callback, | ||
141 | resolve: resolve, | ||
142 | reject: reject | ||
143 | }); | ||
144 | |||
145 | showNextMsg(); | ||
146 | }); | ||
147 | } else { | ||
148 | msgQueue.push({ | ||
149 | options: merge({}, defaults, MessageBox.defaults, options), | ||
150 | callback: callback | ||
151 | }); | ||
152 | |||
153 | showNextMsg(); | ||
154 | } | ||
155 | }; | ||
156 | |||
157 | MessageBox.setDefaults = defaults => { | ||
158 | MessageBox.defaults = defaults; | ||
159 | }; | ||
160 | |||
161 | MessageBox.alert = (message, title, options) => { | ||
162 | if (typeof title === 'object') { | ||
163 | options = title; | ||
164 | title = ''; | ||
165 | } else if (title === undefined) { | ||
166 | title = ''; | ||
167 | } | ||
168 | return MessageBox(merge({ | ||
169 | title: title, | ||
170 | message: message, | ||
171 | $type: 'alert', | ||
172 | closeOnPressEscape: false, | ||
173 | closeOnClickModal: false | ||
174 | }, options)); | ||
175 | }; | ||
176 | |||
177 | MessageBox.close = () => { | ||
178 | instance.doClose(); | ||
179 | instance.visible = false; | ||
180 | msgQueue = []; | ||
181 | currentMsg = null; | ||
182 | }; | ||
183 | |||
184 | export default MessageBox; | ||
185 | export { MessageBox }; |
src/components/MessageBox/src/main.vue
0 → 100644
1 | <template> | ||
2 | <transition name="msgbox-fade"> | ||
3 | <div class="el-message-box__wrapper" tabindex="-1" v-show="visible" @click.self="handleWrapperClick" role="dialog" | ||
4 | aria-modal="true" :aria-label="title || 'dialog'"> | ||
5 | <div class="el-message-box" :class="[customClass, center && 'el-message-box--center']"> | ||
6 | <div class="el-message-box__header" v-if="title !== null"> | ||
7 | <div class="el-message-box__title"> | ||
8 | <div :class="['el-message-box__status', icon]" v-if="icon && center"> | ||
9 | </div> | ||
10 | <span>{{ title }}</span> | ||
11 | </div> | ||
12 | <button type="button" class="el-message-box__headerbtn" aria-label="Close" v-if="showClose" | ||
13 | @click="handleAction(distinguishCancelAndClose ? 'close' : 'cancel')" | ||
14 | @keydown.enter="handleAction(distinguishCancelAndClose ? 'close' : 'cancel')"> | ||
15 | <i class="el-message-box__close el-icon-close"></i> | ||
16 | </button> | ||
17 | </div> | ||
18 | <div class="el-message-box__content"> | ||
19 | <div class="el-message-box__message" v-if="message !== ''"> | ||
20 | <slot> | ||
21 | <p>{{ message }}</p> | ||
22 | </slot> | ||
23 | </div> | ||
24 | </div> | ||
25 | </div> | ||
26 | </div> | ||
27 | </transition> | ||
28 | </template> | ||
29 | |||
30 | <script type="text/babel"> | ||
31 | import Popup from 'element-ui/src/utils/popup'; | ||
32 | import Locale from 'element-ui/src/mixins/locale'; | ||
33 | import { addClass, removeClass } from 'element-ui/src/utils/dom'; | ||
34 | import { t } from 'element-ui/src/locale'; | ||
35 | import Dialog from 'element-ui/src/utils/aria-dialog'; | ||
36 | |||
37 | let messageBox; | ||
38 | let typeMap = { | ||
39 | success: 'success', | ||
40 | info: 'info', | ||
41 | warning: 'warning', | ||
42 | error: 'error' | ||
43 | }; | ||
44 | |||
45 | export default { | ||
46 | mixins: [Popup, Locale], | ||
47 | |||
48 | props: { | ||
49 | modal: { | ||
50 | default: true | ||
51 | }, | ||
52 | lockScroll: { | ||
53 | default: true | ||
54 | }, | ||
55 | showClose: { | ||
56 | type: Boolean, | ||
57 | default: true | ||
58 | }, | ||
59 | closeOnClickModal: { | ||
60 | default: true | ||
61 | }, | ||
62 | closeOnPressEscape: { | ||
63 | default: true | ||
64 | }, | ||
65 | closeOnHashChange: { | ||
66 | default: true | ||
67 | }, | ||
68 | center: { | ||
69 | default: false, | ||
70 | type: Boolean | ||
71 | }, | ||
72 | roundButton: { | ||
73 | default: false, | ||
74 | type: Boolean | ||
75 | } | ||
76 | }, | ||
77 | |||
78 | components: { | ||
79 | ElInput, | ||
80 | ElButton | ||
81 | }, | ||
82 | |||
83 | computed: { | ||
84 | icon () { | ||
85 | const { type, iconClass } = this; | ||
86 | return iconClass || (type && typeMap[type] ? `el-icon-${typeMap[type]}` : ''); | ||
87 | }, | ||
88 | |||
89 | confirmButtonClasses () { | ||
90 | return `el-button--primary ${this.confirmButtonClass}`; | ||
91 | }, | ||
92 | cancelButtonClasses () { | ||
93 | return `${this.cancelButtonClass}`; | ||
94 | } | ||
95 | }, | ||
96 | |||
97 | methods: { | ||
98 | getSafeClose () { | ||
99 | const currentId = this.uid; | ||
100 | return () => { | ||
101 | this.$nextTick(() => { | ||
102 | if (currentId === this.uid) this.doClose(); | ||
103 | }); | ||
104 | }; | ||
105 | }, | ||
106 | doClose () { | ||
107 | if (!this.visible) return; | ||
108 | this.visible = false; | ||
109 | this._closing = true; | ||
110 | |||
111 | this.onClose && this.onClose(); | ||
112 | messageBox.closeDialog(); // 解绑 | ||
113 | if (this.lockScroll) { | ||
114 | setTimeout(this.restoreBodyStyle, 200); | ||
115 | } | ||
116 | this.opened = false; | ||
117 | this.doAfterClose(); | ||
118 | setTimeout(() => { | ||
119 | if (this.action) this.callback(this.action, this); | ||
120 | }); | ||
121 | }, | ||
122 | |||
123 | handleWrapperClick () { | ||
124 | if (this.closeOnClickModal) { | ||
125 | this.handleAction(this.distinguishCancelAndClose ? 'close' : 'cancel'); | ||
126 | } | ||
127 | }, | ||
128 | |||
129 | handleInputEnter () { | ||
130 | if (this.inputType !== 'textarea') { | ||
131 | return this.handleAction('confirm'); | ||
132 | } | ||
133 | }, | ||
134 | |||
135 | handleAction (action) { | ||
136 | if (this.$type === 'prompt' && action === 'confirm' && !this.validate()) { | ||
137 | return; | ||
138 | } | ||
139 | this.action = action; | ||
140 | if (typeof this.beforeClose === 'function') { | ||
141 | this.close = this.getSafeClose(); | ||
142 | this.beforeClose(action, this, this.close); | ||
143 | } else { | ||
144 | this.doClose(); | ||
145 | } | ||
146 | }, | ||
147 | |||
148 | validate () { | ||
149 | if (this.$type === 'prompt') { | ||
150 | const inputPattern = this.inputPattern; | ||
151 | if (inputPattern && !inputPattern.test(this.inputValue || '')) { | ||
152 | this.editorErrorMessage = this.inputErrorMessage || t('el.messagebox.error'); | ||
153 | addClass(this.getInputElement(), 'invalid'); | ||
154 | return false; | ||
155 | } | ||
156 | const inputValidator = this.inputValidator; | ||
157 | if (typeof inputValidator === 'function') { | ||
158 | const validateResult = inputValidator(this.inputValue); | ||
159 | if (validateResult === false) { | ||
160 | this.editorErrorMessage = this.inputErrorMessage || t('el.messagebox.error'); | ||
161 | addClass(this.getInputElement(), 'invalid'); | ||
162 | return false; | ||
163 | } | ||
164 | if (typeof validateResult === 'string') { | ||
165 | this.editorErrorMessage = validateResult; | ||
166 | addClass(this.getInputElement(), 'invalid'); | ||
167 | return false; | ||
168 | } | ||
169 | } | ||
170 | } | ||
171 | this.editorErrorMessage = ''; | ||
172 | removeClass(this.getInputElement(), 'invalid'); | ||
173 | return true; | ||
174 | }, | ||
175 | getFirstFocus () { | ||
176 | const btn = this.$el.querySelector('.el-message-box__btns .el-button'); | ||
177 | const title = this.$el.querySelector('.el-message-box__btns .el-message-box__title'); | ||
178 | return btn || title; | ||
179 | }, | ||
180 | getInputElement () { | ||
181 | const inputRefs = this.$refs.input.$refs; | ||
182 | return inputRefs.input || inputRefs.textarea; | ||
183 | }, | ||
184 | handleClose () { | ||
185 | this.handleAction('close'); | ||
186 | } | ||
187 | }, | ||
188 | |||
189 | watch: { | ||
190 | inputValue: { | ||
191 | immediate: true, | ||
192 | handler (val) { | ||
193 | this.$nextTick(_ => { | ||
194 | if (this.$type === 'prompt' && val !== null) { | ||
195 | this.validate(); | ||
196 | } | ||
197 | }); | ||
198 | } | ||
199 | }, | ||
200 | |||
201 | visible (val) { | ||
202 | if (val) { | ||
203 | this.uid++; | ||
204 | if (this.$type === 'alert' || this.$type === 'confirm') { | ||
205 | this.$nextTick(() => { | ||
206 | this.$refs.confirm.$el.focus(); | ||
207 | }); | ||
208 | } | ||
209 | this.focusAfterClosed = document.activeElement; | ||
210 | messageBox = new Dialog(this.$el, this.focusAfterClosed, this.getFirstFocus()); | ||
211 | } | ||
212 | |||
213 | // prompt | ||
214 | if (this.$type !== 'prompt') return; | ||
215 | if (val) { | ||
216 | setTimeout(() => { | ||
217 | if (this.$refs.input && this.$refs.input.$el) { | ||
218 | this.getInputElement().focus(); | ||
219 | } | ||
220 | }, 500); | ||
221 | } else { | ||
222 | this.editorErrorMessage = ''; | ||
223 | removeClass(this.getInputElement(), 'invalid'); | ||
224 | } | ||
225 | } | ||
226 | }, | ||
227 | |||
228 | mounted () { | ||
229 | this.$nextTick(() => { | ||
230 | if (this.closeOnHashChange) { | ||
231 | window.addEventListener('hashchange', this.close); | ||
232 | } | ||
233 | }); | ||
234 | }, | ||
235 | |||
236 | beforeDestroy () { | ||
237 | if (this.closeOnHashChange) { | ||
238 | window.removeEventListener('hashchange', this.close); | ||
239 | } | ||
240 | setTimeout(() => { | ||
241 | messageBox.closeDialog(); | ||
242 | }); | ||
243 | }, | ||
244 | |||
245 | data () { | ||
246 | return { | ||
247 | uid: 1, | ||
248 | title: undefined, | ||
249 | message: '', | ||
250 | type: '', | ||
251 | iconClass: '', | ||
252 | customClass: '', | ||
253 | showInput: false, | ||
254 | inputValue: null, | ||
255 | inputPlaceholder: '', | ||
256 | inputType: 'text', | ||
257 | inputPattern: null, | ||
258 | inputValidator: null, | ||
259 | inputErrorMessage: '', | ||
260 | showConfirmButton: true, | ||
261 | showCancelButton: false, | ||
262 | action: '', | ||
263 | confirmButtonText: '', | ||
264 | cancelButtonText: '', | ||
265 | confirmButtonLoading: false, | ||
266 | cancelButtonLoading: false, | ||
267 | confirmButtonClass: '', | ||
268 | confirmButtonDisabled: false, | ||
269 | cancelButtonClass: '', | ||
270 | editorErrorMessage: null, | ||
271 | callback: null, | ||
272 | dangerouslyUseHTMLString: false, | ||
273 | focusAfterClosed: null, | ||
274 | isOnComposition: false, | ||
275 | distinguishCancelAndClose: false | ||
276 | }; | ||
277 | } | ||
278 | }; | ||
279 | </script> |
-
Please register or sign in to post a comment