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