diff --git a/packages/element3/packages/message-box/__tests__/MessageBox.spec.js b/packages/element3/packages/message-box/__tests__/MessageBox.spec.js
deleted file mode 100644
index da4d87b75..000000000
--- a/packages/element3/packages/message-box/__tests__/MessageBox.spec.js
+++ /dev/null
@@ -1,3 +0,0 @@
-describe('MessageBox.vue', () => {
- test('todo', () => {})
-})
diff --git a/packages/element3/packages/theme-chalk/src/index.scss b/packages/element3/packages/theme-chalk/src/index.scss
index 22e27c655..6f71cb034 100644
--- a/packages/element3/packages/theme-chalk/src/index.scss
+++ b/packages/element3/packages/theme-chalk/src/index.scss
@@ -72,6 +72,7 @@
@import "./input.scss";
@import "./link.scss";
@import "./message.scss";
+@import "./messageBox.scss";
@import "./notification.scss";
@import "./progress.scss";
@import "./radio.scss";
diff --git a/packages/element3/packages/theme-chalk/src/messageBox.scss b/packages/element3/packages/theme-chalk/src/messageBox.scss
new file mode 100644
index 000000000..03db363b0
--- /dev/null
+++ b/packages/element3/packages/theme-chalk/src/messageBox.scss
@@ -0,0 +1,226 @@
+@import 'mixins/mixins';
+@import 'common/var';
+@import 'common/popup';
+@import 'button';
+@import 'input';
+
+@include b(message-box) {
+ display: inline-block;
+ width: $--msgbox-width;
+ padding-bottom: 10px;
+ vertical-align: middle;
+ background-color: $--color-white;
+ border-radius: $--msgbox-border-radius;
+ border: 1px solid $--border-color-lighter;
+ font-size: $--messagebox-font-size;
+ box-shadow: $--box-shadow-light;
+ text-align: left;
+ overflow: hidden;
+ backface-visibility: hidden;
+
+ @include e(wrapper) {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ text-align: center;
+
+ &::after {
+ content: '';
+ display: inline-block;
+ height: 100%;
+ width: 0;
+ vertical-align: middle;
+ }
+ }
+
+ @include e(header) {
+ position: relative;
+ padding: $--msgbox-padding-primary;
+ padding-bottom: 10px;
+ }
+
+ @include e(title) {
+ padding-left: 0;
+ margin-bottom: 0;
+ font-size: $--messagebox-font-size;
+ line-height: 1;
+ color: $--messagebox-title-color;
+ }
+
+ @include e(headerbtn) {
+ position: absolute;
+ top: $--msgbox-padding-primary;
+ right: $--msgbox-padding-primary;
+ padding: 0;
+ border: none;
+ outline: none;
+ background: transparent;
+ font-size: $--message-close-size;
+ cursor: pointer;
+
+ .el-message-box__close {
+ color: $--color-info;
+ }
+
+ &:focus,
+ &:hover {
+ .el-message-box__close {
+ color: $--color-primary;
+ }
+ }
+ }
+
+ @include e(content) {
+ padding: 10px $--msgbox-padding-primary;
+ color: $--messagebox-content-color;
+ font-size: $--messagebox-content-font-size;
+ }
+
+ @include e(container) {
+ position: relative;
+ }
+
+ @include e(input) {
+ padding-top: 15px;
+
+ & input.invalid {
+ border-color: $--color-danger;
+ &:focus {
+ border-color: $--color-danger;
+ }
+ }
+ }
+
+ @include e(status) {
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ font-size: 24px !important;
+
+ &::before {
+ // 防止图标切割
+ padding-left: 1px;
+ }
+
+ + .el-message-box__message {
+ padding-left: 36px;
+ padding-right: 12px;
+ }
+
+ &.el-icon-success {
+ color: $--messagebox-success-color;
+ }
+
+ &.el-icon-info {
+ color: $--messagebox-info-color;
+ }
+
+ &.el-icon-warning {
+ color: $--messagebox-warning-color;
+ }
+
+ &.el-icon-error {
+ color: $--messagebox-danger-color;
+ }
+ }
+
+ @include e(message) {
+ margin: 0;
+
+ & p {
+ margin: 0;
+ line-height: 24px;
+ }
+ }
+
+ @include e(errormsg) {
+ color: $--color-danger;
+ font-size: $--messagebox-error-font-size;
+ min-height: 18px;
+ margin-top: 2px;
+ }
+
+ @include e(btns) {
+ padding: 5px 15px 0;
+ text-align: right;
+
+ & button:nth-child(2) {
+ margin-left: 10px;
+ }
+ }
+
+ @include e(btns-reverse) {
+ flex-direction: row-reverse;
+ }
+
+ // centerAlign 布局
+ @include m(center) {
+ padding-bottom: 30px;
+
+ @include e(header) {
+ padding-top: 30px;
+ }
+
+ @include e(title) {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ @include e(status) {
+ position: relative;
+ top: auto;
+ padding-right: 5px;
+ text-align: center;
+ transform: translateY(-1px);
+ }
+
+ @include e(message) {
+ margin-left: 0;
+ }
+
+ @include e((btns, content)) {
+ text-align: center;
+ }
+
+ @include e(content) {
+ $padding-horizontal: $--msgbox-padding-primary + 12px;
+
+ padding-left: $padding-horizontal;
+ padding-right: $padding-horizontal;
+ }
+ }
+}
+
+.msgbox-fade-enter-active {
+ animation: msgbox-fade-in 0.3s;
+}
+
+.msgbox-fade-leave-active {
+ animation: msgbox-fade-out 0.3s;
+}
+
+@keyframes msgbox-fade-in {
+ 0% {
+ transform: translate3d(0, -20px, 0);
+ opacity: 0;
+ }
+ 100% {
+ transform: translate3d(0, 0, 0);
+ opacity: 1;
+ }
+}
+
+@keyframes msgbox-fade-out {
+ 0% {
+ transform: translate3d(0, 0, 0);
+ opacity: 1;
+ }
+ 100% {
+ transform: translate3d(0, -20px, 0);
+ opacity: 0;
+ }
+}
diff --git a/packages/element3/src/components/MessageBox/index.js b/packages/element3/src/components/MessageBox/index.js
new file mode 100644
index 000000000..ecd39579e
--- /dev/null
+++ b/packages/element3/src/components/MessageBox/index.js
@@ -0,0 +1 @@
+export { MessageBox as Msgbox } from './src/MessageBox.js'
diff --git a/packages/element3/src/components/MessageBox/src/MessageBox.js b/packages/element3/src/components/MessageBox/src/MessageBox.js
new file mode 100644
index 000000000..34d0c3ee1
--- /dev/null
+++ b/packages/element3/src/components/MessageBox/src/MessageBox.js
@@ -0,0 +1,129 @@
+import { isVNode } from 'vue'
+import { isObject, isUndefined } from '../../../utils/types'
+import { createComponent } from '../../../composables/component'
+import msgboxVue from './MessageBox.vue'
+
+let currentMsg, instance
+let msgQueue = []
+
+const defaultCallback = (action) => {
+ if (currentMsg && currentMsg.resolve) {
+ const isConfirm = action === 'confirm'
+ const isCancelOrClose = action === 'cancel' || action === 'close'
+ const isReject = currentMsg.reject && isCancelOrClose
+ if (isReject) {
+ currentMsg.reject({ action })
+ return
+ }
+ const isShow = instance.proxy.showInput
+ const value = instance.proxy.inputValue
+ const result = isShow ? { value, action } : { action }
+ if (isConfirm) {
+ currentMsg.resolve(result)
+ }
+ }
+}
+
+const initInstance = (currentMsg, VNode = null) => {
+ instance = createComponent(msgboxVue, currentMsg.options, VNode)
+ MessageBox.instance = instance
+}
+
+const showNextMsg = () => {
+ if (msgQueue.length <= 0) return
+ currentMsg = msgQueue.shift()
+ const options = currentMsg.options
+
+ if (isUndefined(options.callback)) options.callback = defaultCallback
+
+ const oldCb = options.callback
+ options.callback = (action, instance) => {
+ oldCb(action, instance)
+ }
+
+ initInstance(
+ currentMsg,
+ isVNode(options.message) ? () => options.message : null
+ )
+ document.body.appendChild(instance.vnode.el)
+}
+
+const MessageBox = function (options) {
+ let callback = null
+ if (options.callback) {
+ callback = options.callback
+ }
+ let promiseInstance = new Promise((resolve, reject) => {
+ // eslint-disable-line
+ msgQueue.push({
+ options: options,
+ callback: callback,
+ resolve: resolve,
+ reject: reject
+ })
+ showNextMsg()
+ })
+ promiseInstance.instance = instance
+ return promiseInstance
+}
+
+const mergeCondition = (message, title, options) => {
+ if (isObject(title)) {
+ options = title
+ title = ''
+ } else if (isUndefined(title)) {
+ title = ''
+ }
+ if (isObject(message)) {
+ options = message
+ message = ''
+ }
+ return Object.assign(
+ {
+ title: title,
+ message: message,
+ confirmButtonText: '确认',
+ cancelButtonText: '取消'
+ },
+ options
+ )
+}
+
+const kindOfMessageBox = {
+ alert: {
+ category: 'alert',
+ closeOnPressEscape: false
+ },
+ confirm: {
+ type: 'info',
+ category: 'confirm',
+ showCancelButton: true
+ },
+ prompt: {
+ showInput: true,
+ category: 'prompt',
+ showCancelButton: true,
+ inputErrorMessage: '输入的数据不合法!'
+ },
+ msgbox: MessageBox
+}
+
+for (let key in kindOfMessageBox) {
+ MessageBox[key] = (message, title, options) => {
+ return MessageBox(
+ Object.assign(
+ kindOfMessageBox[key],
+ mergeCondition(message, title, options)
+ )
+ )
+ }
+}
+
+MessageBox.close = () => {
+ instance.proxy.closeHandle()
+ msgQueue = []
+ currentMsg = null
+}
+
+export default MessageBox
+export { MessageBox }
diff --git a/packages/element3/src/components/MessageBox/src/MessageBox.vue b/packages/element3/src/components/MessageBox/src/MessageBox.vue
new file mode 100644
index 000000000..904061e95
--- /dev/null
+++ b/packages/element3/src/components/MessageBox/src/MessageBox.vue
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ message }}
+
+
+
+
+
+
+
+
+
+
+ {{ cancelButtonText }}
+
+
+ {{ confirmButtonText }}
+
+
+
+
+
+
+
+
diff --git a/packages/element3/src/components/MessageBox/src/props.js b/packages/element3/src/components/MessageBox/src/props.js
new file mode 100644
index 000000000..561dc0173
--- /dev/null
+++ b/packages/element3/src/components/MessageBox/src/props.js
@@ -0,0 +1,150 @@
+import { t } from '../../../../src/locale'
+const props = {
+ inputPattern: {
+ type: RegExp,
+ default: null
+ },
+ inputValidator: {
+ type: [Function],
+ default: null
+ },
+ inputErrorMessage: {
+ type: String,
+ default: () => t('el.messagebox.error')
+ },
+ modalFade: {
+ type: Boolean,
+ default: true
+ },
+ callback: {
+ type: Function,
+ default: () => {}
+ },
+ closeOnHashChange: {
+ type: Boolean,
+ default: true
+ },
+ closeOnPressEscape: {
+ type: Boolean,
+ default: true
+ },
+ distinguishCancelAndClose: {
+ type: Boolean,
+ default: false
+ },
+ closeOnClickModal: {
+ type: Boolean,
+ default: true
+ },
+ cancelButtonLoading: {
+ type: Boolean,
+ default: false
+ },
+ roundButton: {
+ type: Boolean,
+ default: false
+ },
+ cancelButtonClass: {
+ type: String,
+ default: null
+ },
+ confirmButtonClass: {
+ type: String,
+ default: null
+ },
+ showCancelButton: {
+ type: Boolean,
+ default: false
+ },
+ showConfirmButton: {
+ type: Boolean,
+ default: true
+ },
+ confirmButtonText: {
+ type: String,
+ default: () => t('el.messagebox.cancel')
+ },
+ cancelButtonText: {
+ type: String,
+ default: () => t('el.messagebox.confirm')
+ },
+ category: {
+ type: String,
+ default: 'alert',
+ validator(val) {
+ return ['confirm', 'prompt', 'alert'].includes(val)
+ }
+ },
+ inputValue: {
+ type: String,
+ default: ''
+ },
+ inputPlaceholder: {
+ type: String,
+ default: ''
+ },
+ inputType: {
+ type: String,
+ default: 'text'
+ },
+ showInput: {
+ type: Boolean,
+ default: false
+ },
+ dangerouslyUseHTMLString: {
+ type: Boolean,
+ default: false
+ },
+ message: {
+ type: [Object, String],
+ default() {
+ return {}
+ }
+ },
+ lockScroll: {
+ type: Boolean,
+ default: true
+ },
+ modalAppendToBody: {
+ type: Boolean,
+ default: false
+ },
+ modal: {
+ type: Boolean,
+ default: true
+ },
+ center: {
+ type: Boolean,
+ default: false
+ },
+ title: {
+ type: String,
+ default: null
+ },
+ customClass: {
+ type: String,
+ default: null
+ },
+ type: {
+ type: [String, null],
+ default: null,
+ validator(val) {
+ return (
+ ['success', 'warning', 'info', 'error'].includes(val) || val === null
+ )
+ }
+ },
+ iconClass: {
+ type: String,
+ default: null
+ },
+ showClose: {
+ type: Boolean,
+ default: true
+ },
+ beforeClose: {
+ type: Function,
+ default: null
+ }
+}
+export default props
diff --git a/packages/element3/src/components/MessageBox/src/use.js b/packages/element3/src/components/MessageBox/src/use.js
new file mode 100644
index 000000000..19db8791d
--- /dev/null
+++ b/packages/element3/src/components/MessageBox/src/use.js
@@ -0,0 +1,89 @@
+import { toRefs, nextTick, unref, onMounted, onUnmounted, computed } from 'vue'
+import { usePopup } from '../../../composables/popup'
+import { isFunction } from '@vue/shared'
+export function useHandleList(state, instance, validate) {
+ const { close } = usePopup(state)
+ const {
+ visible,
+ callback,
+ category,
+ inputType,
+ distinguishCancelAndClose,
+ closeOnClickModal,
+ action,
+ beforeClose
+ } = toRefs(state)
+ const closeHandle = () => {
+ visible.value = false
+ close()
+ nextTick(() => {
+ unref(callback)(state.action, instance.proxy)
+ })
+ }
+ const handleAction = (_action) => {
+ if (unref(category) === 'prompt' && _action === 'confirm' && !validate()) {
+ return
+ }
+ action.value = _action
+ if (isFunction(beforeClose.value)) {
+ beforeClose.value(_action, instance.proxy, closeHandle)
+ } else {
+ closeHandle()
+ }
+ }
+
+ const handleInputEnter = () => {
+ if (unref(inputType) !== 'textarea') {
+ return handleAction('confirm')
+ }
+ }
+
+ const handleWrapperClick = () => {
+ if (unref(closeOnClickModal)) {
+ handleAction(unref(distinguishCancelAndClose) ? 'close' : 'cancel')
+ }
+ }
+
+ return {
+ closeHandle,
+ handleAction,
+ handleWrapperClick,
+ handleInputEnter
+ }
+}
+
+export function watchElement(state, handleAction, closeHandle) {
+ const {
+ closeOnHashChange,
+ closeOnPressEscape,
+ distinguishCancelAndClose
+ } = toRefs(state)
+ const { open } = usePopup(state)
+ const handleKeyup = (element = {}) => {
+ if (element.code !== 'Escape') return
+ if (unref(closeOnPressEscape)) {
+ handleAction(unref(distinguishCancelAndClose) ? 'close' : 'cancel')
+ }
+ }
+ onMounted(() => {
+ if (unref(closeOnHashChange)) {
+ window.addEventListener('hashchange', closeHandle)
+ }
+ window.addEventListener('keyup', handleKeyup)
+ nextTick(() => {
+ open()
+ })
+ })
+ onUnmounted(() => {
+ if (unref(closeOnHashChange)) {
+ window.removeEventListener('hashchange', closeHandle)
+ }
+ window.removeEventListener('keyup', handleKeyup)
+ })
+}
+
+export function classIcon(iconClass, type) {
+ return computed(() => {
+ return unref(iconClass) || (unref(type) ? `el-icon-${unref(type)}` : '')
+ })
+}
diff --git a/packages/element3/src/components/MessageBox/src/validate.js b/packages/element3/src/components/MessageBox/src/validate.js
new file mode 100644
index 000000000..7f47b9fbf
--- /dev/null
+++ b/packages/element3/src/components/MessageBox/src/validate.js
@@ -0,0 +1,57 @@
+import { toRefs, unref, ref, watch, nextTick, computed } from 'vue'
+import { addClass, removeClass } from '../../../utils/dom'
+export default (state, instance) => {
+ const editorErrorMessage = ref(null)
+ const {
+ category,
+ inputPattern,
+ inputValue,
+ inputValidator,
+ inputErrorMessage
+ } = toRefs(state)
+ const getInputElement = () => {
+ const inputRefs = instance.refs.input.$refs
+ return inputRefs.input || inputRefs.textarea
+ }
+ function getValidateResult(errorMsg, value) {
+ editorErrorMessage.value = errorMsg
+ value
+ ? removeClass(getInputElement(), 'invalid')
+ : addClass(getInputElement(), 'invalid')
+ return value
+ }
+ const isCategoryPrompt = computed(() => {
+ return unref(category) === 'prompt'
+ })
+ const isEegularResult = computed(() => {
+ const v =
+ unref(inputPattern) && !unref(inputPattern).test(unref(inputValue))
+ return unref(isCategoryPrompt) && v
+ })
+
+ const validate = () => {
+ if (unref(isEegularResult)) {
+ return getValidateResult(unref(inputErrorMessage), false)
+ }
+ const _inputValidator = unref(inputValidator)
+ if (typeof _inputValidator === 'function' && unref(isCategoryPrompt)) {
+ const validateResult = _inputValidator(unref(inputValue))
+ const isString = typeof validateResult === 'string'
+ if (isString) return getValidateResult(validateResult, false)
+ if (!validateResult)
+ return getValidateResult(unref(inputErrorMessage), false)
+ }
+ return getValidateResult('', true)
+ }
+ watch(inputValue, (val) => {
+ nextTick(() => {
+ if (unref(category) === 'prompt' && val !== null) {
+ validate()
+ }
+ })
+ })
+ return {
+ validate,
+ editorErrorMessage
+ }
+}
diff --git a/packages/element3/src/components/MessageBox/tests/MessageBox.spec.js b/packages/element3/src/components/MessageBox/tests/MessageBox.spec.js
new file mode 100644
index 000000000..0382b1412
--- /dev/null
+++ b/packages/element3/src/components/MessageBox/tests/MessageBox.spec.js
@@ -0,0 +1,134 @@
+import { flushPromises } from '@vue/test-utils'
+import { nextTick, h } from 'vue'
+import { merge } from 'lodash-es'
+import messageBox, { MessageBox } from '../src/MessageBox.js'
+const selector = '.el-message-box__wrapper'
+function testCallback(name, options = {}) {
+ const message = '请输入邮箱'
+ const o = {
+ prompt: {
+ message,
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ cancelButtonClass: 'mmm'
+ },
+ confirm: {
+ message,
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ confirmButtonClass: 'mmm'
+ },
+ msgbox: {
+ message,
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ confirmButtonClass: 'mmm'
+ }
+ }
+ return messageBox[name](merge(o[name], options))
+}
+describe('MessageBox.js', () => {
+ afterEach(() => {
+ const el = document.querySelector('.el-message-box__wrapper')
+ if (!el) return
+ if (el.parentNode) {
+ el.parentNode.removeChild(el)
+ }
+ })
+ test('alert', async () => {
+ const { instance } = messageBox.alert({
+ title: '消息',
+ message: '这是一段内容'
+ })
+
+ expect(instance.props.title).toBe('消息')
+ expect(instance.props.message).toEqual('这是一段内容')
+ })
+ test('messageBox of message is html', async () => {
+ let instanceProprety = ''
+ const callback = jest.fn((action, instance) => {
+ instanceProprety = instance
+ })
+ const { instance } = messageBox.alert(
+ `这是 HTML 片段`,
+ `html片段`,
+ {
+ dangerouslyUseHTMLString: true,
+ callback
+ }
+ )
+ expect(instance.proxy.message).toBe(
+ '这是 HTML 片段'
+ )
+ expect(instance.proxy.title).toBe('html片段')
+ expect(instance.proxy.dangerouslyUseHTMLString).toBeTruthy()
+ instance.proxy.closeHandle()
+ await flushPromises()
+ expect(instanceProprety.message).toBeTruthy()
+ expect(callback).toHaveBeenCalled()
+ })
+ test('confirm', async () => {
+ const { instance } = messageBox.confirm({
+ type: 'warning',
+ title: '消息',
+ message: '这是一段内容'
+ })
+ expect(instance.props.type).toBe('warning')
+ })
+ test('kind of prompt', async () => {
+ let v = ''
+ const callback = jest.fn(({ value }) => {
+ v = value
+ })
+ const instance = testCallback('prompt', {
+ confirmButtonClass: 'mmmm',
+ inputPattern: /[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?/,
+ inputErrorMessage: '邮箱格式不正确'
+ })
+ const btn = document.querySelector('.mmmm')
+ instance.then(callback)
+ btn.click()
+ await flushPromises()
+ expect(callback).not.toHaveBeenCalled()
+ instance.instance.proxy.inputValue = '409187100@qq.com'
+ btn.click()
+ await flushPromises()
+ expect(callback).toHaveBeenCalled()
+ expect(v).toBeTruthy()
+ })
+ test('was invoked', async () => {
+ const callback = jest.fn(() => {})
+ const instance = testCallback('prompt', {
+ message: '请输入邮箱'
+ })
+
+ instance.catch(callback)
+ await nextTick()
+ const btn = document.querySelector('.mmm')
+ await btn.click()
+ await flushPromises()
+ expect(callback).toHaveBeenCalled()
+ })
+ test('paramter', async () => {
+ const { instance } = messageBox.alert('请输入邮箱', { title: 'aaa' })
+ expect(instance.proxy.title).toBe('aaa')
+ expect(instance.proxy.message).toBe('请输入邮箱')
+ })
+
+ test('showInput is false', async () => {
+ const callback = jest.fn(() => {})
+ const instance = testCallback('confirm')
+ instance.then(callback)
+ const btn = document.querySelector('.mmm')
+ await btn.click()
+ await flushPromises()
+ expect(callback).toHaveBeenCalled()
+ })
+ test('message is vnode', async () => {
+ const message = h('div', '4')
+ const { instance } = MessageBox.alert({
+ message
+ })
+ expect(instance.proxy.$slots.default()[0]).toEqual(message)
+ })
+})
diff --git a/packages/element3/src/components/MessageBox/tests/MessageBox.vue.spec.js b/packages/element3/src/components/MessageBox/tests/MessageBox.vue.spec.js
new file mode 100644
index 000000000..3eac0d2e6
--- /dev/null
+++ b/packages/element3/src/components/MessageBox/tests/MessageBox.vue.spec.js
@@ -0,0 +1,343 @@
+import { mount } from '@vue/test-utils'
+import { nextTick, h, ref } from 'vue'
+import MessageBox from '../src/MessageBox.vue'
+describe('MessageBox.vue', () => {
+ describe('snapshot', () => {
+ const wrapper = mount(MessageBox)
+ expect(wrapper.element).toMatchSnapshot()
+ })
+ describe('proprety', () => {
+ it('proprety title', () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ title: 'chushihua'
+ }
+ })
+ expect(wrapper.get('.el-message-box__title')).toHaveTextContent(
+ 'chushihua'
+ )
+ })
+ it('proprety center', () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ center: true
+ }
+ })
+ expect(wrapper.get('.el-message-box--center').exists()).toBeTruthy()
+ })
+ it('proprety customClass', () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ customClass: 'customClass'
+ }
+ })
+ expect(wrapper.get('.customClass').exists()).toBeTruthy()
+ })
+ it('proprety iconClass', () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ iconClass: 'iconClass'
+ }
+ })
+ expect(wrapper.vm.icon).toBe('iconClass')
+ })
+ it('iconclass with center', () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ iconClass: 'iconClass',
+ center: true,
+ title: 'title'
+ }
+ })
+ expect(wrapper.get('.iconClass').exists()).toBeTruthy()
+ })
+ it('proprety type', () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ title: 'title',
+ center: true,
+ type: 'info'
+ }
+ })
+ expect(wrapper.find('.el-icon-info').exists()).toBeTruthy()
+ })
+ it('showClose', () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ title: '12',
+ showClose: false
+ }
+ })
+ expect(wrapper.find('.el-message-box__headerbtn').exists()).toBeFalsy()
+ })
+ it('showClose toBeTruthy', () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ title: '12'
+ }
+ })
+ expect(wrapper.get('.el-message-box__headerbtn').exists()).toBeTruthy()
+ })
+ it('proprety beforeClose', async () => {
+ let object = {}
+ const wrapper = mount(MessageBox, {
+ props: {
+ title: '12',
+ beforeClose: (action, instance, done) => {
+ object.action = action
+ object.instance = instance
+ object.done = done
+ }
+ }
+ })
+ await wrapper.get('.el-message-box__headerbtn').trigger('click')
+ expect(wrapper.componentVM).toEqual(object.instance)
+ expect(object.action).toBe('cancel')
+ })
+ it('review messageBox when value was done', async () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ title: '12',
+ beforeClose: (action, instance, done) => {
+ done()
+ }
+ }
+ })
+ await wrapper.get('.el-message-box__headerbtn').trigger('click')
+ expect(wrapper.get('.el-message-box__headerbtn').isVisible()).toBeFalsy()
+ expect(wrapper.find('.v-modal').exists()).toBeFalsy()
+ })
+ it('showClose lockScroll', () => {
+ document.body.classList.remove('el-popup-parent--hidden')
+ mount(MessageBox, {
+ props: {
+ title: '12',
+ lockScroll: false
+ },
+ attachTo: document.getElementById('body')
+ })
+ expect(document.body.className).not.toBe('el-popup-parent--hidden')
+ })
+ it('meesage of normal', () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ message: '333'
+ }
+ })
+ expect(wrapper.get('.el-message-box__message > p')).toHaveTextContent(
+ '333'
+ )
+ })
+ it('meesage is VNode', () => {
+ const v = h('p', null, [
+ h('span', null, '内容可以是 '),
+ h('i', { style: 'color: teal' }, 'VNode')
+ ])
+ const wrapper = mount(MessageBox, {
+ slots: {
+ default: v
+ }
+ })
+ expect(wrapper.componentVM.$slots.default()[0]).toEqual(v)
+ })
+ it('dangerouslyUseHTMLString', () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ dangerouslyUseHTMLString: true,
+ message: '
444
'
+ }
+ })
+ expect(wrapper.get('.mmm').exists()).toBeTruthy()
+ })
+ it('handleInputEnter', async () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ inputType: 'texg'
+ }
+ })
+ wrapper.componentVM.handleInputEnter()
+ await nextTick()
+ expect(wrapper.componentVM.visible).toBeFalsy()
+ })
+ it('closeOnPressEscape', async () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ closeOnPressEscape: true
+ }
+ })
+ const event = new KeyboardEvent('keyup', {
+ code: 'Escape'
+ })
+ window.dispatchEvent(event)
+ expect(wrapper.get('.el-message-box__wrapper').isVisible()).toBeTruthy()
+ expect(wrapper.componentVM.visible).toBeFalsy()
+ })
+ it('handleWrapperClick', async () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ closeOnClickModal: true
+ }
+ })
+ await wrapper.get('.el-message-box__wrapper').trigger('click')
+ await nextTick()
+ expect(wrapper.componentVM.action).toBe('cancel')
+ })
+ it('closeOnHashChange', async () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ closeOnHashChange: true
+ }
+ })
+ window.dispatchEvent(new Event('hashchange'))
+ expect(wrapper.get('.el-message-box__wrapper').isVisible()).toBeTruthy()
+ expect(wrapper.componentVM.visible).toBeFalsy()
+ })
+ it('showInput', () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ showInput: true
+ }
+ })
+ expect(wrapper.get('.el-message-box__input').isVisible()).toBeTruthy()
+ })
+ it('inputValue inputPlaceholder', () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ showInput: true,
+ inputValue: '444',
+ inputPlaceholder: '555'
+ }
+ })
+ expect(wrapper.get('.el-input__inner').element.value).toEqual('444')
+ expect(wrapper.get('.el-input__inner').attributes('placeholder')).toEqual(
+ '555'
+ )
+ })
+ it('cancelButtonText, showCancelButton', () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ cancelButtonText: 'cancel',
+ showCancelButton: true,
+ cancelButtonClass: 'vvv'
+ }
+ })
+ expect(wrapper.get('.vvv')).toHaveTextContent('cancel')
+ expect(wrapper.get('.vvv').isVisible()).toBeTruthy()
+ })
+ it('confirmButtonClass showConfirmButton, confirmButtonText', () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ confirmButtonText: 'cancel',
+ showConfirmButton: true,
+ confirmButtonClass: 'mmm'
+ }
+ })
+ expect(wrapper.get('.el-button--primary').classes()).toContain('mmm')
+ expect(wrapper.get('.mmm')).toHaveTextContent('cancel')
+ expect(wrapper.get('.el-message-box__btns').isVisible()).toBeTruthy()
+ })
+ it('proprety callback', async () => {
+ let object = ref(null)
+ const wrapper = mount(MessageBox, {
+ props: {
+ title: '12',
+ callback(action) {
+ object.value = action
+ }
+ }
+ })
+ await wrapper.get('.el-message-box__headerbtn').trigger('click')
+ await nextTick()
+ expect(object.value).toBe('cancel')
+ })
+ it('validate correct', async () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ title: '12',
+ category: 'prompt',
+ inputPattern: /[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?/,
+ confirmButtonText: '确定',
+ cancelButtonText: '取消'
+ }
+ })
+ await wrapper.findComponent({ ref: 'input' }).setValue('2323')
+ await nextTick()
+ expect(wrapper.componentVM.editorErrorMessage).toBe('输入的数据不合法!')
+ expect(wrapper.get('.el-input__inner').classes()).toHaveLength(2)
+ })
+ it('validate fail', async () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ title: '12',
+ category: 'prompt',
+ inputValidator() {
+ return '失败'
+ },
+ confirmButtonText: '确定',
+ cancelButtonText: '取消'
+ }
+ })
+ await wrapper.findComponent({ ref: 'input' }).setValue('2323')
+ await nextTick()
+ expect(wrapper.get('.el-input__inner').classes()).toHaveLength(2)
+ expect(wrapper.componentVM.editorErrorMessage).toBe('失败')
+ })
+ it('inputValidator', async () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ title: '12',
+ category: 'prompt',
+ inputValidator() {
+ return false
+ },
+ confirmButtonText: '确定',
+ cancelButtonText: '取消'
+ }
+ })
+ await wrapper.findComponent({ ref: 'input' }).setValue('2323')
+ await nextTick()
+ expect(wrapper.get('.el-input__inner').classes()).toHaveLength(2)
+ })
+ })
+ describe('test modal closeOnClickModal', () => {
+ it('open modal', async () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ modal: true,
+ closeOnClickModal: true
+ }
+ })
+ await nextTick()
+ expect(wrapper.get('.v-modal').exists()).toBeTruthy()
+ await wrapper.get('.v-modal').trigger('click')
+ await nextTick()
+ expect(wrapper.find('.v-moda').exists()).toBeFalsy()
+ })
+ it('open modal', async () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ modal: true,
+ closeOnClickModal: true
+ }
+ })
+ await nextTick()
+ expect(wrapper.get('.v-modal').exists()).toBeTruthy()
+ await wrapper.get('.v-modal').trigger('keyup', { keyCode: 'Escape' })
+ await nextTick()
+ expect(wrapper.find('.v-moda').exists()).toBeFalsy()
+ })
+ it('open modal', async () => {
+ const wrapper = mount(MessageBox, {
+ props: {
+ modal: true,
+ closeOnClickModal: true
+ }
+ })
+ await nextTick()
+ expect(wrapper.get('.v-modal').exists()).toBeTruthy()
+ await wrapper.get('.v-modal').trigger('hashchange')
+ await nextTick()
+ expect(wrapper.find('.v-moda').exists()).toBeFalsy()
+ })
+ })
+})
diff --git a/packages/element3/src/components/MessageBox/tests/Prop.spec.js b/packages/element3/src/components/MessageBox/tests/Prop.spec.js
new file mode 100644
index 000000000..4e1264919
--- /dev/null
+++ b/packages/element3/src/components/MessageBox/tests/Prop.spec.js
@@ -0,0 +1,10 @@
+import props from '../src/props.js'
+describe('porps', () => {
+ test('type', () => {
+ expect(props.type.validator('success')).toBeTruthy()
+ expect(props.type.validator()).toBeFalsy()
+ })
+ test('category', () => {
+ expect(props.category.validator('alert')).toBeTruthy()
+ })
+})
diff --git a/packages/element3/src/components/MessageBox/tests/Validate.spec.js b/packages/element3/src/components/MessageBox/tests/Validate.spec.js
new file mode 100644
index 000000000..5b6749636
--- /dev/null
+++ b/packages/element3/src/components/MessageBox/tests/Validate.spec.js
@@ -0,0 +1,57 @@
+import validateFunction from '../src/validate'
+import { mount } from '@vue/test-utils'
+import { ElInput } from '../../Input'
+import { reactive, getCurrentInstance } from 'vue'
+describe('it was the validate functional ', () => {
+ function createComponent(data) {
+ return {
+ setup() {
+ const instance = getCurrentInstance()
+ const state = data
+ const { validate } = validateFunction(state, instance)
+ return {
+ validate,
+ inputValue: state.inputValue,
+ inputErrorMessage: state.inputErrorMessage
+ }
+ },
+ components: {
+ ElInput
+ },
+ template: `
`
+ }
+ }
+ it('the validate returned false when property was not have inputValidator', async () => {
+ let data = reactive({
+ inputValue: null,
+ category: 'prompt',
+ inputPattern: /^1.+/,
+ inputErrorMessage: 'aaa'
+ })
+ const wrapper = mount(createComponent(data))
+ expect(wrapper.vm.validate()).toBeFalsy()
+ expect(wrapper.find('.invalid')).toBeTruthy()
+ expect(wrapper.vm.inputErrorMessage).toBe('aaa')
+ })
+ it('when the inputValidator was the function and was the true or the false', async () => {
+ let data = reactive({
+ inputValue: 232,
+ category: 'prompt',
+ inputErrorMessage: 'aaa',
+ inputValidator(value) {
+ return value === null ? false : value === true ? true : 'true'
+ }
+ })
+ const wrapper = mount(createComponent(data))
+ expect(wrapper.vm.validate()).toBeFalsy()
+ expect(wrapper.find('.invalid')).toBeTruthy()
+ expect(wrapper.vm.inputErrorMessage).toBe('aaa')
+ wrapper.vm.inputValue = '2'
+ expect(wrapper.vm.validate()).toBeFalsy()
+ expect(wrapper.find('.invalid')).toBeTruthy()
+ expect(wrapper.vm.inputErrorMessage).toBe('aaa')
+ data.inputValue = true
+ expect(wrapper.vm.validate()).toBeTruthy()
+ expect(wrapper.find('.invalid').exists()).toBeFalsy()
+ })
+})
diff --git a/packages/element3/src/components/MessageBox/tests/__snapshots__/MessageBox.vue.spec.js.snap b/packages/element3/src/components/MessageBox/tests/__snapshots__/MessageBox.vue.spec.js.snap
new file mode 100644
index 000000000..dd382c022
--- /dev/null
+++ b/packages/element3/src/components/MessageBox/tests/__snapshots__/MessageBox.vue.spec.js.snap
@@ -0,0 +1,86 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/packages/element3/src/composables/popup.js b/packages/element3/src/composables/popup.js
index 1b51d04ff..aedf061af 100644
--- a/packages/element3/src/composables/popup.js
+++ b/packages/element3/src/composables/popup.js
@@ -7,9 +7,9 @@ import {
watch,
toRefs
} from 'vue'
+import { merge } from 'lodash-es'
import PopupManager from '../../src/utils/popup/popup-manager'
import getScrollBarWidth from '../../src/utils/scrollbar-width'
-import merge from '../../src/utils/merge'
import { getStyle, addClass, removeClass, hasClass } from '../../src/utils/dom'
let idSeed = 1
@@ -53,6 +53,7 @@ function usePopup(props) {
const { visible, modal, modalAppendToBody, lockScroll, closeDelay } = toRefs(
props
)
+
const opened = ref(false)
const bodyPaddingRight = ref(null)
const computedBodyPaddingRight = ref(0)
@@ -68,9 +69,8 @@ function usePopup(props) {
if (!rendered.value) {
rendered.value = true
}
-
- const props = merge({}, instance.proxy, options)
-
+ // instance.proxy, options)
+ const props = merge(instance.proxy, options)
if (_closeTimer) {
clearTimeout(_closeTimer)
_closeTimer = 0
@@ -87,7 +87,7 @@ function usePopup(props) {
}
}
const doOpen = (props) => {
- if (instance.proxy.$isServer) return
+ // if (instance.proxy.$isServer) return
if (_opening) return
if (opened.value) return
_opening = true
@@ -106,7 +106,7 @@ function usePopup(props) {
PopupManager.openModal(
_popupId,
PopupManager.nextZIndex(),
- modalAppendToBody.value ? undefined : dom,
+ modalAppendToBody?.value ? undefined : dom,
props.modalClass,
props.modalFade
)
diff --git a/packages/element3/src/index.js b/packages/element3/src/index.js
index cb7532c22..c70ab1a08 100644
--- a/packages/element3/src/index.js
+++ b/packages/element3/src/index.js
@@ -57,7 +57,7 @@ import ElLoading from '../packages/loading'
import { Message } from './components/Message'
-import { Msgbox } from '../packages/message-box'
+import { Msgbox } from './components/MessageBox'
import { Notification } from './components/Notification'
// Navigation
diff --git a/packages/element3/types/element3.d.ts b/packages/element3/types/element3.d.ts
index 67515324f..275a214ba 100644
--- a/packages/element3/types/element3.d.ts
+++ b/packages/element3/types/element3.d.ts
@@ -43,7 +43,7 @@ export { ElRate } from './rate'
export { Message } from './message'
export { useNotify } from './notification'
export { ElLoading } from './loading'
-export { useMsgbox } from './message-box'
+export { Msgbox } from './message-box'
export { ElSteps } from './steps'
export { ElUpload } from './upload'
export { ElTabs } from './tabs'
diff --git a/packages/element3/types/message-box.d.ts b/packages/element3/types/message-box.d.ts
index 67e3394e1..e007e251f 100644
--- a/packages/element3/types/message-box.d.ts
+++ b/packages/element3/types/message-box.d.ts
@@ -173,7 +173,6 @@ declare module '@vue/runtime-core' {
export interface ComponentCustomProperties {
/** Show a message box */
$msgbox: ElMessageBox
-
/** Show an alert message box */
$alert: ElMessageBoxShortcutMethod
diff --git a/packages/element3/types/messageBox.d.ts b/packages/element3/types/messageBox.d.ts
new file mode 100644
index 000000000..27731e546
--- /dev/null
+++ b/packages/element3/types/messageBox.d.ts
@@ -0,0 +1,183 @@
+import Vue, { VNode } from 'vue'
+import { MessageType } from './message'
+
+export type MessageBoxCloseAction = 'confirm' | 'cancel' | 'close'
+export type MessageBoxData = MessageBoxInputData | MessageBoxCloseAction
+
+export interface MessageBoxInputData {
+ value: string
+ action: MessageBoxCloseAction
+}
+
+export interface MessageBoxInputValidator {
+ (value: string): boolean | string
+}
+
+export const Msgbox: ElMessageBox
+
+interface IMessageBox {
+ title: string
+ message: string
+ type: MessageType
+ iconClass: string
+ customClass: string
+ showInput: boolean
+ showClose: boolean
+ inputValue: string
+ inputPlaceholder: string
+ inputType: string
+ inputPattern: RegExp
+ inputValidator: MessageBoxInputValidator
+ inputErrorMessage: string
+ showConfirmButton: boolean
+ showCancelButton: boolean
+ action: MessageBoxCloseAction
+ dangerouslyUseHTMLString: boolean
+ confirmButtonText: string
+ cancelButtonText: string
+ confirmButtonLoading: boolean
+ cancelButtonLoading: boolean
+ confirmButtonClass: string
+ confirmButtonDisabled: boolean
+ cancelButtonClass: string
+ editorErrorMessage: string
+}
+
+/** Options used in MessageBox */
+export interface ElMessageBoxOptions {
+ /** Title of the MessageBox */
+ title?: string
+
+ /** Content of the MessageBox */
+ message?: string | VNode
+
+ /** Message type, used for icon display */
+ type?: MessageType
+
+ /** Custom icon's class */
+ iconClass?: string
+
+ /** Custom class name for MessageBox */
+ customClass?: string
+
+ /** MessageBox closing callback if you don't prefer Promise */
+ callback?: (action: MessageBoxCloseAction, instance: ElMessageBox) => void
+
+ /** Callback before MessageBox closes, and it will prevent MessageBox from closing */
+ beforeClose?: (
+ action: MessageBoxCloseAction,
+ instance: ElMessageBox,
+ done: () => void
+ ) => void
+
+ /** Whether to lock body scroll when MessageBox prompts */
+ lockScroll?: boolean
+
+ /** Whether to show a cancel button */
+ showCancelButton?: boolean
+
+ /** Whether to show a confirm button */
+ showConfirmButton?: boolean
+
+ /** Whether to show a close button */
+ showClose?: boolean
+
+ /** Text content of cancel button */
+ cancelButtonText?: string
+
+ /** Text content of confirm button */
+ confirmButtonText?: string
+
+ /** Custom class name of cancel button */
+ cancelButtonClass?: string
+
+ /** Custom class name of confirm button */
+ confirmButtonClass?: string
+
+ /** Whether to align the content in center */
+ center?: boolean
+
+ /** Whether message is treated as HTML string */
+ dangerouslyUseHTMLString?: boolean
+
+ /** Whether to use round button */
+ roundButton?: boolean
+
+ /** Whether MessageBox can be closed by clicking the mask */
+ closeOnClickModal?: boolean
+
+ /** Whether MessageBox can be closed by pressing the ESC */
+ closeOnPressEscape?: boolean
+
+ /** Whether to close MessageBox when hash changes */
+ closeOnHashChange?: boolean
+
+ /** Whether to show an input */
+ showInput?: boolean
+
+ /** Placeholder of input */
+ inputPlaceholder?: string
+
+ /** Initial value of input */
+ inputValue?: string
+
+ /** Regexp for the input */
+ inputPattern?: RegExp
+
+ /** Input Type: text, textArea, password or number */
+ inputType?: string
+
+ /** Validation function for the input. Should returns a boolean or string. If a string is returned, it will be assigned to inputErrorMessage */
+ inputValidator?: MessageBoxInputValidator
+
+ /** Error message when validation fails */
+ inputErrorMessage?: string
+
+ /** Whether to distinguish canceling and closing */
+ distinguishCancelAndClose?: boolean
+}
+
+export interface ElMessageBoxShortcutMethod {
+ (
+ message: string,
+ title: string,
+ options?: ElMessageBoxOptions
+ ): Promise
+ (message: string, options?: ElMessageBoxOptions): Promise
+}
+
+export interface ElMessageBox {
+ /** Show a message box */
+ (message: string, title?: string, type?: string): Promise
+
+ /** Show a message box */
+ (options: ElMessageBoxOptions): Promise
+
+ /** Show an alert message box */
+ alert: ElMessageBoxShortcutMethod
+
+ /** Show a confirm message box */
+ confirm: ElMessageBoxShortcutMethod
+
+ /** Show a prompt message box */
+ prompt: ElMessageBoxShortcutMethod
+
+ /** Set default options of message boxes */
+ setDefaults(defaults: ElMessageBoxOptions): void
+
+ /** Close current message box */
+ close(): void
+}
+
+declare module '@vue/runtime-core' {
+ export interface ComponentCustomProperties {
+ /** Show an alert message box */
+ $alert: ElMessageBoxShortcutMethod
+
+ /** Show a confirm message box */
+ $confirm: ElMessageBoxShortcutMethod
+
+ /** Show a prompt message box */
+ $prompt: ElMessageBoxShortcutMethod
+ }
+}
diff --git a/packages/website/src/docs/message-box.md b/packages/website/src/docs/message-box.md
index 6ea74e25f..414763289 100644
--- a/packages/website/src/docs/message-box.md
+++ b/packages/website/src/docs/message-box.md
@@ -3,7 +3,7 @@
模拟系统的消息提示框而实现的一套模态对话框组件,用于消息提示、确认消息和提交内容。
:::tip
-从场景上说,MessageBox 的作用是美化系统自带的 `alert`、`confirm` 和 `prompt`,因此适合展示较为简单的内容。如果需要弹出较为复杂的内容,请使用 Dialog。
+`msgbox`、`alert`、`confirm` 和 `prompt`,因此适合展示较为简单的内容。如果需要弹出较为复杂的内容,请使用 Dialog。
:::
### 消息提示
@@ -175,11 +175,16 @@
done()
}
}
- }).then((action) => {
+ }).then(({action}) => {
Message({
type: 'info',
message: 'action: ' + action
})
+ }).catch(() => {
+ Message({
+ type: 'info',
+ message: '已取消'
+ })
})
}
}
@@ -217,7 +222,17 @@
{
dangerouslyUseHTMLString: true
}
- )
+ ).then(({action}) => {
+ Message({
+ type: 'info',
+ message: 'action: ' + action
+ })
+ }).catch(() => {
+ Message({
+ type: 'info',
+ message: '已取消'
+ })
+ })
}
}
}