wangxueliang
7 years ago
26 changed files with 858 additions and 57 deletions
@ -0,0 +1,70 @@
|
||||
<script> |
||||
import PropTypes from '../_util/vue-types' |
||||
import Button from '../button' |
||||
import BaseMixin from '../_util/BaseMixin' |
||||
import buttonTypes from '../button/buttonTypes' |
||||
const ButtonType = buttonTypes().type |
||||
const ActionButtonProps = { |
||||
type: ButtonType, |
||||
actionFn: PropTypes.func, |
||||
closeModal: PropTypes.func, |
||||
autoFocus: PropTypes.bool, |
||||
} |
||||
|
||||
export default { |
||||
mixins: [BaseMixin], |
||||
props: ActionButtonProps, |
||||
data () { |
||||
return { |
||||
loading: false, |
||||
} |
||||
}, |
||||
mounted () { |
||||
if (this.autoFocus) { |
||||
this.timeoutId = setTimeout(() => this.$el.focus()) |
||||
} |
||||
}, |
||||
beforeDestroy () { |
||||
clearTimeout(this.timeoutId) |
||||
}, |
||||
methods: { |
||||
onClick () { |
||||
const { actionFn, closeModal } = this |
||||
if (actionFn) { |
||||
let ret |
||||
if (actionFn.length) { |
||||
ret = actionFn(closeModal) |
||||
} else { |
||||
ret = actionFn() |
||||
if (!ret) { |
||||
closeModal() |
||||
} |
||||
} |
||||
if (ret && ret.then) { |
||||
this.setState({ loading: true }) |
||||
ret.then((...args) => { |
||||
// It's unnecessary to set loading=false, for the Modal will be unmounted after close. |
||||
// this.setState({ loading: false }); |
||||
closeModal(...args) |
||||
}, () => { |
||||
// See: https://github.com/ant-design/ant-design/issues/6183 |
||||
this.setState({ loading: false }) |
||||
}) |
||||
} |
||||
} else { |
||||
closeModal() |
||||
} |
||||
}, |
||||
}, |
||||
|
||||
render () { |
||||
const { type, $slots, loading } = this |
||||
return ( |
||||
<Button type={type} onClick={this.onClick} loading={loading}> |
||||
{$slots.default} |
||||
</Button> |
||||
) |
||||
}, |
||||
} |
||||
|
||||
</script> |
@ -0,0 +1,71 @@
|
||||
<script> |
||||
import classNames from 'classnames' |
||||
import Icon from '../icon' |
||||
import Dialog from './Modal' |
||||
import ActionButton from './ActionButton' |
||||
import { getConfirmLocale } from './locale' |
||||
|
||||
export default { |
||||
functional: true, |
||||
render (h, context) { |
||||
const { data: props } = context |
||||
const { onCancel, onOk, close, zIndex, afterClose, visible } = props |
||||
const iconType = props.iconType || 'question-circle' |
||||
const okType = props.okType || 'primary' |
||||
const prefixCls = props.prefixCls || 'ant-confirm' |
||||
// 默认为 true,保持向下兼容 |
||||
const okCancel = ('okCancel' in props) ? props.okCancel : true |
||||
const width = props.width || 416 |
||||
const style = props.style || {} |
||||
// 默认为 false,保持旧版默认行为 |
||||
const maskClosable = props.maskClosable === undefined ? false : props.maskClosable |
||||
const runtimeLocale = getConfirmLocale() |
||||
const okText = props.okText || |
||||
(okCancel ? runtimeLocale.okText : runtimeLocale.justOkText) |
||||
const cancelText = props.cancelText || runtimeLocale.cancelText |
||||
|
||||
const classString = classNames( |
||||
prefixCls, |
||||
`${prefixCls}-${props.type}`, |
||||
props.class, |
||||
) |
||||
|
||||
const cancelButton = okCancel && ( |
||||
<ActionButton actionFn={onCancel} closeModal={close}> |
||||
{cancelText} |
||||
</ActionButton> |
||||
) |
||||
|
||||
return ( |
||||
<Dialog |
||||
class={classString} |
||||
onCancel={(e) => close({ triggerCancel: true }, e)} |
||||
visible={visible} |
||||
title='' |
||||
transitionName='zoom' |
||||
footer='' |
||||
maskTransitionName='fade' |
||||
maskClosable={maskClosable} |
||||
style={style} |
||||
width={width} |
||||
zIndex={zIndex} |
||||
afterClose={afterClose} |
||||
> |
||||
<div class={`${prefixCls}-body-wrapper`}> |
||||
<div class={`${prefixCls}-body`}> |
||||
<Icon type={iconType} /> |
||||
<span class={`${prefixCls}-title`}>{props.title}</span> |
||||
<div class={`${prefixCls}-content`}>{props.content}</div> |
||||
</div> |
||||
<div class={`${prefixCls}-btns`}> |
||||
{cancelButton} |
||||
<ActionButton type={okType} actionFn={onOk} closeModal={close} autoFocus> |
||||
{okText} |
||||
</ActionButton> |
||||
</div> |
||||
</div> |
||||
</Dialog>) |
||||
}, |
||||
} |
||||
|
||||
</script> |
@ -0,0 +1,153 @@
|
||||
<script> |
||||
import Dialog from '../vc-dialog' |
||||
import PropTypes from '../_util/vue-types' |
||||
import addEventListener from '../_util/Dom/addEventListener' |
||||
import Button from '../button' |
||||
import buttonTypes from '../button/buttonTypes' |
||||
const ButtonType = buttonTypes().type |
||||
import LocaleReceiver from '../locale-provider/LocaleReceiver' |
||||
import { getConfirmLocale } from './locale' |
||||
import { initDefaultProps, getComponentFromProp, getClass, getStyle } from '../_util/props-util' |
||||
|
||||
let mousePosition = null |
||||
let mousePositionEventBinded = false |
||||
function noop () {} |
||||
const modalProps = (defaultProps = {}) => { |
||||
const props = { |
||||
prefixCls: PropTypes.string, |
||||
/** 对话框是否可见*/ |
||||
visible: PropTypes.bool, |
||||
/** 确定按钮 loading*/ |
||||
confirmLoading: PropTypes.bool, |
||||
/** 标题*/ |
||||
title: PropTypes.any, |
||||
/** 是否显示右上角的关闭按钮*/ |
||||
closable: PropTypes.bool, |
||||
/** 点击确定回调*/ |
||||
// onOk: (e: React.MouseEvent<any>) => void, |
||||
/** 点击模态框右上角叉、取消按钮、Props.maskClosable 值为 true 时的遮罩层或键盘按下 Esc 时的回调*/ |
||||
// onCancel: (e: React.MouseEvent<any>) => void, |
||||
afterClose: PropTypes.func.def(noop), |
||||
/** 宽度*/ |
||||
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), |
||||
/** 底部内容*/ |
||||
footer: PropTypes.any, |
||||
/** 确认按钮文字*/ |
||||
okText: PropTypes.string, |
||||
/** 确认按钮类型*/ |
||||
okType: ButtonType, |
||||
/** 取消按钮文字*/ |
||||
cancelText: PropTypes.string, |
||||
/** 点击蒙层是否允许关闭*/ |
||||
maskClosable: PropTypes.bool, |
||||
destroyOnClose: PropTypes.bool, |
||||
wrapClassName: PropTypes.string, |
||||
maskTransitionName: PropTypes.string, |
||||
transitionName: PropTypes.string, |
||||
getContainer: PropTypes.func, |
||||
zIndex: PropTypes.number, |
||||
bodyStyle: PropTypes.object, |
||||
maskStyle: PropTypes.object, |
||||
mask: PropTypes.bool, |
||||
} |
||||
return initDefaultProps(props, defaultProps) |
||||
} |
||||
|
||||
export default { |
||||
props: modalProps({ |
||||
prefixCls: 'ant-modal', |
||||
width: 520, |
||||
transitionName: 'zoom', |
||||
maskTransitionName: 'fade', |
||||
confirmLoading: false, |
||||
visible: false, |
||||
okType: 'primary', |
||||
}), |
||||
// static info: ModalFunc; |
||||
// static success: ModalFunc; |
||||
// static error: ModalFunc; |
||||
// static warn: ModalFunc; |
||||
// static warning: ModalFunc; |
||||
// static confirm: ModalFunc; |
||||
methods: { |
||||
handleCancel (e) { |
||||
this.$emit('cancel', e) |
||||
}, |
||||
|
||||
handleOk (e) { |
||||
this.$emit('ok', e) |
||||
}, |
||||
renderFooter (locale) { |
||||
const { okType, confirmLoading } = this |
||||
return ( |
||||
<div> |
||||
<Button |
||||
onClick={this.handleCancel} |
||||
> |
||||
{getComponentFromProp(this, 'cancelText') || locale.cancelText} |
||||
</Button> |
||||
<Button |
||||
type={okType} |
||||
loading={confirmLoading} |
||||
onClick={this.handleOk} |
||||
> |
||||
{getComponentFromProp(this, 'okText') || locale.okText} |
||||
</Button> |
||||
</div> |
||||
) |
||||
}, |
||||
}, |
||||
mounted () { |
||||
if (mousePositionEventBinded) { |
||||
return |
||||
} |
||||
// 只有点击事件支持从鼠标位置动画展开 |
||||
addEventListener(document.documentElement, 'click', (e) => { |
||||
mousePosition = { |
||||
x: e.pageX, |
||||
y: e.pageY, |
||||
} |
||||
// 100ms 内发生过点击事件,则从点击位置动画展示 |
||||
// 否则直接 zoom 展示 |
||||
// 这样可以兼容非点击方式展开 |
||||
setTimeout(() => { mousePosition = null }, 100) |
||||
}) |
||||
mousePositionEventBinded = true |
||||
}, |
||||
|
||||
render () { |
||||
const { visible, $listeners, $slots } = this |
||||
|
||||
const defaultFooter = ( |
||||
<LocaleReceiver |
||||
componentName='Modal' |
||||
defaultLocale={getConfirmLocale()} |
||||
children={this.renderFooter} |
||||
/> |
||||
) |
||||
const footer = getComponentFromProp(this, 'footer') |
||||
const title = getComponentFromProp(this, 'title') |
||||
const dialogProps = { |
||||
props: { |
||||
...this.$props, |
||||
title, |
||||
footer: typeof footer === undefined ? defaultFooter : footer, |
||||
visible: visible, |
||||
mousePosition, |
||||
}, |
||||
on: { |
||||
...$listeners, |
||||
close: this.handleCancel, |
||||
}, |
||||
class: getClass(this), |
||||
style: getStyle(this), |
||||
} |
||||
return ( |
||||
<Dialog {...dialogProps}> |
||||
{$slots.default} |
||||
</Dialog> |
||||
) |
||||
}, |
||||
} |
||||
|
||||
</script> |
@ -0,0 +1,39 @@
|
||||
import Vue from 'vue' |
||||
import ConfirmDialog from './ConfirmDialog' |
||||
export default function confirm (config) { |
||||
const div = document.createElement('div') |
||||
document.body.appendChild(div) |
||||
let confirmDialogInstance = null |
||||
function close (...args) { |
||||
destroy(...args) |
||||
} |
||||
|
||||
function destroy (...args) { |
||||
if (confirmDialogInstance && div.parentNode) { |
||||
confirmDialogInstance.$destroy() |
||||
confirmDialogInstance = null |
||||
div.parentNode.removeChild(div) |
||||
} |
||||
const triggerCancel = args && args.length && |
||||
args.some(param => param && param.triggerCancel) |
||||
if (config.onCancel && triggerCancel) { |
||||
config.onCancel(...args) |
||||
} |
||||
} |
||||
|
||||
function render (props) { |
||||
return new Vue({ |
||||
el: div, |
||||
render () { |
||||
return <ConfirmDialog {...props} /> |
||||
}, |
||||
}) |
||||
} |
||||
|
||||
confirmDialogInstance = render({ ...config, visible: true, close }) |
||||
|
||||
return { |
||||
destroy: close, |
||||
} |
||||
} |
||||
|
@ -0,0 +1,51 @@
|
||||
|
||||
<cn> |
||||
#### 基本 |
||||
第一个对话框。 |
||||
</cn> |
||||
|
||||
<us> |
||||
#### Basic |
||||
Basic modal. |
||||
</us> |
||||
|
||||
```html |
||||
<template> |
||||
<div> |
||||
<a-button type="primary" @click="showModal">Open</a-button> |
||||
<a-modal |
||||
title="Basic Modal" |
||||
:visible="visible" |
||||
@ok="handleOk" |
||||
@cancel="handleCancel" |
||||
> |
||||
<p>Some contents...</p> |
||||
<p>Some contents...</p> |
||||
<p>Some contents...</p> |
||||
</a-modal> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
export default { |
||||
data() { |
||||
return { |
||||
visible: false, |
||||
} |
||||
}, |
||||
methods: { |
||||
showModal() { |
||||
this.visible = true |
||||
}, |
||||
handleOk(e) { |
||||
console.log(e); |
||||
this.visible = false |
||||
}, |
||||
handleCancel(e) { |
||||
console.log(e); |
||||
this.visible = false |
||||
}, |
||||
} |
||||
} |
||||
</script> |
||||
``` |
||||
|
@ -0,0 +1,58 @@
|
||||
|
||||
<cn> |
||||
#### 确认对话框 |
||||
使用 `confirm()` 可以快捷地弹出确认框。 |
||||
</cn> |
||||
|
||||
<us> |
||||
#### Confirmation modal dialog |
||||
To use `confirm()` to popup a confirmation modal dialog. |
||||
</us> |
||||
|
||||
```html |
||||
<template> |
||||
<div> |
||||
<a-button @click="showConfirm"> |
||||
Confirm |
||||
</a-button> |
||||
<a-button @click="showDeleteConfirm" type="dashed"> |
||||
Delete |
||||
</a-button> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
export default { |
||||
methods: { |
||||
showConfirm() { |
||||
this.$modalConfirm({ |
||||
title: 'Do you Want to delete these items?', |
||||
content: 'Some descriptions', |
||||
onOk() { |
||||
console.log('OK'); |
||||
}, |
||||
onCancel() { |
||||
console.log('Cancel'); |
||||
}, |
||||
}); |
||||
}, |
||||
|
||||
showDeleteConfirm() { |
||||
this.$modalConfirm({ |
||||
title: 'Are you sure delete this task?', |
||||
content: 'Some descriptions', |
||||
okText: 'Yes', |
||||
okType: 'danger', |
||||
cancelText: 'No', |
||||
onOk() { |
||||
console.log('OK'); |
||||
}, |
||||
onCancel() { |
||||
console.log('Cancel'); |
||||
}, |
||||
}); |
||||
}, |
||||
} |
||||
} |
||||
</script> |
||||
``` |
||||
|
@ -0,0 +1,66 @@
|
||||
import Modal from './Modal' |
||||
import modalConfirm from './confirm' |
||||
|
||||
// export { ActionButtonProps } from './ActionButton'
|
||||
// export { ModalProps, ModalFuncProps } from './Modal'
|
||||
|
||||
const info = function (props) { |
||||
const config = { |
||||
type: 'info', |
||||
iconType: 'info-circle', |
||||
okCancel: false, |
||||
...props, |
||||
} |
||||
return modalConfirm(config) |
||||
} |
||||
|
||||
const success = function (props) { |
||||
const config = { |
||||
type: 'success', |
||||
iconType: 'check-circle', |
||||
okCancel: false, |
||||
...props, |
||||
} |
||||
return modalConfirm(config) |
||||
} |
||||
|
||||
const error = function (props) { |
||||
const config = { |
||||
type: 'error', |
||||
iconType: 'cross-circle', |
||||
okCancel: false, |
||||
...props, |
||||
} |
||||
return modalConfirm(config) |
||||
} |
||||
|
||||
const warning = function (props) { |
||||
const config = { |
||||
type: 'warning', |
||||
iconType: 'exclamation-circle', |
||||
okCancel: false, |
||||
...props, |
||||
} |
||||
return modalConfirm(config) |
||||
} |
||||
const warn = warning |
||||
|
||||
const confirm = function (props) { |
||||
const config = { |
||||
type: 'confirm', |
||||
okCancel: true, |
||||
...props, |
||||
} |
||||
return modalConfirm(config) |
||||
} |
||||
|
||||
export { |
||||
info, |
||||
success, |
||||
error, |
||||
warning, |
||||
warn, |
||||
confirm, |
||||
} |
||||
|
||||
export default Modal |
@ -0,0 +1,28 @@
|
||||
import defaultLocale from '../locale-provider/default' |
||||
|
||||
// export interface ModalLocale {
|
||||
// okText: string;
|
||||
// cancelText: string;
|
||||
// justOkText: string;
|
||||
// }
|
||||
|
||||
let runtimeLocale = { |
||||
...defaultLocale.Modal, |
||||
} |
||||
|
||||
export function changeConfirmLocale (newLocale) { |
||||
if (newLocale) { |
||||
runtimeLocale = { |
||||
...runtimeLocale, |
||||
...newLocale, |
||||
} |
||||
} else { |
||||
runtimeLocale = { |
||||
...defaultLocale.Modal, |
||||
} |
||||
} |
||||
} |
||||
|
||||
export function getConfirmLocale () { |
||||
return runtimeLocale |
||||
} |
@ -0,0 +1,71 @@
|
||||
@import "../../style/mixins/index"; |
||||
|
||||
@confirm-prefix-cls: ~"@{ant-prefix}-confirm"; |
||||
|
||||
.@{confirm-prefix-cls} { |
||||
.@{ant-prefix}-modal-header { |
||||
display: none; |
||||
} |
||||
|
||||
.@{ant-prefix}-modal-close { |
||||
display: none; |
||||
} |
||||
|
||||
.@{ant-prefix}-modal-body { |
||||
padding: 32px 32px 24px; |
||||
} |
||||
|
||||
&-body-wrapper { |
||||
.clearfix(); |
||||
} |
||||
|
||||
&-body { |
||||
.@{confirm-prefix-cls}-title { |
||||
color: @heading-color; |
||||
font-weight: 500; |
||||
font-size: @font-size-lg; |
||||
line-height: 22px; |
||||
} |
||||
|
||||
.@{confirm-prefix-cls}-content { |
||||
margin-left: 38px; |
||||
font-size: @font-size-base; |
||||
color: @text-color; |
||||
margin-top: 8px; |
||||
} |
||||
|
||||
> .@{iconfont-css-prefix} { |
||||
font-size: 22px; |
||||
margin-right: 16px; |
||||
float: left; |
||||
min-height: 48px; |
||||
} |
||||
} |
||||
|
||||
.@{confirm-prefix-cls}-btns { |
||||
margin-top: 24px; |
||||
float: right; |
||||
|
||||
button + button { |
||||
margin-left: 8px; |
||||
margin-bottom: 0; |
||||
} |
||||
} |
||||
|
||||
&-error &-body > .@{iconfont-css-prefix} { |
||||
color: @error-color; |
||||
} |
||||
|
||||
&-warning &-body > .@{iconfont-css-prefix}, |
||||
&-confirm &-body > .@{iconfont-css-prefix} { |
||||
color: @warning-color; |
||||
} |
||||
|
||||
&-info &-body > .@{iconfont-css-prefix} { |
||||
color: @info-color; |
||||
} |
||||
|
||||
&-success &-body > .@{iconfont-css-prefix} { |
||||
color: @success-color; |
||||
} |
||||
} |
@ -0,0 +1,5 @@
|
||||
import '../../style/index.less' |
||||
import './index.less' |
||||
|
||||
// style dependencies
|
||||
import '../../button/style' |
@ -0,0 +1,4 @@
|
||||
@import "../../style/themes/default"; |
||||
@import "../../style/mixins/index"; |
||||
@import "./modal"; |
||||
@import "./confirm"; |
@ -0,0 +1,146 @@
|
||||
@dialog-prefix-cls: ~"@{ant-prefix}-modal"; |
||||
|
||||
.@{dialog-prefix-cls} { |
||||
.reset-component; |
||||
position: relative; |
||||
width: auto; |
||||
margin: 0 auto; |
||||
top: 100px; |
||||
padding-bottom: 24px; |
||||
|
||||
&-wrap { |
||||
position: fixed; |
||||
overflow: auto; |
||||
top: 0; |
||||
right: 0; |
||||
bottom: 0; |
||||
left: 0; |
||||
z-index: @zindex-modal; |
||||
-webkit-overflow-scrolling: touch; |
||||
outline: 0; |
||||
} |
||||
|
||||
&-title { |
||||
margin: 0; |
||||
font-size: @font-size-lg; |
||||
line-height: 22px; |
||||
font-weight: 500; |
||||
color: @heading-color; |
||||
} |
||||
|
||||
&-content { |
||||
position: relative; |
||||
background-color: @component-background; |
||||
border: 0; |
||||
border-radius: @border-radius-base; |
||||
background-clip: padding-box; |
||||
box-shadow: @shadow-2; |
||||
} |
||||
|
||||
&-close { |
||||
cursor: pointer; |
||||
border: 0; |
||||
background: transparent; |
||||
position: absolute; |
||||
right: 0; |
||||
top: 0; |
||||
z-index: 10; |
||||
font-weight: 700; |
||||
line-height: 1; |
||||
text-decoration: none; |
||||
transition: color .3s; |
||||
color: @text-color-secondary; |
||||
outline: 0; |
||||
padding: 0; |
||||
|
||||
&-x { |
||||
display: block; |
||||
font-style: normal; |
||||
vertical-align: baseline; |
||||
text-align: center; |
||||
text-transform: none; |
||||
text-rendering: auto; |
||||
width: 56px; |
||||
height: 56px; |
||||
line-height: 56px; |
||||
font-size: @font-size-lg; |
||||
|
||||
&:before { |
||||
content: "\e633"; |
||||
display: block; |
||||
font-family: "anticon" !important; |
||||
} |
||||
} |
||||
|
||||
&:focus, |
||||
&:hover { |
||||
color: #444; |
||||
text-decoration: none; |
||||
} |
||||
} |
||||
|
||||
&-header { |
||||
padding: 16px 24px; |
||||
border-radius: @border-radius-base @border-radius-base 0 0; |
||||
background: @component-background; |
||||
color: @text-color; |
||||
border-bottom: @border-width-base @border-style-base @border-color-split; |
||||
} |
||||
|
||||
&-body { |
||||
padding: 24px; |
||||
font-size: @font-size-base; |
||||
line-height: @line-height-base; |
||||
} |
||||
|
||||
&-footer { |
||||
border-top: @border-width-base @border-style-base @border-color-split; |
||||
padding: 10px 16px; |
||||
text-align: right; |
||||
border-radius: 0 0 @border-radius-base @border-radius-base; |
||||
button + button { |
||||
margin-left: 8px; |
||||
margin-bottom: 0; |
||||
} |
||||
} |
||||
|
||||
&.zoom-enter, |
||||
&.zoom-appear { |
||||
animation-duration: @animation-duration-slow; |
||||
transform: none; // reset scale avoid mousePosition bug |
||||
opacity: 0; |
||||
} |
||||
|
||||
&-mask { |
||||
position: fixed; |
||||
top: 0; |
||||
right: 0; |
||||
left: 0; |
||||
bottom: 0; |
||||
background-color: #373737; |
||||
background-color: @modal-mask-bg; // lesshint duplicateProperty: false |
||||
height: 100%; |
||||
z-index: @zindex-modal-mask; |
||||
filter: ~"alpha(opacity=50)"; |
||||
|
||||
&-hidden { |
||||
display: none; |
||||
} |
||||
} |
||||
|
||||
&-open { |
||||
overflow: hidden; |
||||
} |
||||
} |
||||
|
||||
@media (max-width: @screen-md) { |
||||
.@{dialog-prefix-cls} { |
||||
width: auto !important; |
||||
margin: 10px; |
||||
} |
||||
.vertical-center-modal { |
||||
.@{dialog-prefix-cls} { |
||||
flex: 1; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,2 @@
|
||||
import DialogWrap from './DialogWrap' |
||||
export default DialogWrap |
Loading…
Reference in new issue