Merge branch '2.0' into 2.0
This commit is contained in:
commit
eeaf90df89
453 changed files with 86037 additions and 26227 deletions
|
@ -10,7 +10,7 @@
|
|||
<span :class="descClasses"><slot name="desc"></slot></span>
|
||||
<a :class="closeClasses" v-if="closable" @click="close">
|
||||
<slot name="close">
|
||||
<Icon type="ios-close-empty"></Icon>
|
||||
<Icon type="ios-close"></Icon>
|
||||
</slot>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -80,19 +80,20 @@
|
|||
|
||||
switch (this.type) {
|
||||
case 'success':
|
||||
type = 'checkmark-circled';
|
||||
type = 'ios-checkmark-circle';
|
||||
break;
|
||||
case 'info':
|
||||
type = 'information-circled';
|
||||
type = 'ios-information-circle';
|
||||
break;
|
||||
case 'warning':
|
||||
type = 'android-alert';
|
||||
type = 'ios-alert';
|
||||
break;
|
||||
case 'error':
|
||||
type = 'close-circled';
|
||||
type = 'ios-close-circle';
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.desc) type += '-outline';
|
||||
return type;
|
||||
}
|
||||
},
|
||||
|
|
2
src/components/anchor-link/index.js
Normal file
2
src/components/anchor-link/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
import AnchorLink from '../anchor/anchor-link.vue';
|
||||
export default AnchorLink;
|
59
src/components/anchor/anchor-link.vue
Normal file
59
src/components/anchor/anchor-link.vue
Normal file
|
@ -0,0 +1,59 @@
|
|||
<template>
|
||||
<div :class="anchorLinkClasses">
|
||||
<a :class="linkTitleClasses" :href="href" :data-scroll-offset="scrollOffset" :data-href="href" @click.prevent="goAnchor" :title="title">{{ title }}</a>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'AnchorLink',
|
||||
inject: ['anchorCom'],
|
||||
props: {
|
||||
href: String,
|
||||
title: String,
|
||||
scrollOffset: {
|
||||
type: Number,
|
||||
default () {
|
||||
return this.anchorCom.scrollOffset;
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
prefix: 'ivu-anchor-link'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
anchorLinkClasses () {
|
||||
return [
|
||||
this.prefix,
|
||||
this.anchorCom.currentLink === this.href ? `${this.prefix}-active` : ''
|
||||
];
|
||||
},
|
||||
linkTitleClasses () {
|
||||
return [
|
||||
`${this.prefix}-title`
|
||||
];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goAnchor () {
|
||||
this.currentLink = this.href;
|
||||
this.anchorCom.handleHashChange();
|
||||
this.anchorCom.handleScrollTo();
|
||||
this.anchorCom.$emit('on-select', this.href);
|
||||
const isRoute = this.$router;
|
||||
if (isRoute) {
|
||||
this.$router.push(this.href);
|
||||
} else {
|
||||
window.location.href = this.href;
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.$nextTick(() => {
|
||||
this.anchorCom.init();
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
198
src/components/anchor/anchor.vue
Normal file
198
src/components/anchor/anchor.vue
Normal file
|
@ -0,0 +1,198 @@
|
|||
<template>
|
||||
<component :is="wrapperComponent" :offset-top="offsetTop" :offset-bottom="offsetBottom" @on-change="handleAffixStateChange">
|
||||
<div :class="`${prefix}-wrapper`" :style="wrapperStyle">
|
||||
<div :class="`${prefix}`">
|
||||
<div :class="`${prefix}-ink`">
|
||||
<span v-show="showInk" :class="`${prefix}-ink-ball`" :style="{top: `${inkTop}px`}"></span>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
<script>
|
||||
import { scrollTop, findComponentsDownward, sharpMatcherRegx } from '../../utils/assist';
|
||||
import { on, off } from '../../utils/dom';
|
||||
export default {
|
||||
name: 'Anchor',
|
||||
provide () {
|
||||
return {
|
||||
anchorCom: this
|
||||
};
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
prefix: 'ivu-anchor',
|
||||
isAffixed: false, // current affixed state
|
||||
inkTop: 0,
|
||||
animating: false, // if is scrolling now
|
||||
currentLink: '', // current show link => #href -> currentLink = #href
|
||||
currentId: '', // current show title id => #href -> currentId = href
|
||||
scrollContainer: null,
|
||||
scrollElement: null,
|
||||
titlesOffsetArr: [],
|
||||
wrapperTop: 0,
|
||||
upperFirstTitle: true
|
||||
};
|
||||
},
|
||||
props: {
|
||||
affix: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
offsetTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
offsetBottom: Number,
|
||||
bounds: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
// container: [String, HTMLElement], // HTMLElement 在 SSR 下不支持
|
||||
container: null,
|
||||
showInk: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
scrollOffset: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
wrapperComponent () {
|
||||
return this.affix ? 'Affix' : 'div';
|
||||
},
|
||||
wrapperStyle () {
|
||||
return {
|
||||
maxHeight: this.offsetTop ? `calc(100vh - ${this.offsetTop}px)` : '100vh'
|
||||
};
|
||||
},
|
||||
containerIsWindow () {
|
||||
return this.scrollContainer === window;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleAffixStateChange (state) {
|
||||
this.isAffixed = this.affix && state;
|
||||
},
|
||||
handleScroll (e) {
|
||||
this.upperFirstTitle = e.target.scrollTop < this.titlesOffsetArr[0].offset;
|
||||
if (this.animating) return;
|
||||
this.updateTitleOffset();
|
||||
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop || e.target.scrollTop;
|
||||
this.getCurrentScrollAtTitleId(scrollTop);
|
||||
},
|
||||
handleHashChange () {
|
||||
const url = window.location.href;
|
||||
const sharpLinkMatch = sharpMatcherRegx.exec(url);
|
||||
if (!sharpLinkMatch) return;
|
||||
this.currentLink = sharpLinkMatch[0];
|
||||
this.currentId = sharpLinkMatch[1];
|
||||
},
|
||||
handleScrollTo () {
|
||||
const anchor = document.getElementById(this.currentId);
|
||||
const currentLinkElementA = document.querySelector(`a[data-href="${this.currentLink}"]`);
|
||||
let offset = this.scrollOffset;
|
||||
if (currentLinkElementA) {
|
||||
offset = parseFloat(currentLinkElementA.getAttribute('data-scroll-offset'));
|
||||
}
|
||||
|
||||
if (!anchor) return;
|
||||
const offsetTop = anchor.offsetTop - this.wrapperTop - offset;
|
||||
this.animating = true;
|
||||
scrollTop(this.scrollContainer, this.scrollElement.scrollTop, offsetTop, 600, () => {
|
||||
this.animating = false;
|
||||
});
|
||||
this.handleSetInkTop();
|
||||
},
|
||||
handleSetInkTop () {
|
||||
const currentLinkElementA = document.querySelector(`a[data-href="${this.currentLink}"]`);
|
||||
if (!currentLinkElementA) return;
|
||||
const elementATop = currentLinkElementA.offsetTop;
|
||||
const top = (elementATop < 0 ? this.offsetTop : elementATop);
|
||||
this.inkTop = top;
|
||||
},
|
||||
updateTitleOffset () {
|
||||
const links = findComponentsDownward(this, 'AnchorLink').map(link => {
|
||||
return link.href;
|
||||
});
|
||||
const idArr = links.map(link => {
|
||||
return link.split('#')[1];
|
||||
});
|
||||
let offsetArr = [];
|
||||
idArr.forEach(id => {
|
||||
const titleEle = document.getElementById(id);
|
||||
if (titleEle) offsetArr.push({
|
||||
link: `#${id}`,
|
||||
offset: titleEle.offsetTop - this.scrollElement.offsetTop
|
||||
});
|
||||
});
|
||||
this.titlesOffsetArr = offsetArr;
|
||||
},
|
||||
getCurrentScrollAtTitleId (scrollTop) {
|
||||
let i = -1;
|
||||
let len = this.titlesOffsetArr.length;
|
||||
let titleItem = {
|
||||
link: '#',
|
||||
offset: 0
|
||||
};
|
||||
scrollTop += this.bounds;
|
||||
while (++i < len) {
|
||||
let currentEle = this.titlesOffsetArr[i];
|
||||
let nextEle = this.titlesOffsetArr[i + 1];
|
||||
if (scrollTop >= currentEle.offset && scrollTop < ((nextEle && nextEle.offset) || Infinity)) {
|
||||
titleItem = this.titlesOffsetArr[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.currentLink = titleItem.link;
|
||||
this.handleSetInkTop();
|
||||
},
|
||||
getContainer () {
|
||||
this.scrollContainer = this.container ? (typeof this.container === 'string' ? document.querySelector(this.container) : this.container) : window;
|
||||
this.scrollElement = this.container ? this.scrollContainer : (document.documentElement || document.body);
|
||||
},
|
||||
removeListener () {
|
||||
off(this.scrollContainer, 'scroll', this.handleScroll);
|
||||
off(window, 'hashchange', this.handleHashChange);
|
||||
},
|
||||
init () {
|
||||
// const anchorLink = findComponentDownward(this, 'AnchorLink');
|
||||
this.handleHashChange();
|
||||
this.$nextTick(() => {
|
||||
this.removeListener();
|
||||
this.getContainer();
|
||||
this.wrapperTop = this.containerIsWindow ? 0 : this.scrollElement.offsetTop;
|
||||
this.handleScrollTo();
|
||||
this.handleSetInkTop();
|
||||
this.updateTitleOffset();
|
||||
this.upperFirstTitle = this.scrollElement.scrollTop < this.titlesOffsetArr[0].offset;
|
||||
on(this.scrollContainer, 'scroll', this.handleScroll);
|
||||
on(window, 'hashchange', this.handleHashChange);
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$route' () {
|
||||
this.handleHashChange();
|
||||
this.$nextTick(() => {
|
||||
this.handleScrollTo();
|
||||
});
|
||||
},
|
||||
container () {
|
||||
this.init();
|
||||
},
|
||||
currentLink (newHref, oldHref) {
|
||||
this.$emit('on-change', newHref, oldHref);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.init();
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.removeListener();
|
||||
}
|
||||
};
|
||||
</script>
|
2
src/components/anchor/index.js
Normal file
2
src/components/anchor/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
import Anchor from './anchor.vue';
|
||||
export default Anchor;
|
|
@ -8,6 +8,7 @@
|
|||
:placeholder="placeholder"
|
||||
:size="size"
|
||||
:placement="placement"
|
||||
:value="currentValue"
|
||||
filterable
|
||||
remote
|
||||
auto-complete
|
||||
|
@ -72,6 +73,9 @@
|
|||
size: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['small', 'large', 'default']);
|
||||
},
|
||||
default () {
|
||||
return !this.$IVIEW || this.$IVIEW.size === '' ? 'default' : this.$IVIEW.size;
|
||||
}
|
||||
},
|
||||
icon: {
|
||||
|
@ -89,7 +93,9 @@
|
|||
},
|
||||
transfer: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default () {
|
||||
return this.$IVIEW.transfer === '' ? false : this.$IVIEW.transfer;
|
||||
}
|
||||
},
|
||||
name: {
|
||||
type: String
|
||||
|
@ -124,7 +130,9 @@
|
|||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
this.disableEmitChange = true;
|
||||
if(this.currentValue !== val){
|
||||
this.disableEmitChange = true;
|
||||
}
|
||||
this.currentValue = val;
|
||||
},
|
||||
currentValue (val) {
|
||||
|
@ -144,21 +152,20 @@
|
|||
},
|
||||
handleChange (val) {
|
||||
this.currentValue = val;
|
||||
this.$refs.select.model = val;
|
||||
this.$refs.input.blur();
|
||||
this.$emit('on-select', val);
|
||||
},
|
||||
handleFocus () {
|
||||
this.$refs.select.visible = true;
|
||||
handleFocus (event) {
|
||||
this.$emit('on-focus', event);
|
||||
},
|
||||
handleBlur () {
|
||||
this.$refs.select.visible = false;
|
||||
handleBlur (event) {
|
||||
this.$emit('on-blur', event);
|
||||
},
|
||||
handleClear () {
|
||||
if (!this.clearable) return;
|
||||
this.currentValue = '';
|
||||
this.$refs.select.model = '';
|
||||
this.$refs.select.reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<span :class="classes">
|
||||
<img :src="src" v-if="src">
|
||||
<Icon :type="icon" v-else-if="icon"></Icon>
|
||||
<Icon :type="icon" :custom="customIcon" v-else-if="icon || customIcon"></Icon>
|
||||
<span ref="children" :class="[prefixCls + '-string']" :style="childrenStyle" v-else><slot></slot></span>
|
||||
</span>
|
||||
</template>
|
||||
|
@ -25,19 +25,26 @@
|
|||
validator (value) {
|
||||
return oneOf(value, ['small', 'large', 'default']);
|
||||
},
|
||||
default: 'default'
|
||||
default () {
|
||||
return !this.$IVIEW || this.$IVIEW.size === '' ? 'default' : this.$IVIEW.size;
|
||||
}
|
||||
},
|
||||
src: {
|
||||
type: String
|
||||
},
|
||||
icon: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
customIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
prefixCls: prefixCls,
|
||||
scale: 1,
|
||||
childrenWidth: 0,
|
||||
isSlotShow: false
|
||||
};
|
||||
},
|
||||
|
@ -49,7 +56,7 @@
|
|||
`${prefixCls}-${this.size}`,
|
||||
{
|
||||
[`${prefixCls}-image`]: !!this.src,
|
||||
[`${prefixCls}-icon`]: !!this.icon
|
||||
[`${prefixCls}-icon`]: !!this.icon || !!this.customIcon
|
||||
}
|
||||
];
|
||||
},
|
||||
|
@ -62,7 +69,7 @@
|
|||
transform: `scale(${this.scale})`,
|
||||
position: 'absolute',
|
||||
display: 'inline-block',
|
||||
left: `calc(50% - ${Math.round(this.$refs.children.offsetWidth / 2)}px)`
|
||||
left: `calc(50% - ${Math.round(this.childrenWidth / 2)}px)`
|
||||
};
|
||||
}
|
||||
return style;
|
||||
|
@ -72,11 +79,12 @@
|
|||
setScale () {
|
||||
this.isSlotShow = !this.src && !this.icon;
|
||||
if (this.$refs.children) {
|
||||
const childrenWidth = this.$refs.children.offsetWidth;
|
||||
// set children width again to make slot centered
|
||||
this.childrenWidth = this.$refs.children.offsetWidth;
|
||||
const avatarWidth = this.$el.getBoundingClientRect().width;
|
||||
// add 4px gap for each side to get better performance
|
||||
if (avatarWidth - 8 < childrenWidth) {
|
||||
this.scale = (avatarWidth - 8) / childrenWidth;
|
||||
if (avatarWidth - 8 < this.childrenWidth) {
|
||||
this.scale = (avatarWidth - 8) / this.childrenWidth;
|
||||
} else {
|
||||
this.scale = 1;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div :class="classes" :style="styles" @click="back">
|
||||
<slot>
|
||||
<div :class="innerClasses">
|
||||
<i class="ivu-icon ivu-icon-chevron-up"></i>
|
||||
<i class="ivu-icon ivu-icon-ios-arrow-up"></i>
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
|
|
|
@ -1,20 +1,25 @@
|
|||
<template>
|
||||
<span v-if="dot" :class="classes" ref="badge">
|
||||
<slot></slot>
|
||||
<sup :class="dotClasses" v-show="badge"></sup>
|
||||
<sup :class="dotClasses" :style="styles" v-show="badge"></sup>
|
||||
</span>
|
||||
<span v-else-if="status" :class="classes" class="ivu-badge-status" ref="badge">
|
||||
<span :class="statusClasses"></span>
|
||||
<span class="ivu-badge-status-text">{{ text }}</span>
|
||||
</span>
|
||||
<span v-else :class="classes" ref="badge">
|
||||
<slot></slot>
|
||||
<sup v-if="count" :class="countClasses" v-show="badge">{{ finalCount }}</sup>
|
||||
<sup v-if="hasCount" :style="styles" :class="countClasses" v-show="badge">{{ finalCount }}</sup>
|
||||
</span>
|
||||
</template>
|
||||
<script>
|
||||
import { oneOf } from '../../utils/assist';
|
||||
const prefixCls = 'ivu-badge';
|
||||
|
||||
export default {
|
||||
name: 'Badge',
|
||||
props: {
|
||||
count: [Number, String],
|
||||
count: Number,
|
||||
dot: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
|
@ -23,7 +28,28 @@
|
|||
type: [Number, String],
|
||||
default: 99
|
||||
},
|
||||
className: String
|
||||
className: String,
|
||||
showZero: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
status: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['success', 'processing', 'default', 'error', 'warning']);
|
||||
}
|
||||
},
|
||||
type: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['success', 'primary', 'normal', 'error', 'warning', 'info']);
|
||||
}
|
||||
},
|
||||
offset: {
|
||||
type: Array
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
classes () {
|
||||
|
@ -37,11 +63,29 @@
|
|||
`${prefixCls}-count`,
|
||||
{
|
||||
[`${this.className}`]: !!this.className,
|
||||
[`${prefixCls}-count-alone`]: this.alone
|
||||
[`${prefixCls}-count-alone`]: this.alone,
|
||||
[`${prefixCls}-count-${this.type}`]: !!this.type
|
||||
}
|
||||
];
|
||||
},
|
||||
statusClasses () {
|
||||
return [
|
||||
`${prefixCls}-status-dot`,
|
||||
{
|
||||
[`${prefixCls}-status-${this.status}`]: !!this.status
|
||||
}
|
||||
];
|
||||
},
|
||||
styles () {
|
||||
const style = {};
|
||||
if (this.offset && this.offset.length === 2) {
|
||||
style['margin-top'] = `${this.offset[0]}px`;
|
||||
style['margin-right'] = `${this.offset[1]}px`;
|
||||
}
|
||||
return style;
|
||||
},
|
||||
finalCount () {
|
||||
if (this.text !== '') return this.text;
|
||||
return parseInt(this.count) >= parseInt(this.overflowCount) ? `${this.overflowCount}+` : this.count;
|
||||
},
|
||||
badge () {
|
||||
|
@ -60,7 +104,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
if (this.text !== '') status = true;
|
||||
|
||||
return status || this.showZero;
|
||||
},
|
||||
hasCount() {
|
||||
if(this.count || this.text !== '') return true;
|
||||
if(this.showZero && parseInt(this.count) === 0) return true;
|
||||
else return false;
|
||||
},
|
||||
alone () {
|
||||
return this.$slots.default === undefined;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Thanks to https://github.com/ElemeFE/element/blob/dev/src/transitions/collapse-transition.js
|
||||
|
||||
import { addClass, removeClass } from '../../utils/assist';
|
||||
|
||||
const Transition = {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
></render-cell>
|
||||
</div>
|
||||
<a :class="[baseClass + '-close']" @click="close" v-if="closable">
|
||||
<i class="ivu-icon ivu-icon-ios-close-empty"></i>
|
||||
<i class="ivu-icon ivu-icon-ios-close"></i>
|
||||
</a>
|
||||
</template>
|
||||
<template v-if="type === 'message'">
|
||||
|
@ -21,7 +21,7 @@
|
|||
></render-cell>
|
||||
</div>
|
||||
<a :class="[baseClass + '-close']" @click="close" v-if="closable">
|
||||
<i class="ivu-icon ivu-icon-ios-close-empty"></i>
|
||||
<i class="ivu-icon ivu-icon-ios-close"></i>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div :class="classes" :style="styles">
|
||||
<div :class="classes" :style="wrapStyles">
|
||||
<Notice
|
||||
v-for="notice in notices"
|
||||
:key="notice.name"
|
||||
|
@ -21,6 +21,8 @@
|
|||
<script>
|
||||
import Notice from './notice.vue';
|
||||
|
||||
import { transferIndex, transferIncrease } from '../../../utils/transfer-queue';
|
||||
|
||||
const prefixCls = 'ivu-notification';
|
||||
let seed = 0;
|
||||
const now = Date.now();
|
||||
|
@ -54,7 +56,8 @@
|
|||
},
|
||||
data () {
|
||||
return {
|
||||
notices: []
|
||||
notices: [],
|
||||
tIndex: this.handleGetIndex()
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -65,6 +68,12 @@
|
|||
[`${this.className}`]: !!this.className
|
||||
}
|
||||
];
|
||||
},
|
||||
wrapStyles () {
|
||||
let styles = Object.assign({}, this.styles);
|
||||
styles['z-index'] = 1010 + this.tIndex;
|
||||
|
||||
return styles;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -82,6 +91,7 @@
|
|||
}, notice);
|
||||
|
||||
this.notices.push(_notice);
|
||||
this.tIndex = this.handleGetIndex();
|
||||
},
|
||||
close (name) {
|
||||
const notices = this.notices;
|
||||
|
@ -94,7 +104,11 @@
|
|||
},
|
||||
closeAll () {
|
||||
this.notices = [];
|
||||
}
|
||||
},
|
||||
handleGetIndex () {
|
||||
transferIncrease();
|
||||
return transferIndex;
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* */
|
||||
import Vue from 'vue';
|
||||
const isServer = Vue.prototype.$isServer;
|
||||
const Popper = isServer ? function() {} : require('popper.js'); // eslint-disable-line
|
||||
const Popper = isServer ? function() {} : require('popper.js/dist/umd/popper.js'); // eslint-disable-line
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
@ -29,8 +29,14 @@ export default {
|
|||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
gpuAcceleration: false,
|
||||
boundariesElement: 'body' // todo 暂时注释,发现在 vue 2 里方向暂时可以自动识别了,待验证(还是有问题的)
|
||||
modifiers: {
|
||||
computeStyle:{
|
||||
gpuAcceleration: false,
|
||||
},
|
||||
preventOverflow :{
|
||||
boundariesElement: 'window'
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
|
@ -54,10 +60,10 @@ export default {
|
|||
},
|
||||
visible(val) {
|
||||
if (val) {
|
||||
if (this.handleIndexIncrease) this.handleIndexIncrease(); // just use for Poptip
|
||||
this.updatePopper();
|
||||
this.$emit('on-popper-show');
|
||||
} else {
|
||||
this.destroyPopper();
|
||||
this.$emit('on-popper-hide');
|
||||
}
|
||||
this.$emit('input', val);
|
||||
|
@ -81,14 +87,18 @@ export default {
|
|||
}
|
||||
|
||||
options.placement = this.placement;
|
||||
options.offset = this.offset;
|
||||
|
||||
this.popperJS = new Popper(reference, popper, options);
|
||||
this.popperJS.onCreate(popper => {
|
||||
this.resetTransformOrigin(popper);
|
||||
if (!options.modifiers.offset) {
|
||||
options.modifiers.offset = {};
|
||||
}
|
||||
options.modifiers.offset.offset = this.offset;
|
||||
options.onCreate =()=>{
|
||||
this.$nextTick(this.updatePopper);
|
||||
this.$emit('created', this);
|
||||
});
|
||||
};
|
||||
|
||||
this.popperJS = new Popper(reference, popper, options);
|
||||
|
||||
},
|
||||
updatePopper() {
|
||||
if (isServer) return;
|
||||
|
@ -99,21 +109,12 @@ export default {
|
|||
if (this.visible) return;
|
||||
this.popperJS.destroy();
|
||||
this.popperJS = null;
|
||||
},
|
||||
destroyPopper() {
|
||||
if (isServer) return;
|
||||
if (this.popperJS) {
|
||||
this.resetTransformOrigin(this.popperJS);
|
||||
}
|
||||
},
|
||||
resetTransformOrigin(popper) {
|
||||
if (isServer) return;
|
||||
let placementMap = {top: 'bottom', bottom: 'top', left: 'right', right: 'left'};
|
||||
let placement = popper._popper.getAttribute('x-placement').split('-')[0];
|
||||
let origin = placementMap[placement];
|
||||
popper._popper.style.transformOrigin = ['top', 'bottom'].indexOf(placement) > -1 ? `center ${ origin }` : `${ origin } center`;
|
||||
}
|
||||
},
|
||||
updated (){
|
||||
this.$nextTick(()=>this.updatePopper());
|
||||
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (isServer) return;
|
||||
if (this.popperJS) {
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
<template>
|
||||
<span>
|
||||
<a v-if="to || href" :class="linkClasses" @click="handleClick">
|
||||
<a
|
||||
v-if="to"
|
||||
:href="linkUrl"
|
||||
:target="target"
|
||||
:class="linkClasses"
|
||||
@click.exact="handleCheckClick($event, false)"
|
||||
@click.ctrl="handleCheckClick($event, true)"
|
||||
@click.meta="handleCheckClick($event, true)">
|
||||
<slot></slot>
|
||||
</a>
|
||||
<span v-else :class="linkClasses">
|
||||
|
@ -13,22 +20,14 @@
|
|||
</span>
|
||||
</template>
|
||||
<script>
|
||||
// todo 3.0 时废弃 href
|
||||
import mixinsLink from '../../mixins/link';
|
||||
const prefixCls = 'ivu-breadcrumb-item';
|
||||
|
||||
export default {
|
||||
name: 'BreadcrumbItem',
|
||||
mixins: [ mixinsLink ],
|
||||
props: {
|
||||
href: {
|
||||
type: [Object, String]
|
||||
},
|
||||
to: {
|
||||
type: [Object, String]
|
||||
},
|
||||
replace: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
@ -46,16 +45,6 @@
|
|||
},
|
||||
mounted () {
|
||||
this.showSeparator = this.$slots.separator !== undefined;
|
||||
},
|
||||
methods: {
|
||||
handleClick () {
|
||||
const isRoute = this.$router;
|
||||
if (isRoute) {
|
||||
this.replace ? this.$router.replace(this.to || this.href) : this.$router.push(this.to || this.href);
|
||||
} else {
|
||||
window.location.href = this.to || this.href;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -14,6 +14,9 @@
|
|||
size: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['small', 'large', 'default']);
|
||||
},
|
||||
default () {
|
||||
return !this.$IVIEW || this.$IVIEW.size === '' ? 'default' : this.$IVIEW.size;
|
||||
}
|
||||
},
|
||||
shape: {
|
||||
|
|
|
@ -1,28 +1,45 @@
|
|||
<template>
|
||||
<a
|
||||
v-if="to"
|
||||
:class="classes"
|
||||
:disabled="disabled"
|
||||
:href="linkUrl"
|
||||
:target="target"
|
||||
@click.exact="handleClickLink($event, false)"
|
||||
@click.ctrl="handleClickLink($event, true)"
|
||||
@click.meta="handleClickLink($event, true)">
|
||||
<Icon class="ivu-load-loop" type="ios-loading" v-if="loading"></Icon>
|
||||
<Icon :type="icon" :custom="customIcon" v-if="(icon || customIcon) && !loading"></Icon>
|
||||
<span v-if="showSlot" ref="slot"><slot></slot></span>
|
||||
</a>
|
||||
<button
|
||||
v-else
|
||||
:type="htmlType"
|
||||
:class="classes"
|
||||
:disabled="disabled"
|
||||
@click="handleClick">
|
||||
<Icon class="ivu-load-loop" type="load-c" v-if="loading"></Icon>
|
||||
<Icon :type="icon" v-if="icon && !loading"></Icon>
|
||||
@click="handleClickLink">
|
||||
<Icon class="ivu-load-loop" type="ios-loading" v-if="loading"></Icon>
|
||||
<Icon :type="icon" :custom="customIcon" v-if="(icon || customIcon) && !loading"></Icon>
|
||||
<span v-if="showSlot" ref="slot"><slot></slot></span>
|
||||
</button>
|
||||
</template>
|
||||
<script>
|
||||
import Icon from '../icon';
|
||||
import { oneOf } from '../../utils/assist';
|
||||
import mixinsLink from '../../mixins/link';
|
||||
|
||||
const prefixCls = 'ivu-btn';
|
||||
|
||||
export default {
|
||||
name: 'Button',
|
||||
mixins: [ mixinsLink ],
|
||||
components: { Icon },
|
||||
props: {
|
||||
type: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['primary', 'ghost', 'dashed', 'text', 'info', 'success', 'warning', 'error', 'default']);
|
||||
}
|
||||
return oneOf(value, ['default', 'primary', 'dashed', 'text', 'info', 'success', 'warning', 'error']);
|
||||
},
|
||||
default: 'default'
|
||||
},
|
||||
shape: {
|
||||
validator (value) {
|
||||
|
@ -32,6 +49,9 @@
|
|||
size: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['small', 'large', 'default']);
|
||||
},
|
||||
default () {
|
||||
return !this.$IVIEW || this.$IVIEW.size === '' ? 'default' : this.$IVIEW.size;
|
||||
}
|
||||
},
|
||||
loading: Boolean,
|
||||
|
@ -42,10 +62,21 @@
|
|||
return oneOf(value, ['button', 'submit', 'reset']);
|
||||
}
|
||||
},
|
||||
icon: String,
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
customIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
long: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
ghost: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
|
@ -57,20 +88,24 @@
|
|||
classes () {
|
||||
return [
|
||||
`${prefixCls}`,
|
||||
`${prefixCls}-${this.type}`,
|
||||
{
|
||||
[`${prefixCls}-${this.type}`]: !!this.type,
|
||||
[`${prefixCls}-long`]: this.long,
|
||||
[`${prefixCls}-${this.shape}`]: !!this.shape,
|
||||
[`${prefixCls}-${this.size}`]: !!this.size,
|
||||
[`${prefixCls}-${this.size}`]: this.size !== 'default',
|
||||
[`${prefixCls}-loading`]: this.loading != null && this.loading,
|
||||
[`${prefixCls}-icon-only`]: !this.showSlot && (!!this.icon || this.loading)
|
||||
[`${prefixCls}-icon-only`]: !this.showSlot && (!!this.icon || !!this.customIcon || this.loading),
|
||||
[`${prefixCls}-ghost`]: this.ghost
|
||||
}
|
||||
];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick (event) {
|
||||
// Ctrl or CMD and click, open in new window when use `to`
|
||||
handleClickLink (event, new_window = false) {
|
||||
this.$emit('click', event);
|
||||
|
||||
this.handleCheckClick(event, new_window);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
<template>
|
||||
<div :class="classes">
|
||||
<div :class="headClasses" v-if="showHead"><slot name="title"></slot></div>
|
||||
<div :class="headClasses" v-if="showHead"><slot name="title">
|
||||
<p v-if="title">
|
||||
<Icon v-if="icon" :type="icon"></Icon>
|
||||
<span>{{title}}</span>
|
||||
</p>
|
||||
</slot></div>
|
||||
<div :class="extraClasses" v-if="showExtra"><slot name="extra"></slot></div>
|
||||
<div :class="bodyClasses" :style="bodyStyles"><slot></slot></div>
|
||||
</div>
|
||||
|
@ -8,10 +13,11 @@
|
|||
<script>
|
||||
const prefixCls = 'ivu-card';
|
||||
const defaultPadding = 16;
|
||||
import Icon from '../icon/icon.vue';
|
||||
|
||||
export default {
|
||||
name: 'Card',
|
||||
|
||||
components: { Icon },
|
||||
props: {
|
||||
bordered: {
|
||||
type: Boolean,
|
||||
|
@ -28,6 +34,12 @@
|
|||
padding: {
|
||||
type: Number,
|
||||
default: defaultPadding
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
}
|
||||
},
|
||||
data () {
|
||||
|
@ -67,7 +79,7 @@
|
|||
}
|
||||
},
|
||||
mounted () {
|
||||
this.showHead = this.$slots.title !== undefined;
|
||||
this.showHead = this.title || this.$slots.title !== undefined;
|
||||
this.showExtra = this.$slots.extra !== undefined;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -34,6 +34,13 @@
|
|||
this.$parent.initCopyTrackDom();
|
||||
});
|
||||
}
|
||||
},
|
||||
height (val) {
|
||||
if (val && this.$parent.loop) {
|
||||
this.$nextTick(() => {
|
||||
this.$parent.initCopyTrackDom();
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div :class="classes">
|
||||
<button :class="arrowClasses" class="left" @click="arrowEvent(-1)">
|
||||
<Icon type="chevron-left"></Icon>
|
||||
<button type="button" :class="arrowClasses" class="left" @click="arrowEvent(-1)">
|
||||
<Icon type="ios-arrow-back"></Icon>
|
||||
</button>
|
||||
<div :class="[prefixCls + '-list']">
|
||||
<div :class="[prefixCls + '-track', showCopyTrack ? '' : 'higher']" :style="trackStyles" ref="originTrack">
|
||||
|
@ -10,15 +10,15 @@
|
|||
<div :class="[prefixCls + '-track', showCopyTrack ? 'higher' : '']" :style="copyTrackStyles" ref="copyTrack" v-if="loop">
|
||||
</div>
|
||||
</div>
|
||||
<button :class="arrowClasses" class="right" @click="arrowEvent(1)">
|
||||
<Icon type="chevron-right"></Icon>
|
||||
<button type="button" :class="arrowClasses" class="right" @click="arrowEvent(1)">
|
||||
<Icon type="ios-arrow-forward"></Icon>
|
||||
</button>
|
||||
<ul :class="dotsClasses">
|
||||
<template v-for="n in slides.length">
|
||||
<li :class="[n - 1 === currentIndex ? prefixCls + '-active' : '']"
|
||||
@click="dotsEvent('click', n - 1)"
|
||||
@mouseover="dotsEvent('hover', n - 1)">
|
||||
<button :class="[radiusDot ? 'radius' : '']"></button>
|
||||
<button type="button" :class="[radiusDot ? 'radius' : '']"></button>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
|
@ -227,6 +227,7 @@
|
|||
} else {
|
||||
this.trackIndex = index;
|
||||
}
|
||||
this.currentIndex = index;
|
||||
},
|
||||
add (offset) {
|
||||
// 获取单个轨道的图片数
|
||||
|
@ -243,8 +244,8 @@
|
|||
this.updateTrackPos(this.hideTrackPos);
|
||||
}
|
||||
// 获取当前展示图片的索引值
|
||||
let index = this.showCopyTrack ? this.copyTrackIndex : this.trackIndex;
|
||||
index += offset;
|
||||
const oldIndex = this.showCopyTrack ? this.copyTrackIndex : this.trackIndex;
|
||||
let index = oldIndex + offset;
|
||||
while (index < 0) index += slidesLen;
|
||||
if (((offset > 0 && index === slidesLen) || (offset < 0 && index === slidesLen - 1)) && this.loop) {
|
||||
// 极限值(左滑:当前索引为总图片张数, 右滑:当前索引为总图片张数 - 1)切换轨道
|
||||
|
@ -255,7 +256,9 @@
|
|||
if (!this.loop) index = index % this.slides.length;
|
||||
this.updateTrackIndex(index);
|
||||
}
|
||||
this.$emit('input', index === this.slides.length ? 0 : index);
|
||||
this.currentIndex = index === this.slides.length ? 0 : index;
|
||||
this.$emit('on-change', oldIndex, this.currentIndex);
|
||||
this.$emit('input', this.currentIndex);
|
||||
},
|
||||
arrowEvent (offset) {
|
||||
this.setAutoplay();
|
||||
|
@ -294,9 +297,6 @@
|
|||
autoplaySpeed () {
|
||||
this.setAutoplay();
|
||||
},
|
||||
currentIndex (val, oldVal) {
|
||||
this.$emit('on-change', oldVal, val);
|
||||
},
|
||||
trackIndex () {
|
||||
this.updateOffset();
|
||||
},
|
||||
|
@ -307,8 +307,10 @@
|
|||
this.updatePos();
|
||||
},
|
||||
value (val) {
|
||||
this.currentIndex = val;
|
||||
this.trackIndex = val;
|
||||
// this.currentIndex = val;
|
||||
// this.trackIndex = val;
|
||||
this.updateTrackIndex(val);
|
||||
this.setAutoplay();
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div :class="classes" v-clickoutside="handleClose">
|
||||
<div :class="classes" v-click-outside="handleClose">
|
||||
<div :class="[prefixCls + '-rel']" @click="toggleOpen" ref="reference">
|
||||
<input type="hidden" :name="name" :value="currentValue">
|
||||
<slot>
|
||||
|
@ -16,16 +16,17 @@
|
|||
:class="[prefixCls + '-label']"
|
||||
v-show="filterable && query === ''"
|
||||
@click="handleFocus">{{ displayRender }}</div>
|
||||
<Icon type="ios-close" :class="[prefixCls + '-arrow']" v-show="showCloseIcon" @click.native.stop="clearSelect"></Icon>
|
||||
<Icon type="arrow-down-b" :class="[prefixCls + '-arrow']"></Icon>
|
||||
<Icon type="ios-close-circle" :class="[prefixCls + '-arrow']" v-show="showCloseIcon" @click.native.stop="clearSelect"></Icon>
|
||||
<Icon type="ios-arrow-down" :class="[prefixCls + '-arrow']"></Icon>
|
||||
</slot>
|
||||
</div>
|
||||
<transition name="slide-up">
|
||||
<transition name="transition-drop">
|
||||
<Drop
|
||||
v-show="visible"
|
||||
:class="{ [prefixCls + '-transfer']: transfer }"
|
||||
ref="drop"
|
||||
:data-transfer="transfer"
|
||||
:transfer="transfer"
|
||||
v-transfer-dom>
|
||||
<div>
|
||||
<Caspanel
|
||||
|
@ -57,7 +58,7 @@
|
|||
import Drop from '../select/dropdown.vue';
|
||||
import Icon from '../icon/icon.vue';
|
||||
import Caspanel from './caspanel.vue';
|
||||
import clickoutside from '../../directives/clickoutside';
|
||||
import {directive as clickOutside} from 'v-click-outside-x';
|
||||
import TransferDom from '../../directives/transfer-dom';
|
||||
import { oneOf } from '../../utils/assist';
|
||||
import Emitter from '../../mixins/emitter';
|
||||
|
@ -70,7 +71,7 @@
|
|||
name: 'Cascader',
|
||||
mixins: [ Emitter, Locale ],
|
||||
components: { iInput, Drop, Icon, Caspanel },
|
||||
directives: { clickoutside, TransferDom },
|
||||
directives: { clickOutside, TransferDom },
|
||||
props: {
|
||||
data: {
|
||||
type: Array,
|
||||
|
@ -97,7 +98,10 @@
|
|||
},
|
||||
size: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['small', 'large']);
|
||||
return oneOf(value, ['small', 'large', 'default']);
|
||||
},
|
||||
default () {
|
||||
return !this.$IVIEW || this.$IVIEW.size === '' ? 'default' : this.$IVIEW.size;
|
||||
}
|
||||
},
|
||||
trigger: {
|
||||
|
@ -128,7 +132,9 @@
|
|||
},
|
||||
transfer: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default () {
|
||||
return !this.$IVIEW || this.$IVIEW.transfer === '' ? false : this.$IVIEW.transfer;
|
||||
}
|
||||
},
|
||||
name: {
|
||||
type: String
|
||||
|
@ -357,6 +363,7 @@
|
|||
if (this.transfer) {
|
||||
this.$refs.drop.update();
|
||||
}
|
||||
this.broadcast('Drop', 'on-update-popper');
|
||||
} else {
|
||||
if (this.filterable) {
|
||||
this.query = '';
|
||||
|
@ -365,6 +372,7 @@
|
|||
if (this.transfer) {
|
||||
this.$refs.drop.destroy();
|
||||
}
|
||||
this.broadcast('Drop', 'on-destroy-popper');
|
||||
}
|
||||
this.$emit('on-visible-change', val);
|
||||
},
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<li :class="classes">
|
||||
{{ data.label }}
|
||||
<i v-if="showArrow" class="ivu-icon ivu-icon-ios-arrow-right"></i>
|
||||
<i v-if="showLoading" class="ivu-icon ivu-icon-load-c ivu-load-loop"></i>
|
||||
<i v-if="showArrow" class="ivu-icon ivu-icon-ios-arrow-forward"></i>
|
||||
<i v-if="showLoading" class="ivu-icon ivu-icon-ios-loading ivu-load-loop"></i>
|
||||
</li>
|
||||
</template>
|
||||
<script>
|
||||
|
|
|
@ -77,8 +77,10 @@
|
|||
|
||||
// return value back recursion // 向上递归,设置临时选中值(并非真实选中)
|
||||
const backItem = this.getBaseItem(item);
|
||||
this.tmpItem = backItem;
|
||||
this.emitUpdate([backItem]);
|
||||
if (backItem.label !== this.tmpItem.label || backItem.value !== this.tmpItem.value) {
|
||||
this.tmpItem = backItem;
|
||||
this.emitUpdate([backItem]);
|
||||
}
|
||||
if (item.children && item.children.length){
|
||||
this.sublist = item.children;
|
||||
this.dispatch('Cascader', 'on-result-change', {
|
||||
|
|
2
src/components/cell-group/index.js
Normal file
2
src/components/cell-group/index.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
import CellGroup from '../cell/cell-group.vue';
|
||||
export default CellGroup;
|
20
src/components/cell/cell-group.vue
Normal file
20
src/components/cell/cell-group.vue
Normal file
|
@ -0,0 +1,20 @@
|
|||
<template>
|
||||
<div class="ivu-cell-group">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'CellGroup',
|
||||
provide () {
|
||||
return {
|
||||
cellGroup: this
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleClick (name) {
|
||||
this.$emit('on-click', name);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
32
src/components/cell/cell-item.vue
Normal file
32
src/components/cell/cell-item.vue
Normal file
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<div class="ivu-cell-item">
|
||||
<div class="ivu-cell-icon">
|
||||
<slot name="icon"></slot>
|
||||
</div>
|
||||
<div class="ivu-cell-main">
|
||||
<div class="ivu-cell-title"><slot>{{ title }}</slot></div>
|
||||
<div class="ivu-cell-label"><slot name="label">{{ label }}</slot></div>
|
||||
</div>
|
||||
<div class="ivu-cell-footer">
|
||||
<span class="ivu-cell-extra"><slot name="extra">{{ extra }}</slot></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
extra: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
95
src/components/cell/cell.vue
Normal file
95
src/components/cell/cell.vue
Normal file
|
@ -0,0 +1,95 @@
|
|||
<template>
|
||||
<div :class="classes">
|
||||
<a
|
||||
v-if="to"
|
||||
:href="linkUrl"
|
||||
:target="target"
|
||||
class="ivu-cell-link"
|
||||
@click.exact="handleClickItem($event, false)"
|
||||
@click.ctrl="handleClickItem($event, true)"
|
||||
@click.meta="handleClickItem($event, true)">
|
||||
<CellItem :title="title" :label="label" :extra="extra">
|
||||
<slot name="icon" slot="icon"></slot>
|
||||
<slot slot="default"></slot>
|
||||
<slot name="extra" slot="extra"></slot>
|
||||
<slot name="label" slot="label"></slot>
|
||||
</CellItem>
|
||||
</a>
|
||||
<div class="ivu-cell-link" v-else @click="handleClickItem">
|
||||
<CellItem :title="title" :label="label" :extra="extra">
|
||||
<slot name="icon" slot="icon"></slot>
|
||||
<slot slot="default"></slot>
|
||||
<slot name="extra" slot="extra"></slot>
|
||||
<slot name="label" slot="label"></slot>
|
||||
</CellItem>
|
||||
</div>
|
||||
<div class="ivu-cell-arrow" v-if="to">
|
||||
<slot name="arrow">
|
||||
<Icon type="ios-arrow-forward"></Icon>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import CellItem from './cell-item.vue';
|
||||
import Icon from '../icon/icon.vue';
|
||||
import mixinsLink from '../../mixins/link';
|
||||
|
||||
const prefixCls = 'ivu-cell';
|
||||
|
||||
export default {
|
||||
name: 'Cell',
|
||||
inject: ['cellGroup'],
|
||||
mixins: [ mixinsLink ],
|
||||
components: { CellItem, Icon },
|
||||
props: {
|
||||
name: {
|
||||
type: [String, Number]
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
extra: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
selected: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
prefixCls: prefixCls
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
classes () {
|
||||
return [
|
||||
`${prefixCls}`,
|
||||
{
|
||||
[`${prefixCls}-disabled`]: this.disabled,
|
||||
[`${prefixCls}-selected`]: this.selected,
|
||||
[`${prefixCls}-with-link`]: this.to
|
||||
}
|
||||
];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClickItem (event, new_window) {
|
||||
this.cellGroup.handleClick(this.name);
|
||||
|
||||
this.handleCheckClick(event, new_window);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
5
src/components/cell/index.js
Normal file
5
src/components/cell/index.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import Cell from './cell.vue';
|
||||
import CellGroup from './cell-group.vue';
|
||||
|
||||
Cell.Group = CellGroup;
|
||||
export default Cell;
|
|
@ -22,6 +22,9 @@
|
|||
size: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['small', 'large', 'default']);
|
||||
},
|
||||
default () {
|
||||
return !this.$IVIEW || this.$IVIEW.size === '' ? 'default' : this.$IVIEW.size;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -63,6 +63,9 @@
|
|||
size: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['small', 'large', 'default']);
|
||||
},
|
||||
default () {
|
||||
return !this.$IVIEW || this.$IVIEW.size === '' ? 'default' : this.$IVIEW.size;
|
||||
}
|
||||
},
|
||||
name: {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div :style="circleSize" :class="wrapClasses">
|
||||
<svg viewBox="0 0 100 100">
|
||||
<path :d="pathString" :stroke="trailColor" :stroke-width="trailWidth" :fill-opacity="0"/>
|
||||
<path :d="pathString" :stroke-linecap="strokeLinecap" :stroke="strokeColor" :stroke-width="strokeWidth" fill-opacity="0" :style="pathStyle"/>
|
||||
<path :d="pathString" :stroke="trailColor" :stroke-width="trailWidth" :fill-opacity="0" :style="trailStyle" />
|
||||
<path :d="pathString" :stroke-linecap="strokeLinecap" :stroke="strokeColor" :stroke-width="computedStrokeWidth" fill-opacity="0" :style="pathStyle" />
|
||||
</svg>
|
||||
<div :class="innerClasses">
|
||||
<slot></slot>
|
||||
|
@ -31,7 +31,7 @@
|
|||
},
|
||||
strokeColor: {
|
||||
type: String,
|
||||
default: '#2db7f5'
|
||||
default: '#2d8cf0'
|
||||
},
|
||||
strokeLinecap: {
|
||||
validator (value) {
|
||||
|
@ -46,6 +46,10 @@
|
|||
trailColor: {
|
||||
type: String,
|
||||
default: '#eaeef2'
|
||||
},
|
||||
dashboard: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -55,23 +59,53 @@
|
|||
height: `${this.size}px`
|
||||
};
|
||||
},
|
||||
computedStrokeWidth () {
|
||||
return this.percent === 0 && this.dashboard ? 0 : this.strokeWidth;
|
||||
},
|
||||
radius () {
|
||||
return 50 - this.strokeWidth / 2;
|
||||
},
|
||||
pathString () {
|
||||
return `M 50,50 m 0,-${this.radius}
|
||||
a ${this.radius},${this.radius} 0 1 1 0,${2 * this.radius}
|
||||
a ${this.radius},${this.radius} 0 1 1 0,-${2 * this.radius}`;
|
||||
if (this.dashboard) {
|
||||
return `M 50,50 m 0,${this.radius}
|
||||
a ${this.radius},${this.radius} 0 1 1 0,-${2 * this.radius}
|
||||
a ${this.radius},${this.radius} 0 1 1 0,${2 * this.radius}`;
|
||||
} else {
|
||||
return `M 50,50 m 0,-${this.radius}
|
||||
a ${this.radius},${this.radius} 0 1 1 0,${2 * this.radius}
|
||||
a ${this.radius},${this.radius} 0 1 1 0,-${2 * this.radius}`;
|
||||
}
|
||||
},
|
||||
len () {
|
||||
return Math.PI * 2 * this.radius;
|
||||
},
|
||||
trailStyle () {
|
||||
let style = {};
|
||||
if (this.dashboard) {
|
||||
style = {
|
||||
'stroke-dasharray': `${this.len - 75}px ${this.len}px`,
|
||||
'stroke-dashoffset': `-${75 / 2}px`,
|
||||
'transition': 'stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s'
|
||||
};
|
||||
}
|
||||
return style;
|
||||
},
|
||||
pathStyle () {
|
||||
return {
|
||||
'stroke-dasharray': `${this.len}px ${this.len}px`,
|
||||
'stroke-dashoffset': `${((100 - this.percent) / 100 * this.len)}px`,
|
||||
'transition': 'stroke-dashoffset 0.6s ease 0s, stroke 0.6s ease'
|
||||
};
|
||||
let style = {};
|
||||
if (this.dashboard) {
|
||||
style = {
|
||||
'stroke-dasharray': `${(this.percent / 100) * (this.len - 75)}px ${this.len}px`,
|
||||
'stroke-dashoffset': `-${75 / 2}px`,
|
||||
'transition': 'stroke-dashoffset .3s ease 0s, stroke-dasharray .6s ease 0s, stroke .6s, stroke-width .06s ease .6s'
|
||||
};
|
||||
} else {
|
||||
style = {
|
||||
'stroke-dasharray': `${this.len}px ${this.len}px`,
|
||||
'stroke-dashoffset': `${((100 - this.percent) / 100 * this.len)}px`,
|
||||
'transition': 'stroke-dashoffset 0.6s ease 0s, stroke 0.6s ease'
|
||||
};
|
||||
}
|
||||
return style;
|
||||
},
|
||||
wrapClasses () {
|
||||
return `${prefixCls}`;
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
},
|
||||
value: {
|
||||
type: [Array, String]
|
||||
},
|
||||
simple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
|
@ -24,7 +28,12 @@
|
|||
},
|
||||
computed: {
|
||||
classes () {
|
||||
return `${prefixCls}`;
|
||||
return [
|
||||
`${prefixCls}`,
|
||||
{
|
||||
[`${prefixCls}-simple`]: this.simple
|
||||
}
|
||||
];
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
@ -36,15 +45,8 @@
|
|||
|
||||
this.$children.forEach((child, index) => {
|
||||
const name = child.name || index.toString();
|
||||
let isActive = false;
|
||||
|
||||
if (self.accordion) {
|
||||
isActive = activeKey === name;
|
||||
} else {
|
||||
isActive = activeKey.indexOf(name) > -1;
|
||||
}
|
||||
|
||||
child.isActive = isActive;
|
||||
child.isActive = activeKey.indexOf(name) > -1;
|
||||
child.index = index;
|
||||
});
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div :class="itemClasses">
|
||||
<div :class="headerClasses" @click="toggle">
|
||||
<Icon type="arrow-right-b"></Icon>
|
||||
<Icon type="ios-arrow-forward" v-if="!hideArrow"></Icon>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<collapse-transition>
|
||||
|
@ -22,6 +22,10 @@
|
|||
props: {
|
||||
name: {
|
||||
type: String
|
||||
},
|
||||
hideArrow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
|
|
|
@ -1,77 +1,103 @@
|
|||
<template>
|
||||
<div class="ivu-color-picker-alpha">
|
||||
<div class="ivu-color-picker-alpha-checkboard-wrap">
|
||||
<div class="ivu-color-picker-alpha-checkerboard"></div>
|
||||
<div
|
||||
:class="[prefixCls + '-alpha']"
|
||||
tabindex="0"
|
||||
@click="$el.focus()"
|
||||
@keydown.esc="handleEscape"
|
||||
@keydown.left="handleLeft"
|
||||
@keydown.right="handleRight"
|
||||
@keydown.up="handleUp"
|
||||
@keydown.down="handleDown"
|
||||
>
|
||||
<div :class="[prefixCls + '-alpha-checkboard-wrap']">
|
||||
<div :class="[prefixCls + '-alpha-checkerboard']"></div>
|
||||
</div>
|
||||
<div class="ivu-color-picker-alpha-gradient" :style="{background: gradientColor}"></div>
|
||||
<div class="ivu-color-picker-alpha-container" ref="container"
|
||||
@mousedown="handleMouseDown"
|
||||
@touchmove="handleChange"
|
||||
@touchstart="handleChange">
|
||||
<div class="ivu-color-picker-alpha-pointer" :style="{left: colors.a * 100 + '%'}">
|
||||
<div class="ivu-color-picker-alpha-picker"></div>
|
||||
<div
|
||||
:style="gradientStyle"
|
||||
:class="[prefixCls + '-alpha-gradient']"></div>
|
||||
<div
|
||||
ref="container"
|
||||
:class="[prefixCls + '-alpha-container']"
|
||||
@mousedown="handleMouseDown"
|
||||
@touchmove="handleChange"
|
||||
@touchstart="handleChange">
|
||||
<div
|
||||
:style="{top: 0, left: `${value.a * 100}%`}"
|
||||
:class="[prefixCls + '-alpha-pointer']">
|
||||
<div :class="[prefixCls + '-alpha-picker']"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Alpha',
|
||||
props: {
|
||||
value: Object,
|
||||
onChange: Function
|
||||
import HSAMixin from './hsaMixin';
|
||||
import Prefixes from './prefixMixin';
|
||||
import {clamp, toRGBAString} from './utils';
|
||||
|
||||
export default {
|
||||
name: 'Alpha',
|
||||
|
||||
mixins: [HSAMixin, Prefixes],
|
||||
|
||||
data() {
|
||||
const normalStep = 1;
|
||||
const jumpStep = 10;
|
||||
|
||||
return {
|
||||
left: -normalStep,
|
||||
right: normalStep,
|
||||
up: jumpStep,
|
||||
down: -jumpStep,
|
||||
powerKey: 'shiftKey',
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
gradientStyle() {
|
||||
const {r, g, b} = this.value.rgba;
|
||||
const start = toRGBAString({r, g, b, a: 0});
|
||||
const finish = toRGBAString({r, g, b, a: 1});
|
||||
|
||||
return {background: `linear-gradient(to right, ${start} 0%, ${finish} 100%)`};
|
||||
},
|
||||
computed: {
|
||||
colors () {
|
||||
return this.value;
|
||||
},
|
||||
gradientColor () {
|
||||
const rgba = this.colors.rgba;
|
||||
const rgbStr = [rgba.r, rgba.g, rgba.b].join(',');
|
||||
return 'linear-gradient(to right, rgba(' + rgbStr + ', 0) 0%, rgba(' + rgbStr + ', 1) 100%)';
|
||||
},
|
||||
|
||||
methods: {
|
||||
change(newAlpha) {
|
||||
const {h, s, l} = this.value.hsl;
|
||||
const {a} = this.value;
|
||||
|
||||
if (a !== newAlpha) {
|
||||
this.$emit('change', {h, s, l, a: newAlpha, source: 'rgba'});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange (e, skip) {
|
||||
!skip && e.preventDefault();
|
||||
const container = this.$refs.container;
|
||||
const containerWidth = container.clientWidth;
|
||||
handleSlide(e, direction) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const xOffset = container.getBoundingClientRect().left + window.pageXOffset;
|
||||
const pageX = e.pageX || (e.touches ? e.touches[0].pageX : 0);
|
||||
const left = pageX - xOffset;
|
||||
this.change(clamp(e[this.powerKey] ? direction : Math.round(this.value.hsl.a * 100 + direction) / 100, 0, 1));
|
||||
},
|
||||
handleChange(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
let a;
|
||||
if (left < 0) {
|
||||
a = 0;
|
||||
} else if (left > containerWidth) {
|
||||
a = 1;
|
||||
} else {
|
||||
a = Math.round(left * 100 / containerWidth) / 100;
|
||||
}
|
||||
const left = this.getLeft(e);
|
||||
|
||||
if (this.colors.a !== a) {
|
||||
this.$emit('change', {
|
||||
h: this.colors.hsl.h,
|
||||
s: this.colors.hsl.s,
|
||||
l: this.colors.hsl.l,
|
||||
a: a,
|
||||
source: 'rgba'
|
||||
});
|
||||
}
|
||||
},
|
||||
handleMouseDown (e) {
|
||||
this.handleChange(e, true);
|
||||
window.addEventListener('mousemove', this.handleChange);
|
||||
window.addEventListener('mouseup', this.handleMouseUp);
|
||||
},
|
||||
handleMouseUp () {
|
||||
this.unbindEventListeners();
|
||||
},
|
||||
unbindEventListeners () {
|
||||
window.removeEventListener('mousemove', this.handleChange);
|
||||
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
if (left < 0) {
|
||||
this.change(0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
const {clientWidth} = this.$refs.container;
|
||||
|
||||
if (left > clientWidth) {
|
||||
this.change(1);
|
||||
return;
|
||||
}
|
||||
|
||||
this.change(Math.round(left * 100 / clientWidth) / 100);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,374 +1,479 @@
|
|||
<template>
|
||||
<div :class="classes" v-clickoutside="handleClose">
|
||||
<div ref="reference" @click="toggleVisible" :class="wrapClasses">
|
||||
<input type="hidden" :name="name" :value="currentValue">
|
||||
<i class="ivu-icon ivu-icon-arrow-down-b ivu-input-icon ivu-input-icon-normal"></i>
|
||||
<div :class="inputClasses">
|
||||
<div
|
||||
v-click-outside.capture="handleClose"
|
||||
v-click-outside:mousedown.capture="handleClose"
|
||||
:class="classes">
|
||||
<div
|
||||
ref="reference"
|
||||
:class="wrapClasses"
|
||||
@click="toggleVisible">
|
||||
<input
|
||||
:name="name"
|
||||
:value="currentValue"
|
||||
type="hidden">
|
||||
<i :class="arrowClasses"></i>
|
||||
<div
|
||||
ref="input"
|
||||
:tabindex="disabled ? undefined : 0"
|
||||
:class="inputClasses"
|
||||
@keydown.tab="onTab"
|
||||
@keydown.esc="onEscape"
|
||||
@keydown.up="onArrow"
|
||||
@keydown.down="onArrow"
|
||||
>
|
||||
<div :class="[prefixCls + '-color']">
|
||||
<div :class="[prefixCls + '-color-empty']" v-show="value === '' && !visible">
|
||||
<i class="ivu-icon ivu-icon-ios-close-empty"></i>
|
||||
<div
|
||||
v-show="value === '' && !visible"
|
||||
:class="[prefixCls + '-color-empty']">
|
||||
<i :class="[iconPrefixCls, iconPrefixCls + '-ios-close']"></i>
|
||||
</div>
|
||||
<div v-show="value || visible" :style="{backgroundColor: displayedColor}"></div>
|
||||
<div
|
||||
v-show="value || visible"
|
||||
:style="displayedColorStyle"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<transition :name="transition">
|
||||
<transition name="transition-drop">
|
||||
<Drop
|
||||
v-transfer-dom
|
||||
v-show="visible"
|
||||
@click.native="handleTransferClick"
|
||||
:class="{ [prefixCls + '-transfer']: transfer }"
|
||||
class-name="ivu-transfer-no-max-height"
|
||||
:placement="placement"
|
||||
ref="drop"
|
||||
:placement="placement"
|
||||
:data-transfer="transfer"
|
||||
v-transfer-dom>
|
||||
<div :class="[prefixCls + '-picker']">
|
||||
<div :class="[prefixCls + '-picker-wrapper']">
|
||||
<div :class="[prefixCls + '-picker-panel']">
|
||||
<Saturation v-model="saturationColors" @change="childChange"></Saturation>
|
||||
:transfer="transfer"
|
||||
:class="dropClasses"
|
||||
>
|
||||
<transition name="fade">
|
||||
<div
|
||||
v-if="visible"
|
||||
:class="[prefixCls + '-picker']">
|
||||
<div :class="[prefixCls + '-picker-wrapper']">
|
||||
<div :class="[prefixCls + '-picker-panel']">
|
||||
<Saturation
|
||||
ref="saturation"
|
||||
v-model="saturationColors"
|
||||
:focused="visible"
|
||||
@change="childChange"
|
||||
@keydown.native.tab="handleFirstTab"
|
||||
></Saturation>
|
||||
</div>
|
||||
<div
|
||||
v-if="hue"
|
||||
:class="[prefixCls + '-picker-hue-slider']">
|
||||
<Hue
|
||||
v-model="saturationColors"
|
||||
@change="childChange"></Hue>
|
||||
</div>
|
||||
<div
|
||||
v-if="alpha"
|
||||
:class="[prefixCls + '-picker-alpha-slider']">
|
||||
<Alpha
|
||||
v-model="saturationColors"
|
||||
@change="childChange"></Alpha>
|
||||
</div>
|
||||
<recommend-colors
|
||||
v-if="colors.length"
|
||||
:list="colors"
|
||||
:class="[prefixCls + '-picker-colors']"
|
||||
@picker-color="handleSelectColor"></recommend-colors>
|
||||
<recommend-colors
|
||||
v-if="!colors.length && recommend"
|
||||
:list="recommendedColor"
|
||||
:class="[prefixCls + '-picker-colors']"
|
||||
@picker-color="handleSelectColor"></recommend-colors>
|
||||
</div>
|
||||
<div v-if="hue" :class="[prefixCls + '-picker-hue-slider']">
|
||||
<Hue v-model="saturationColors" @change="childChange"></Hue>
|
||||
<div :class="[prefixCls + '-confirm']">
|
||||
<span :class="confirmColorClasses">
|
||||
<template v-if="editable">
|
||||
<i-input :value="formatColor" size="small" @on-enter="handleEditColor" @on-blur="handleEditColor"></i-input>
|
||||
</template>
|
||||
<template v-else>{{formatColor}}</template>
|
||||
</span>
|
||||
<i-button
|
||||
ref="clear"
|
||||
:tabindex="0"
|
||||
size="small"
|
||||
@click.native="handleClear"
|
||||
@keydown.enter="handleClear"
|
||||
@keydown.native.esc="closer"
|
||||
>{{t('i.datepicker.clear')}}</i-button>
|
||||
<i-button
|
||||
ref="ok"
|
||||
:tabindex="0"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click.native="handleSuccess"
|
||||
@keydown.native.tab="handleLastTab"
|
||||
@keydown.enter="handleSuccess"
|
||||
@keydown.native.esc="closer"
|
||||
>{{t('i.datepicker.ok')}}</i-button>
|
||||
</div>
|
||||
<div v-if="alpha" :class="[prefixCls + '-picker-alpha-slider']">
|
||||
<Alpha v-model="saturationColors" @change="childChange"></Alpha>
|
||||
</div>
|
||||
<recommend-colors
|
||||
v-if="colors.length"
|
||||
:list="colors"
|
||||
:class="[prefixCls + '-picker-colors']"
|
||||
@picker-color="handleSelectColor"></recommend-colors>
|
||||
<recommend-colors
|
||||
v-if="!colors.length && recommend"
|
||||
:list="recommendedColor"
|
||||
:class="[prefixCls + '-picker-colors']"
|
||||
@picker-color="handleSelectColor"></recommend-colors>
|
||||
</div>
|
||||
<div :class="[prefixCls + '-confirm']">
|
||||
<span :class="[prefixCls + '-confirm-color']">{{ formatColor }}</span>
|
||||
<Confirm @on-pick-success="handleSuccess" @on-pick-clear="handleClear"></Confirm>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</Drop>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import tinycolor from 'tinycolor2';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import {directive as clickOutside} from 'v-click-outside-x';
|
||||
import TransferDom from '../../directives/transfer-dom';
|
||||
import Drop from '../../components/select/dropdown.vue';
|
||||
import RecommendColors from './recommend-colors.vue';
|
||||
import Saturation from './saturation.vue';
|
||||
import Hue from './hue.vue';
|
||||
import Alpha from './alpha.vue';
|
||||
import iInput from '../input/input.vue';
|
||||
import iButton from '../button/button.vue';
|
||||
import Locale from '../../mixins/locale';
|
||||
import {oneOf} from '../../utils/assist';
|
||||
import Emitter from '../../mixins/emitter';
|
||||
import Prefixes from './prefixMixin';
|
||||
import {changeColor, toRGBAString} from './utils';
|
||||
|
||||
import clickoutside from '../../directives/clickoutside';
|
||||
import TransferDom from '../../directives/transfer-dom';
|
||||
export default {
|
||||
name: 'ColorPicker',
|
||||
|
||||
import Drop from '../../components/select/dropdown.vue';
|
||||
import RecommendColors from './recommend-colors.vue';
|
||||
import Confirm from '../date-picker/base/confirm.vue';
|
||||
import Saturation from './saturation.vue';
|
||||
import Hue from './hue.vue';
|
||||
import Alpha from './alpha.vue';
|
||||
components: {Drop, RecommendColors, Saturation, Hue, Alpha, iInput, iButton},
|
||||
|
||||
import { oneOf } from '../../utils/assist';
|
||||
import Emitter from '../../mixins/emitter';
|
||||
directives: {clickOutside, TransferDom},
|
||||
|
||||
const prefixCls = 'ivu-color-picker';
|
||||
const inputPrefixCls = 'ivu-input';
|
||||
mixins: [Emitter, Locale, Prefixes],
|
||||
|
||||
function _colorChange (data, oldHue) {
|
||||
data = data === '' ? '#2d8cf0' : data;
|
||||
const alpha = data && data.a;
|
||||
let color;
|
||||
|
||||
// hsl is better than hex between conversions
|
||||
if (data && data.hsl) {
|
||||
color = tinycolor(data.hsl);
|
||||
} else if (data && data.hex && data.hex.length > 0) {
|
||||
color = tinycolor(data.hex);
|
||||
} else {
|
||||
color = tinycolor(data);
|
||||
}
|
||||
|
||||
if (color && (color._a === undefined || color._a === null)) {
|
||||
color.setAlpha(alpha || 1);
|
||||
}
|
||||
|
||||
const hsl = color.toHsl();
|
||||
const hsv = color.toHsv();
|
||||
|
||||
if (hsl.s === 0) {
|
||||
hsv.h = hsl.h = data.h || (data.hsl && data.hsl.h) || oldHue || 0;
|
||||
}
|
||||
|
||||
// when the hsv.v is less than 0.0164 (base on test)
|
||||
// because of possible loss of precision
|
||||
// the result of hue and saturation would be miscalculated
|
||||
if (hsv.v < 0.0164) {
|
||||
hsv.h = data.h || (data.hsv && data.hsv.h) || 0;
|
||||
hsv.s = data.s || (data.hsv && data.hsv.s) || 0;
|
||||
}
|
||||
|
||||
if (hsl.l < 0.01) {
|
||||
hsl.h = data.h || (data.hsl && data.hsl.h) || 0;
|
||||
hsl.s = data.s || (data.hsl && data.hsl.s) || 0;
|
||||
}
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
hue: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
alpha: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
recommend: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
format: {
|
||||
type: String,
|
||||
validator(value) {
|
||||
return oneOf(value, ['hsl', 'hsv', 'hex', 'rgb']);
|
||||
},
|
||||
default: undefined,
|
||||
},
|
||||
colors: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
size: {
|
||||
validator(value) {
|
||||
return oneOf(value, ['small', 'large', 'default']);
|
||||
},
|
||||
default () {
|
||||
return !this.$IVIEW || this.$IVIEW.size === '' ? 'default' : this.$IVIEW.size;
|
||||
}
|
||||
},
|
||||
hideDropDown: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
validator(value) {
|
||||
return oneOf(value, [
|
||||
'top',
|
||||
'top-start',
|
||||
'top-end',
|
||||
'bottom',
|
||||
'bottom-start',
|
||||
'bottom-end',
|
||||
'left',
|
||||
'left-start',
|
||||
'left-end',
|
||||
'right',
|
||||
'right-start',
|
||||
'right-end',
|
||||
]);
|
||||
},
|
||||
default: 'bottom',
|
||||
},
|
||||
transfer: {
|
||||
type: Boolean,
|
||||
default () {
|
||||
return !this.$IVIEW || this.$IVIEW.transfer === '' ? false : this.$IVIEW.transfer;
|
||||
}
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
editable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
hsl: hsl,
|
||||
hex: color.toHexString().toUpperCase(),
|
||||
rgba: color.toRgb(),
|
||||
hsv: hsv,
|
||||
oldHue: data.h || oldHue || hsl.h,
|
||||
source: data.source,
|
||||
a: data.a || color.getAlpha()
|
||||
val: changeColor(this.value),
|
||||
currentValue: this.value,
|
||||
dragging: false,
|
||||
visible: false,
|
||||
recommendedColor: [
|
||||
'#2d8cf0',
|
||||
'#19be6b',
|
||||
'#ff9900',
|
||||
'#ed4014',
|
||||
'#00b5ff',
|
||||
'#19c919',
|
||||
'#f9e31c',
|
||||
'#ea1a1a',
|
||||
'#9b1dea',
|
||||
'#00c2b1',
|
||||
'#ac7a33',
|
||||
'#1d35ea',
|
||||
'#8bc34a',
|
||||
'#f16b62',
|
||||
'#ea4ca3',
|
||||
'#0d94aa',
|
||||
'#febd79',
|
||||
'#5d4037',
|
||||
'#00bcd4',
|
||||
'#f06292',
|
||||
'#cddc39',
|
||||
'#607d8b',
|
||||
'#000000',
|
||||
'#ffffff',
|
||||
],
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
export default {
|
||||
name: 'ColorPicker',
|
||||
mixins: [ Emitter ],
|
||||
components: { Drop, Confirm, RecommendColors, Saturation, Hue, Alpha },
|
||||
directives: { clickoutside, TransferDom },
|
||||
props: {
|
||||
value: {
|
||||
type: String
|
||||
computed: {
|
||||
arrowClasses() {
|
||||
return [
|
||||
this.iconPrefixCls,
|
||||
`${this.iconPrefixCls}-ios-arrow-down`,
|
||||
`${this.inputPrefixCls}-icon`,
|
||||
`${this.inputPrefixCls}-icon-normal`,
|
||||
];
|
||||
},
|
||||
transition() {
|
||||
return oneOf(this.placement, ['bottom-start', 'bottom', 'bottom-end']) ? 'slide-up' : 'fade';
|
||||
},
|
||||
saturationColors: {
|
||||
get() {
|
||||
return this.val;
|
||||
},
|
||||
hue: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
set(newVal) {
|
||||
this.val = newVal;
|
||||
this.$emit('on-active-change', this.formatColor);
|
||||
},
|
||||
alpha: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
recommend: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
format: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['hsl', 'hsv', 'hex', 'rgb']);
|
||||
}
|
||||
},
|
||||
colors: {
|
||||
type: Array,
|
||||
default () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
size: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['small', 'large', 'default']);
|
||||
},
|
||||
classes() {
|
||||
return [
|
||||
`${this.prefixCls}`,
|
||||
{
|
||||
[`${this.prefixCls}-transfer`]: this.transfer,
|
||||
},
|
||||
default: 'default'
|
||||
},
|
||||
placement: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'left-start', 'left-end', 'right', 'right-start', 'right-end']);
|
||||
];
|
||||
},
|
||||
wrapClasses() {
|
||||
return [
|
||||
`${this.prefixCls}-rel`,
|
||||
`${this.prefixCls}-${this.size}`,
|
||||
`${this.inputPrefixCls}-wrapper`,
|
||||
`${this.inputPrefixCls}-wrapper-${this.size}`,
|
||||
{
|
||||
[`${this.prefixCls}-disabled`]: this.disabled,
|
||||
},
|
||||
default: 'bottom'
|
||||
},
|
||||
transfer: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
name: {
|
||||
type: String
|
||||
}
|
||||
];
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
val: _colorChange(this.value),
|
||||
currentValue: this.value,
|
||||
prefixCls: prefixCls,
|
||||
visible: false,
|
||||
disableCloseUnderTransfer: false, // transfer 模式下,点击Drop也会触发关闭
|
||||
recommendedColor: [
|
||||
'#2d8cf0',
|
||||
'#19be6b',
|
||||
'#ff9900',
|
||||
'#ed3f14',
|
||||
'#00b5ff',
|
||||
'#19c919',
|
||||
'#f9e31c',
|
||||
'#ea1a1a',
|
||||
'#9b1dea',
|
||||
'#00c2b1',
|
||||
'#ac7a33',
|
||||
'#1d35ea',
|
||||
'#8bc34a',
|
||||
'#f16b62',
|
||||
'#ea4ca3',
|
||||
'#0d94aa',
|
||||
'#febd79',
|
||||
'#5d4037',
|
||||
'#00bcd4',
|
||||
'#f06292',
|
||||
'#cddc39',
|
||||
'#607d8b',
|
||||
'#000000',
|
||||
'#ffffff'
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
transition () {
|
||||
if (this.placement === 'bottom-start' || this.placement === 'bottom' || this.placement === 'bottom-end') {
|
||||
return 'slide-up';
|
||||
} else {
|
||||
return 'fade';
|
||||
}
|
||||
},
|
||||
saturationColors: {
|
||||
get () {
|
||||
return this.val;
|
||||
inputClasses() {
|
||||
return [
|
||||
`${this.prefixCls}-input`,
|
||||
`${this.inputPrefixCls}`,
|
||||
`${this.inputPrefixCls}-${this.size}`,
|
||||
{
|
||||
[`${this.prefixCls}-focused`]: this.visible,
|
||||
[`${this.prefixCls}-disabled`]: this.disabled,
|
||||
},
|
||||
set (newVal) {
|
||||
this.val = newVal;
|
||||
this.$emit('on-active-change', this.formatColor);
|
||||
}
|
||||
},
|
||||
classes () {
|
||||
return [
|
||||
`${prefixCls}`,
|
||||
{
|
||||
[`${prefixCls}-transfer`]: this.transfer
|
||||
}
|
||||
];
|
||||
},
|
||||
wrapClasses () {
|
||||
return [
|
||||
`${prefixCls}-rel`,
|
||||
`${prefixCls}-${this.size}`,
|
||||
`${inputPrefixCls}-wrapper`,
|
||||
`${inputPrefixCls}-wrapper-${this.size}`
|
||||
];
|
||||
},
|
||||
inputClasses () {
|
||||
return [
|
||||
`${prefixCls}-input`,
|
||||
`${inputPrefixCls}`,
|
||||
`${inputPrefixCls}-${this.size}`,
|
||||
{
|
||||
[`${inputPrefixCls}-disabled`]: this.disabled
|
||||
}
|
||||
];
|
||||
},
|
||||
displayedColor () {
|
||||
let color;
|
||||
if (this.visible) {
|
||||
const rgba = this.saturationColors.rgba;
|
||||
color = {
|
||||
r: rgba.r,
|
||||
g: rgba.g,
|
||||
b: rgba.b,
|
||||
a: rgba.a
|
||||
};
|
||||
} else {
|
||||
color = tinycolor(this.value).toRgb();
|
||||
}
|
||||
return `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`;
|
||||
},
|
||||
formatColor () {
|
||||
const value = this.saturationColors;
|
||||
const format = this.format;
|
||||
let color;
|
||||
|
||||
const rgba = `rgba(${value.rgba.r}, ${value.rgba.g}, ${value.rgba.b}, ${value.rgba.a})`;
|
||||
if (format) {
|
||||
if (format === 'hsl') {
|
||||
color = tinycolor(value.hsl).toHslString();
|
||||
} else if (format === 'hsv') {
|
||||
color = tinycolor(value.hsv).toHsvString();
|
||||
} else if (format === 'hex') {
|
||||
color = value.hex;
|
||||
} else if (format === 'rgb') {
|
||||
color = rgba;
|
||||
}
|
||||
} else if (this.alpha) {
|
||||
color = rgba;
|
||||
} else {
|
||||
color = value.hex;
|
||||
}
|
||||
return color;
|
||||
}
|
||||
];
|
||||
},
|
||||
watch: {
|
||||
value (newVal) {
|
||||
this.val = _colorChange(newVal);
|
||||
},
|
||||
visible (val) {
|
||||
this.val = _colorChange(this.value);
|
||||
if (val) {
|
||||
this.$refs.drop.update();
|
||||
} else {
|
||||
this.$refs.drop.destroy();
|
||||
}
|
||||
}
|
||||
dropClasses() {
|
||||
return [
|
||||
`${this.transferPrefixCls}-no-max-height`,
|
||||
{
|
||||
[`${this.prefixCls}-transfer`]: this.transfer,
|
||||
[`${this.prefixCls}-hide-drop`]: this.hideDropDown,
|
||||
},
|
||||
];
|
||||
},
|
||||
methods: {
|
||||
// 开启 transfer 时,点击 Drop 即会关闭,这里不让其关闭
|
||||
handleTransferClick () {
|
||||
if (this.transfer) this.disableCloseUnderTransfer = true;
|
||||
},
|
||||
handleClose () {
|
||||
if (this.disableCloseUnderTransfer) {
|
||||
this.disableCloseUnderTransfer = false;
|
||||
return false;
|
||||
}
|
||||
this.visible = false;
|
||||
},
|
||||
toggleVisible () {
|
||||
this.visible = !this.visible;
|
||||
},
|
||||
childChange (data) {
|
||||
this.colorChange(data);
|
||||
},
|
||||
colorChange (data, oldHue) {
|
||||
this.oldHue = this.saturationColors.hsl.h;
|
||||
this.saturationColors = _colorChange(data, oldHue || this.oldHue);
|
||||
},
|
||||
isValidHex (hex) {
|
||||
return tinycolor(hex).isValid();
|
||||
},
|
||||
simpleCheckForValidColor (data) {
|
||||
const keysToCheck = ['r', 'g', 'b', 'a', 'h', 's', 'l', 'v'];
|
||||
let checked = 0;
|
||||
let passed = 0;
|
||||
displayedColorStyle() {
|
||||
return {backgroundColor: toRGBAString(this.visible ? this.saturationColors.rgba : tinycolor(this.value).toRgb())};
|
||||
},
|
||||
formatColor() {
|
||||
const {format, saturationColors} = this;
|
||||
|
||||
for (let i = 0; i < keysToCheck.length; i++) {
|
||||
const letter = keysToCheck[i];
|
||||
if (data[letter]) {
|
||||
checked++;
|
||||
if (!isNaN(data[letter])) {
|
||||
passed++;
|
||||
}
|
||||
}
|
||||
if (format) {
|
||||
if (format === 'hsl') {
|
||||
return tinycolor(saturationColors.hsl).toHslString();
|
||||
}
|
||||
|
||||
if (checked === passed) {
|
||||
return data;
|
||||
if (format === 'hsv') {
|
||||
return tinycolor(saturationColors.hsv).toHsvString();
|
||||
}
|
||||
},
|
||||
handleSuccess () {
|
||||
const color = this.formatColor;
|
||||
this.currentValue = color;
|
||||
this.$emit('input', color);
|
||||
this.$emit('on-change', color);
|
||||
this.dispatch('FormItem', 'on-form-change', color);
|
||||
this.handleClose();
|
||||
},
|
||||
handleClear () {
|
||||
this.currentValue = '';
|
||||
this.$emit('input', '');
|
||||
this.$emit('on-change', '');
|
||||
this.dispatch('FormItem', 'on-form-change', '');
|
||||
this.handleClose();
|
||||
},
|
||||
handleSelectColor (color) {
|
||||
this.val = _colorChange(color);
|
||||
|
||||
if (format === 'hex') {
|
||||
return saturationColors.hex;
|
||||
}
|
||||
|
||||
if (format === 'rgb') {
|
||||
return toRGBAString(saturationColors.rgba);
|
||||
}
|
||||
} else if (this.alpha) {
|
||||
return toRGBAString(saturationColors.rgba);
|
||||
}
|
||||
|
||||
return saturationColors.hex;
|
||||
},
|
||||
confirmColorClasses () {
|
||||
return [
|
||||
`${this.prefixCls}-confirm-color`,
|
||||
{
|
||||
[`${this.prefixCls}-confirm-color-editable`]: this.editable
|
||||
}
|
||||
];
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(newVal) {
|
||||
this.val = changeColor(newVal);
|
||||
},
|
||||
visible(val) {
|
||||
this.val = changeColor(this.value);
|
||||
this.$refs.drop[val ? 'update' : 'destroy']();
|
||||
this.$emit('on-open-change', Boolean(val));
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$on('on-escape-keydown', this.closer);
|
||||
this.$on('on-dragging', this.setDragging);
|
||||
},
|
||||
|
||||
methods: {
|
||||
setDragging(value) {
|
||||
this.dragging = value;
|
||||
},
|
||||
handleClose(event) {
|
||||
if (this.visible) {
|
||||
if (this.dragging || event.type === 'mousedown') {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.transfer) {
|
||||
const {$el} = this.$refs.drop;
|
||||
if ($el === event.target || $el.contains(event.target)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.closer(event);
|
||||
return;
|
||||
}
|
||||
|
||||
this.visible = false;
|
||||
},
|
||||
toggleVisible() {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.visible = !this.visible;
|
||||
this.$refs.input.focus();
|
||||
},
|
||||
childChange(data) {
|
||||
this.colorChange(data);
|
||||
},
|
||||
colorChange(data, oldHue) {
|
||||
this.oldHue = this.saturationColors.hsl.h;
|
||||
this.saturationColors = changeColor(data, oldHue || this.oldHue);
|
||||
},
|
||||
closer(event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
this.visible = false;
|
||||
this.$refs.input.focus();
|
||||
},
|
||||
handleButtons(event, value) {
|
||||
this.currentValue = value;
|
||||
this.$emit('input', value);
|
||||
this.$emit('on-change', value);
|
||||
this.dispatch('FormItem', 'on-form-change', value);
|
||||
this.closer(event);
|
||||
},
|
||||
handleSuccess(event) {
|
||||
this.handleButtons(event, this.formatColor);
|
||||
this.$emit('on-pick-success');
|
||||
},
|
||||
handleClear(event) {
|
||||
this.handleButtons(event, '');
|
||||
this.$emit('on-pick-clear');
|
||||
},
|
||||
handleSelectColor(color) {
|
||||
this.val = changeColor(color);
|
||||
this.$emit('on-active-change', this.formatColor);
|
||||
},
|
||||
handleEditColor (event) {
|
||||
const value = event.target.value;
|
||||
this.handleSelectColor(value);
|
||||
},
|
||||
handleFirstTab(event) {
|
||||
if (event.shiftKey) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.$refs.ok.$el.focus();
|
||||
}
|
||||
},
|
||||
handleLastTab(event) {
|
||||
if (!event.shiftKey) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.$refs.saturation.$el.focus();
|
||||
}
|
||||
},
|
||||
onTab(event) {
|
||||
if (this.visible) {
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
onEscape(event) {
|
||||
if (this.visible) {
|
||||
this.closer(event);
|
||||
}
|
||||
},
|
||||
onArrow(event) {
|
||||
if (!this.visible) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.visible = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
7
src/components/color-picker/handleEscapeMixin.js
Normal file
7
src/components/color-picker/handleEscapeMixin.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
export default {
|
||||
methods: {
|
||||
handleEscape(e) {
|
||||
this.dispatch('ColorPicker', 'on-escape-keydown', e);
|
||||
},
|
||||
},
|
||||
};
|
78
src/components/color-picker/hsaMixin.js
Normal file
78
src/components/color-picker/hsaMixin.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
import Emitter from '../../mixins/emitter';
|
||||
import handleEscapeMixin from './handleEscapeMixin';
|
||||
import {getTouches} from './utils';
|
||||
import { on, off } from '../../utils/dom';
|
||||
|
||||
export default {
|
||||
mixins: [Emitter, handleEscapeMixin],
|
||||
|
||||
props: {
|
||||
focused: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
value: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.unbindEventListeners();
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.focused) {
|
||||
setTimeout(() => this.$el.focus(), 1);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleLeft(e) {
|
||||
this.handleSlide(e, this.left, 'left');
|
||||
},
|
||||
handleRight(e) {
|
||||
this.handleSlide(e, this.right, 'right');
|
||||
},
|
||||
handleUp(e) {
|
||||
this.handleSlide(e, this.up, 'up');
|
||||
},
|
||||
handleDown(e) {
|
||||
this.handleSlide(e, this.down, 'down');
|
||||
},
|
||||
handleMouseDown(e) {
|
||||
this.dispatch('ColorPicker', 'on-dragging', true);
|
||||
this.handleChange(e, true);
|
||||
// window.addEventListener('mousemove', this.handleChange, false);
|
||||
// window.addEventListener('mouseup', this.handleMouseUp, false);
|
||||
on(window, 'mousemove', this.handleChange);
|
||||
on(window, 'mouseup', this.handleMouseUp);
|
||||
},
|
||||
handleMouseUp() {
|
||||
this.unbindEventListeners();
|
||||
},
|
||||
unbindEventListeners() {
|
||||
// window.removeEventListener('mousemove', this.handleChange);
|
||||
// window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
off(window, 'mousemove', this.handleChange);
|
||||
off(window, 'mouseup', this.handleMouseUp);
|
||||
// This timeout is required so that the click handler for click-outside
|
||||
// has the chance to run before the mouseup removes the dragging flag.
|
||||
setTimeout(() => this.dispatch('ColorPicker', 'on-dragging', false), 1);
|
||||
},
|
||||
getLeft(e) {
|
||||
const {container} = this.$refs;
|
||||
const xOffset = container.getBoundingClientRect().left + window.pageXOffset;
|
||||
const pageX = e.pageX || getTouches(e, 'PageX');
|
||||
|
||||
return pageX - xOffset;
|
||||
},
|
||||
getTop(e) {
|
||||
const {container} = this.$refs;
|
||||
const yOffset = container.getBoundingClientRect().top + window.pageYOffset;
|
||||
const pageY = e.pageY || getTouches(e, 'PageY');
|
||||
|
||||
return pageY - yOffset;
|
||||
},
|
||||
},
|
||||
};
|
|
@ -1,86 +1,101 @@
|
|||
<template>
|
||||
<div class="ivu-color-picker-hue">
|
||||
<div class="ivu-color-picker-hue-container" ref="container"
|
||||
@mousedown="handleMouseDown"
|
||||
@touchmove="handleChange"
|
||||
@touchstart="handleChange">
|
||||
<div class="ivu-color-picker-hue-pointer" :style="{top: 0, left: pointerLeft}">
|
||||
<div class="ivu-color-picker-hue-picker"></div>
|
||||
<div
|
||||
:class="[prefixCls + '-hue']"
|
||||
tabindex="0"
|
||||
@click="$el.focus()"
|
||||
@keydown.esc="handleEscape"
|
||||
@keydown.left="handleLeft"
|
||||
@keydown.right="handleRight"
|
||||
@keydown.up="handleUp"
|
||||
@keydown.down="handleDown"
|
||||
>
|
||||
<div
|
||||
ref="container"
|
||||
:class="[prefixCls + '-hue-container']"
|
||||
@mousedown="handleMouseDown"
|
||||
@touchmove="handleChange"
|
||||
@touchstart="handleChange">
|
||||
<div
|
||||
:style="{top: 0, left: `${percent}%`}"
|
||||
:class="[prefixCls + '-hue-pointer']">
|
||||
<div :class="[prefixCls + '-hue-picker']"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Hue',
|
||||
props: {
|
||||
value: Object
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
oldHue: 0,
|
||||
pullDirection: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
colors () {
|
||||
const h = this.value.hsl.h;
|
||||
if (h !== 0 && h - this.oldHue > 0) this.pullDirection = 'right';
|
||||
if (h !== 0 && h - this.oldHue < 0) this.pullDirection = 'left';
|
||||
this.oldHue = h;
|
||||
import HASMixin from './hsaMixin';
|
||||
import Prefixes from './prefixMixin';
|
||||
import {clamp} from './utils';
|
||||
|
||||
return this.value;
|
||||
},
|
||||
pointerLeft () {
|
||||
if (this.colors.hsl.h === 0 && this.pullDirection === 'right') return '100%';
|
||||
return (this.colors.hsl.h * 100) / 360 + '%';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange (e, skip) {
|
||||
!skip && e.preventDefault();
|
||||
export default {
|
||||
name: 'Hue',
|
||||
|
||||
const container = this.$refs.container;
|
||||
const containerWidth = container.clientWidth;
|
||||
mixins: [HASMixin, Prefixes],
|
||||
|
||||
const xOffset = container.getBoundingClientRect().left + window.pageXOffset;
|
||||
const pageX = e.pageX || (e.touches ? e.touches[0].pageX : 0);
|
||||
const left = pageX - xOffset;
|
||||
data() {
|
||||
const normalStep = 1 / 360 * 25;
|
||||
const jumpStep = 20 * normalStep;
|
||||
|
||||
let h;
|
||||
let percent;
|
||||
return {
|
||||
left: -normalStep,
|
||||
right: normalStep,
|
||||
up: jumpStep,
|
||||
down: -jumpStep,
|
||||
powerKey: 'shiftKey',
|
||||
percent: clamp(this.value.hsl.h * 100 / 360, 0, 100),
|
||||
};
|
||||
},
|
||||
|
||||
if (left < 0) {
|
||||
h = 0;
|
||||
} else if (left > containerWidth) {
|
||||
h = 360;
|
||||
} else {
|
||||
percent = left * 100 / containerWidth;
|
||||
h = (360 * percent / 100);
|
||||
}
|
||||
|
||||
if (this.colors.hsl.h !== h) {
|
||||
this.$emit('change', {
|
||||
h: h,
|
||||
s: this.colors.hsl.s,
|
||||
l: this.colors.hsl.l,
|
||||
a: this.colors.hsl.a,
|
||||
source: 'hsl'
|
||||
});
|
||||
}
|
||||
},
|
||||
handleMouseDown (e) {
|
||||
this.handleChange(e, true);
|
||||
window.addEventListener('mousemove', this.handleChange);
|
||||
window.addEventListener('mouseup', this.handleMouseUp);
|
||||
},
|
||||
handleMouseUp () {
|
||||
this.unbindEventListeners();
|
||||
},
|
||||
unbindEventListeners () {
|
||||
window.removeEventListener('mousemove', this.handleChange);
|
||||
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
}
|
||||
watch: {
|
||||
value () {
|
||||
this.percent = clamp(this.value.hsl.h * 100 / 360, 0, 100);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
},
|
||||
|
||||
methods: {
|
||||
change(percent) {
|
||||
this.percent = clamp(percent, 0, 100);
|
||||
|
||||
const {h, s, l, a} = this.value.hsl;
|
||||
const newHue = clamp(percent / 100 * 360, 0, 360);
|
||||
|
||||
if (h !== newHue) {
|
||||
this.$emit('change', {h: newHue, s, l, a, source: 'hsl'});
|
||||
}
|
||||
},
|
||||
handleSlide(e, direction) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (e[this.powerKey]) {
|
||||
this.change(direction < 0 ? 0 : 100);
|
||||
return;
|
||||
}
|
||||
|
||||
this.change(this.percent + direction);
|
||||
},
|
||||
handleChange(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const left = this.getLeft(e);
|
||||
|
||||
if (left < 0) {
|
||||
this.change(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const {clientWidth} = this.$refs.container;
|
||||
|
||||
if (left > clientWidth) {
|
||||
this.change(100);
|
||||
return;
|
||||
}
|
||||
|
||||
this.change(left * 100 / clientWidth);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
import ColorPicker from './color-picker.vue';
|
||||
export default ColorPicker;
|
||||
|
||||
export default ColorPicker;
|
||||
|
|
10
src/components/color-picker/prefixMixin.js
Normal file
10
src/components/color-picker/prefixMixin.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
export default {
|
||||
data() {
|
||||
return {
|
||||
prefixCls: 'ivu-color-picker',
|
||||
inputPrefixCls: 'ivu-input',
|
||||
iconPrefixCls: 'ivu-icon',
|
||||
transferPrefixCls: 'ivu-transfer',
|
||||
};
|
||||
},
|
||||
};
|
|
@ -1,20 +1,153 @@
|
|||
<template>
|
||||
<div>
|
||||
<div
|
||||
ref="reference"
|
||||
tabindex="0"
|
||||
@click="handleClick"
|
||||
@keydown.esc="handleEscape"
|
||||
@keydown.enter="handleEnter"
|
||||
@keydown.left="handleArrow($event, 'x', left)"
|
||||
@keydown.right="handleArrow($event, 'x', right)"
|
||||
@keydown.up="handleArrow($event, 'y', up)"
|
||||
@keydown.down="handleArrow($event, 'y', down)"
|
||||
@blur="blurColor"
|
||||
@focus="focusColor"
|
||||
>
|
||||
<template v-for="(item, index) in list">
|
||||
<span @click="handleClick(index)"><em :style="{'background': item}"></em></span>
|
||||
<br v-if="(index + 1) % 12 === 0 && index !== 0 && (index + 1) !== list.length">
|
||||
<div
|
||||
:key="item + ':' + index"
|
||||
:class="[prefixCls + '-picker-colors-wrapper']">
|
||||
<div :data-color-id="index">
|
||||
<div
|
||||
:style="{background: item}"
|
||||
:class="[prefixCls + '-picker-colors-wrapper-color']"
|
||||
></div>
|
||||
<div
|
||||
:ref="'color-circle-' + index"
|
||||
:class="[prefixCls + '-picker-colors-wrapper-circle', hideClass]"></div>
|
||||
</div>
|
||||
</div>
|
||||
<br v-if="lineBreak(list, index)">
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
list: Array
|
||||
import Emitter from '../../mixins/emitter';
|
||||
import HandleEscapeMixin from './handleEscapeMixin';
|
||||
import Prefixes from './prefixMixin';
|
||||
import {clamp} from './utils';
|
||||
|
||||
export default {
|
||||
name: 'RecommendedColors',
|
||||
|
||||
mixins: [Emitter, HandleEscapeMixin, Prefixes],
|
||||
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default: undefined,
|
||||
},
|
||||
methods: {
|
||||
handleClick (index) {
|
||||
this.$emit('picker-color', this.list[index]);
|
||||
},
|
||||
|
||||
data() {
|
||||
const columns = 12;
|
||||
const rows = Math.ceil(this.list.length / columns);
|
||||
const normalStep = 1;
|
||||
|
||||
return {
|
||||
left: -normalStep,
|
||||
right: normalStep,
|
||||
up: -normalStep,
|
||||
down: normalStep,
|
||||
powerKey: 'shiftKey',
|
||||
grid: {x: 1, y: 1},
|
||||
rows,
|
||||
columns,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
hideClass() {
|
||||
return `${this.prefixCls}-hide`;
|
||||
},
|
||||
linearIndex() {
|
||||
return this.getLinearIndex(this.grid);
|
||||
},
|
||||
currentCircle() {
|
||||
return this.$refs[`color-circle-${this.linearIndex}`][0];
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
getLinearIndex(grid) {
|
||||
return this.columns * (grid.y - 1) + grid.x - 1;
|
||||
},
|
||||
getMaxLimit(axis) {
|
||||
return axis === 'x' ? this.columns : this.rows;
|
||||
},
|
||||
handleArrow(e, axis, direction) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.blurColor();
|
||||
|
||||
const grid = {...this.grid};
|
||||
|
||||
if (e[this.powerKey]) {
|
||||
if (direction < 0) {
|
||||
grid[axis] = 1;
|
||||
} else {
|
||||
grid[axis] = this.getMaxLimit(axis);
|
||||
}
|
||||
} else {
|
||||
grid[axis] += direction;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
const index = this.getLinearIndex(grid);
|
||||
|
||||
if (index >= 0 && index < this.list.length) {
|
||||
this.grid[axis] = clamp(grid[axis], 1, this.getMaxLimit(axis));
|
||||
}
|
||||
|
||||
this.focusColor();
|
||||
},
|
||||
blurColor() {
|
||||
this.currentCircle.classList.add(this.hideClass);
|
||||
},
|
||||
focusColor() {
|
||||
this.currentCircle.classList.remove(this.hideClass);
|
||||
},
|
||||
handleEnter(e) {
|
||||
this.handleClick(e, this.currentCircle);
|
||||
},
|
||||
handleClick(e, circle) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.$refs.reference.focus();
|
||||
|
||||
const target = circle || e.target;
|
||||
const colorId = target.dataset.colorId || target.parentElement.dataset.colorId;
|
||||
|
||||
if (colorId) {
|
||||
this.blurColor();
|
||||
const id = Number(colorId) + 1;
|
||||
this.grid.x = id % this.columns || this.columns;
|
||||
this.grid.y = Math.ceil(id / this.columns);
|
||||
this.focusColor();
|
||||
this.$emit('picker-color', this.list[colorId]);
|
||||
this.$emit('change', {hex: this.list[colorId], source: 'hex'});
|
||||
}
|
||||
},
|
||||
lineBreak(list, index) {
|
||||
if (!index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const nextIndex = index + 1;
|
||||
|
||||
return nextIndex < list.length && nextIndex % this.columns === 0;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,98 +1,101 @@
|
|||
<template>
|
||||
<div class="ivu-color-picker-saturation-wrapper">
|
||||
<div
|
||||
:class="[prefixCls + '-saturation-wrapper']"
|
||||
tabindex="0"
|
||||
@keydown.esc="handleEscape"
|
||||
@click="$el.focus()"
|
||||
@keydown.left="handleLeft"
|
||||
@keydown.right="handleRight"
|
||||
@keydown.up="handleUp"
|
||||
@keydown.down="handleDown"
|
||||
>
|
||||
<div
|
||||
class="ivu-color-picker-saturation"
|
||||
:style="{background: bgColor}"
|
||||
ref="container"
|
||||
:style="bgColorStyle"
|
||||
:class="[prefixCls + '-saturation']"
|
||||
@mousedown="handleMouseDown">
|
||||
<div class="ivu-color-picker-saturation--white"></div>
|
||||
<div class="ivu-color-picker-saturation--black"></div>
|
||||
<div class="ivu-color-picker-saturation-pointer" :style="{top: pointerTop, left: pointerLeft}">
|
||||
<div class="ivu-color-picker-saturation-circle"></div>
|
||||
<div :class="[prefixCls + '-saturation--white']"></div>
|
||||
<div :class="[prefixCls + '-saturation--black']"></div>
|
||||
<div
|
||||
:style="pointerStyle"
|
||||
:class="[prefixCls + '-saturation-pointer']">
|
||||
<div :class="[prefixCls + '-saturation-circle']"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import throttle from 'lodash.throttle';
|
||||
|
||||
export default {
|
||||
name: 'Saturation',
|
||||
props: {
|
||||
value: Object
|
||||
<script>
|
||||
import HSAMixin from './hsaMixin';
|
||||
import Prefixes from './prefixMixin';
|
||||
import {clamp, getIncrement} from './utils';
|
||||
import { on, off } from '../../utils/dom';
|
||||
|
||||
export default {
|
||||
name: 'Saturation',
|
||||
|
||||
mixins: [HSAMixin, Prefixes],
|
||||
|
||||
data() {
|
||||
const normalStep = 0.01;
|
||||
|
||||
return {
|
||||
left: -normalStep,
|
||||
right: normalStep,
|
||||
up: normalStep,
|
||||
down: -normalStep,
|
||||
multiplier: 10,
|
||||
powerKey: 'shiftKey',
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
bgColorStyle() {
|
||||
return {background: `hsl(${this.value.hsv.h}, 100%, 50%)`};
|
||||
},
|
||||
data () {
|
||||
return {};
|
||||
pointerStyle() {
|
||||
return {top: `${-(this.value.hsv.v * 100) + 1 + 100}%`, left: `${this.value.hsv.s * 100}%`};
|
||||
},
|
||||
computed: {
|
||||
colors () {
|
||||
return this.value;
|
||||
},
|
||||
bgColor () {
|
||||
return `hsl(${this.colors.hsv.h}, 100%, 50%)`;
|
||||
},
|
||||
pointerTop () {
|
||||
return (-(this.colors.hsv.v * 100) + 1) + 100 + '%';
|
||||
},
|
||||
pointerLeft () {
|
||||
return this.colors.hsv.s * 100 + '%';
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
change(h, s, v, a) {
|
||||
this.$emit('change', {h, s, v, a, source: 'hsva'});
|
||||
},
|
||||
methods: {
|
||||
throttle: throttle((fn, data) => {fn(data);}, 20,
|
||||
{
|
||||
'leading': true,
|
||||
'trailing': false
|
||||
}),
|
||||
handleChange (e, skip) {
|
||||
!skip && e.preventDefault();
|
||||
const container = this.$refs.container;
|
||||
const containerWidth = container.clientWidth;
|
||||
const containerHeight = container.clientHeight;
|
||||
const xOffset = container.getBoundingClientRect().left + window.pageXOffset;
|
||||
const yOffset = container.getBoundingClientRect().top + window.pageYOffset;
|
||||
const pageX = e.pageX || (e.touches ? e.touches[0].pageX : 0);
|
||||
const pageY = e.pageY || (e.touches ? e.touches[0].pageY : 0);
|
||||
let left = pageX - xOffset;
|
||||
let top = pageY - yOffset;
|
||||
if (left < 0) {
|
||||
left = 0;
|
||||
} else if (left > containerWidth) {
|
||||
left = containerWidth;
|
||||
} else if (top < 0) {
|
||||
top = 0;
|
||||
} else if (top > containerHeight) {
|
||||
top = containerHeight;
|
||||
}
|
||||
const saturation = left / containerWidth;
|
||||
let bright = -(top / containerHeight) + 1;
|
||||
bright = bright > 0 ? bright : 0;
|
||||
bright = bright > 1 ? 1 : bright;
|
||||
this.throttle(this.onChange, {
|
||||
h: this.colors.hsv.h,
|
||||
s: saturation,
|
||||
v: bright,
|
||||
a: this.colors.hsv.a,
|
||||
source: 'hsva'
|
||||
});
|
||||
},
|
||||
onChange (param) {
|
||||
this.$emit('change', param);
|
||||
},
|
||||
handleMouseDown () {
|
||||
// this.handleChange(e, true)
|
||||
window.addEventListener('mousemove', this.handleChange);
|
||||
window.addEventListener('mouseup', this.handleChange);
|
||||
window.addEventListener('mouseup', this.handleMouseUp);
|
||||
},
|
||||
handleMouseUp () {
|
||||
this.unbindEventListeners();
|
||||
},
|
||||
unbindEventListeners () {
|
||||
window.removeEventListener('mousemove', this.handleChange);
|
||||
window.removeEventListener('mouseup', this.handleChange);
|
||||
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
handleSlide(e, direction, key) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const isPowerKey = e[this.powerKey];
|
||||
const increment = isPowerKey ? direction * this.multiplier : direction;
|
||||
const {h, s, v, a} = this.value.hsv;
|
||||
const saturation = clamp(s + getIncrement(key, ['left', 'right'], increment), 0, 1);
|
||||
const bright = clamp(v + getIncrement(key, ['up', 'down'], increment), 0, 1);
|
||||
|
||||
this.change(h, saturation, bright, a);
|
||||
},
|
||||
handleChange(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const {clientWidth, clientHeight} = this.$refs.container;
|
||||
const left = clamp(this.getLeft(e), 0, clientWidth);
|
||||
const top = clamp(this.getTop(e), 0, clientHeight);
|
||||
const saturation = left / clientWidth;
|
||||
const bright = clamp(1 - top / clientHeight, 0, 1);
|
||||
|
||||
this.change(this.value.hsv.h, saturation, bright, this.value.hsv.a);
|
||||
},
|
||||
handleMouseDown(e) {
|
||||
HSAMixin.methods.handleMouseDown.call(this, e);
|
||||
// window.addEventListener('mouseup', this.handleChange, false);
|
||||
on(window, 'mouseup', this.handleChange);
|
||||
},
|
||||
unbindEventListeners(e) {
|
||||
HSAMixin.methods.unbindEventListeners.call(this, e);
|
||||
// window.removeEventListener('mouseup', this.handleChange);
|
||||
off(window, 'mouseup', this.handleChange);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
118
src/components/color-picker/utils.js
Normal file
118
src/components/color-picker/utils.js
Normal file
|
@ -0,0 +1,118 @@
|
|||
import tinycolor from 'tinycolor2';
|
||||
import {oneOf} from '../../utils/assist';
|
||||
|
||||
function setAlpha(data, alpha) {
|
||||
const color = tinycolor(data);
|
||||
const {_a} = color;
|
||||
|
||||
if (_a === undefined || _a === null) {
|
||||
color.setAlpha(alpha || 1);
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
function getColor(data, colorData) {
|
||||
const alpha = colorData && colorData.a;
|
||||
|
||||
if (colorData) {
|
||||
// hsl is better than hex between conversions
|
||||
if (colorData.hsl) {
|
||||
return setAlpha(colorData.hsl, alpha);
|
||||
}
|
||||
|
||||
if (colorData.hex && colorData.hex.length > 0) {
|
||||
return setAlpha(colorData.hex, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
return setAlpha(colorData, alpha);
|
||||
}
|
||||
|
||||
export function changeColor(data, oldHue) {
|
||||
const colorData = data === '' ? '#2d8cf0' : data;
|
||||
const color = getColor(data, colorData);
|
||||
const hsl = color.toHsl();
|
||||
const hsv = color.toHsv();
|
||||
|
||||
if (hsl.s === 0) {
|
||||
hsl.h = colorData.h || (colorData.hsl && colorData.hsl.h) || oldHue || 0;
|
||||
hsv.h = hsl.h;
|
||||
}
|
||||
|
||||
// when the hsv.v is less than 0.0164 (base on test)
|
||||
// because of possible loss of precision
|
||||
// the result of hue and saturation would be miscalculated
|
||||
if (hsv.v < 0.0164) {
|
||||
hsv.h = colorData.h || (colorData.hsv && colorData.hsv.h) || 0;
|
||||
hsv.s = colorData.s || (colorData.hsv && colorData.hsv.s) || 0;
|
||||
}
|
||||
|
||||
if (hsl.l < 0.01) {
|
||||
hsl.h = colorData.h || (colorData.hsl && colorData.hsl.h) || 0;
|
||||
hsl.s = colorData.s || (colorData.hsl && colorData.hsl.s) || 0;
|
||||
}
|
||||
|
||||
return {
|
||||
hsl,
|
||||
hex: color.toHexString().toUpperCase(),
|
||||
rgba: color.toRgb(),
|
||||
hsv,
|
||||
oldHue: colorData.h || oldHue || hsl.h,
|
||||
source: colorData.source,
|
||||
a: colorData.a || color.getAlpha(),
|
||||
};
|
||||
}
|
||||
|
||||
export function clamp(value, min, max) {
|
||||
if (value < min) {
|
||||
return min;
|
||||
}
|
||||
|
||||
if (value > max) {
|
||||
return max;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export function getIncrement(key, keys, increment) {
|
||||
return oneOf(key, keys) ? increment : 0;
|
||||
}
|
||||
|
||||
export function getTouches(e, prop) {
|
||||
return e.touches ? e.touches[0][prop] : 0;
|
||||
}
|
||||
|
||||
export function toRGBAString(rgba) {
|
||||
const {r, g, b, a} = rgba;
|
||||
|
||||
return `rgba(${[r, g, b, a].join(',')})`;
|
||||
}
|
||||
|
||||
export function isValidHex(hex) {
|
||||
return tinycolor(hex).isValid();
|
||||
}
|
||||
|
||||
function checkIteratee(data, counts, letter) {
|
||||
let {checked, passed} = counts;
|
||||
const value = data[letter];
|
||||
|
||||
if (value) {
|
||||
checked += 1;
|
||||
|
||||
if (Number.isFinite(value)) {
|
||||
passed += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return {checked, passed};
|
||||
}
|
||||
|
||||
const keysToCheck = ['r', 'g', 'b', 'a', 'h', 's', 'l', 'v'];
|
||||
|
||||
export function simpleCheckForValidColor(data) {
|
||||
const results = keysToCheck.reduce(checkIteratee.bind(null, data), {checked: 0, passed: 0});
|
||||
|
||||
return results.checked === results.passed ? data : undefined;
|
||||
}
|
|
@ -1,37 +1,47 @@
|
|||
<template>
|
||||
<div :class="[prefixCls + '-confirm']">
|
||||
<span :class="timeClasses" v-if="showTime" @click="handleToggleTime">
|
||||
<template v-if="isTime">{{ t('i.datepicker.selectDate') }}</template>
|
||||
<template v-else>{{ t('i.datepicker.selectTime') }}</template>
|
||||
</span>
|
||||
<i-button size="small" type="text" @click.native="handleClear">{{ t('i.datepicker.clear') }}</i-button>
|
||||
<i-button size="small" type="primary" @click.native="handleSuccess">{{ t('i.datepicker.ok') }}</i-button>
|
||||
<div :class="[prefixCls + '-confirm']" @keydown.tab.capture="handleTab">
|
||||
<i-button :class="timeClasses" size="small" type="text" :disabled="timeDisabled" v-if="showTime" @click="handleToggleTime">
|
||||
{{labels.time}}
|
||||
</i-button>
|
||||
<i-button size="small" @click.native="handleClear" @keydown.enter.native="handleClear">
|
||||
{{labels.clear}}
|
||||
</i-button>
|
||||
<i-button size="small" type="primary" @click.native="handleSuccess" @keydown.enter.native="handleSuccess">
|
||||
{{labels.ok}}
|
||||
</i-button>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import iButton from '../../button/button.vue';
|
||||
import Locale from '../../../mixins/locale';
|
||||
import Emitter from '../../../mixins/emitter';
|
||||
|
||||
const prefixCls = 'ivu-picker';
|
||||
|
||||
export default {
|
||||
mixins: [ Locale ],
|
||||
components: { iButton },
|
||||
mixins: [Locale, Emitter],
|
||||
components: {iButton},
|
||||
props: {
|
||||
showTime: false,
|
||||
isTime: false,
|
||||
timeDisabled: false
|
||||
},
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
prefixCls: prefixCls
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
timeClasses () {
|
||||
return {
|
||||
[`${prefixCls}-confirm-time-disabled`]: this.timeDisabled
|
||||
};
|
||||
return `${prefixCls}-confirm-time`;
|
||||
},
|
||||
labels(){
|
||||
const labels = ['time', 'clear', 'ok'];
|
||||
const values = [(this.isTime ? 'selectDate' : 'selectTime'), 'clear', 'ok'];
|
||||
return labels.reduce((obj, key, i) => {
|
||||
obj[key] = this.t('i.datepicker.' + values[i]);
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -44,6 +54,17 @@
|
|||
handleToggleTime () {
|
||||
if (this.timeDisabled) return;
|
||||
this.$emit('on-pick-toggle-time');
|
||||
this.dispatch('CalendarPicker', 'focus-input');
|
||||
},
|
||||
handleTab(e) {
|
||||
const tabbables = [...this.$el.children];
|
||||
const expectedFocus = tabbables[e.shiftKey ? 'shift' : 'pop']();
|
||||
|
||||
if (document.activeElement === expectedFocus) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.dispatch('CalendarPicker', 'focus-input');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,237 +1,97 @@
|
|||
<template>
|
||||
<div
|
||||
:class="classes"
|
||||
@mousemove="handleMouseMove">
|
||||
<div :class="classes">
|
||||
<div :class="[prefixCls + '-header']">
|
||||
<span v-for="day in headerDays" :key="day">
|
||||
{{day}}
|
||||
</span>
|
||||
</div>
|
||||
<span :class="getCellCls(cell)" v-for="(cell, index) in readCells"><em :index="index" @click="handleClick(cell)">{{ cell.text }}</em></span>
|
||||
<span
|
||||
:class="getCellCls(cell)"
|
||||
v-for="(cell, i) in cells"
|
||||
:key="String(cell.date) + i"
|
||||
@click="handleClick(cell, $event)"
|
||||
@mouseenter="handleMouseMove(cell)"
|
||||
>
|
||||
<em>{{ cell.desc }}</em>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getFirstDayOfMonth, getDayCountOfMonth } from '../util';
|
||||
import { deepCopy } from '../../../utils/assist';
|
||||
import { clearHours, isInRange } from '../util';
|
||||
import Locale from '../../../mixins/locale';
|
||||
import jsCalendar from 'js-calendar';
|
||||
|
||||
const prefixCls = 'ivu-date-picker-cells';
|
||||
import mixin from './mixin';
|
||||
import prefixCls from './prefixCls';
|
||||
|
||||
const clearHours = function (time) {
|
||||
const cloneDate = new Date(time);
|
||||
cloneDate.setHours(0, 0, 0, 0);
|
||||
return cloneDate.getTime();
|
||||
};
|
||||
|
||||
export default {
|
||||
mixins: [ Locale ],
|
||||
mixins: [ Locale, mixin ],
|
||||
|
||||
props: {
|
||||
date: {},
|
||||
year: {},
|
||||
month: {},
|
||||
selectionMode: {
|
||||
default: 'day'
|
||||
/* more props in mixin */
|
||||
showWeekNumbers: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabledDate: {},
|
||||
minDate: {},
|
||||
maxDate: {},
|
||||
rangeState: {
|
||||
default () {
|
||||
return {
|
||||
endDate: null,
|
||||
selecting: false
|
||||
};
|
||||
}
|
||||
},
|
||||
value: ''
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
prefixCls: prefixCls,
|
||||
readCells: []
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
'rangeState.endDate' (newVal) {
|
||||
this.markRange(newVal);
|
||||
},
|
||||
minDate(newVal, oldVal) {
|
||||
if (newVal && !oldVal) {
|
||||
this.rangeState.selecting = true;
|
||||
this.markRange(newVal);
|
||||
} else if (!newVal) {
|
||||
this.rangeState.selecting = false;
|
||||
this.markRange(newVal);
|
||||
} else {
|
||||
this.markRange();
|
||||
}
|
||||
},
|
||||
maxDate(newVal, oldVal) {
|
||||
if (newVal && !oldVal) {
|
||||
this.rangeState.selecting = false;
|
||||
this.markRange(newVal);
|
||||
// this.$emit('on-pick', {
|
||||
// minDate: this.minDate,
|
||||
// maxDate: this.maxDate
|
||||
// });
|
||||
}
|
||||
},
|
||||
cells: {
|
||||
handler (cells) {
|
||||
this.readCells = cells;
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
classes () {
|
||||
return [
|
||||
`${prefixCls}`
|
||||
`${prefixCls}`,
|
||||
{
|
||||
[`${prefixCls}-show-week-numbers`]: this.showWeekNumbers
|
||||
}
|
||||
];
|
||||
},
|
||||
calendar(){
|
||||
const weekStartDay = Number(this.t('i.datepicker.weekStartDay'));
|
||||
return new jsCalendar.Generator({onlyDays: !this.showWeekNumbers, weekStart: weekStartDay});
|
||||
},
|
||||
headerDays () {
|
||||
const weekStartDay = Number(this.t('i.datepicker.weekStartDay'));
|
||||
const translatedDays = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'].map(item => {
|
||||
return this.t('i.datepicker.weeks.' + item);
|
||||
});
|
||||
const weekDays = translatedDays.splice(weekStartDay, 7 - weekStartDay).concat(translatedDays.splice(0, weekStartDay));
|
||||
return weekDays;
|
||||
return this.showWeekNumbers ? [''].concat(weekDays) : weekDays;
|
||||
},
|
||||
cells () {
|
||||
const date = new Date(this.year, this.month, 1);
|
||||
const weekStartDay = Number(this.t('i.datepicker.weekStartDay'));
|
||||
const day = (getFirstDayOfMonth(date) || 7) - weekStartDay; // day of first day
|
||||
const tableYear = this.tableDate.getFullYear();
|
||||
const tableMonth = this.tableDate.getMonth();
|
||||
const today = clearHours(new Date()); // timestamp of today
|
||||
const selectDay = clearHours(new Date(this.value)); // timestamp of selected day
|
||||
const minDay = clearHours(new Date(this.minDate));
|
||||
const maxDay = clearHours(new Date(this.maxDate));
|
||||
const selectedDays = this.dates.filter(Boolean).map(clearHours); // timestamp of selected days
|
||||
const [minDay, maxDay] = this.dates.map(clearHours);
|
||||
const rangeStart = this.rangeState.from && clearHours(this.rangeState.from);
|
||||
const rangeEnd = this.rangeState.to && clearHours(this.rangeState.to);
|
||||
|
||||
const dateCountOfMonth = getDayCountOfMonth(date.getFullYear(), date.getMonth());
|
||||
const dateCountOfLastMonth = getDayCountOfMonth(date.getFullYear(), (date.getMonth() === 0 ? 11 : date.getMonth() - 1));
|
||||
const isRange = this.selectionMode === 'range';
|
||||
const disabledTestFn = typeof this.disabledDate === 'function' && this.disabledDate;
|
||||
|
||||
const disabledDate = this.disabledDate;
|
||||
return this.calendar(tableYear, tableMonth, (cell) => {
|
||||
// normalize date offset from the dates provided by jsCalendar
|
||||
if (cell.date instanceof Date) cell.date.setTime(cell.date.getTime() + cell.date.getTimezoneOffset() * 60000);
|
||||
|
||||
let cells = [];
|
||||
const cell_tmpl = {
|
||||
text: '',
|
||||
type: '',
|
||||
date: null,
|
||||
selected: false,
|
||||
disabled: false,
|
||||
range: false,
|
||||
start: false,
|
||||
end: false
|
||||
};
|
||||
if (day !== 7) {
|
||||
for (let i = 0; i < day; i++) {
|
||||
const cell = deepCopy(cell_tmpl);
|
||||
cell.type = 'prev-month';
|
||||
cell.text = dateCountOfLastMonth - (day - 1) + i;
|
||||
cell.date = new Date(this.year, this.month - 1, cell.text);
|
||||
const time = clearHours(cell.date);
|
||||
cell.disabled = typeof disabledDate === 'function' && disabledDate(new Date(time));
|
||||
cells.push(cell);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 1; i <= dateCountOfMonth; i++) {
|
||||
const cell = deepCopy(cell_tmpl);
|
||||
cell.text = i;
|
||||
cell.date = new Date(this.year, this.month, cell.text);
|
||||
const time = clearHours(cell.date);
|
||||
cell.type = time === today ? 'today' : 'normal';
|
||||
cell.selected = time === selectDay;
|
||||
cell.disabled = typeof disabledDate === 'function' && disabledDate(new Date(time));
|
||||
cell.range = time >= minDay && time <= maxDay;
|
||||
cell.start = this.minDate && time === minDay;
|
||||
cell.end = this.maxDate && time === maxDay;
|
||||
|
||||
cells.push(cell);
|
||||
}
|
||||
|
||||
const nextMonthCount = 42 - cells.length;
|
||||
for (let i = 1; i <= nextMonthCount; i++) {
|
||||
const cell = deepCopy(cell_tmpl);
|
||||
cell.type = 'next-month';
|
||||
cell.text = i;
|
||||
cell.date = new Date(this.year, this.month + 1, cell.text);
|
||||
const time = clearHours(cell.date);
|
||||
cell.disabled = typeof disabledDate === 'function' && disabledDate(new Date(time));
|
||||
cells.push(cell);
|
||||
}
|
||||
|
||||
return cells;
|
||||
const time = cell.date && clearHours(cell.date);
|
||||
const dateIsInCurrentMonth = cell.date && tableMonth === cell.date.getMonth();
|
||||
return {
|
||||
...cell,
|
||||
type: time === today ? 'today' : cell.type,
|
||||
selected: dateIsInCurrentMonth && selectedDays.includes(time),
|
||||
disabled: (cell.date && disabledTestFn) && disabledTestFn(new Date(time)),
|
||||
range: dateIsInCurrentMonth && isRange && isInRange(time, rangeStart, rangeEnd),
|
||||
start: dateIsInCurrentMonth && isRange && time === minDay,
|
||||
end: dateIsInCurrentMonth && isRange && time === maxDay
|
||||
};
|
||||
}).cells.slice(this.showWeekNumbers ? 8 : 0);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick (cell) {
|
||||
|
||||
if (cell.disabled) return;
|
||||
const newDate = cell.date;
|
||||
|
||||
if (this.selectionMode === 'range') {
|
||||
if (this.minDate && this.maxDate) {
|
||||
const minDate = new Date(newDate.getTime());
|
||||
const maxDate = null;
|
||||
this.rangeState.selecting = true;
|
||||
this.markRange(this.minDate);
|
||||
|
||||
this.$emit('on-pick', {minDate, maxDate}, false);
|
||||
} else if (this.minDate && !this.maxDate) {
|
||||
if (newDate >= this.minDate) {
|
||||
const maxDate = new Date(newDate.getTime());
|
||||
this.rangeState.selecting = false;
|
||||
|
||||
this.$emit('on-pick', {minDate: this.minDate, maxDate});
|
||||
} else {
|
||||
const minDate = new Date(newDate.getTime());
|
||||
|
||||
this.$emit('on-pick', {minDate, maxDate: this.maxDate}, false);
|
||||
}
|
||||
} else if (!this.minDate) {
|
||||
const minDate = new Date(newDate.getTime());
|
||||
this.rangeState.selecting = true;
|
||||
this.markRange(this.minDate);
|
||||
|
||||
this.$emit('on-pick', {minDate, maxDate: this.maxDate}, false);
|
||||
}
|
||||
} else {
|
||||
this.$emit('on-pick', newDate);
|
||||
}
|
||||
this.$emit('on-pick-click');
|
||||
},
|
||||
handleMouseMove (event) {
|
||||
if (!this.rangeState.selecting) return;
|
||||
|
||||
this.$emit('on-changerange', {
|
||||
minDate: this.minDate,
|
||||
maxDate: this.maxDate,
|
||||
rangeState: this.rangeState
|
||||
});
|
||||
|
||||
const target = event.target;
|
||||
if (target.tagName === 'EM') {
|
||||
const cell = this.cells[parseInt(event.target.getAttribute('index'))];
|
||||
// if (cell.disabled) return; // todo 待确定
|
||||
this.rangeState.endDate = cell.date;
|
||||
}
|
||||
},
|
||||
markRange (maxDate) {
|
||||
const minDate = this.minDate;
|
||||
if (!maxDate) maxDate = this.maxDate;
|
||||
|
||||
const minDay = clearHours(new Date(minDate));
|
||||
const maxDay = clearHours(new Date(maxDate));
|
||||
|
||||
this.cells.forEach(cell => {
|
||||
if (cell.type === 'today' || cell.type === 'normal') {
|
||||
const time = clearHours(new Date(this.year, this.month, cell.text));
|
||||
cell.range = time >= minDay && time <= maxDay;
|
||||
cell.start = minDate && time === minDay;
|
||||
cell.end = maxDate && time === maxDay;
|
||||
}
|
||||
});
|
||||
},
|
||||
getCellCls (cell) {
|
||||
return [
|
||||
`${prefixCls}-cell`,
|
||||
|
@ -239,9 +99,12 @@
|
|||
[`${prefixCls}-cell-selected`]: cell.selected || cell.start || cell.end,
|
||||
[`${prefixCls}-cell-disabled`]: cell.disabled,
|
||||
[`${prefixCls}-cell-today`]: cell.type === 'today',
|
||||
[`${prefixCls}-cell-prev-month`]: cell.type === 'prev-month',
|
||||
[`${prefixCls}-cell-next-month`]: cell.type === 'next-month',
|
||||
[`${prefixCls}-cell-range`]: cell.range && !cell.start && !cell.end
|
||||
[`${prefixCls}-cell-prev-month`]: cell.type === 'prevMonth',
|
||||
[`${prefixCls}-cell-next-month`]: cell.type === 'nextMonth',
|
||||
[`${prefixCls}-cell-week-label`]: cell.type === 'weekLabel',
|
||||
[`${prefixCls}-cell-range`]: cell.range && !cell.start && !cell.end,
|
||||
[`${prefixCls}-focused`]: clearHours(cell.date) === clearHours(this.focusedDate)
|
||||
|
||||
}
|
||||
];
|
||||
},
|
||||
|
|
57
src/components/date-picker/base/mixin.js
Normal file
57
src/components/date-picker/base/mixin.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
|
||||
import {clearHours} from '../util';
|
||||
|
||||
export default {
|
||||
name: 'PanelTable',
|
||||
props: {
|
||||
tableDate: {
|
||||
type: Date,
|
||||
required: true
|
||||
},
|
||||
disabledDate: {
|
||||
type: Function
|
||||
},
|
||||
selectionMode: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
value: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
rangeState: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
from: null,
|
||||
to: null,
|
||||
selecting: false
|
||||
})
|
||||
},
|
||||
focusedDate: {
|
||||
type: Date,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dates(){
|
||||
const {selectionMode, value, rangeState} = this;
|
||||
const rangeSelecting = selectionMode === 'range' && rangeState.selecting;
|
||||
return rangeSelecting ? [rangeState.from] : value;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClick (cell) {
|
||||
if (cell.disabled || cell.type === 'weekLabel') return;
|
||||
const newDate = new Date(clearHours(cell.date));
|
||||
|
||||
this.$emit('on-pick', newDate);
|
||||
this.$emit('on-pick-click');
|
||||
},
|
||||
handleMouseMove (cell) {
|
||||
if (!this.rangeState.selecting) return;
|
||||
if (cell.disabled) return;
|
||||
const newDate = cell.date;
|
||||
this.$emit('on-change-range', newDate);
|
||||
},
|
||||
}
|
||||
};
|
|
@ -1,27 +1,28 @@
|
|||
<template>
|
||||
<div :class="classes" @click="handleClick">
|
||||
<span :class="getCellCls(cell)" v-for="(cell, index) in cells"><em :index="index">{{ tCell(cell.text) }}</em></span>
|
||||
<div :class="classes">
|
||||
<span
|
||||
:class="getCellCls(cell)"
|
||||
v-for="cell in cells"
|
||||
@click="handleClick(cell)"
|
||||
@mouseenter="handleMouseMove(cell)"
|
||||
|
||||
>
|
||||
<em>{{ cell.text }}</em>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { clearHours } from '../util';
|
||||
import { deepCopy } from '../../../utils/assist';
|
||||
import Locale from '../../../mixins/locale';
|
||||
const prefixCls = 'ivu-date-picker-cells';
|
||||
import mixin from './mixin';
|
||||
import prefixCls from './prefixCls';
|
||||
|
||||
export default {
|
||||
mixins: [ Locale ],
|
||||
props: {
|
||||
date: {},
|
||||
month: {
|
||||
type: Number
|
||||
},
|
||||
disabledDate: {},
|
||||
selectionMode: {
|
||||
default: 'month'
|
||||
}
|
||||
},
|
||||
mixins: [ Locale, mixin ],
|
||||
props: {/* in mixin */},
|
||||
computed: {
|
||||
classes () {
|
||||
classes() {
|
||||
return [
|
||||
`${prefixCls}`,
|
||||
`${prefixCls}-month`
|
||||
|
@ -35,15 +36,18 @@
|
|||
disabled: false
|
||||
};
|
||||
|
||||
const tableYear = this.tableDate.getFullYear();
|
||||
const selectedDays = this.dates.filter(Boolean).map(date => clearHours(new Date(date.getFullYear(), date.getMonth(), 1)));
|
||||
const focusedDate = clearHours(new Date(this.focusedDate.getFullYear(), this.focusedDate.getMonth(), 1));
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const cell = deepCopy(cell_tmpl);
|
||||
cell.text = i + 1;
|
||||
|
||||
const date = new Date(this.date);
|
||||
date.setMonth(i);
|
||||
cell.disabled = typeof this.disabledDate === 'function' && this.disabledDate(date) && this.selectionMode === 'month';
|
||||
|
||||
cell.selected = Number(this.month) === i;
|
||||
cell.date = new Date(tableYear, i, 1);
|
||||
cell.text = this.tCell(i + 1);
|
||||
const day = clearHours(cell.date);
|
||||
cell.disabled = typeof this.disabledDate === 'function' && this.disabledDate(cell.date) && this.selectionMode === 'month';
|
||||
cell.selected = selectedDays.includes(day);
|
||||
cell.focused = day === focusedDate;
|
||||
cells.push(cell);
|
||||
}
|
||||
|
||||
|
@ -56,23 +60,14 @@
|
|||
`${prefixCls}-cell`,
|
||||
{
|
||||
[`${prefixCls}-cell-selected`]: cell.selected,
|
||||
[`${prefixCls}-cell-disabled`]: cell.disabled
|
||||
[`${prefixCls}-cell-disabled`]: cell.disabled,
|
||||
[`${prefixCls}-cell-focused`]: cell.focused,
|
||||
[`${prefixCls}-cell-range`]: cell.range && !cell.start && !cell.end
|
||||
}
|
||||
];
|
||||
},
|
||||
handleClick (event) {
|
||||
const target = event.target;
|
||||
if (target.tagName === 'EM') {
|
||||
const index = parseInt(event.target.getAttribute('index'));
|
||||
const cell = this.cells[index];
|
||||
if (cell.disabled) return;
|
||||
|
||||
this.$emit('on-pick', index);
|
||||
}
|
||||
this.$emit('on-pick-click');
|
||||
},
|
||||
tCell (cell) {
|
||||
return this.t(`i.datepicker.months.m${cell}`);
|
||||
tCell (nr) {
|
||||
return this.t(`i.datepicker.months.m${nr}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
2
src/components/date-picker/base/prefixCls.js
Normal file
2
src/components/date-picker/base/prefixCls.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
|
||||
export default 'ivu-date-picker-cells';
|
|
@ -22,21 +22,23 @@
|
|||
import { deepCopy, scrollTop, firstUpperCase } from '../../../utils/assist';
|
||||
|
||||
const prefixCls = 'ivu-time-picker-cells';
|
||||
const timeParts = ['hours', 'minutes', 'seconds'];
|
||||
|
||||
export default {
|
||||
name: 'TimeSpinner',
|
||||
mixins: [Options],
|
||||
props: {
|
||||
hours: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
default: NaN
|
||||
},
|
||||
minutes: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
default: NaN
|
||||
},
|
||||
seconds: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
default: NaN
|
||||
},
|
||||
showSeconds: {
|
||||
type: Boolean,
|
||||
|
@ -51,7 +53,9 @@
|
|||
return {
|
||||
spinerSteps: [1, 1, 1].map((one, i) => Math.abs(this.steps[i]) || one),
|
||||
prefixCls: prefixCls,
|
||||
compiled: false
|
||||
compiled: false,
|
||||
focusedColumn: -1, // which column inside the picker
|
||||
focusedTime: [0, 0, 0] // the values array into [hh, mm, ss]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -66,6 +70,7 @@
|
|||
hoursList () {
|
||||
let hours = [];
|
||||
const step = this.spinerSteps[0];
|
||||
const focusedHour = this.focusedColumn === 0 && this.focusedTime[0];
|
||||
const hour_tmpl = {
|
||||
text: 0,
|
||||
selected: false,
|
||||
|
@ -76,6 +81,7 @@
|
|||
for (let i = 0; i < 24; i += step) {
|
||||
const hour = deepCopy(hour_tmpl);
|
||||
hour.text = i;
|
||||
hour.focused = i === focusedHour;
|
||||
|
||||
if (this.disabledHours.length && this.disabledHours.indexOf(i) > -1) {
|
||||
hour.disabled = true;
|
||||
|
@ -90,6 +96,7 @@
|
|||
minutesList () {
|
||||
let minutes = [];
|
||||
const step = this.spinerSteps[1];
|
||||
const focusedMinute = this.focusedColumn === 1 && this.focusedTime[1];
|
||||
const minute_tmpl = {
|
||||
text: 0,
|
||||
selected: false,
|
||||
|
@ -100,6 +107,7 @@
|
|||
for (let i = 0; i < 60; i += step) {
|
||||
const minute = deepCopy(minute_tmpl);
|
||||
minute.text = i;
|
||||
minute.focused = i === focusedMinute;
|
||||
|
||||
if (this.disabledMinutes.length && this.disabledMinutes.indexOf(i) > -1) {
|
||||
minute.disabled = true;
|
||||
|
@ -113,6 +121,7 @@
|
|||
secondsList () {
|
||||
let seconds = [];
|
||||
const step = this.spinerSteps[2];
|
||||
const focusedMinute = this.focusedColumn === 2 && this.focusedTime[2];
|
||||
const second_tmpl = {
|
||||
text: 0,
|
||||
selected: false,
|
||||
|
@ -123,6 +132,7 @@
|
|||
for (let i = 0; i < 60; i += step) {
|
||||
const second = deepCopy(second_tmpl);
|
||||
second.text = i;
|
||||
second.focused = i === focusedMinute;
|
||||
|
||||
if (this.disabledSeconds.length && this.disabledSeconds.indexOf(i) > -1) {
|
||||
second.disabled = true;
|
||||
|
@ -141,15 +151,32 @@
|
|||
`${prefixCls}-cell`,
|
||||
{
|
||||
[`${prefixCls}-cell-selected`]: cell.selected,
|
||||
[`${prefixCls}-cell-focused`]: cell.focused,
|
||||
[`${prefixCls}-cell-disabled`]: cell.disabled
|
||||
|
||||
}
|
||||
];
|
||||
},
|
||||
chooseValue(values){
|
||||
const changes = timeParts.reduce((obj, part, i) => {
|
||||
const value = values[i];
|
||||
if (this[part] === value) return obj;
|
||||
return {
|
||||
...obj,
|
||||
[part]: value
|
||||
};
|
||||
}, {});
|
||||
if (Object.keys(changes).length > 0) {
|
||||
this.emitChange(changes);
|
||||
}
|
||||
},
|
||||
handleClick (type, cell) {
|
||||
if (cell.disabled) return;
|
||||
const data = {};
|
||||
data[type] = cell.text;
|
||||
this.$emit('on-change', data);
|
||||
const data = {[type]: cell.text};
|
||||
this.emitChange(data);
|
||||
},
|
||||
emitChange(changes){
|
||||
this.$emit('on-change', changes);
|
||||
this.$emit('on-pick-click');
|
||||
},
|
||||
scroll (type, index) {
|
||||
|
@ -168,15 +195,19 @@
|
|||
return index;
|
||||
},
|
||||
updateScroll () {
|
||||
const times = ['hours', 'minutes', 'seconds'];
|
||||
this.$nextTick(() => {
|
||||
times.forEach(type => {
|
||||
timeParts.forEach(type => {
|
||||
this.$refs[type].scrollTop = 24 * this[`${type}List`].findIndex(obj => obj.text == this[type]);
|
||||
});
|
||||
});
|
||||
},
|
||||
formatTime (text) {
|
||||
return text < 10 ? '0' + text : text;
|
||||
},
|
||||
updateFocusedTime(col, time) {
|
||||
this.focusedColumn = col;
|
||||
this.focusedTime = time.slice();
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@ -191,10 +222,16 @@
|
|||
seconds (val) {
|
||||
if (!this.compiled) return;
|
||||
this.scroll('seconds', this.secondsList.findIndex(obj => obj.text == val));
|
||||
},
|
||||
focusedTime(updated, old){
|
||||
timeParts.forEach((part, i) => {
|
||||
if (updated[i] === old[i] || typeof updated[i] === 'undefined') return;
|
||||
const valueIndex = this[`${part}List`].findIndex(obj => obj.text === updated[i]);
|
||||
this.scroll(part, valueIndex);
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.updateScroll();
|
||||
this.$nextTick(() => this.compiled = true);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,30 +1,34 @@
|
|||
<template>
|
||||
<div :class="classes" @click="handleClick">
|
||||
<span :class="getCellCls(cell)" v-for="(cell, index) in cells"><em :index="index">{{ cell.text }}</em></span>
|
||||
<div :class="classes">
|
||||
<span
|
||||
:class="getCellCls(cell)"
|
||||
v-for="cell in cells"
|
||||
@click="handleClick(cell)"
|
||||
@mouseenter="handleMouseMove(cell)"
|
||||
>
|
||||
<em>{{ cell.date.getFullYear() }}</em>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { clearHours } from '../util';
|
||||
import { deepCopy } from '../../../utils/assist';
|
||||
const prefixCls = 'ivu-date-picker-cells';
|
||||
import mixin from './mixin';
|
||||
import prefixCls from './prefixCls';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
date: {},
|
||||
year: {},
|
||||
disabledDate: {},
|
||||
selectionMode: {
|
||||
default: 'year'
|
||||
}
|
||||
},
|
||||
mixins: [ mixin ],
|
||||
|
||||
props: {/* in mixin */},
|
||||
computed: {
|
||||
classes () {
|
||||
classes() {
|
||||
return [
|
||||
`${prefixCls}`,
|
||||
`${prefixCls}-year`
|
||||
];
|
||||
},
|
||||
startYear() {
|
||||
return Math.floor(this.year / 10) * 10;
|
||||
return Math.floor(this.tableDate.getFullYear() / 10) * 10;
|
||||
},
|
||||
cells () {
|
||||
let cells = [];
|
||||
|
@ -34,15 +38,16 @@
|
|||
disabled: false
|
||||
};
|
||||
|
||||
const selectedDays = this.dates.filter(Boolean).map(date => clearHours(new Date(date.getFullYear(), 0, 1)));
|
||||
const focusedDate = clearHours(new Date(this.focusedDate.getFullYear(), 0, 1));
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const cell = deepCopy(cell_tmpl);
|
||||
cell.text = this.startYear + i;
|
||||
|
||||
const date = new Date(this.date);
|
||||
date.setFullYear(cell.text);
|
||||
cell.disabled = typeof this.disabledDate === 'function' && this.disabledDate(date) && this.selectionMode === 'year';
|
||||
|
||||
cell.selected = Number(this.year) === cell.text;
|
||||
cell.date = new Date(this.startYear + i, 0, 1);
|
||||
cell.disabled = typeof this.disabledDate === 'function' && this.disabledDate(cell.date) && this.selectionMode === 'year';
|
||||
const day = clearHours(cell.date);
|
||||
cell.selected = selectedDays.includes(day);
|
||||
cell.focused = day === focusedDate;
|
||||
cells.push(cell);
|
||||
}
|
||||
|
||||
|
@ -55,26 +60,12 @@
|
|||
`${prefixCls}-cell`,
|
||||
{
|
||||
[`${prefixCls}-cell-selected`]: cell.selected,
|
||||
[`${prefixCls}-cell-disabled`]: cell.disabled
|
||||
[`${prefixCls}-cell-disabled`]: cell.disabled,
|
||||
[`${prefixCls}-cell-focused`]: cell.focused,
|
||||
[`${prefixCls}-cell-range`]: cell.range && !cell.start && !cell.end
|
||||
}
|
||||
];
|
||||
},
|
||||
nextTenYear() {
|
||||
this.$emit('on-pick', Number(this.year) + 10, false);
|
||||
},
|
||||
prevTenYear() {
|
||||
this.$emit('on-pick', Number(this.year) - 10, false);
|
||||
},
|
||||
handleClick (event) {
|
||||
const target = event.target;
|
||||
if (target.tagName === 'EM') {
|
||||
const cell = this.cells[parseInt(event.target.getAttribute('index'))];
|
||||
if (cell.disabled) return;
|
||||
|
||||
this.$emit('on-pick', cell.text);
|
||||
}
|
||||
this.$emit('on-pick-click');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
65
src/components/date-picker/panel/Date/date-panel-mixin.js
Normal file
65
src/components/date-picker/panel/Date/date-panel-mixin.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
|
||||
import { oneOf } from '../../../../utils/assist';
|
||||
import {initTimeDate } from '../../util';
|
||||
|
||||
|
||||
export default {
|
||||
props: {
|
||||
showTime: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
format: {
|
||||
type: String,
|
||||
default: 'yyyy-MM-dd'
|
||||
},
|
||||
selectionMode: {
|
||||
type: String,
|
||||
validator (value) {
|
||||
return oneOf(value, ['year', 'month', 'date', 'time']);
|
||||
},
|
||||
default: 'date'
|
||||
},
|
||||
shortcuts: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
disabledDate: {
|
||||
type: Function,
|
||||
default: () => false
|
||||
},
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => [initTimeDate(), initTimeDate()]
|
||||
},
|
||||
timePickerOptions: {
|
||||
default: () => ({}),
|
||||
type: Object,
|
||||
},
|
||||
showWeekNumbers: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
startDate: {
|
||||
type: Date
|
||||
},
|
||||
pickerType: {
|
||||
type: String,
|
||||
require: true
|
||||
},
|
||||
focusedDate: {
|
||||
type: Date,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isTime(){
|
||||
return this.currentView === 'time';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleToggleTime(){
|
||||
this.currentView = this.currentView === 'time' ? 'date' : 'time';
|
||||
},
|
||||
}
|
||||
};
|
378
src/components/date-picker/panel/Date/date-range.vue
Normal file
378
src/components/date-picker/panel/Date/date-range.vue
Normal file
|
@ -0,0 +1,378 @@
|
|||
<template>
|
||||
<div :class="classes" @mousedown.prevent>
|
||||
<div :class="[prefixCls + '-sidebar']" v-if="shortcuts.length">
|
||||
<div
|
||||
:class="[prefixCls + '-shortcut']"
|
||||
v-for="shortcut in shortcuts"
|
||||
@click="handleShortcutClick(shortcut)">{{ shortcut.text }}</div>
|
||||
</div>
|
||||
<div :class="panelBodyClasses">
|
||||
<div :class="[prefixCls + '-content', prefixCls + '-content-left']" v-show="!isTime">
|
||||
<div :class="[datePrefixCls + '-header']" v-show="currentView !== 'time'">
|
||||
<span
|
||||
:class="iconBtnCls('prev', '-double')"
|
||||
@click="prevYear('left')"><Icon type="ios-arrow-back"></Icon></span>
|
||||
<span
|
||||
v-if="leftPickerTable === 'date-table'"
|
||||
:class="iconBtnCls('prev')"
|
||||
@click="prevMonth('left')"
|
||||
v-show="currentView === 'date'"><Icon type="ios-arrow-back"></Icon></span>
|
||||
<date-panel-label
|
||||
:date-panel-label="leftDatePanelLabel"
|
||||
:current-view="leftDatePanelView"
|
||||
:date-prefix-cls="datePrefixCls"></date-panel-label>
|
||||
<span
|
||||
v-if="splitPanels || leftPickerTable !== 'date-table'"
|
||||
:class="iconBtnCls('next', '-double')"
|
||||
@click="nextYear('left')"><Icon type="ios-arrow-forward"></Icon></span>
|
||||
<span
|
||||
v-if="splitPanels && leftPickerTable === 'date-table'"
|
||||
:class="iconBtnCls('next')"
|
||||
@click="nextMonth('left')"
|
||||
v-show="currentView === 'date'"><Icon type="ios-arrow-forward"></Icon></span>
|
||||
</div>
|
||||
<component
|
||||
:is="leftPickerTable"
|
||||
ref="leftYearTable"
|
||||
v-if="currentView !== 'time'"
|
||||
:table-date="leftPanelDate"
|
||||
selection-mode="range"
|
||||
:disabled-date="disabledDate"
|
||||
:range-state="rangeState"
|
||||
:show-week-numbers="showWeekNumbers"
|
||||
:value="preSelecting.left ? [dates[0]] : dates"
|
||||
:focused-date="focusedDate"
|
||||
|
||||
@on-change-range="handleChangeRange"
|
||||
@on-pick="panelPickerHandlers.left"
|
||||
@on-pick-click="handlePickClick"
|
||||
></component>
|
||||
</div>
|
||||
<div :class="[prefixCls + '-content', prefixCls + '-content-right']" v-show="!isTime">
|
||||
<div :class="[datePrefixCls + '-header']" v-show="currentView !== 'time'">
|
||||
<span
|
||||
v-if="splitPanels || rightPickerTable !== 'date-table'"
|
||||
:class="iconBtnCls('prev', '-double')"
|
||||
@click="prevYear('right')"><Icon type="ios-arrow-back"></Icon></span>
|
||||
<span
|
||||
v-if="splitPanels && rightPickerTable === 'date-table'"
|
||||
:class="iconBtnCls('prev')"
|
||||
@click="prevMonth('right')"
|
||||
v-show="currentView === 'date'"><Icon type="ios-arrow-back"></Icon></span>
|
||||
<date-panel-label
|
||||
:date-panel-label="rightDatePanelLabel"
|
||||
:current-view="rightDatePanelView"
|
||||
:date-prefix-cls="datePrefixCls"></date-panel-label>
|
||||
<span
|
||||
:class="iconBtnCls('next', '-double')"
|
||||
@click="nextYear('right')"><Icon type="ios-arrow-forward"></Icon></span>
|
||||
<span
|
||||
v-if="rightPickerTable === 'date-table'"
|
||||
:class="iconBtnCls('next')"
|
||||
@click="nextMonth('right')"
|
||||
v-show="currentView === 'date'"><Icon type="ios-arrow-forward"></Icon></span>
|
||||
</div>
|
||||
<component
|
||||
:is="rightPickerTable"
|
||||
ref="rightYearTable"
|
||||
v-if="currentView !== 'time'"
|
||||
:table-date="rightPanelDate"
|
||||
selection-mode="range"
|
||||
:range-state="rangeState"
|
||||
:disabled-date="disabledDate"
|
||||
:show-week-numbers="showWeekNumbers"
|
||||
:value="preSelecting.right ? [dates[dates.length - 1]] : dates"
|
||||
:focused-date="focusedDate"
|
||||
|
||||
@on-change-range="handleChangeRange"
|
||||
@on-pick="panelPickerHandlers.right"
|
||||
@on-pick-click="handlePickClick"></component>
|
||||
</div>
|
||||
<div :class="[prefixCls + '-content']" v-show="isTime">
|
||||
<time-picker
|
||||
ref="timePicker"
|
||||
v-if="currentView === 'time'"
|
||||
:value="dates"
|
||||
:format="format"
|
||||
:time-disabled="timeDisabled"
|
||||
v-bind="timePickerOptions"
|
||||
@on-pick="handleRangePick"
|
||||
@on-pick-click="handlePickClick"
|
||||
@on-pick-clear="handlePickClear"
|
||||
@on-pick-success="handlePickSuccess"
|
||||
@on-pick-toggle-time="handleToggleTime"
|
||||
></time-picker>
|
||||
</div>
|
||||
<Confirm
|
||||
v-if="confirm"
|
||||
:show-time="showTime"
|
||||
:is-time="isTime"
|
||||
:time-disabled="timeDisabled"
|
||||
@on-pick-toggle-time="handleToggleTime"
|
||||
@on-pick-clear="handlePickClear"
|
||||
@on-pick-success="handlePickSuccess"></Confirm>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Icon from '../../../icon/icon.vue';
|
||||
import DateTable from '../../base/date-table.vue';
|
||||
import YearTable from '../../base/year-table.vue';
|
||||
import MonthTable from '../../base/month-table.vue';
|
||||
import TimePicker from '../Time/time-range.vue';
|
||||
import Confirm from '../../base/confirm.vue';
|
||||
|
||||
import { toDate, initTimeDate, formatDateLabels } from '../../util';
|
||||
import datePanelLabel from './date-panel-label.vue';
|
||||
|
||||
import Mixin from '../panel-mixin';
|
||||
import DateMixin from './date-panel-mixin';
|
||||
import Locale from '../../../../mixins/locale';
|
||||
|
||||
const prefixCls = 'ivu-picker-panel';
|
||||
const datePrefixCls = 'ivu-date-picker';
|
||||
|
||||
const dateSorter = (a, b) => {
|
||||
if (!a || !b) return 0;
|
||||
return a.getTime() - b.getTime();
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'RangeDatePickerPanel',
|
||||
mixins: [ Mixin, Locale, DateMixin ],
|
||||
components: { Icon, DateTable, YearTable, MonthTable, TimePicker, Confirm, datePanelLabel },
|
||||
props: {
|
||||
// more props in the mixin
|
||||
splitPanels: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
},
|
||||
data(){
|
||||
const [minDate, maxDate] = this.value.map(date => date || initTimeDate());
|
||||
const leftPanelDate = this.startDate ? this.startDate : minDate;
|
||||
|
||||
return {
|
||||
prefixCls: prefixCls,
|
||||
datePrefixCls: datePrefixCls,
|
||||
dates: this.value,
|
||||
rangeState: {from: this.value[0], to: this.value[1], selecting: minDate && !maxDate},
|
||||
currentView: this.selectionMode || 'range',
|
||||
leftPickerTable: `${this.selectionMode}-table`,
|
||||
rightPickerTable: `${this.selectionMode}-table`,
|
||||
leftPanelDate: leftPanelDate,
|
||||
rightPanelDate: new Date(leftPanelDate.getFullYear(), leftPanelDate.getMonth() + 1, 1)
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
classes(){
|
||||
return [
|
||||
`${prefixCls}-body-wrapper`,
|
||||
`${datePrefixCls}-with-range`,
|
||||
{
|
||||
[`${prefixCls}-with-sidebar`]: this.shortcuts.length,
|
||||
[`${datePrefixCls}-with-week-numbers`]: this.showWeekNumbers
|
||||
}
|
||||
];
|
||||
},
|
||||
panelBodyClasses(){
|
||||
return [
|
||||
prefixCls + '-body',
|
||||
{
|
||||
[prefixCls + '-body-time']: this.showTime,
|
||||
[prefixCls + '-body-date']: !this.showTime,
|
||||
}
|
||||
];
|
||||
},
|
||||
leftDatePanelLabel(){
|
||||
return this.panelLabelConfig('left');
|
||||
},
|
||||
rightDatePanelLabel(){
|
||||
return this.panelLabelConfig('right');
|
||||
},
|
||||
leftDatePanelView(){
|
||||
return this.leftPickerTable.split('-').shift();
|
||||
},
|
||||
rightDatePanelView(){
|
||||
return this.rightPickerTable.split('-').shift();
|
||||
},
|
||||
timeDisabled(){
|
||||
return !(this.dates[0] && this.dates[1]);
|
||||
},
|
||||
preSelecting(){
|
||||
const tableType = `${this.currentView}-table`;
|
||||
|
||||
return {
|
||||
left: this.leftPickerTable !== tableType,
|
||||
right: this.rightPickerTable !== tableType,
|
||||
};
|
||||
},
|
||||
panelPickerHandlers(){
|
||||
return {
|
||||
left: this.preSelecting.left ? this.handlePreSelection.bind(this, 'left') : this.handleRangePick,
|
||||
right: this.preSelecting.right ? this.handlePreSelection.bind(this, 'right') : this.handleRangePick,
|
||||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
const minDate = newVal[0] ? toDate(newVal[0]) : null;
|
||||
const maxDate = newVal[1] ? toDate(newVal[1]) : null;
|
||||
this.dates = [minDate, maxDate].sort(dateSorter);
|
||||
|
||||
this.rangeState = {
|
||||
from: this.dates[0],
|
||||
to: this.dates[1],
|
||||
selecting: false
|
||||
};
|
||||
|
||||
|
||||
// set panels positioning
|
||||
this.setPanelDates(this.startDate || this.dates[0] || new Date());
|
||||
},
|
||||
currentView(currentView){
|
||||
const leftMonth = this.leftPanelDate.getMonth();
|
||||
const rightMonth = this.rightPanelDate.getMonth();
|
||||
const isSameYear = this.leftPanelDate.getFullYear() === this.rightPanelDate.getFullYear();
|
||||
|
||||
if (currentView === 'date' && isSameYear && leftMonth === rightMonth){
|
||||
this.changePanelDate('right', 'Month', 1);
|
||||
}
|
||||
if (currentView === 'month' && isSameYear){
|
||||
this.changePanelDate('right', 'FullYear', 1);
|
||||
}
|
||||
if (currentView === 'year' && isSameYear){
|
||||
this.changePanelDate('right', 'FullYear', 10);
|
||||
}
|
||||
},
|
||||
selectionMode(type){
|
||||
this.currentView = type || 'range';
|
||||
},
|
||||
focusedDate(date){
|
||||
this.setPanelDates(date || new Date());
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reset(){
|
||||
this.currentView = this.selectionMode;
|
||||
this.leftPickerTable = `${this.currentView}-table`;
|
||||
this.rightPickerTable = `${this.currentView}-table`;
|
||||
},
|
||||
setPanelDates(leftPanelDate){
|
||||
this.leftPanelDate = leftPanelDate;
|
||||
const rightPanelDate = new Date(leftPanelDate.getFullYear(), leftPanelDate.getMonth() + 1, 1);
|
||||
const splitRightPanelDate = this.dates[1]? this.dates[1].getTime() : this.dates[1];
|
||||
this.rightPanelDate = this.splitPanels ? new Date(Math.max(splitRightPanelDate, rightPanelDate.getTime())) : rightPanelDate;
|
||||
},
|
||||
panelLabelConfig (direction) {
|
||||
const locale = this.t('i.locale');
|
||||
const datePanelLabel = this.t('i.datepicker.datePanelLabel');
|
||||
const handler = type => {
|
||||
const fn = type == 'month' ? this.showMonthPicker : this.showYearPicker;
|
||||
return () => fn(direction);
|
||||
};
|
||||
|
||||
const date = this[`${direction}PanelDate`];
|
||||
const { labels, separator } = formatDateLabels(locale, datePanelLabel, date);
|
||||
|
||||
return {
|
||||
separator: separator,
|
||||
labels: labels.map(obj => ((obj.handler = handler(obj.type)), obj))
|
||||
};
|
||||
},
|
||||
prevYear (panel) {
|
||||
const increment = this.currentView === 'year' ? -10 : -1;
|
||||
this.changePanelDate(panel, 'FullYear', increment);
|
||||
},
|
||||
nextYear (panel) {
|
||||
const increment = this.currentView === 'year' ? 10 : 1;
|
||||
this.changePanelDate(panel, 'FullYear', increment);
|
||||
},
|
||||
prevMonth(panel){
|
||||
this.changePanelDate(panel, 'Month', -1);
|
||||
},
|
||||
nextMonth(panel){
|
||||
this.changePanelDate(panel, 'Month', 1);
|
||||
},
|
||||
changePanelDate(panel, type, increment, updateOtherPanel = true){
|
||||
const current = new Date(this[`${panel}PanelDate`]);
|
||||
current[`set${type}`](current[`get${type}`]() + increment);
|
||||
this[`${panel}PanelDate`] = current;
|
||||
|
||||
if (!updateOtherPanel) return;
|
||||
|
||||
if (this.splitPanels){
|
||||
// change other panel if dates overlap
|
||||
const otherPanel = panel === 'left' ? 'right' : 'left';
|
||||
if (panel === 'left' && this.leftPanelDate >= this.rightPanelDate){
|
||||
this.changePanelDate(otherPanel, type, 1);
|
||||
}
|
||||
if (panel === 'right' && this.rightPanelDate <= this.leftPanelDate){
|
||||
this.changePanelDate(otherPanel, type, -1);
|
||||
}
|
||||
} else {
|
||||
// keep the panels together
|
||||
const otherPanel = panel === 'left' ? 'right' : 'left';
|
||||
const currentDate = this[`${otherPanel}PanelDate`];
|
||||
const temp = new Date(currentDate);
|
||||
|
||||
if (type === 'Month') {
|
||||
const nextMonthLastDate = new Date(
|
||||
temp.getFullYear(), temp.getMonth() + increment + 1, 0
|
||||
).getDate();
|
||||
temp.setDate(Math.min(nextMonthLastDate, temp.getDate()));
|
||||
}
|
||||
|
||||
temp[`set${type}`](temp[`get${type}`]() + increment);
|
||||
this[`${otherPanel}PanelDate`] = temp;
|
||||
}
|
||||
},
|
||||
showYearPicker (panel) {
|
||||
this[`${panel}PickerTable`] = 'year-table';
|
||||
},
|
||||
showMonthPicker (panel) {
|
||||
this[`${panel}PickerTable`] = 'month-table';
|
||||
},
|
||||
handlePreSelection(panel, value){
|
||||
this[`${panel}PanelDate`] = value;
|
||||
const currentViewType = this[`${panel}PickerTable`];
|
||||
if (currentViewType === 'year-table') this[`${panel}PickerTable`] = 'month-table';
|
||||
else this[`${panel}PickerTable`] = `${this.currentView}-table`;
|
||||
|
||||
if (!this.splitPanels){
|
||||
const otherPanel = panel === 'left' ? 'right' : 'left';
|
||||
this[`${otherPanel}PanelDate`] = value;
|
||||
|
||||
const increment = otherPanel === 'left' ? -1 : 1; // #3973
|
||||
|
||||
this.changePanelDate(otherPanel, 'Month', increment, false);
|
||||
}
|
||||
},
|
||||
handleRangePick (val, type) {
|
||||
if (this.rangeState.selecting || this.currentView === 'time'){
|
||||
if (this.currentView === 'time'){
|
||||
this.dates = val;
|
||||
} else {
|
||||
const [minDate, maxDate] = [this.rangeState.from, val].sort(dateSorter);
|
||||
this.dates = [minDate, maxDate];
|
||||
this.rangeState = {
|
||||
from: minDate,
|
||||
to: maxDate,
|
||||
selecting: false
|
||||
};
|
||||
}
|
||||
this.handleConfirm(false, type || 'date');
|
||||
} else {
|
||||
this.rangeState = {
|
||||
from: val,
|
||||
to: null,
|
||||
selecting: true
|
||||
};
|
||||
}
|
||||
},
|
||||
handleChangeRange (val) {
|
||||
this.rangeState.to = val;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
213
src/components/date-picker/panel/Date/date.vue
Normal file
213
src/components/date-picker/panel/Date/date.vue
Normal file
|
@ -0,0 +1,213 @@
|
|||
<template>
|
||||
<div :class="classes" @mousedown.prevent>
|
||||
<div :class="[prefixCls + '-sidebar']" v-if="shortcuts.length">
|
||||
<div
|
||||
:class="[prefixCls + '-shortcut']"
|
||||
v-for="shortcut in shortcuts"
|
||||
@click="handleShortcutClick(shortcut)">{{ shortcut.text }}</div>
|
||||
</div>
|
||||
<div :class="[prefixCls + '-body']">
|
||||
<div :class="[datePrefixCls + '-header']" v-show="currentView !== 'time'">
|
||||
<span
|
||||
:class="iconBtnCls('prev', '-double')"
|
||||
@click="changeYear(-1)"><Icon type="ios-arrow-back"></Icon></span>
|
||||
<span
|
||||
v-if="pickerTable === 'date-table'"
|
||||
:class="iconBtnCls('prev')"
|
||||
@click="changeMonth(-1)"
|
||||
v-show="currentView === 'date'"><Icon type="ios-arrow-back"></Icon></span>
|
||||
<date-panel-label
|
||||
:date-panel-label="datePanelLabel"
|
||||
:current-view="pickerTable.split('-').shift()"
|
||||
:date-prefix-cls="datePrefixCls"></date-panel-label>
|
||||
<span
|
||||
:class="iconBtnCls('next', '-double')"
|
||||
@click="changeYear(+1)"><Icon type="ios-arrow-forward"></Icon></span>
|
||||
<span
|
||||
v-if="pickerTable === 'date-table'"
|
||||
:class="iconBtnCls('next')"
|
||||
@click="changeMonth(+1)"
|
||||
v-show="currentView === 'date'"><Icon type="ios-arrow-forward"></Icon></span>
|
||||
</div>
|
||||
<div :class="[prefixCls + '-content']">
|
||||
<component
|
||||
:is="pickerTable"
|
||||
ref="pickerTable"
|
||||
v-if="currentView !== 'time'"
|
||||
:table-date="panelDate"
|
||||
:show-week-numbers="showWeekNumbers"
|
||||
:value="dates"
|
||||
:selection-mode="selectionMode"
|
||||
:disabled-date="disabledDate"
|
||||
:focused-date="focusedDate"
|
||||
|
||||
@on-pick="panelPickerHandlers"
|
||||
@on-pick-click="handlePickClick"
|
||||
></component>
|
||||
</div>
|
||||
<div :class="[prefixCls + '-content']" v-show="isTime">
|
||||
<time-picker
|
||||
ref="timePicker"
|
||||
v-if="currentView === 'time'"
|
||||
:value="dates"
|
||||
:format="format"
|
||||
:time-disabled="timeDisabled"
|
||||
:disabled-date="disabledDate"
|
||||
:focused-date="focusedDate"
|
||||
|
||||
v-bind="timePickerOptions"
|
||||
@on-pick="handlePick"
|
||||
@on-pick-click="handlePickClick"
|
||||
@on-pick-clear="handlePickClear"
|
||||
@on-pick-success="handlePickSuccess"
|
||||
@on-pick-toggle-time="handleToggleTime"
|
||||
></time-picker>
|
||||
</div>
|
||||
<Confirm
|
||||
v-if="confirm"
|
||||
:show-time="showTime"
|
||||
:is-time="isTime"
|
||||
@on-pick-toggle-time="handleToggleTime"
|
||||
@on-pick-clear="handlePickClear"
|
||||
@on-pick-success="handlePickSuccess"
|
||||
></Confirm>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Icon from '../../../icon/icon.vue';
|
||||
import DateTable from '../../base/date-table.vue';
|
||||
import YearTable from '../../base/year-table.vue';
|
||||
import MonthTable from '../../base/month-table.vue';
|
||||
import TimePicker from '../Time/time.vue';
|
||||
import Confirm from '../../base/confirm.vue';
|
||||
import datePanelLabel from './date-panel-label.vue';
|
||||
|
||||
import Mixin from '../panel-mixin';
|
||||
import DateMixin from './date-panel-mixin';
|
||||
import Locale from '../../../../mixins/locale';
|
||||
|
||||
import { siblingMonth, formatDateLabels } from '../../util';
|
||||
|
||||
const prefixCls = 'ivu-picker-panel';
|
||||
const datePrefixCls = 'ivu-date-picker';
|
||||
|
||||
export default {
|
||||
name: 'DatePickerPanel',
|
||||
mixins: [ Mixin, Locale, DateMixin ],
|
||||
components: { Icon, DateTable, YearTable, MonthTable, TimePicker, Confirm, datePanelLabel },
|
||||
props: {
|
||||
// more props in the mixin
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
const {selectionMode, value} = this;
|
||||
|
||||
const dates = value.slice().sort();
|
||||
return {
|
||||
prefixCls: prefixCls,
|
||||
datePrefixCls: datePrefixCls,
|
||||
currentView: selectionMode || 'date',
|
||||
pickerTable: this.getTableType(selectionMode),
|
||||
dates: dates,
|
||||
panelDate: this.startDate || dates[0] || new Date()
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
classes () {
|
||||
return [
|
||||
`${prefixCls}-body-wrapper`,
|
||||
{
|
||||
[`${prefixCls}-with-sidebar`]: this.shortcuts.length
|
||||
}
|
||||
];
|
||||
},
|
||||
panelPickerHandlers(){
|
||||
return this.pickerTable === `${this.currentView}-table` ? this.handlePick : this.handlePreSelection;
|
||||
},
|
||||
datePanelLabel () {
|
||||
const locale = this.t('i.locale');
|
||||
const datePanelLabel = this.t('i.datepicker.datePanelLabel');
|
||||
const date = this.panelDate;
|
||||
const { labels, separator } = formatDateLabels(locale, datePanelLabel, date);
|
||||
|
||||
const handler = type => {
|
||||
return () => this.pickerTable = this.getTableType(type);
|
||||
};
|
||||
|
||||
return {
|
||||
separator: separator,
|
||||
labels: labels.map(obj => ((obj.handler = handler(obj.type)), obj))
|
||||
};
|
||||
},
|
||||
timeDisabled(){
|
||||
return !this.dates[0];
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (newVal) {
|
||||
this.dates = newVal;
|
||||
const panelDate = this.multiple ? this.dates[this.dates.length - 1] : (this.startDate || this.dates[0]);
|
||||
this.panelDate = panelDate || new Date();
|
||||
},
|
||||
currentView (currentView) {
|
||||
this.$emit('on-selection-mode-change', currentView);
|
||||
|
||||
if (this.currentView === 'time') {
|
||||
this.$nextTick(() => {
|
||||
const spinner = this.$refs.timePicker.$refs.timeSpinner;
|
||||
spinner.updateScroll();
|
||||
});
|
||||
}
|
||||
},
|
||||
selectionMode(type){
|
||||
this.currentView = type;
|
||||
this.pickerTable = this.getTableType(type);
|
||||
},
|
||||
focusedDate(date){
|
||||
const isDifferentYear = date.getFullYear() !== this.panelDate.getFullYear();
|
||||
const isDifferentMonth = isDifferentYear || date.getMonth() !== this.panelDate.getMonth();
|
||||
if (isDifferentYear || isDifferentMonth){
|
||||
if (!this.multiple) this.panelDate = date;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reset(){
|
||||
this.currentView = this.selectionMode;
|
||||
this.pickerTable = this.getTableType(this.currentView);
|
||||
},
|
||||
changeYear(dir){
|
||||
if (this.selectionMode === 'year' || this.pickerTable === 'year-table'){
|
||||
this.panelDate = new Date(this.panelDate.getFullYear() + dir * 10, 0, 1);
|
||||
} else {
|
||||
this.panelDate = siblingMonth(this.panelDate, dir * 12);
|
||||
}
|
||||
},
|
||||
getTableType(currentView){
|
||||
return currentView.match(/^time/) ? 'time-picker' : `${currentView}-table`;
|
||||
},
|
||||
changeMonth(dir){
|
||||
this.panelDate = siblingMonth(this.panelDate, dir);
|
||||
},
|
||||
handlePreSelection(value){
|
||||
this.panelDate = value;
|
||||
if (this.pickerTable === 'year-table') this.pickerTable = 'month-table';
|
||||
else this.pickerTable = this.getTableType(this.currentView);
|
||||
|
||||
},
|
||||
handlePick (value, type) {
|
||||
const {selectionMode, panelDate} = this;
|
||||
if (selectionMode === 'year') value = new Date(value.getFullYear(), 0, 1);
|
||||
else if (selectionMode === 'month') value = new Date(panelDate.getFullYear(), value.getMonth(), 1);
|
||||
else value = new Date(value);
|
||||
|
||||
this.dates = [value];
|
||||
this.$emit('on-pick', value, false, type || selectionMode);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
162
src/components/date-picker/panel/Time/time-range.vue
Normal file
162
src/components/date-picker/panel/Time/time-range.vue
Normal file
|
@ -0,0 +1,162 @@
|
|||
<template>
|
||||
<div :class="classes" @mousedown.prevent>
|
||||
<div :class="[prefixCls + '-body']">
|
||||
<div :class="[prefixCls + '-content', prefixCls + '-content-left']">
|
||||
<div :class="[timePrefixCls + '-header']">
|
||||
<template v-if="showDate">{{ leftDatePanelLabel }}</template>
|
||||
<template v-else>{{ t('i.datepicker.startTime') }}</template>
|
||||
</div>
|
||||
<time-spinner
|
||||
ref="timeSpinner"
|
||||
:steps="steps"
|
||||
:show-seconds="showSeconds"
|
||||
:hours="value[0] && dateStart.getHours()"
|
||||
:minutes="value[0] && dateStart.getMinutes()"
|
||||
:seconds="value[0] && dateStart.getSeconds()"
|
||||
:disabled-hours="disabledHours"
|
||||
:disabled-minutes="disabledMinutes"
|
||||
:disabled-seconds="disabledSeconds"
|
||||
:hide-disabled-options="hideDisabledOptions"
|
||||
@on-change="handleStartChange"
|
||||
@on-pick-click="handlePickClick"></time-spinner>
|
||||
</div>
|
||||
<div :class="[prefixCls + '-content', prefixCls + '-content-right']">
|
||||
<div :class="[timePrefixCls + '-header']">
|
||||
<template v-if="showDate">{{ rightDatePanelLabel }}</template>
|
||||
<template v-else>{{ t('i.datepicker.endTime') }}</template>
|
||||
</div>
|
||||
<time-spinner
|
||||
ref="timeSpinnerEnd"
|
||||
:steps="steps"
|
||||
:show-seconds="showSeconds"
|
||||
:hours="value[1] && dateEnd.getHours()"
|
||||
:minutes="value[1] && dateEnd.getMinutes()"
|
||||
:seconds="value[1] && dateEnd.getSeconds()"
|
||||
:disabled-hours="disabledHours"
|
||||
:disabled-minutes="disabledMinutes"
|
||||
:disabled-seconds="disabledSeconds"
|
||||
:hide-disabled-options="hideDisabledOptions"
|
||||
@on-change="handleEndChange"
|
||||
@on-pick-click="handlePickClick"></time-spinner>
|
||||
</div>
|
||||
<Confirm
|
||||
v-if="confirm"
|
||||
@on-pick-clear="handlePickClear"
|
||||
@on-pick-success="handlePickSuccess"></Confirm>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import TimeSpinner from '../../base/time-spinner.vue';
|
||||
import Confirm from '../../base/confirm.vue';
|
||||
import Options from '../../time-mixins';
|
||||
|
||||
|
||||
import Mixin from '../panel-mixin';
|
||||
import Locale from '../../../../mixins/locale';
|
||||
|
||||
import { initTimeDate, formatDateLabels } from '../../util';
|
||||
|
||||
const prefixCls = 'ivu-picker-panel';
|
||||
const timePrefixCls = 'ivu-time-picker';
|
||||
|
||||
const capitalize = (str) => str[0].toUpperCase() + str.slice(1);
|
||||
|
||||
export default {
|
||||
name: 'RangeTimePickerPanel',
|
||||
mixins: [ Mixin, Locale, Options ],
|
||||
components: { TimeSpinner, Confirm },
|
||||
props: {
|
||||
steps: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
format: {
|
||||
type: String,
|
||||
default: 'HH:mm:ss'
|
||||
},
|
||||
value: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
data () {
|
||||
const [dateStart, dateEnd] = this.value.slice();
|
||||
return {
|
||||
prefixCls: prefixCls,
|
||||
timePrefixCls: timePrefixCls,
|
||||
showDate: false,
|
||||
dateStart: dateStart || initTimeDate(),
|
||||
dateEnd: dateEnd || initTimeDate()
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
classes () {
|
||||
return [
|
||||
`${prefixCls}-body-wrapper`,
|
||||
`${timePrefixCls}-with-range`,
|
||||
{
|
||||
[`${timePrefixCls}-with-seconds`]: this.showSeconds
|
||||
}
|
||||
];
|
||||
},
|
||||
showSeconds () {
|
||||
return !(this.format || '').match(/mm$/);
|
||||
},
|
||||
leftDatePanelLabel () {
|
||||
return this.panelLabelConfig(this.date);
|
||||
},
|
||||
rightDatePanelLabel () {
|
||||
return this.panelLabelConfig(this.dateEnd);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (dates) {
|
||||
const [dateStart, dateEnd] = dates.slice();
|
||||
this.dateStart = dateStart || initTimeDate();
|
||||
this.dateEnd = dateEnd || initTimeDate();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
panelLabelConfig (date) {
|
||||
const locale = this.t('i.locale');
|
||||
const datePanelLabel = this.t('i.datepicker.datePanelLabel');
|
||||
const { labels, separator } = formatDateLabels(locale, datePanelLabel, date || initTimeDate());
|
||||
return [labels[0].label, separator, labels[1].label].join('');
|
||||
},
|
||||
handleChange (start, end, emit = true) {
|
||||
|
||||
const dateStart = new Date(this.dateStart);
|
||||
let dateEnd = new Date(this.dateEnd);
|
||||
|
||||
// set dateStart
|
||||
Object.keys(start).forEach(type => {
|
||||
dateStart[`set${capitalize(type)}`](start[type]);
|
||||
});
|
||||
|
||||
// set dateEnd
|
||||
Object.keys(end).forEach(type => {
|
||||
dateEnd[`set${capitalize(type)}`](end[type]);
|
||||
});
|
||||
|
||||
// judge endTime > startTime?
|
||||
if (dateEnd < dateStart) dateEnd = dateStart;
|
||||
|
||||
if (emit) this.$emit('on-pick', [dateStart, dateEnd], 'time');
|
||||
},
|
||||
handleStartChange (date) {
|
||||
this.handleChange(date, {});
|
||||
},
|
||||
handleEndChange (date) {
|
||||
this.handleChange({}, date);
|
||||
},
|
||||
updateScroll () {
|
||||
this.$refs.timeSpinner.updateScroll();
|
||||
this.$refs.timeSpinnerEnd.updateScroll();
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.$parent && this.$parent.$options.name === 'DatePicker') this.showDate = true;
|
||||
}
|
||||
};
|
||||
</script>
|
145
src/components/date-picker/panel/Time/time.vue
Normal file
145
src/components/date-picker/panel/Time/time.vue
Normal file
|
@ -0,0 +1,145 @@
|
|||
<template>
|
||||
<div :class="[prefixCls + '-body-wrapper']" @mousedown.prevent>
|
||||
<div :class="[prefixCls + '-body']">
|
||||
<div :class="[timePrefixCls + '-header']" v-if="showDate">{{ visibleDate }}</div>
|
||||
<div :class="[prefixCls + '-content']">
|
||||
<time-spinner
|
||||
ref="timeSpinner"
|
||||
:show-seconds="showSeconds"
|
||||
:steps="steps"
|
||||
:hours="timeSlots[0]"
|
||||
:minutes="timeSlots[1]"
|
||||
:seconds="timeSlots[2]"
|
||||
:disabled-hours="disabledHMS.disabledHours"
|
||||
:disabled-minutes="disabledHMS.disabledMinutes"
|
||||
:disabled-seconds="disabledHMS.disabledSeconds"
|
||||
:hide-disabled-options="hideDisabledOptions"
|
||||
@on-change="handleChange"
|
||||
@on-pick-click="handlePickClick"></time-spinner>
|
||||
</div>
|
||||
<Confirm
|
||||
v-if="confirm"
|
||||
@on-pick-clear="handlePickClear"
|
||||
@on-pick-success="handlePickSuccess"></Confirm>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import TimeSpinner from '../../base/time-spinner.vue';
|
||||
import Confirm from '../../base/confirm.vue';
|
||||
import Options from '../../time-mixins';
|
||||
|
||||
|
||||
import Mixin from '../panel-mixin';
|
||||
import Locale from '../../../../mixins/locale';
|
||||
|
||||
import { initTimeDate } from '../../util';
|
||||
|
||||
const prefixCls = 'ivu-picker-panel';
|
||||
const timePrefixCls = 'ivu-time-picker';
|
||||
|
||||
const capitalize = (str) => str[0].toUpperCase() + str.slice(1);
|
||||
const mergeDateHMS = (date, hours, minutes, seconds) => {
|
||||
const newDate = new Date(date.getTime());
|
||||
newDate.setHours(hours);
|
||||
newDate.setMinutes(minutes);
|
||||
newDate.setSeconds(seconds);
|
||||
return newDate;
|
||||
};
|
||||
const unique = (el, i, arr) => arr.indexOf(el) === i;
|
||||
const returnFalse = () => false;
|
||||
|
||||
export default {
|
||||
name: 'TimePickerPanel',
|
||||
mixins: [ Mixin, Locale, Options ],
|
||||
components: { TimeSpinner, Confirm },
|
||||
props: {
|
||||
disabledDate: {
|
||||
type: Function,
|
||||
default: returnFalse
|
||||
},
|
||||
steps: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
format: {
|
||||
type: String,
|
||||
default: 'HH:mm:ss'
|
||||
},
|
||||
value: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
prefixCls: prefixCls,
|
||||
timePrefixCls: timePrefixCls,
|
||||
date: this.value[0] || initTimeDate(),
|
||||
showDate: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showSeconds () {
|
||||
return !(this.format || '').match(/mm$/);
|
||||
},
|
||||
visibleDate () { // TODO
|
||||
const date = this.date;
|
||||
const month = date.getMonth() + 1;
|
||||
const tYear = this.t('i.datepicker.year');
|
||||
const tMonth = this.t(`i.datepicker.month${month}`);
|
||||
return `${date.getFullYear()}${tYear} ${tMonth}`;
|
||||
},
|
||||
timeSlots(){
|
||||
if (!this.value[0]) return [];
|
||||
return ['getHours', 'getMinutes', 'getSeconds'].map(slot => this.date[slot]());
|
||||
},
|
||||
disabledHMS(){
|
||||
const disabledTypes = ['disabledHours', 'disabledMinutes', 'disabledSeconds'];
|
||||
if (this.disabledDate === returnFalse || !this.value[0]) {
|
||||
const disabled = disabledTypes.reduce(
|
||||
(obj, type) => (obj[type] = this[type], obj), {}
|
||||
);
|
||||
return disabled;
|
||||
} else {
|
||||
const slots = [24, 60, 60];
|
||||
const disabled = ['Hours', 'Minutes', 'Seconds'].map(type => this[`disabled${type}`]);
|
||||
const disabledHMS = disabled.map((preDisabled, j) => {
|
||||
const slot = slots[j];
|
||||
const toDisable = preDisabled;
|
||||
for (let i = 0; i < slot; i+= (this.steps[j] || 1)){
|
||||
const hms = this.timeSlots.map((slot, x) => x === j ? i : slot);
|
||||
const testDateTime = mergeDateHMS(this.date, ...hms);
|
||||
if (this.disabledDate(testDateTime, true)) toDisable.push(i);
|
||||
}
|
||||
return toDisable.filter(unique);
|
||||
});
|
||||
return disabledTypes.reduce(
|
||||
(obj, type, i) => (obj[type] = disabledHMS[i], obj), {}
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (dates) {
|
||||
let newVal = dates[0] || initTimeDate();
|
||||
newVal = new Date(newVal);
|
||||
this.date = newVal;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange (date, emit = true) {
|
||||
|
||||
const newDate = new Date(this.date);
|
||||
Object.keys(date).forEach(
|
||||
type => newDate[`set${capitalize(type)}`](date[type])
|
||||
);
|
||||
|
||||
if (emit) this.$emit('on-pick', newDate, 'time');
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
if (this.$parent && this.$parent.$options.name === 'DatePicker') this.showDate = true;
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -1,414 +0,0 @@
|
|||
<template>
|
||||
<div :class="classes" @mousedown.prevent>
|
||||
<div :class="[prefixCls + '-sidebar']" v-if="shortcuts.length">
|
||||
<div
|
||||
:class="[prefixCls + '-shortcut']"
|
||||
v-for="shortcut in shortcuts"
|
||||
@click="handleShortcutClick(shortcut)">{{ shortcut.text }}</div>
|
||||
</div>
|
||||
<div :class="[prefixCls + '-body']">
|
||||
<div :class="[prefixCls + '-content', prefixCls + '-content-left']" v-show="!isTime">
|
||||
<div :class="[datePrefixCls + '-header']" v-show="leftCurrentView !== 'time'">
|
||||
<span
|
||||
:class="iconBtnCls('prev', '-double')"
|
||||
@click="prevYear('left')"><Icon type="ios-arrow-left"></Icon></span>
|
||||
<span
|
||||
:class="iconBtnCls('prev')"
|
||||
@click="prevMonth"
|
||||
v-show="leftCurrentView === 'date'"><Icon type="ios-arrow-left"></Icon></span>
|
||||
<date-panel-label
|
||||
:date-panel-label="leftDatePanelLabel"
|
||||
:current-view="leftCurrentView"
|
||||
:date-prefix-cls="datePrefixCls"/>
|
||||
<span
|
||||
:class="iconBtnCls('next', '-double')"
|
||||
@click="nextYear('left')"
|
||||
v-show="leftCurrentView === 'year' || leftCurrentView === 'month'"><Icon type="ios-arrow-right"></Icon></span>
|
||||
</div>
|
||||
<date-table
|
||||
v-show="leftCurrentView === 'date'"
|
||||
:year="leftYear"
|
||||
:month="leftMonth"
|
||||
:date="date"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:range-state="rangeState"
|
||||
selection-mode="range"
|
||||
:disabled-date="disabledDate"
|
||||
@on-changerange="handleChangeRange"
|
||||
@on-pick="handleRangePick"
|
||||
@on-pick-click="handlePickClick"></date-table>
|
||||
<year-table
|
||||
ref="leftYearTable"
|
||||
v-show="leftCurrentView === 'year'"
|
||||
:year="leftTableYear"
|
||||
:date="leftTableDate"
|
||||
selection-mode="range"
|
||||
:disabled-date="disabledDate"
|
||||
@on-pick="handleLeftYearPick"
|
||||
@on-pick-click="handlePickClick"></year-table>
|
||||
<month-table
|
||||
ref="leftMonthTable"
|
||||
v-show="leftCurrentView === 'month'"
|
||||
:month="leftMonth"
|
||||
:date="leftTableDate"
|
||||
selection-mode="range"
|
||||
:disabled-date="disabledDate"
|
||||
@on-pick="handleLeftMonthPick"
|
||||
@on-pick-click="handlePickClick"></month-table>
|
||||
</div>
|
||||
<div :class="[prefixCls + '-content', prefixCls + '-content-right']" v-show="!isTime">
|
||||
<div :class="[datePrefixCls + '-header']" v-show="rightCurrentView !== 'time'">
|
||||
<span
|
||||
:class="iconBtnCls('prev', '-double')"
|
||||
@click="prevYear('right')"
|
||||
v-show="rightCurrentView === 'year' || rightCurrentView === 'month'"><Icon type="ios-arrow-left"></Icon></span>
|
||||
<date-panel-label
|
||||
:date-panel-label="rightDatePanelLabel"
|
||||
:current-view="rightCurrentView"
|
||||
:date-prefix-cls="datePrefixCls"/>
|
||||
<span
|
||||
:class="iconBtnCls('next', '-double')"
|
||||
@click="nextYear('right')"><Icon type="ios-arrow-right"></Icon></span>
|
||||
<span
|
||||
:class="iconBtnCls('next')"
|
||||
@click="nextMonth"
|
||||
v-show="rightCurrentView === 'date'"><Icon type="ios-arrow-right"></Icon></span>
|
||||
</div>
|
||||
<date-table
|
||||
v-show="rightCurrentView === 'date'"
|
||||
:year="rightYear"
|
||||
:month="rightMonth"
|
||||
:date="rightDate"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:range-state="rangeState"
|
||||
selection-mode="range"
|
||||
:disabled-date="disabledDate"
|
||||
@on-changerange="handleChangeRange"
|
||||
@on-pick="handleRangePick"
|
||||
@on-pick-click="handlePickClick"></date-table>
|
||||
<year-table
|
||||
ref="rightYearTable"
|
||||
v-show="rightCurrentView === 'year'"
|
||||
:year="rightTableYear"
|
||||
:date="rightTableDate"
|
||||
selection-mode="range"
|
||||
:disabled-date="disabledDate"
|
||||
@on-pick="handleRightYearPick"
|
||||
@on-pick-click="handlePickClick"></year-table>
|
||||
<month-table
|
||||
ref="rightMonthTable"
|
||||
v-show="rightCurrentView === 'month'"
|
||||
:month="rightMonth"
|
||||
:date="rightTableDate"
|
||||
selection-mode="range"
|
||||
:disabled-date="disabledDate"
|
||||
@on-pick="handleRightMonthPick"
|
||||
@on-pick-click="handlePickClick"></month-table>
|
||||
</div>
|
||||
<div :class="[prefixCls + '-content']" v-show="isTime">
|
||||
<time-picker
|
||||
ref="timePicker"
|
||||
v-show="isTime"
|
||||
@on-pick="handleTimePick"
|
||||
@on-pick-click="handlePickClick"></time-picker>
|
||||
</div>
|
||||
<Confirm
|
||||
v-if="confirm"
|
||||
:show-time="showTime"
|
||||
:is-time="isTime"
|
||||
:time-disabled="timeDisabled"
|
||||
@on-pick-toggle-time="handleToggleTime"
|
||||
@on-pick-clear="handlePickClear"
|
||||
@on-pick-success="handlePickSuccess"></Confirm>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Icon from '../../icon/icon.vue';
|
||||
import DateTable from '../base/date-table.vue';
|
||||
import YearTable from '../base/year-table.vue';
|
||||
import MonthTable from '../base/month-table.vue';
|
||||
import TimePicker from './time-range.vue';
|
||||
import Confirm from '../base/confirm.vue';
|
||||
import { toDate, prevMonth, nextMonth, initTimeDate, formatDateLabels } from '../util';
|
||||
import datePanelLabel from './date-panel-label.vue';
|
||||
|
||||
import Mixin from './mixin';
|
||||
import Locale from '../../../mixins/locale';
|
||||
|
||||
const prefixCls = 'ivu-picker-panel';
|
||||
const datePrefixCls = 'ivu-date-picker';
|
||||
|
||||
export default {
|
||||
name: 'DatePicker',
|
||||
mixins: [ Mixin, Locale ],
|
||||
components: { Icon, DateTable, YearTable, MonthTable, TimePicker, Confirm, datePanelLabel },
|
||||
data () {
|
||||
return {
|
||||
prefixCls: prefixCls,
|
||||
datePrefixCls: datePrefixCls,
|
||||
shortcuts: [],
|
||||
date: initTimeDate(),
|
||||
value: '',
|
||||
minDate: '',
|
||||
maxDate: '',
|
||||
confirm: false,
|
||||
rangeState: {
|
||||
endDate: null,
|
||||
selecting: false
|
||||
},
|
||||
showTime: false,
|
||||
disabledDate: '',
|
||||
leftCurrentView: 'date',
|
||||
rightCurrentView: 'date',
|
||||
selectionMode: 'range',
|
||||
leftTableYear: null,
|
||||
rightTableYear: null,
|
||||
isTime: false,
|
||||
format: 'yyyy-MM-dd'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
classes () {
|
||||
return [
|
||||
`${prefixCls}-body-wrapper`,
|
||||
`${datePrefixCls}-with-range`,
|
||||
{
|
||||
[`${prefixCls}-with-sidebar`]: this.shortcuts.length
|
||||
}
|
||||
];
|
||||
},
|
||||
leftYear () {
|
||||
return this.date.getFullYear();
|
||||
},
|
||||
leftTableDate () {
|
||||
if (this.leftCurrentView === 'year' || this.leftCurrentView === 'month') {
|
||||
return new Date(this.leftTableYear);
|
||||
} else {
|
||||
return this.date;
|
||||
}
|
||||
},
|
||||
leftMonth () {
|
||||
return this.date.getMonth();
|
||||
},
|
||||
rightYear () {
|
||||
return this.rightDate.getFullYear();
|
||||
},
|
||||
rightTableDate () {
|
||||
if (this.rightCurrentView === 'year' || this.rightCurrentView === 'month') {
|
||||
return new Date(this.rightTableYear);
|
||||
} else {
|
||||
return this.date;
|
||||
}
|
||||
},
|
||||
rightMonth () {
|
||||
return this.rightDate.getMonth();
|
||||
},
|
||||
rightDate () {
|
||||
const newDate = new Date(this.date);
|
||||
const month = newDate.getMonth();
|
||||
newDate.setDate(1);
|
||||
|
||||
if (month === 11) {
|
||||
newDate.setFullYear(newDate.getFullYear() + 1);
|
||||
newDate.setMonth(0);
|
||||
} else {
|
||||
newDate.setMonth(month + 1);
|
||||
}
|
||||
return newDate;
|
||||
},
|
||||
leftDatePanelLabel () {
|
||||
if (!this.leftYear) return null; // not ready yet
|
||||
return this.panelLabelConfig('left');
|
||||
},
|
||||
rightDatePanelLabel () {
|
||||
if (!this.leftYear) return null; // not ready yet
|
||||
return this.panelLabelConfig('right');
|
||||
},
|
||||
timeDisabled () {
|
||||
return !(this.minDate && this.maxDate);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
if (!newVal) {
|
||||
this.minDate = null;
|
||||
this.maxDate = null;
|
||||
} else if (Array.isArray(newVal)) {
|
||||
this.minDate = newVal[0] ? toDate(newVal[0]) : null;
|
||||
this.maxDate = newVal[1] ? toDate(newVal[1]) : null;
|
||||
if (this.minDate) this.date = new Date(this.minDate);
|
||||
}
|
||||
if (this.showTime) this.$refs.timePicker.value = newVal;
|
||||
},
|
||||
minDate (val) {
|
||||
if (this.showTime) this.$refs.timePicker.date = val;
|
||||
},
|
||||
maxDate (val) {
|
||||
if (this.showTime) this.$refs.timePicker.dateEnd = val;
|
||||
},
|
||||
format (val) {
|
||||
if (this.showTime) this.$refs.timePicker.format = val;
|
||||
},
|
||||
isTime (val) {
|
||||
if (val) this.$refs.timePicker.updateScroll();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
panelLabelConfig (direction) {
|
||||
const locale = this.t('i.locale');
|
||||
const datePanelLabel = this.t('i.datepicker.datePanelLabel');
|
||||
const handler = type => {
|
||||
const fn = type == 'month' ? this.showMonthPicker : this.showYearPicker;
|
||||
return () => fn(direction);
|
||||
};
|
||||
|
||||
const date = new Date(this[`${direction}Year`], this[`${direction}Month`]);
|
||||
const { labels, separator } = formatDateLabels(locale, datePanelLabel, date);
|
||||
|
||||
return {
|
||||
separator: separator,
|
||||
labels: labels.map(obj => ((obj.handler = handler(obj.type)), obj))
|
||||
};
|
||||
},
|
||||
resetDate () {
|
||||
this.date = new Date(this.date);
|
||||
this.leftTableYear = this.date.getFullYear();
|
||||
this.rightTableYear = this.rightDate.getFullYear();
|
||||
},
|
||||
handleClear() {
|
||||
this.minDate = null;
|
||||
this.maxDate = null;
|
||||
this.date = new Date();
|
||||
this.handleConfirm();
|
||||
if (this.showTime) this.$refs.timePicker.handleClear();
|
||||
},
|
||||
resetView(reset = false) {
|
||||
this.leftCurrentView = 'date';
|
||||
this.rightCurrentView = 'date';
|
||||
|
||||
this.leftTableYear = this.leftYear;
|
||||
this.rightTableYear = this.rightYear;
|
||||
|
||||
if (reset) this.isTime = false;
|
||||
},
|
||||
prevYear (direction) {
|
||||
if (this[`${direction}CurrentView`] === 'year') {
|
||||
this.$refs[`${direction}YearTable`].prevTenYear();
|
||||
} else if (this[`${direction}CurrentView`] === 'month') {
|
||||
this[`${direction}TableYear`]--;
|
||||
} else {
|
||||
const date = this.date;
|
||||
date.setFullYear(date.getFullYear() - 1);
|
||||
this.resetDate();
|
||||
}
|
||||
},
|
||||
nextYear (direction) {
|
||||
if (this[`${direction}CurrentView`] === 'year') {
|
||||
this.$refs[`${direction}YearTable`].nextTenYear();
|
||||
} else if (this[`${direction}CurrentView`] === 'month') {
|
||||
this[`${direction}TableYear`]++;
|
||||
} else {
|
||||
const date = this.date;
|
||||
date.setFullYear(date.getFullYear() + 1);
|
||||
this.resetDate();
|
||||
}
|
||||
},
|
||||
prevMonth () {
|
||||
this.date = prevMonth(this.date);
|
||||
},
|
||||
nextMonth () {
|
||||
this.date = nextMonth(this.date);
|
||||
},
|
||||
handleLeftYearPick (year, close = true) {
|
||||
this.handleYearPick(year, close, 'left');
|
||||
},
|
||||
handleRightYearPick (year, close = true) {
|
||||
this.handleYearPick(year, close, 'right');
|
||||
},
|
||||
handleYearPick (year, close, direction) {
|
||||
this[`${direction}TableYear`] = year;
|
||||
if (!close) return;
|
||||
|
||||
this[`${direction}CurrentView`] = 'month';
|
||||
},
|
||||
handleLeftMonthPick (month) {
|
||||
this.handleMonthPick(month, 'left');
|
||||
},
|
||||
handleRightMonthPick (month) {
|
||||
this.handleMonthPick(month, 'right');
|
||||
},
|
||||
handleMonthPick (month, direction) {
|
||||
let year = this[`${direction}TableYear`];
|
||||
if (direction === 'right') {
|
||||
if (month === 0) {
|
||||
month = 11;
|
||||
year--;
|
||||
} else {
|
||||
month--;
|
||||
}
|
||||
}
|
||||
|
||||
this.date.setYear(year);
|
||||
this.date.setMonth(month);
|
||||
this[`${direction}CurrentView`] = 'date';
|
||||
this.resetDate();
|
||||
},
|
||||
showYearPicker (direction) {
|
||||
this[`${direction}CurrentView`] = 'year';
|
||||
this[`${direction}TableYear`] = this[`${direction}Year`];
|
||||
},
|
||||
showMonthPicker (direction) {
|
||||
this[`${direction}CurrentView`] = 'month';
|
||||
},
|
||||
handleConfirm(visible) {
|
||||
this.$emit('on-pick', [this.minDate, this.maxDate], visible);
|
||||
},
|
||||
handleRangePick (val, close = true) {
|
||||
if (this.maxDate === val.maxDate && this.minDate === val.minDate) return;
|
||||
|
||||
this.minDate = val.minDate;
|
||||
this.maxDate = val.maxDate;
|
||||
|
||||
// todo Remove when Chromium has fixed bug
|
||||
// https://github.com/iview/iview/issues/2122
|
||||
this.$nextTick(() => {
|
||||
this.minDate = val.minDate;
|
||||
this.maxDate = val.maxDate;
|
||||
});
|
||||
/* end of #2122 patch */
|
||||
|
||||
if (!close) return;
|
||||
// if (!this.showTime) {
|
||||
// this.handleConfirm(false);
|
||||
// }
|
||||
this.handleConfirm(false);
|
||||
},
|
||||
handleChangeRange (val) {
|
||||
this.minDate = val.minDate;
|
||||
this.maxDate = val.maxDate;
|
||||
this.rangeState = val.rangeState;
|
||||
},
|
||||
handleToggleTime () {
|
||||
this.isTime = !this.isTime;
|
||||
},
|
||||
handleTimePick (date) {
|
||||
this.minDate = date[0];
|
||||
this.maxDate = date[1];
|
||||
this.handleConfirm(false);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.showTime) {
|
||||
// todo 这里也到不了
|
||||
this.$refs.timePicker.date = this.minDate;
|
||||
this.$refs.timePicker.dateEnd = this.maxDate;
|
||||
this.$refs.timePicker.value = this.value;
|
||||
this.$refs.timePicker.format = this.format;
|
||||
this.$refs.timePicker.showDate = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -1,261 +0,0 @@
|
|||
<template>
|
||||
<div :class="classes" @mousedown.prevent>
|
||||
<div :class="[prefixCls + '-sidebar']" v-if="shortcuts.length">
|
||||
<div
|
||||
:class="[prefixCls + '-shortcut']"
|
||||
v-for="shortcut in shortcuts"
|
||||
@click="handleShortcutClick(shortcut)">{{ shortcut.text }}</div>
|
||||
</div>
|
||||
<div :class="[prefixCls + '-body']">
|
||||
<div :class="[datePrefixCls + '-header']" v-show="currentView !== 'time'">
|
||||
<span
|
||||
:class="iconBtnCls('prev', '-double')"
|
||||
@click="changeYear(-1)"><Icon type="ios-arrow-left"></Icon></span>
|
||||
<span
|
||||
:class="iconBtnCls('prev')"
|
||||
@click="changeMonth(-1)"
|
||||
v-show="currentView === 'date'"><Icon type="ios-arrow-left"></Icon></span>
|
||||
<date-panel-label
|
||||
:date-panel-label="datePanelLabel"
|
||||
:current-view="currentView"
|
||||
:date-prefix-cls="datePrefixCls"/>
|
||||
<span
|
||||
:class="iconBtnCls('next', '-double')"
|
||||
@click="changeYear(+1)"><Icon type="ios-arrow-right"></Icon></span>
|
||||
<span
|
||||
:class="iconBtnCls('next')"
|
||||
@click="changeMonth(+1)"
|
||||
v-show="currentView === 'date'"><Icon type="ios-arrow-right"></Icon></span>
|
||||
</div>
|
||||
<div :class="[prefixCls + '-content']">
|
||||
<date-table
|
||||
v-show="currentView === 'date'"
|
||||
:year="year"
|
||||
:month="month"
|
||||
:date="date"
|
||||
:value="value"
|
||||
:selection-mode="selectionMode"
|
||||
:disabled-date="disabledDate"
|
||||
@on-pick="handleDatePick"
|
||||
@on-pick-click="handlePickClick"></date-table>
|
||||
<year-table
|
||||
ref="yearTable"
|
||||
v-show="currentView === 'year'"
|
||||
:year="year"
|
||||
:date="date"
|
||||
:selection-mode="selectionMode"
|
||||
:disabled-date="disabledDate"
|
||||
@on-pick="handleYearPick"
|
||||
@on-pick-click="handlePickClick"></year-table>
|
||||
<month-table
|
||||
ref="monthTable"
|
||||
v-show="currentView === 'month'"
|
||||
:month="month"
|
||||
:date="date"
|
||||
:selection-mode="selectionMode"
|
||||
:disabled-date="disabledDate"
|
||||
@on-pick="handleMonthPick"
|
||||
@on-pick-click="handlePickClick"></month-table>
|
||||
<time-picker
|
||||
ref="timePicker"
|
||||
show-date
|
||||
v-show="currentView === 'time'"
|
||||
@on-pick="handleTimePick"
|
||||
@on-pick-click="handlePickClick"></time-picker>
|
||||
</div>
|
||||
<Confirm
|
||||
v-if="confirm"
|
||||
:show-time="showTime"
|
||||
:is-time="isTime"
|
||||
@on-pick-toggle-time="handleToggleTime"
|
||||
@on-pick-clear="handlePickClear"
|
||||
@on-pick-success="handlePickSuccess"></Confirm>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Icon from '../../icon/icon.vue';
|
||||
import DateTable from '../base/date-table.vue';
|
||||
import YearTable from '../base/year-table.vue';
|
||||
import MonthTable from '../base/month-table.vue';
|
||||
import TimePicker from './time.vue';
|
||||
import Confirm from '../base/confirm.vue';
|
||||
import datePanelLabel from './date-panel-label.vue';
|
||||
|
||||
import Mixin from './mixin';
|
||||
import Locale from '../../../mixins/locale';
|
||||
|
||||
import { initTimeDate, siblingMonth, formatDateLabels } from '../util';
|
||||
|
||||
const prefixCls = 'ivu-picker-panel';
|
||||
const datePrefixCls = 'ivu-date-picker';
|
||||
|
||||
export default {
|
||||
name: 'DatePicker',
|
||||
mixins: [ Mixin, Locale ],
|
||||
components: { Icon, DateTable, YearTable, MonthTable, TimePicker, Confirm, datePanelLabel },
|
||||
data () {
|
||||
return {
|
||||
prefixCls: prefixCls,
|
||||
datePrefixCls: datePrefixCls,
|
||||
shortcuts: [],
|
||||
currentView: 'date',
|
||||
date: initTimeDate(),
|
||||
value: '',
|
||||
showTime: false,
|
||||
selectionMode: 'day',
|
||||
disabledDate: '',
|
||||
year: null,
|
||||
month: null,
|
||||
confirm: false,
|
||||
isTime: false,
|
||||
format: 'yyyy-MM-dd'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
classes () {
|
||||
return [
|
||||
`${prefixCls}-body-wrapper`,
|
||||
{
|
||||
[`${prefixCls}-with-sidebar`]: this.shortcuts.length
|
||||
}
|
||||
];
|
||||
},
|
||||
datePanelLabel () {
|
||||
if (!this.year) return null; // not ready yet
|
||||
const locale = this.t('i.locale');
|
||||
const datePanelLabel = this.t('i.datepicker.datePanelLabel');
|
||||
const date = new Date(this.year, this.month);
|
||||
const { labels, separator } = formatDateLabels(locale, datePanelLabel, date);
|
||||
|
||||
const handler = type => {
|
||||
return () => (this.currentView = type);
|
||||
};
|
||||
|
||||
return {
|
||||
separator: separator,
|
||||
labels: labels.map(obj => ((obj.handler = handler(obj.type)), obj))
|
||||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (newVal) {
|
||||
if (!newVal) return;
|
||||
newVal = new Date(newVal);
|
||||
if (!isNaN(newVal)) {
|
||||
this.date = newVal;
|
||||
this.setMonthYear(newVal);
|
||||
}
|
||||
if (this.showTime) this.$refs.timePicker.value = newVal;
|
||||
},
|
||||
date (val) {
|
||||
if (this.showTime) this.$refs.timePicker.date = val;
|
||||
},
|
||||
format (val) {
|
||||
if (this.showTime) this.$refs.timePicker.format = val;
|
||||
},
|
||||
currentView (val) {
|
||||
if (val === 'time') this.$refs.timePicker.updateScroll();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetDate () {
|
||||
this.date = new Date(this.date);
|
||||
},
|
||||
setMonthYear(date){
|
||||
this.month = date.getMonth();
|
||||
this.year = date.getFullYear();
|
||||
},
|
||||
handleClear () {
|
||||
this.date = new Date();
|
||||
this.$emit('on-pick', '');
|
||||
if (this.showTime) this.$refs.timePicker.handleClear();
|
||||
},
|
||||
resetView (reset = false) {
|
||||
if (this.currentView !== 'time' || reset) {
|
||||
if (this.selectionMode === 'month') {
|
||||
this.currentView = 'month';
|
||||
} else if (this.selectionMode === 'year') {
|
||||
this.currentView = 'year';
|
||||
} else {
|
||||
this.currentView = 'date';
|
||||
}
|
||||
}
|
||||
this.setMonthYear(this.date);
|
||||
if (reset) this.isTime = false;
|
||||
},
|
||||
changeYear(dir){
|
||||
if (this.currentView === 'year') {
|
||||
this.$refs.yearTable[dir == 1 ? 'nextTenYear' : 'prevTenYear']();
|
||||
} else {
|
||||
this.year+= dir;
|
||||
this.date = siblingMonth(this.date, dir * 12);
|
||||
}
|
||||
},
|
||||
changeMonth(dir){
|
||||
this.date = siblingMonth(this.date, dir);
|
||||
this.setMonthYear(this.date);
|
||||
},
|
||||
handleToggleTime () {
|
||||
if (this.currentView === 'date') {
|
||||
this.currentView = 'time';
|
||||
this.isTime = true;
|
||||
} else if (this.currentView === 'time') {
|
||||
this.currentView = 'date';
|
||||
this.isTime = false;
|
||||
}
|
||||
},
|
||||
handleYearPick(year, close = true) {
|
||||
this.year = year;
|
||||
if (!close) return;
|
||||
|
||||
this.date.setFullYear(year);
|
||||
if (this.selectionMode === 'year') {
|
||||
this.$emit('on-pick', new Date(year, 0, 1));
|
||||
} else {
|
||||
this.currentView = 'month';
|
||||
}
|
||||
|
||||
this.resetDate();
|
||||
},
|
||||
handleMonthPick (month) {
|
||||
this.month = month;
|
||||
this.date.setMonth(month);
|
||||
if (this.selectionMode !== 'month') {
|
||||
this.currentView = 'date';
|
||||
this.resetDate();
|
||||
} else {
|
||||
this.year && this.date.setFullYear(this.year);
|
||||
this.resetDate();
|
||||
const value = new Date(this.date.getFullYear(), month, 1);
|
||||
this.$emit('on-pick', value);
|
||||
}
|
||||
},
|
||||
handleDatePick (value) {
|
||||
if (this.selectionMode === 'day') {
|
||||
this.$emit('on-pick', new Date(value.getTime()));
|
||||
this.date = new Date(value);
|
||||
}
|
||||
},
|
||||
handleTimePick (date) {
|
||||
this.handleDatePick(date);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.selectionMode === 'month') {
|
||||
this.currentView = 'month';
|
||||
}
|
||||
|
||||
if (this.date && !this.year) {
|
||||
this.setMonthYear(this.date);
|
||||
}
|
||||
if (this.showTime) {
|
||||
// todo 这里可能有问题,并不能进入到这里,但不影响正常使用
|
||||
this.$refs.timePicker.date = this.date;
|
||||
this.$refs.timePicker.value = this.value;
|
||||
this.$refs.timePicker.format = this.format;
|
||||
this.$refs.timePicker.showDate = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -1,27 +0,0 @@
|
|||
const prefixCls = 'ivu-picker-panel';
|
||||
const datePrefixCls = 'ivu-date-picker';
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
iconBtnCls (direction, type = '') {
|
||||
return [
|
||||
`${prefixCls}-icon-btn`,
|
||||
`${datePrefixCls}-${direction}-btn`,
|
||||
`${datePrefixCls}-${direction}-btn-arrow${type}`,
|
||||
];
|
||||
},
|
||||
handleShortcutClick (shortcut) {
|
||||
if (shortcut.value) this.$emit('on-pick', shortcut.value());
|
||||
if (shortcut.onClick) shortcut.onClick(this);
|
||||
},
|
||||
handlePickClear () {
|
||||
this.$emit('on-pick-clear');
|
||||
},
|
||||
handlePickSuccess () {
|
||||
this.$emit('on-pick-success');
|
||||
},
|
||||
handlePickClick () {
|
||||
this.$emit('on-pick-click');
|
||||
}
|
||||
}
|
||||
};
|
56
src/components/date-picker/panel/panel-mixin.js
Normal file
56
src/components/date-picker/panel/panel-mixin.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
const prefixCls = 'ivu-picker-panel';
|
||||
const datePrefixCls = 'ivu-date-picker';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
confirm: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
iconBtnCls (direction, type = '') {
|
||||
return [
|
||||
`${prefixCls}-icon-btn`,
|
||||
`${datePrefixCls}-${direction}-btn`,
|
||||
`${datePrefixCls}-${direction}-btn-arrow${type}`,
|
||||
];
|
||||
},
|
||||
handleShortcutClick (shortcut) {
|
||||
if (shortcut.value) this.$emit('on-pick', shortcut.value());
|
||||
if (shortcut.onClick) shortcut.onClick(this);
|
||||
},
|
||||
handlePickClear () {
|
||||
this.resetView();
|
||||
this.$emit('on-pick-clear');
|
||||
},
|
||||
handlePickSuccess () {
|
||||
this.resetView();
|
||||
this.$emit('on-pick-success');
|
||||
},
|
||||
handlePickClick () {
|
||||
this.$emit('on-pick-click');
|
||||
},
|
||||
resetView(){
|
||||
setTimeout(
|
||||
() => this.currentView = this.selectionMode,
|
||||
500 // 500ms so the dropdown can close before changing
|
||||
);
|
||||
},
|
||||
handleClear() {
|
||||
this.dates = this.dates.map(() => null);
|
||||
this.rangeState = {};
|
||||
this.$emit('on-pick', this.dates);
|
||||
this.handleConfirm();
|
||||
// if (this.showTime) this.$refs.timePicker.handleClear();
|
||||
},
|
||||
handleConfirm(visible, type) {
|
||||
this.$emit('on-pick', this.dates, visible, type || this.type);
|
||||
},
|
||||
onToggleVisibility(open){
|
||||
const {timeSpinner, timeSpinnerEnd} = this.$refs;
|
||||
if (open && timeSpinner) timeSpinner.updateScroll();
|
||||
if (open && timeSpinnerEnd) timeSpinnerEnd.updateScroll();
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,214 +0,0 @@
|
|||
<template>
|
||||
<div :class="classes" @mousedown.prevent>
|
||||
<div :class="[prefixCls + '-body']">
|
||||
<div :class="[prefixCls + '-content', prefixCls + '-content-left']">
|
||||
<div :class="[timePrefixCls + '-header']">
|
||||
<template v-if="showDate">{{ leftDatePanelLabel }}</template>
|
||||
<template v-else>{{ t('i.datepicker.startTime') }}</template>
|
||||
</div>
|
||||
<time-spinner
|
||||
ref="timeSpinner"
|
||||
:steps="steps"
|
||||
:show-seconds="showSeconds"
|
||||
:hours="hours"
|
||||
:minutes="minutes"
|
||||
:seconds="seconds"
|
||||
:disabled-hours="disabledHours"
|
||||
:disabled-minutes="disabledMinutes"
|
||||
:disabled-seconds="disabledSeconds"
|
||||
:hide-disabled-options="hideDisabledOptions"
|
||||
@on-change="handleStartChange"
|
||||
@on-pick-click="handlePickClick"></time-spinner>
|
||||
</div>
|
||||
<div :class="[prefixCls + '-content', prefixCls + '-content-right']">
|
||||
<div :class="[timePrefixCls + '-header']">
|
||||
<template v-if="showDate">{{ rightDatePanelLabel }}</template>
|
||||
<template v-else>{{ t('i.datepicker.endTime') }}</template>
|
||||
</div>
|
||||
<time-spinner
|
||||
ref="timeSpinnerEnd"
|
||||
:steps="steps"
|
||||
:show-seconds="showSeconds"
|
||||
:hours="hoursEnd"
|
||||
:minutes="minutesEnd"
|
||||
:seconds="secondsEnd"
|
||||
:disabled-hours="disabledHours"
|
||||
:disabled-minutes="disabledMinutes"
|
||||
:disabled-seconds="disabledSeconds"
|
||||
:hide-disabled-options="hideDisabledOptions"
|
||||
@on-change="handleEndChange"
|
||||
@on-pick-click="handlePickClick"></time-spinner>
|
||||
</div>
|
||||
<Confirm
|
||||
v-if="confirm"
|
||||
@on-pick-clear="handlePickClear"
|
||||
@on-pick-success="handlePickSuccess"></Confirm>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import TimeSpinner from '../base/time-spinner.vue';
|
||||
import Confirm from '../base/confirm.vue';
|
||||
|
||||
import Mixin from './mixin';
|
||||
import Locale from '../../../mixins/locale';
|
||||
|
||||
import { initTimeDate, toDate, formatDate, formatDateLabels } from '../util';
|
||||
|
||||
const prefixCls = 'ivu-picker-panel';
|
||||
const timePrefixCls = 'ivu-time-picker';
|
||||
|
||||
export default {
|
||||
name: 'TimePicker',
|
||||
mixins: [ Mixin, Locale ],
|
||||
components: { TimeSpinner, Confirm },
|
||||
props: {
|
||||
steps: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
prefixCls: prefixCls,
|
||||
timePrefixCls: timePrefixCls,
|
||||
format: 'HH:mm:ss',
|
||||
showDate: false,
|
||||
date: initTimeDate(),
|
||||
dateEnd: initTimeDate(),
|
||||
value: '',
|
||||
hours: '',
|
||||
minutes: '',
|
||||
seconds: '',
|
||||
hoursEnd: '',
|
||||
minutesEnd: '',
|
||||
secondsEnd: '',
|
||||
disabledHours: [],
|
||||
disabledMinutes: [],
|
||||
disabledSeconds: [],
|
||||
hideDisabledOptions: false,
|
||||
confirm: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
classes () {
|
||||
return [
|
||||
`${prefixCls}-body-wrapper`,
|
||||
`${timePrefixCls}-with-range`,
|
||||
{
|
||||
[`${timePrefixCls}-with-seconds`]: this.showSeconds
|
||||
}
|
||||
];
|
||||
},
|
||||
showSeconds () {
|
||||
return (this.format || '').indexOf('ss') !== -1;
|
||||
},
|
||||
leftDatePanelLabel () {
|
||||
return this.panelLabelConfig(this.date);
|
||||
},
|
||||
rightDatePanelLabel () {
|
||||
return this.panelLabelConfig(this.dateEnd);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (newVal) {
|
||||
if (!newVal) return;
|
||||
if (Array.isArray(newVal)) {
|
||||
const valStart = newVal[0] ? toDate(newVal[0]) : false;
|
||||
const valEnd = newVal[1] ? toDate(newVal[1]) : false;
|
||||
|
||||
if (valStart && valEnd) {
|
||||
this.handleChange(
|
||||
{
|
||||
hours: valStart.getHours(),
|
||||
minutes: valStart.getMinutes(),
|
||||
seconds: valStart.getSeconds()
|
||||
},
|
||||
{
|
||||
hours: valEnd.getHours(),
|
||||
minutes: valEnd.getMinutes(),
|
||||
seconds: valEnd.getSeconds()
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
panelLabelConfig (date) {
|
||||
const locale = this.t('i.locale');
|
||||
const datePanelLabel = this.t('i.datepicker.datePanelLabel');
|
||||
const { labels, separator } = formatDateLabels(locale, datePanelLabel, date || initTimeDate());
|
||||
return [labels[0].label, separator, labels[1].label].join('');
|
||||
},
|
||||
handleClear() {
|
||||
this.date = initTimeDate();
|
||||
this.dateEnd = initTimeDate();
|
||||
this.hours = '';
|
||||
this.minutes = '';
|
||||
this.seconds = '';
|
||||
this.hoursEnd = '';
|
||||
this.minutesEnd = '';
|
||||
this.secondsEnd = '';
|
||||
},
|
||||
handleChange (date, dateEnd, emit = true) {
|
||||
const oldDateEnd = new Date(this.dateEnd);
|
||||
|
||||
if (date.hours !== undefined) {
|
||||
this.date.setHours(date.hours);
|
||||
this.hours = this.date.getHours();
|
||||
}
|
||||
if (date.minutes !== undefined) {
|
||||
this.date.setMinutes(date.minutes);
|
||||
this.minutes = this.date.getMinutes();
|
||||
}
|
||||
if (date.seconds !== undefined) {
|
||||
this.date.setSeconds(date.seconds);
|
||||
this.seconds = this.date.getSeconds();
|
||||
}
|
||||
if (dateEnd.hours !== undefined) {
|
||||
this.dateEnd.setHours(dateEnd.hours);
|
||||
this.hoursEnd = this.dateEnd.getHours();
|
||||
}
|
||||
if (dateEnd.minutes !== undefined) {
|
||||
this.dateEnd.setMinutes(dateEnd.minutes);
|
||||
this.minutesEnd = this.dateEnd.getMinutes();
|
||||
}
|
||||
if (dateEnd.seconds !== undefined) {
|
||||
this.dateEnd.setSeconds(dateEnd.seconds);
|
||||
this.secondsEnd = this.dateEnd.getSeconds();
|
||||
}
|
||||
// judge endTime > startTime?
|
||||
if (this.dateEnd < this.date) {
|
||||
this.$nextTick(() => {
|
||||
this.dateEnd = new Date(this.date);
|
||||
this.hoursEnd = this.dateEnd.getHours();
|
||||
this.minutesEnd = this.dateEnd.getMinutes();
|
||||
this.secondsEnd = this.dateEnd.getSeconds();
|
||||
|
||||
const format = 'yyyy-MM-dd HH:mm:ss';
|
||||
if (formatDate(oldDateEnd, format) !== formatDate(this.dateEnd, format)) {
|
||||
if (emit) this.$emit('on-pick', [this.date, this.dateEnd], true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (emit) this.$emit('on-pick', [this.date, this.dateEnd], true);
|
||||
}
|
||||
},
|
||||
handleStartChange (date) {
|
||||
this.handleChange(date, {});
|
||||
},
|
||||
handleEndChange (date) {
|
||||
this.handleChange({}, date);
|
||||
},
|
||||
updateScroll () {
|
||||
this.$refs.timeSpinner.updateScroll();
|
||||
this.$refs.timeSpinnerEnd.updateScroll();
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.$parent && this.$parent.$options.name === 'DatePicker') this.showDate = true;
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -1,123 +0,0 @@
|
|||
<template>
|
||||
<div :class="[prefixCls + '-body-wrapper']" @mousedown.prevent>
|
||||
<div :class="[prefixCls + '-body']">
|
||||
<div :class="[timePrefixCls + '-header']" v-if="showDate">{{ visibleDate }}</div>
|
||||
<div :class="[prefixCls + '-content']">
|
||||
<time-spinner
|
||||
ref="timeSpinner"
|
||||
:show-seconds="showSeconds"
|
||||
:steps="steps"
|
||||
:hours="hours"
|
||||
:minutes="minutes"
|
||||
:seconds="seconds"
|
||||
:disabled-hours="disabledHours"
|
||||
:disabled-minutes="disabledMinutes"
|
||||
:disabled-seconds="disabledSeconds"
|
||||
:hide-disabled-options="hideDisabledOptions"
|
||||
@on-change="handleChange"
|
||||
@on-pick-click="handlePickClick"></time-spinner>
|
||||
</div>
|
||||
<Confirm
|
||||
v-if="confirm"
|
||||
@on-pick-clear="handlePickClear"
|
||||
@on-pick-success="handlePickSuccess"></Confirm>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import TimeSpinner from '../base/time-spinner.vue';
|
||||
import Confirm from '../base/confirm.vue';
|
||||
|
||||
import Mixin from './mixin';
|
||||
import Locale from '../../../mixins/locale';
|
||||
|
||||
import { initTimeDate } from '../util';
|
||||
|
||||
const prefixCls = 'ivu-picker-panel';
|
||||
const timePrefixCls = 'ivu-time-picker';
|
||||
|
||||
export default {
|
||||
name: 'TimePicker',
|
||||
mixins: [ Mixin, Locale ],
|
||||
components: { TimeSpinner, Confirm },
|
||||
props: {
|
||||
steps: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
prefixCls: prefixCls,
|
||||
timePrefixCls: timePrefixCls,
|
||||
date: initTimeDate(),
|
||||
value: '',
|
||||
showDate: false,
|
||||
format: 'HH:mm:ss',
|
||||
hours: '',
|
||||
minutes: '',
|
||||
seconds: '',
|
||||
disabledHours: [],
|
||||
disabledMinutes: [],
|
||||
disabledSeconds: [],
|
||||
hideDisabledOptions: false,
|
||||
confirm: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showSeconds () {
|
||||
return (this.format || '').indexOf('ss') !== -1;
|
||||
},
|
||||
visibleDate () {
|
||||
const date = this.date;
|
||||
const month = date.getMonth() + 1;
|
||||
const tYear = this.t('i.datepicker.year');
|
||||
const tMonth = this.t(`i.datepicker.month${month}`);
|
||||
return `${date.getFullYear()}${tYear} ${tMonth}`;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (newVal) {
|
||||
if (!newVal) return;
|
||||
newVal = new Date(newVal);
|
||||
if (!isNaN(newVal)) {
|
||||
this.date = newVal;
|
||||
this.handleChange({
|
||||
hours: newVal.getHours(),
|
||||
minutes: newVal.getMinutes(),
|
||||
seconds: newVal.getSeconds()
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClear() {
|
||||
this.date = initTimeDate();
|
||||
this.hours = '';
|
||||
this.minutes = '';
|
||||
this.seconds = '';
|
||||
},
|
||||
handleChange (date, emit = true) {
|
||||
if (date.hours !== undefined) {
|
||||
this.date.setHours(date.hours);
|
||||
this.hours = this.date.getHours();
|
||||
}
|
||||
if (date.minutes !== undefined) {
|
||||
this.date.setMinutes(date.minutes);
|
||||
this.minutes = this.date.getMinutes();
|
||||
}
|
||||
if (date.seconds !== undefined) {
|
||||
this.date.setSeconds(date.seconds);
|
||||
this.seconds = this.date.getSeconds();
|
||||
}
|
||||
if (emit) this.$emit('on-pick', this.date, true);
|
||||
},
|
||||
updateScroll () {
|
||||
this.$refs.timeSpinner.updateScroll();
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.$parent && this.$parent.$options.name === 'DatePicker') this.showDate = true;
|
||||
}
|
||||
};
|
||||
</script>
|
File diff suppressed because it is too large
Load diff
|
@ -1,18 +1,11 @@
|
|||
import Vue from 'vue';
|
||||
import Picker from '../picker.vue';
|
||||
import DatePanel from '../panel/date.vue';
|
||||
import DateRangePanel from '../panel/date-range.vue';
|
||||
|
||||
const getPanel = function (type) {
|
||||
if (type === 'daterange' || type === 'datetimerange') {
|
||||
return DateRangePanel;
|
||||
}
|
||||
return DatePanel;
|
||||
};
|
||||
import DatePickerPanel from '../panel/Date/date.vue';
|
||||
import RangeDatePickerPanel from '../panel/Date/date-range.vue';
|
||||
|
||||
import { oneOf } from '../../../utils/assist';
|
||||
|
||||
export default {
|
||||
name: 'CalendarPicker',
|
||||
mixins: [Picker],
|
||||
props: {
|
||||
type: {
|
||||
|
@ -21,29 +14,15 @@ export default {
|
|||
},
|
||||
default: 'date'
|
||||
},
|
||||
value: {}
|
||||
},
|
||||
watch: {
|
||||
type(value){
|
||||
const typeMap = {
|
||||
year: 'year',
|
||||
month: 'month',
|
||||
date: 'day'
|
||||
};
|
||||
const validType = oneOf(value, Object.keys(typeMap));
|
||||
if (validType) this.Panel.selectionMode = typeMap[value];
|
||||
components: { DatePickerPanel, RangeDatePickerPanel },
|
||||
computed: {
|
||||
panel(){
|
||||
const isRange = this.type === 'daterange' || this.type === 'datetimerange';
|
||||
return isRange ? 'RangeDatePickerPanel' : 'DatePickerPanel';
|
||||
},
|
||||
ownPickerProps(){
|
||||
return this.options;
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (!this.currentValue) {
|
||||
if (this.type === 'daterange' || this.type === 'datetimerange') {
|
||||
this.currentValue = ['',''];
|
||||
} else {
|
||||
this.currentValue = '';
|
||||
}
|
||||
}
|
||||
|
||||
const panel = getPanel(this.type);
|
||||
this.Panel = new Vue(panel);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,20 +1,13 @@
|
|||
import Vue from 'vue';
|
||||
import Picker from '../picker.vue';
|
||||
import TimePanel from '../panel/time.vue';
|
||||
import TimeRangePanel from '../panel/time-range.vue';
|
||||
import TimePickerPanel from '../panel/Time/time.vue';
|
||||
import RangeTimePickerPanel from '../panel/Time/time-range.vue';
|
||||
import Options from '../time-mixins';
|
||||
|
||||
const getPanel = function (type) {
|
||||
if (type === 'timerange') {
|
||||
return TimeRangePanel;
|
||||
}
|
||||
return TimePanel;
|
||||
};
|
||||
|
||||
import { oneOf } from '../../../utils/assist';
|
||||
import { findComponentsDownward, oneOf } from '../../../utils/assist';
|
||||
|
||||
export default {
|
||||
mixins: [Picker, Options],
|
||||
components: { TimePickerPanel, RangeTimePickerPanel },
|
||||
props: {
|
||||
type: {
|
||||
validator (value) {
|
||||
|
@ -22,25 +15,29 @@ export default {
|
|||
},
|
||||
default: 'time'
|
||||
},
|
||||
steps: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
value: {}
|
||||
},
|
||||
created () {
|
||||
if (!this.currentValue) {
|
||||
if (this.type === 'timerange') {
|
||||
this.currentValue = ['',''];
|
||||
} else {
|
||||
this.currentValue = '';
|
||||
computed: {
|
||||
panel(){
|
||||
const isRange = this.type === 'timerange';
|
||||
return isRange ? 'RangeTimePickerPanel' : 'TimePickerPanel';
|
||||
},
|
||||
ownPickerProps(){
|
||||
return {
|
||||
disabledHours: this.disabledHours,
|
||||
disabledMinutes: this.disabledMinutes,
|
||||
disabledSeconds: this.disabledSeconds,
|
||||
hideDisabledOptions: this.hideDisabledOptions
|
||||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(visible){
|
||||
if (visible) {
|
||||
this.$nextTick(() => {
|
||||
const spinners = findComponentsDownward(this, 'TimeSpinner');
|
||||
spinners.forEach(instance => instance.updateScroll());
|
||||
});
|
||||
}
|
||||
}
|
||||
const Panel = Vue.extend(getPanel(this.type));
|
||||
this.Panel = new Panel({
|
||||
propsData: {
|
||||
steps: this.steps
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -14,6 +14,18 @@ export const toDate = function(date) {
|
|||
return _date;
|
||||
};
|
||||
|
||||
export const clearHours = function (time) {
|
||||
const cloneDate = new Date(time);
|
||||
cloneDate.setHours(0, 0, 0, 0);
|
||||
return cloneDate.getTime();
|
||||
};
|
||||
|
||||
export const isInRange = (time, a, b) => {
|
||||
if (!a || !b) return false;
|
||||
const [start, end] = [a, b].sort();
|
||||
return time >= start && time <= end;
|
||||
};
|
||||
|
||||
export const formatDate = function(date, format) {
|
||||
date = toDate(date);
|
||||
if (!date) return '';
|
||||
|
@ -122,3 +134,125 @@ export const formatDateLabels = (function() {
|
|||
};
|
||||
};
|
||||
})();
|
||||
|
||||
// Parsers and Formaters
|
||||
export const DEFAULT_FORMATS = {
|
||||
date: 'yyyy-MM-dd',
|
||||
month: 'yyyy-MM',
|
||||
year: 'yyyy',
|
||||
datetime: 'yyyy-MM-dd HH:mm:ss',
|
||||
time: 'HH:mm:ss',
|
||||
timerange: 'HH:mm:ss',
|
||||
daterange: 'yyyy-MM-dd',
|
||||
datetimerange: 'yyyy-MM-dd HH:mm:ss'
|
||||
};
|
||||
|
||||
export const RANGE_SEPARATOR = ' - ';
|
||||
|
||||
const DATE_FORMATTER = function(value, format) {
|
||||
return formatDate(value, format);
|
||||
};
|
||||
const DATE_PARSER = function(text, format) {
|
||||
return parseDate(text, format);
|
||||
};
|
||||
const RANGE_FORMATTER = function(value, format) {
|
||||
if (Array.isArray(value) && value.length === 2) {
|
||||
const start = value[0];
|
||||
const end = value[1];
|
||||
|
||||
if (start && end) {
|
||||
return formatDate(start, format) + RANGE_SEPARATOR + formatDate(end, format);
|
||||
}
|
||||
} else if (!Array.isArray(value) && value instanceof Date){
|
||||
return formatDate(value, format);
|
||||
}
|
||||
return '';
|
||||
};
|
||||
const RANGE_PARSER = function(text, format) {
|
||||
const array = Array.isArray(text) ? text : text.split(RANGE_SEPARATOR);
|
||||
if (array.length === 2) {
|
||||
const range1 = array[0];
|
||||
const range2 = array[1];
|
||||
|
||||
return [
|
||||
range1 instanceof Date ? range1 : parseDate(range1, format),
|
||||
range2 instanceof Date ? range2 : parseDate(range2, format),
|
||||
];
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
export const TYPE_VALUE_RESOLVER_MAP = {
|
||||
default: {
|
||||
formatter(value) {
|
||||
if (!value) return '';
|
||||
return '' + value;
|
||||
},
|
||||
parser(text) {
|
||||
if (text === undefined || text === '') return null;
|
||||
return text;
|
||||
}
|
||||
},
|
||||
date: {
|
||||
formatter: DATE_FORMATTER,
|
||||
parser: DATE_PARSER
|
||||
},
|
||||
datetime: {
|
||||
formatter: DATE_FORMATTER,
|
||||
parser: DATE_PARSER
|
||||
},
|
||||
daterange: {
|
||||
formatter: RANGE_FORMATTER,
|
||||
parser: RANGE_PARSER
|
||||
},
|
||||
datetimerange: {
|
||||
formatter: RANGE_FORMATTER,
|
||||
parser: RANGE_PARSER
|
||||
},
|
||||
timerange: {
|
||||
formatter: RANGE_FORMATTER,
|
||||
parser: RANGE_PARSER
|
||||
},
|
||||
time: {
|
||||
formatter: DATE_FORMATTER,
|
||||
parser: DATE_PARSER
|
||||
},
|
||||
month: {
|
||||
formatter: DATE_FORMATTER,
|
||||
parser: DATE_PARSER
|
||||
},
|
||||
year: {
|
||||
formatter: DATE_FORMATTER,
|
||||
parser: DATE_PARSER
|
||||
},
|
||||
multiple: {
|
||||
formatter: (value, format) => {
|
||||
return value.filter(Boolean).map(date => formatDate(date, format)).join(',');
|
||||
},
|
||||
parser: (value, format) => {
|
||||
const values = typeof value === 'string' ? value.split(',') : value;
|
||||
return values.map(value => {
|
||||
if (value instanceof Date) return value;
|
||||
if (typeof value === 'string') value = value.trim();
|
||||
else if (typeof value !== 'number' && !value) value = '';
|
||||
return parseDate(value, format);
|
||||
});
|
||||
}
|
||||
},
|
||||
number: {
|
||||
formatter(value) {
|
||||
if (!value) return '';
|
||||
return '' + value;
|
||||
},
|
||||
parser(text) {
|
||||
let result = Number(text);
|
||||
|
||||
if (!isNaN(text)) {
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
57
src/components/divider/divider.vue
Normal file
57
src/components/divider/divider.vue
Normal file
|
@ -0,0 +1,57 @@
|
|||
<template>
|
||||
<div :class="classes">
|
||||
<span v-if="hasSlot" :class="slotClasses">
|
||||
<slot></slot>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {oneOf} from '../../utils/assist';
|
||||
|
||||
const prefixCls = 'ivu-divider';
|
||||
|
||||
export default {
|
||||
name: 'Divider',
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'horizontal',
|
||||
validator (value) {
|
||||
return oneOf(value, ['horizontal', 'vertical']);
|
||||
}
|
||||
},
|
||||
orientation: {
|
||||
type: String,
|
||||
default: 'center',
|
||||
validator (value) {
|
||||
return oneOf(value, ['left', 'right', 'center']);
|
||||
}
|
||||
},
|
||||
dashed: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasSlot() {
|
||||
return !!this.$slots.default;
|
||||
},
|
||||
classes() {
|
||||
return [
|
||||
`${prefixCls}`,
|
||||
`${prefixCls}-${this.type}`,
|
||||
{
|
||||
[`${prefixCls}-with-text-${this.orientation}`]: this.hasSlot,
|
||||
[`${prefixCls}-dashed`]: !!this.dashed
|
||||
}
|
||||
];
|
||||
},
|
||||
slotClasses() {
|
||||
return [
|
||||
`${prefixCls}-inner-text`,
|
||||
];
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
3
src/components/divider/index.js
Normal file
3
src/components/divider/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Divider from './divider.vue';
|
||||
|
||||
export default Divider;
|
223
src/components/drawer/drawer.vue
Normal file
223
src/components/drawer/drawer.vue
Normal file
|
@ -0,0 +1,223 @@
|
|||
<template>
|
||||
<div v-transfer-dom :data-transfer="transfer">
|
||||
<transition name="fade">
|
||||
<div :class="maskClasses" :style="maskStyle" v-show="visible" v-if="mask" @click="handleMask"></div>
|
||||
</transition>
|
||||
<div :class="wrapClasses" @click="handleWrapClick">
|
||||
<transition :name="'move-' + placement">
|
||||
<div :class="classes" :style="mainStyles" v-show="visible">
|
||||
<div :class="contentClasses" ref="content">
|
||||
<a class="ivu-drawer-close" v-if="closable" @click="close">
|
||||
<slot name="close">
|
||||
<Icon type="ios-close"></Icon>
|
||||
</slot>
|
||||
</a>
|
||||
<div :class="[prefixCls + '-header']" v-if="showHead"><slot name="header"><div :class="[prefixCls + '-header-inner']">{{ title }}</div></slot></div>
|
||||
<div :class="[prefixCls + '-body']" :style="styles"><slot></slot></div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Icon from '../icon';
|
||||
import { oneOf } from '../../utils/assist';
|
||||
import TransferDom from '../../directives/transfer-dom';
|
||||
import Emitter from '../../mixins/emitter';
|
||||
import ScrollbarMixins from '../modal/mixins-scrollbar';
|
||||
|
||||
const prefixCls = 'ivu-drawer';
|
||||
|
||||
export default {
|
||||
name: 'Drawer',
|
||||
mixins: [ Emitter, ScrollbarMixins ],
|
||||
components: { Icon },
|
||||
directives: { TransferDom },
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String
|
||||
},
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: 256
|
||||
},
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
maskClosable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
mask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
maskStyle: {
|
||||
type: Object
|
||||
},
|
||||
styles: {
|
||||
type: Object
|
||||
},
|
||||
scrollable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
placement: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['left', 'right']);
|
||||
},
|
||||
default: 'right'
|
||||
},
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 1000
|
||||
},
|
||||
transfer: {
|
||||
type: Boolean,
|
||||
default () {
|
||||
return !this.$IVIEW || this.$IVIEW.transfer === '' ? true : this.$IVIEW.transfer;
|
||||
}
|
||||
},
|
||||
className: {
|
||||
type: String
|
||||
},
|
||||
inner: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
prefixCls: prefixCls,
|
||||
visible: this.value,
|
||||
wrapShow: false,
|
||||
showHead: true,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
wrapClasses () {
|
||||
return [
|
||||
`${prefixCls}-wrap`,
|
||||
{
|
||||
[`${prefixCls}-hidden`]: !this.wrapShow,
|
||||
[`${this.className}`]: !!this.className,
|
||||
[`${prefixCls}-no-mask`]: !this.mask,
|
||||
[`${prefixCls}-wrap-inner`]: this.inner
|
||||
}
|
||||
];
|
||||
},
|
||||
mainStyles () {
|
||||
let style = {};
|
||||
|
||||
const width = parseInt(this.width);
|
||||
|
||||
const styleWidth = {
|
||||
width: width <= 100 ? `${width}%` : `${width}px`
|
||||
};
|
||||
|
||||
Object.assign(style, styleWidth);
|
||||
|
||||
return style;
|
||||
},
|
||||
contentClasses () {
|
||||
return [
|
||||
`${prefixCls}-content`,
|
||||
{
|
||||
[`${prefixCls}-content-no-mask`]: !this.mask
|
||||
}
|
||||
];
|
||||
},
|
||||
classes () {
|
||||
return [
|
||||
`${prefixCls}`,
|
||||
`${prefixCls}-${this.placement}`,
|
||||
{
|
||||
[`${prefixCls}-no-header`]: !this.showHead,
|
||||
[`${prefixCls}-inner`]: this.inner
|
||||
}
|
||||
];
|
||||
},
|
||||
maskClasses () {
|
||||
return [
|
||||
`${prefixCls}-mask`,
|
||||
{
|
||||
[`${prefixCls}-mask-inner`]: this.inner
|
||||
}
|
||||
];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.visible = false;
|
||||
this.$emit('input', false);
|
||||
this.$emit('on-close');
|
||||
},
|
||||
handleMask () {
|
||||
if (this.maskClosable && this.mask) {
|
||||
this.close();
|
||||
}
|
||||
},
|
||||
handleWrapClick (event) {
|
||||
// use indexOf,do not use === ,because ivu-modal-wrap can have other custom className
|
||||
const className = event.target.getAttribute('class');
|
||||
if (className && className.indexOf(`${prefixCls}-wrap`) > -1) this.handleMask();
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
if (this.visible) {
|
||||
this.wrapShow = true;
|
||||
}
|
||||
|
||||
let showHead = true;
|
||||
|
||||
if (this.$slots.header === undefined && !this.title) {
|
||||
showHead = false;
|
||||
}
|
||||
|
||||
this.showHead = showHead;
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.removeScrollEffect();
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
this.visible = val;
|
||||
},
|
||||
visible (val) {
|
||||
if (val === false) {
|
||||
this.timer = setTimeout(() => {
|
||||
this.wrapShow = false;
|
||||
this.removeScrollEffect();
|
||||
}, 300);
|
||||
} else {
|
||||
if (this.timer) clearTimeout(this.timer);
|
||||
this.wrapShow = true;
|
||||
if (!this.scrollable) {
|
||||
this.addScrollEffect();
|
||||
}
|
||||
}
|
||||
this.broadcast('Table', 'on-visible-change', val);
|
||||
this.broadcast('Slider', 'on-visible-change', val); // #2852
|
||||
this.$emit('on-visible-change', val);
|
||||
},
|
||||
scrollable (val) {
|
||||
if (!val) {
|
||||
this.addScrollEffect();
|
||||
} else {
|
||||
this.removeScrollEffect();
|
||||
}
|
||||
},
|
||||
title (val) {
|
||||
if (this.$slots.header === undefined) {
|
||||
this.showHead = !!val;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
3
src/components/drawer/index.js
Normal file
3
src/components/drawer/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Drawer from './drawer.vue';
|
||||
|
||||
export default Drawer;
|
|
@ -3,7 +3,7 @@
|
|||
</template>
|
||||
<script>
|
||||
const prefixCls = 'ivu-dropdown-item';
|
||||
|
||||
import { findComponentUpward } from '../../utils/assist';
|
||||
export default {
|
||||
name: 'DropdownItem',
|
||||
props: {
|
||||
|
@ -37,7 +37,7 @@
|
|||
},
|
||||
methods: {
|
||||
handleClick () {
|
||||
const $parent = this.$parent.$parent.$parent;
|
||||
const $parent = findComponentUpward(this, 'Dropdown');
|
||||
const hasChildren = this.$parent && this.$parent.$options.name === 'Dropdown';
|
||||
|
||||
if (this.disabled) {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<template>
|
||||
<div
|
||||
:class="[prefixCls]"
|
||||
v-clickoutside="onClickoutside"
|
||||
v-click-outside="onClickoutside"
|
||||
@mouseenter="handleMouseenter"
|
||||
@mouseleave="handleMouseleave">
|
||||
<div :class="[prefixCls + '-rel']" ref="reference" @click="handleClick"><slot></slot></div>
|
||||
<transition :name="transition">
|
||||
<div :class="relClasses" ref="reference" @click="handleClick" @contextmenu.prevent="handleRightClick"><slot></slot></div>
|
||||
<transition name="transition-drop">
|
||||
<Drop
|
||||
:class="dropdownCls"
|
||||
v-show="currentVisible"
|
||||
|
@ -14,13 +14,14 @@
|
|||
@mouseenter.native="handleMouseenter"
|
||||
@mouseleave.native="handleMouseleave"
|
||||
:data-transfer="transfer"
|
||||
:transfer="transfer"
|
||||
v-transfer-dom><slot name="list"></slot></Drop>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Drop from '../select/dropdown.vue';
|
||||
import clickoutside from '../../directives/clickoutside';
|
||||
import {directive as clickOutside} from 'v-click-outside-x';
|
||||
import TransferDom from '../../directives/transfer-dom';
|
||||
import { oneOf, findComponentUpward } from '../../utils/assist';
|
||||
|
||||
|
@ -28,12 +29,12 @@
|
|||
|
||||
export default {
|
||||
name: 'Dropdown',
|
||||
directives: { clickoutside, TransferDom },
|
||||
directives: { clickOutside, TransferDom },
|
||||
components: { Drop },
|
||||
props: {
|
||||
trigger: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['click', 'hover', 'custom']);
|
||||
return oneOf(value, ['click', 'hover', 'custom', 'contextMenu']);
|
||||
},
|
||||
default: 'hover'
|
||||
},
|
||||
|
@ -49,7 +50,9 @@
|
|||
},
|
||||
transfer: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default () {
|
||||
return !this.$IVIEW || this.$IVIEW.transfer === '' ? false : this.$IVIEW.transfer;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -60,6 +63,14 @@
|
|||
return {
|
||||
[prefixCls + '-transfer']: this.transfer
|
||||
};
|
||||
},
|
||||
relClasses () {
|
||||
return [
|
||||
`${prefixCls}-rel`,
|
||||
{
|
||||
[`${prefixCls}-rel-user-select-none`]: this.trigger === 'contextMenu'
|
||||
}
|
||||
];
|
||||
}
|
||||
},
|
||||
data () {
|
||||
|
@ -89,6 +100,13 @@
|
|||
}
|
||||
this.currentVisible = !this.currentVisible;
|
||||
},
|
||||
handleRightClick () {
|
||||
if (this.trigger === 'custom') return false;
|
||||
if (this.trigger !== 'contextMenu') {
|
||||
return false;
|
||||
}
|
||||
this.currentVisible = !this.currentVisible;
|
||||
},
|
||||
handleMouseenter () {
|
||||
if (this.trigger === 'custom') return false;
|
||||
if (this.trigger !== 'hover') {
|
||||
|
@ -113,6 +131,7 @@
|
|||
},
|
||||
onClickoutside (e) {
|
||||
this.handleClose();
|
||||
this.handleRightClose();
|
||||
if (this.currentVisible) this.$emit('on-clickoutside', e);
|
||||
},
|
||||
handleClose () {
|
||||
|
@ -122,6 +141,13 @@
|
|||
}
|
||||
this.currentVisible = false;
|
||||
},
|
||||
handleRightClose () {
|
||||
if (this.trigger === 'custom') return false;
|
||||
if (this.trigger !== 'contextMenu') {
|
||||
return false;
|
||||
}
|
||||
this.currentVisible = false;
|
||||
},
|
||||
hasParent () {
|
||||
// const $parent = this.$parent.$parent.$parent;
|
||||
const $parent = findComponentUpward(this, 'Dropdown');
|
||||
|
|
|
@ -90,8 +90,12 @@
|
|||
},
|
||||
validateStatus (val) {
|
||||
this.validateState = val;
|
||||
},
|
||||
rules (){
|
||||
this.setRules();
|
||||
}
|
||||
},
|
||||
inject: ['form'],
|
||||
computed: {
|
||||
classes () {
|
||||
return [
|
||||
|
@ -103,13 +107,13 @@
|
|||
}
|
||||
];
|
||||
},
|
||||
form() {
|
||||
let parent = this.$parent;
|
||||
while (parent.$options.name !== 'iForm') {
|
||||
parent = parent.$parent;
|
||||
}
|
||||
return parent;
|
||||
},
|
||||
// form() {
|
||||
// let parent = this.$parent;
|
||||
// while (parent.$options.name !== 'iForm') {
|
||||
// parent = parent.$parent;
|
||||
// }
|
||||
// return parent;
|
||||
// },
|
||||
fieldValue: {
|
||||
cache: false,
|
||||
get() {
|
||||
|
@ -126,22 +130,40 @@
|
|||
},
|
||||
labelStyles () {
|
||||
let style = {};
|
||||
const labelWidth = this.labelWidth || this.form.labelWidth;
|
||||
if (labelWidth) {
|
||||
const labelWidth = this.labelWidth === 0 || this.labelWidth ? this.labelWidth : this.form.labelWidth;
|
||||
|
||||
if (labelWidth || labelWidth === 0) {
|
||||
style.width = `${labelWidth}px`;
|
||||
}
|
||||
return style;
|
||||
},
|
||||
contentStyles () {
|
||||
let style = {};
|
||||
const labelWidth = this.labelWidth || this.form.labelWidth;
|
||||
if (labelWidth) {
|
||||
const labelWidth = this.labelWidth === 0 || this.labelWidth ? this.labelWidth : this.form.labelWidth;
|
||||
|
||||
if (labelWidth || labelWidth === 0) {
|
||||
style.marginLeft = `${labelWidth}px`;
|
||||
}
|
||||
return style;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setRules() {
|
||||
let rules = this.getRules();
|
||||
if (rules.length&&this.required) {
|
||||
return;
|
||||
}else if (rules.length) {
|
||||
rules.every((rule) => {
|
||||
this.isRequired = rule.required;
|
||||
});
|
||||
}else if (this.required){
|
||||
this.isRequired = this.required;
|
||||
}
|
||||
this.$off('on-form-blur', this.onFieldBlur);
|
||||
this.$off('on-form-change', this.onFieldChange);
|
||||
this.$on('on-form-blur', this.onFieldBlur);
|
||||
this.$on('on-form-change', this.onFieldChange);
|
||||
},
|
||||
getRules () {
|
||||
let formRules = this.form.rules;
|
||||
const selfRules = this.rules;
|
||||
|
@ -156,10 +178,14 @@
|
|||
return rules.filter(rule => !rule.trigger || rule.trigger.indexOf(trigger) !== -1);
|
||||
},
|
||||
validate(trigger, callback = function () {}) {
|
||||
const rules = this.getFilteredRule(trigger);
|
||||
let rules = this.getFilteredRule(trigger);
|
||||
if (!rules || rules.length === 0) {
|
||||
callback();
|
||||
return true;
|
||||
if (!this.required) {
|
||||
callback();
|
||||
return true;
|
||||
}else {
|
||||
rules = [{required: true}];
|
||||
}
|
||||
}
|
||||
|
||||
this.validateState = 'validating';
|
||||
|
@ -228,18 +254,7 @@
|
|||
value: this.fieldValue
|
||||
});
|
||||
|
||||
let rules = this.getRules();
|
||||
|
||||
if (rules.length) {
|
||||
rules.every(rule => {
|
||||
if (rule.required) {
|
||||
this.isRequired = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
this.$on('on-form-blur', this.onFieldBlur);
|
||||
this.$on('on-form-change', this.onFieldChange);
|
||||
}
|
||||
this.setRules();
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
<form :class="classes" :autocomplete="autocomplete"><slot></slot></form>
|
||||
</template>
|
||||
<script>
|
||||
// https://github.com/ElemeFE/element/blob/dev/packages/form/src/form.vue
|
||||
import { oneOf } from '../../utils/assist';
|
||||
|
||||
const prefixCls = 'ivu-form';
|
||||
|
@ -40,6 +39,9 @@
|
|||
default: 'off'
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
return { form : this };
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
fields: []
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { oneOf, findComponentsDownward } from '../../utils/assist';
|
||||
import { oneOf, findComponentDownward, findBrothersComponents } from '../../utils/assist';
|
||||
|
||||
const prefixCls = 'ivu-row';
|
||||
|
||||
|
@ -58,7 +58,10 @@
|
|||
},
|
||||
methods: {
|
||||
updateGutter (val) {
|
||||
const Cols = findComponentsDownward(this, 'iCol');
|
||||
// 这里会嵌套寻找,把 Col 里的 Row 里的 Col 也找到,所以用 兄弟找
|
||||
// const Cols = findComponentsDownward(this, 'iCol');
|
||||
const Col = findComponentDownward(this, 'iCol');
|
||||
const Cols = findBrothersComponents(Col, 'iCol', false);
|
||||
if (Cols.length) {
|
||||
Cols.forEach((child) => {
|
||||
if (val !== 0) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<i :class="classes" :style="styles"></i>
|
||||
<i :class="classes" :style="styles" @click="handleClick"></i>
|
||||
</template>
|
||||
<script>
|
||||
const prefixCls = 'ivu-icon';
|
||||
|
@ -7,13 +7,26 @@
|
|||
export default {
|
||||
name: 'Icon',
|
||||
props: {
|
||||
type: String,
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
size: [Number, String],
|
||||
color: String
|
||||
color: String,
|
||||
custom: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
classes () {
|
||||
return `${prefixCls} ${prefixCls}-${this.type}`;
|
||||
return [
|
||||
`${prefixCls}`,
|
||||
{
|
||||
[`${prefixCls}-${this.type}`]: this.type !== '',
|
||||
[`${this.custom}`]: this.custom !== '',
|
||||
}
|
||||
];
|
||||
},
|
||||
styles () {
|
||||
let style = {};
|
||||
|
@ -28,6 +41,11 @@
|
|||
|
||||
return style;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick (event) {
|
||||
this.$emit('click', event);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -3,13 +3,11 @@
|
|||
<div :class="handlerClasses">
|
||||
<a
|
||||
@click="up"
|
||||
@mousedown="preventDefault"
|
||||
:class="upClasses">
|
||||
<span :class="innerUpClasses" @click="preventDefault"></span>
|
||||
</a>
|
||||
<a
|
||||
@click="down"
|
||||
@mousedown="preventDefault"
|
||||
:class="downClasses">
|
||||
<span :class="innerDownClasses" @click="preventDefault"></span>
|
||||
</a>
|
||||
|
@ -26,15 +24,17 @@
|
|||
@blur="blur"
|
||||
@keydown.stop="keyDown"
|
||||
@input="change"
|
||||
@mouseup="preventDefault"
|
||||
@change="change"
|
||||
:readonly="readonly || !editable"
|
||||
:name="name"
|
||||
:value="precisionValue">
|
||||
:value="formatterValue"
|
||||
:placeholder="placeholder">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { oneOf } from '../../utils/assist';
|
||||
import { oneOf, findComponentUpward } from '../../utils/assist';
|
||||
import Emitter from '../../mixins/emitter';
|
||||
|
||||
const prefixCls = 'ivu-input-number';
|
||||
|
@ -80,6 +80,10 @@
|
|||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
activeChange: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
value: {
|
||||
type: Number,
|
||||
default: 1
|
||||
|
@ -87,6 +91,9 @@
|
|||
size: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['small', 'large', 'default']);
|
||||
},
|
||||
default () {
|
||||
return !this.$IVIEW || this.$IVIEW.size === '' ? 'default' : this.$IVIEW.size;
|
||||
}
|
||||
},
|
||||
disabled: {
|
||||
|
@ -113,7 +120,17 @@
|
|||
},
|
||||
elementId: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
formatter: {
|
||||
type: Function
|
||||
},
|
||||
parser: {
|
||||
type: Function
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
@ -169,7 +186,15 @@
|
|||
},
|
||||
precisionValue () {
|
||||
// can not display 1.0
|
||||
if(!this.currentValue) return this.currentValue;
|
||||
return this.precision ? this.currentValue.toFixed(this.precision) : this.currentValue;
|
||||
},
|
||||
formatterValue () {
|
||||
if (this.formatter && this.precisionValue !== null) {
|
||||
return this.formatter(this.precisionValue);
|
||||
} else {
|
||||
return this.precisionValue;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -228,7 +253,16 @@
|
|||
},
|
||||
setValue (val) {
|
||||
// 如果 step 是小数,且没有设置 precision,是有问题的
|
||||
if (!isNaN(this.precision)) val = Number(Number(val).toFixed(this.precision));
|
||||
if (val && !isNaN(this.precision)) val = Number(Number(val).toFixed(this.precision));
|
||||
|
||||
const {min, max} = this;
|
||||
if (val!==null) {
|
||||
if (val > max) {
|
||||
val = max;
|
||||
} else if (val < min) {
|
||||
val = min;
|
||||
}
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.currentValue = val;
|
||||
|
@ -237,13 +271,16 @@
|
|||
this.dispatch('FormItem', 'on-form-change', val);
|
||||
});
|
||||
},
|
||||
focus () {
|
||||
focus (event) {
|
||||
this.focused = true;
|
||||
this.$emit('on-focus');
|
||||
this.$emit('on-focus', event);
|
||||
},
|
||||
blur () {
|
||||
this.focused = false;
|
||||
this.$emit('on-blur');
|
||||
if (!findComponentUpward(this, ['DatePicker', 'TimePicker', 'Cascader', 'Search'])) {
|
||||
this.dispatch('FormItem', 'on-form-blur', this.currentValue);
|
||||
}
|
||||
},
|
||||
keyDown (e) {
|
||||
if (e.keyCode === 38) {
|
||||
|
@ -255,29 +292,25 @@
|
|||
}
|
||||
},
|
||||
change (event) {
|
||||
|
||||
if (event.type == 'input' && !this.activeChange) return;
|
||||
let val = event.target.value.trim();
|
||||
|
||||
if (event.type == 'input' && val.match(/^\-?\.?$|\.$/)) return; // prevent fire early if decimal. If no more input the change event will fire later
|
||||
|
||||
const {min, max} = this;
|
||||
const isEmptyString = val.length === 0;
|
||||
val = Number(val);
|
||||
|
||||
if (event.type == 'change'){
|
||||
if (val === this.currentValue && val > min && val < max) return; // already fired change for input event
|
||||
if (this.parser) {
|
||||
val = this.parser(val);
|
||||
}
|
||||
|
||||
if (!isNaN(val) && !isEmptyString) {
|
||||
this.currentValue = val;
|
||||
const isEmptyString = val.length === 0;
|
||||
if(isEmptyString){
|
||||
this.setValue(null);
|
||||
return;
|
||||
}
|
||||
if (event.type == 'input' && val.match(/^\-?\.?$|\.$/)) return; // prevent fire early if decimal. If no more input the change event will fire later
|
||||
|
||||
if (event.type == 'input' && val < min) return; // prevent fire early in case user is typing a bigger number. Change will handle this otherwise.
|
||||
if (val > max) {
|
||||
this.setValue(max);
|
||||
} else if (val < min) {
|
||||
this.setValue(min);
|
||||
} else {
|
||||
this.setValue(val);
|
||||
}
|
||||
val = Number(val);
|
||||
|
||||
if (!isNaN(val)) {
|
||||
this.currentValue = val;
|
||||
this.setValue(val);
|
||||
} else {
|
||||
event.target.value = this.currentValue;
|
||||
}
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
<div :class="wrapClasses">
|
||||
<template v-if="type !== 'textarea'">
|
||||
<div :class="[prefixCls + '-group-prepend']" v-if="prepend" v-show="slotReady"><slot name="prepend"></slot></div>
|
||||
<i class="ivu-icon" :class="['ivu-icon-ios-close', prefixCls + '-icon', prefixCls + '-icon-clear' , prefixCls + '-icon-normal']" v-if="clearable" @click="handleClear"></i>
|
||||
<i class="ivu-icon" :class="['ivu-icon-ios-close-circle', prefixCls + '-icon', prefixCls + '-icon-clear' , prefixCls + '-icon-normal']" v-if="clearable && currentValue" @click="handleClear"></i>
|
||||
<i class="ivu-icon" :class="['ivu-icon-' + icon, prefixCls + '-icon', prefixCls + '-icon-normal']" v-else-if="icon" @click="handleIconClick"></i>
|
||||
<i class="ivu-icon ivu-icon-ios-search" :class="[prefixCls + '-icon', prefixCls + '-icon-normal', prefixCls + '-search-icon']" v-else-if="search && enterButton === false" @click="handleSearch"></i>
|
||||
<span class="ivu-input-suffix" v-else-if="showSuffix"><slot name="suffix"><i class="ivu-icon" :class="['ivu-icon-' + suffix]" v-if="suffix"></i></slot></span>
|
||||
<transition name="fade">
|
||||
<i class="ivu-icon ivu-icon-load-c ivu-load-loop" :class="[prefixCls + '-icon', prefixCls + '-icon-validate']" v-if="!icon"></i>
|
||||
<i class="ivu-icon ivu-icon-ios-loading ivu-load-loop" :class="[prefixCls + '-icon', prefixCls + '-icon-validate']" v-if="!icon"></i>
|
||||
</transition>
|
||||
<input
|
||||
:id="elementId"
|
||||
|
@ -31,10 +33,16 @@
|
|||
@input="handleInput"
|
||||
@change="handleChange">
|
||||
<div :class="[prefixCls + '-group-append']" v-if="append" v-show="slotReady"><slot name="append"></slot></div>
|
||||
<div :class="[prefixCls + '-group-append', prefixCls + '-search']" v-else-if="search && enterButton" @click="handleSearch">
|
||||
<i class="ivu-icon ivu-icon-ios-search" v-if="enterButton === true"></i>
|
||||
<template v-else>{{ enterButton }}</template>
|
||||
</div>
|
||||
<span class="ivu-input-prefix" v-else-if="showPrefix"><slot name="prefix"><i class="ivu-icon" :class="['ivu-icon-' + prefix]" v-if="prefix"></i></slot></span>
|
||||
</template>
|
||||
<textarea
|
||||
v-else
|
||||
:id="elementId"
|
||||
:wrap="wrap"
|
||||
:autocomplete="autocomplete"
|
||||
:spellcheck="spellcheck"
|
||||
ref="textarea"
|
||||
|
@ -82,6 +90,9 @@
|
|||
size: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['small', 'large', 'default']);
|
||||
},
|
||||
default () {
|
||||
return !this.$IVIEW || this.$IVIEW.size === '' ? 'default' : this.$IVIEW.size;
|
||||
}
|
||||
},
|
||||
placeholder: {
|
||||
|
@ -135,6 +146,28 @@
|
|||
},
|
||||
elementId: {
|
||||
type: String
|
||||
},
|
||||
wrap: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['hard', 'soft']);
|
||||
},
|
||||
default: 'soft'
|
||||
},
|
||||
prefix: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
suffix: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
search: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
enterButton: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
|
@ -144,7 +177,9 @@
|
|||
prepend: true,
|
||||
append: true,
|
||||
slotReady: false,
|
||||
textareaStyles: {}
|
||||
textareaStyles: {},
|
||||
showPrefix: false,
|
||||
showSuffix: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -154,11 +189,12 @@
|
|||
{
|
||||
[`${prefixCls}-wrapper-${this.size}`]: !!this.size,
|
||||
[`${prefixCls}-type`]: this.type,
|
||||
[`${prefixCls}-group`]: this.prepend || this.append,
|
||||
[`${prefixCls}-group-${this.size}`]: (this.prepend || this.append) && !!this.size,
|
||||
[`${prefixCls}-group`]: this.prepend || this.append || (this.search && this.enterButton),
|
||||
[`${prefixCls}-group-${this.size}`]: (this.prepend || this.append || (this.search && this.enterButton)) && !!this.size,
|
||||
[`${prefixCls}-group-with-prepend`]: this.prepend,
|
||||
[`${prefixCls}-group-with-append`]: this.append,
|
||||
[`${prefixCls}-hide-icon`]: this.append // #554
|
||||
[`${prefixCls}-group-with-append`]: this.append || (this.search && this.enterButton),
|
||||
[`${prefixCls}-hide-icon`]: this.append, // #554
|
||||
[`${prefixCls}-with-search`]: (this.search && this.enterButton)
|
||||
}
|
||||
];
|
||||
},
|
||||
|
@ -167,7 +203,9 @@
|
|||
`${prefixCls}`,
|
||||
{
|
||||
[`${prefixCls}-${this.size}`]: !!this.size,
|
||||
[`${prefixCls}-disabled`]: this.disabled
|
||||
[`${prefixCls}-disabled`]: this.disabled,
|
||||
[`${prefixCls}-with-prefix`]: this.showPrefix,
|
||||
[`${prefixCls}-with-suffix`]: this.showSuffix || (this.search && this.enterButton === false)
|
||||
}
|
||||
];
|
||||
},
|
||||
|
@ -183,6 +221,7 @@
|
|||
methods: {
|
||||
handleEnter (event) {
|
||||
this.$emit('on-enter', event);
|
||||
if (this.search) this.$emit('on-search', this.currentValue);
|
||||
},
|
||||
handleKeydown (event) {
|
||||
this.$emit('on-keydown', event);
|
||||
|
@ -207,7 +246,7 @@
|
|||
},
|
||||
handleInput (event) {
|
||||
let value = event.target.value;
|
||||
if (this.number) value = Number.isNaN(Number(value)) ? value : Number(value);
|
||||
if (this.number && value !== '') value = Number.isNaN(Number(value)) ? value : Number(value);
|
||||
this.$emit('input', value);
|
||||
this.setCurrentValue(value);
|
||||
this.$emit('on-change', event);
|
||||
|
@ -255,6 +294,11 @@
|
|||
this.$emit('input', '');
|
||||
this.setCurrentValue('');
|
||||
this.$emit('on-change', e);
|
||||
},
|
||||
handleSearch () {
|
||||
if (this.disabled) return false;
|
||||
this.$refs.input.focus();
|
||||
this.$emit('on-search', this.currentValue);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@ -266,6 +310,8 @@
|
|||
if (this.type !== 'textarea') {
|
||||
this.prepend = this.$slots.prepend !== undefined;
|
||||
this.append = this.$slots.append !== undefined;
|
||||
this.showPrefix = this.prefix !== '' || this.$slots.prefix !== undefined;
|
||||
this.showSuffix = this.suffix !== '' || this.$slots.suffix !== undefined;
|
||||
} else {
|
||||
this.prepend = false;
|
||||
this.append = false;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
:class="wrapClasses"
|
||||
:style="wrapStyles">
|
||||
<span v-show="showZeroTrigger" @click="toggleCollapse" :class="zeroWidthTriggerClasses">
|
||||
<i class="ivu-icon ivu-icon-navicon-round"></i>
|
||||
<i class="ivu-icon ivu-icon-ios-menu"></i>
|
||||
</span>
|
||||
<div :class="childClasses">
|
||||
<slot></slot>
|
||||
|
@ -61,8 +61,7 @@
|
|||
data () {
|
||||
return {
|
||||
prefixCls: prefixCls,
|
||||
mediaMatched: false,
|
||||
isCollapsed: false
|
||||
mediaMatched: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -70,7 +69,7 @@
|
|||
return [
|
||||
`${prefixCls}`,
|
||||
this.siderWidth ? '' : `${prefixCls}-zero-width`,
|
||||
this.isCollapsed ? `${prefixCls}-collapsed` : ''
|
||||
this.value ? `${prefixCls}-collapsed` : ''
|
||||
];
|
||||
},
|
||||
wrapStyles () {
|
||||
|
@ -84,7 +83,7 @@
|
|||
triggerClasses () {
|
||||
return [
|
||||
`${prefixCls}-trigger`,
|
||||
this.isCollapsed ? `${prefixCls}-trigger-collapsed` : '',
|
||||
this.value ? `${prefixCls}-trigger-collapsed` : '',
|
||||
];
|
||||
},
|
||||
childClasses () {
|
||||
|
@ -99,15 +98,15 @@
|
|||
triggerIconClasses () {
|
||||
return [
|
||||
'ivu-icon',
|
||||
`ivu-icon-chevron-${this.reverseArrow ? 'right' : 'left'}`,
|
||||
`ivu-icon-ios-arrow-${this.reverseArrow ? 'forward' : 'back'}`,
|
||||
`${prefixCls}-trigger-icon`,
|
||||
];
|
||||
},
|
||||
siderWidth () {
|
||||
return this.collapsible ? (this.isCollapsed ? (this.mediaMatched ? 0 : parseInt(this.collapsedWidth)) : parseInt(this.width)) : this.width;
|
||||
return this.collapsible ? (this.value ? (this.mediaMatched ? 0 : parseInt(this.collapsedWidth)) : parseInt(this.width)) : this.width;
|
||||
},
|
||||
showZeroTrigger () {
|
||||
return this.collapsible ? (this.mediaMatched && !this.hideTrigger || (parseInt(this.collapsedWidth) === 0) && this.isCollapsed && !this.hideTrigger) : false;
|
||||
return this.collapsible ? (this.mediaMatched && !this.hideTrigger || (parseInt(this.collapsedWidth) === 0) && this.value && !this.hideTrigger) : false;
|
||||
},
|
||||
showBottomTrigger () {
|
||||
return this.collapsible ? !this.mediaMatched && !this.hideTrigger : false;
|
||||
|
@ -115,9 +114,8 @@
|
|||
},
|
||||
methods: {
|
||||
toggleCollapse () {
|
||||
this.isCollapsed = this.collapsible ? !this.isCollapsed : false;
|
||||
this.$emit('input', this.isCollapsed);
|
||||
this.$emit('on-collapse', this.isCollapsed);
|
||||
let value = this.collapsible ? !this.value : false;
|
||||
this.$emit('input', value);
|
||||
},
|
||||
matchMedia () {
|
||||
let matchMedia;
|
||||
|
@ -128,23 +126,21 @@
|
|||
this.mediaMatched = matchMedia(`(max-width: ${dimensionMap[this.breakpoint]})`).matches;
|
||||
|
||||
if (this.mediaMatched !== mediaMatched) {
|
||||
this.isCollapsed = this.collapsible ? this.mediaMatched : false;
|
||||
this.$emit('input', this.mediaMatched);
|
||||
this.$emit('on-collapse', this.mediaMatched);
|
||||
}
|
||||
},
|
||||
onWindowResize () {
|
||||
this.matchMedia();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (stat) {
|
||||
this.$emit('on-collapse', stat);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.defaultCollapsed) {
|
||||
this.isCollapsed = true;
|
||||
this.$emit('input', this.defaultCollapsed);
|
||||
} else {
|
||||
if (this.value !== undefined) {
|
||||
this.isCollapsed = this.value;
|
||||
}
|
||||
}
|
||||
if (this.breakpoint !== undefined) {
|
||||
on(window, 'resize', this.onWindowResize);
|
||||
|
|
|
@ -55,7 +55,7 @@ export default {
|
|||
});
|
||||
|
||||
timer = setInterval(() => {
|
||||
percent += Math.floor(Math.random () * 3 + 5);
|
||||
percent += Math.floor(Math.random () * 3 + 1);
|
||||
if (percent > 95) {
|
||||
clearTimer();
|
||||
}
|
||||
|
|
|
@ -1,15 +1,26 @@
|
|||
<template>
|
||||
<li :class="classes" @click.stop="handleClick" :style="itemStyle"><slot></slot></li>
|
||||
<a
|
||||
v-if="to"
|
||||
:href="linkUrl"
|
||||
:target="target"
|
||||
:class="classes"
|
||||
@click.exact="handleClickItem($event, false)"
|
||||
@click.ctrl="handleClickItem($event, true)"
|
||||
@click.meta="handleClickItem($event, true)"
|
||||
:style="itemStyle"><slot></slot></a>
|
||||
<li v-else :class="classes" @click.stop="handleClickItem" :style="itemStyle"><slot></slot></li>
|
||||
</template>
|
||||
<script>
|
||||
import Emitter from '../../mixins/emitter';
|
||||
import { findComponentUpward } from '../../utils/assist';
|
||||
const prefixCls = 'ivu-menu';
|
||||
import mixin from './mixin';
|
||||
import mixinsLink from '../../mixins/link';
|
||||
|
||||
const prefixCls = 'ivu-menu';
|
||||
|
||||
export default {
|
||||
name: 'MenuItem',
|
||||
mixins: [ Emitter, mixin ],
|
||||
mixins: [ Emitter, mixin, mixinsLink ],
|
||||
props: {
|
||||
name: {
|
||||
type: [String, Number],
|
||||
|
@ -18,7 +29,7 @@
|
|||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
@ -43,15 +54,24 @@
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick () {
|
||||
handleClickItem (event, new_window = false) {
|
||||
if (this.disabled) return;
|
||||
|
||||
let parent = findComponentUpward(this, 'Submenu');
|
||||
|
||||
if (parent) {
|
||||
this.dispatch('Submenu', 'on-menu-item-select', this.name);
|
||||
if (new_window || this.target === '_blank') {
|
||||
// 如果是 new_window,直接新开窗口就行,无需发送状态
|
||||
this.handleCheckClick(event, new_window);
|
||||
let parentMenu = findComponentUpward(this, 'Menu');
|
||||
if (parentMenu) parentMenu.handleEmitSelectEvent(this.name);
|
||||
} else {
|
||||
this.dispatch('Menu', 'on-menu-item-select', this.name);
|
||||
let parent = findComponentUpward(this, 'Submenu');
|
||||
|
||||
if (parent) {
|
||||
this.dispatch('Submenu', 'on-menu-item-select', this.name);
|
||||
} else {
|
||||
this.dispatch('Menu', 'on-menu-item-select', this.name);
|
||||
}
|
||||
|
||||
this.handleCheckClick(event, new_window);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<ul :class="classes" :style="styles"><slot></slot></ul>
|
||||
</template>
|
||||
<script>
|
||||
import { oneOf, findComponentsDownward, findBrothersComponents } from '../../utils/assist';
|
||||
import { oneOf, findComponentsDownward, findComponentsUpward } from '../../utils/assist';
|
||||
import Emitter from '../../mixins/emitter';
|
||||
|
||||
const prefixCls = 'ivu-menu';
|
||||
|
@ -43,7 +43,8 @@
|
|||
},
|
||||
data () {
|
||||
return {
|
||||
currentActiveName: this.activeName
|
||||
currentActiveName: this.activeName,
|
||||
openedNames: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -76,36 +77,64 @@
|
|||
this.broadcast('MenuItem', 'on-update-active-name', this.currentActiveName);
|
||||
},
|
||||
updateOpenKeys (name) {
|
||||
const index = this.openNames.indexOf(name);
|
||||
if (index > -1) {
|
||||
this.openNames.splice(index, 1);
|
||||
let names = [...this.openedNames];
|
||||
const index = names.indexOf(name);
|
||||
if (this.accordion) findComponentsDownward(this, 'Submenu').forEach(item => {
|
||||
item.opened = false;
|
||||
});
|
||||
if (index >= 0) {
|
||||
let currentSubmenu = null;
|
||||
findComponentsDownward(this, 'Submenu').forEach(item => {
|
||||
if (item.name === name) {
|
||||
currentSubmenu = item;
|
||||
item.opened = false;
|
||||
}
|
||||
});
|
||||
findComponentsUpward(currentSubmenu, 'Submenu').forEach(item => {
|
||||
item.opened = true;
|
||||
});
|
||||
findComponentsDownward(currentSubmenu, 'Submenu').forEach(item => {
|
||||
item.opened = false;
|
||||
});
|
||||
} else {
|
||||
this.openNames.push(name);
|
||||
if (this.accordion) {
|
||||
let currentSubmenu = {};
|
||||
let currentSubmenu = null;
|
||||
findComponentsDownward(this, 'Submenu').forEach(item => {
|
||||
if (item.name === name) currentSubmenu = item;
|
||||
if (item.name === name) {
|
||||
currentSubmenu = item;
|
||||
item.opened = true;
|
||||
}
|
||||
});
|
||||
findBrothersComponents(currentSubmenu, 'Submenu').forEach(item => {
|
||||
let index = this.openNames.indexOf(item.name);
|
||||
this.openNames.splice(index, index >= 0 ? 1 : 0);
|
||||
findComponentsUpward(currentSubmenu, 'Submenu').forEach(item => {
|
||||
item.opened = true;
|
||||
});
|
||||
} else {
|
||||
findComponentsDownward(this, 'Submenu').forEach(item => {
|
||||
if (item.name === name) item.opened = true;
|
||||
});
|
||||
this.openNames.push(name);
|
||||
}
|
||||
}
|
||||
let openedNames = findComponentsDownward(this, 'Submenu').filter(item => item.opened).map(item => item.name);
|
||||
this.openedNames = [...openedNames];
|
||||
this.$emit('on-open-change', openedNames);
|
||||
},
|
||||
updateOpened () {
|
||||
const items = findComponentsDownward(this, 'Submenu');
|
||||
|
||||
if (items.length) {
|
||||
items.forEach(item => {
|
||||
if (this.openNames.indexOf(item.name) > -1) item.opened = true;
|
||||
if (this.openedNames.indexOf(item.name) > -1) item.opened = true;
|
||||
else item.opened = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
handleEmitSelectEvent (name) {
|
||||
this.$emit('on-select', name);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.updateActiveName();
|
||||
this.openedNames = [...this.openNames];
|
||||
this.updateOpened();
|
||||
this.$on('on-menu-item-select', (name) => {
|
||||
this.currentActiveName = name;
|
||||
|
@ -113,8 +142,8 @@
|
|||
});
|
||||
},
|
||||
watch: {
|
||||
openNames () {
|
||||
this.$emit('on-open-change', this.openNames);
|
||||
openNames (names) {
|
||||
this.openedNames = names;
|
||||
},
|
||||
activeName (val) {
|
||||
this.currentActiveName = val;
|
||||
|
|
|
@ -7,7 +7,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
hasParentSubmenu () {
|
||||
return findComponentUpward(this, 'Submenu');
|
||||
return !!findComponentUpward(this, 'Submenu');
|
||||
},
|
||||
parentSubmenuNum () {
|
||||
return findComponentsUpward(this, 'Submenu').length;
|
||||
|
@ -16,4 +16,4 @@ export default {
|
|||
return this.menu.mode;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -13,11 +13,11 @@ let messageInstance;
|
|||
let name = 1;
|
||||
|
||||
const iconTypes = {
|
||||
'info': 'information-circled',
|
||||
'success': 'checkmark-circled',
|
||||
'warning': 'android-alert',
|
||||
'error': 'close-circled',
|
||||
'loading': 'load-c'
|
||||
'info': 'ios-information-circle',
|
||||
'success': 'ios-checkmark-circle',
|
||||
'warning': 'ios-alert',
|
||||
'error': 'ios-close-circle',
|
||||
'loading': 'ios-loading'
|
||||
};
|
||||
|
||||
function getMessageInstance () {
|
||||
|
@ -46,7 +46,7 @@ function notice (content = '', duration = defaults.duration, type, onClose = fun
|
|||
transitionName: 'move-up',
|
||||
content: `
|
||||
<div class="${prefixCls}-custom-content ${prefixCls}-${type}">
|
||||
<i class="${iconPrefixCls} ${iconPrefixCls}-${iconType}${loadCls}"></i>
|
||||
<i class="${iconPrefixCls} ${iconPrefixCls}-${iconType} ${loadCls}"></i>
|
||||
<span>${content}</span>
|
||||
</div>
|
||||
`,
|
||||
|
|
|
@ -62,6 +62,22 @@ Modal.newInstance = properties => {
|
|||
attrs: {
|
||||
class: `${prefixCls}-body`
|
||||
}
|
||||
}, [
|
||||
h('div', {
|
||||
domProps: {
|
||||
innerHTML: this.body
|
||||
}
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
// when render with no title, hide head
|
||||
let head_render;
|
||||
if (this.title) {
|
||||
head_render = h('div', {
|
||||
attrs: {
|
||||
class: `${prefixCls}-head`
|
||||
}
|
||||
}, [
|
||||
h('div', {
|
||||
class: this.iconTypeCls
|
||||
|
@ -71,8 +87,11 @@ Modal.newInstance = properties => {
|
|||
})
|
||||
]),
|
||||
h('div', {
|
||||
attrs: {
|
||||
class: `${prefixCls}-head-title`
|
||||
},
|
||||
domProps: {
|
||||
innerHTML: this.body
|
||||
innerHTML: this.title
|
||||
}
|
||||
})
|
||||
]);
|
||||
|
@ -98,20 +117,7 @@ Modal.newInstance = properties => {
|
|||
class: prefixCls
|
||||
}
|
||||
}, [
|
||||
h('div', {
|
||||
attrs: {
|
||||
class: `${prefixCls}-head`
|
||||
}
|
||||
}, [
|
||||
h('div', {
|
||||
attrs: {
|
||||
class: `${prefixCls}-head-title`
|
||||
},
|
||||
domProps: {
|
||||
innerHTML: this.title
|
||||
}
|
||||
})
|
||||
]),
|
||||
head_render,
|
||||
body_render,
|
||||
h('div', {
|
||||
attrs: {
|
||||
|
@ -124,8 +130,8 @@ Modal.newInstance = properties => {
|
|||
computed: {
|
||||
iconTypeCls () {
|
||||
return [
|
||||
`${prefixCls}-body-icon`,
|
||||
`${prefixCls}-body-icon-${this.iconType}`
|
||||
`${prefixCls}-head-icon`,
|
||||
`${prefixCls}-head-icon-${this.iconType}`
|
||||
];
|
||||
},
|
||||
iconNameCls () {
|
||||
|
@ -192,19 +198,19 @@ Modal.newInstance = properties => {
|
|||
|
||||
switch (props.icon) {
|
||||
case 'info':
|
||||
modal.$parent.iconName = 'information-circled';
|
||||
modal.$parent.iconName = 'ios-information-circle';
|
||||
break;
|
||||
case 'success':
|
||||
modal.$parent.iconName = 'checkmark-circled';
|
||||
modal.$parent.iconName = 'ios-checkmark-circle';
|
||||
break;
|
||||
case 'warning':
|
||||
modal.$parent.iconName = 'android-alert';
|
||||
modal.$parent.iconName = 'ios-alert';
|
||||
break;
|
||||
case 'error':
|
||||
modal.$parent.iconName = 'close-circled';
|
||||
modal.$parent.iconName = 'ios-close-circle';
|
||||
break;
|
||||
case 'confirm':
|
||||
modal.$parent.iconName = 'help-circled';
|
||||
modal.$parent.iconName = 'ios-help-circle';
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// used for Modal & $Spin
|
||||
// used for Modal & $Spin & Drawer
|
||||
import { getScrollBarSize } from '../../utils/assist';
|
||||
export default {
|
||||
methods: {
|
||||
|
@ -13,6 +13,10 @@ export default {
|
|||
this.scrollBarWidth = getScrollBarSize();
|
||||
}
|
||||
},
|
||||
checkMaskInVisible () {
|
||||
let masks = document.getElementsByClassName('ivu-modal-mask') || [];
|
||||
return Array.from(masks).every(m => m.style.display === 'none' || m.classList.contains('fade-leave-to'));
|
||||
},
|
||||
setScrollBar () {
|
||||
if (this.bodyIsOverflowing && this.scrollBarWidth !== undefined) {
|
||||
document.body.style.paddingRight = `${this.scrollBarWidth}px`;
|
||||
|
@ -27,8 +31,10 @@ export default {
|
|||
document.body.style.overflow = 'hidden';
|
||||
},
|
||||
removeScrollEffect() {
|
||||
document.body.style.overflow = '';
|
||||
this.resetScrollBar();
|
||||
if (this.checkMaskInVisible()) {
|
||||
document.body.style.overflow = '';
|
||||
this.resetScrollBar();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
<template>
|
||||
<div v-transfer-dom :data-transfer="transfer">
|
||||
<transition :name="transitionNames[1]">
|
||||
<div :class="maskClasses" v-show="visible" @click="mask"></div>
|
||||
<div :class="maskClasses" :style="wrapStyles" v-show="visible" v-if="showMask" @click="handleMask"></div>
|
||||
</transition>
|
||||
<div :class="wrapClasses" @click="handleWrapClick">
|
||||
<div :class="wrapClasses" :style="wrapStyles" @click="handleWrapClick">
|
||||
<transition :name="transitionNames[0]" @after-leave="animationFinish">
|
||||
<div :class="classes" :style="mainStyles" v-show="visible">
|
||||
<div :class="[prefixCls + '-content']">
|
||||
<div :class="contentClasses" ref="content" :style="contentStyles" @click="handleClickModal">
|
||||
<a :class="[prefixCls + '-close']" v-if="closable" @click="close">
|
||||
<slot name="close">
|
||||
<Icon type="ios-close-empty"></Icon>
|
||||
<Icon type="ios-close"></Icon>
|
||||
</slot>
|
||||
</a>
|
||||
<div :class="[prefixCls + '-header']" v-if="showHead"><slot name="header"><div :class="[prefixCls + '-header-inner']">{{ title }}</div></slot></div>
|
||||
<div :class="[prefixCls + '-header']"
|
||||
@mousedown="handleMoveStart"
|
||||
v-if="showHead"
|
||||
><slot name="header"><div :class="[prefixCls + '-header-inner']">{{ title }}</div></slot></div>
|
||||
<div :class="[prefixCls + '-body']"><slot></slot></div>
|
||||
<div :class="[prefixCls + '-footer']" v-if="!footerHide">
|
||||
<slot name="footer">
|
||||
|
@ -34,6 +37,11 @@
|
|||
import Emitter from '../../mixins/emitter';
|
||||
import ScrollbarMixins from './mixins-scrollbar';
|
||||
|
||||
import { on, off } from '../../utils/dom';
|
||||
import { findComponentsDownward } from '../../utils/assist';
|
||||
|
||||
import { transferIndex as modalIndex, transferIncrease as modalIncrease } from '../../utils/transfer-queue';
|
||||
|
||||
const prefixCls = 'ivu-modal';
|
||||
|
||||
export default {
|
||||
|
@ -93,9 +101,27 @@
|
|||
}
|
||||
},
|
||||
transfer: {
|
||||
type: Boolean,
|
||||
default () {
|
||||
return !this.$IVIEW || this.$IVIEW.transfer === '' ? true : this.$IVIEW.transfer;
|
||||
}
|
||||
},
|
||||
fullscreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
mask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
draggable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 1000
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
@ -103,7 +129,15 @@
|
|||
wrapShow: false,
|
||||
showHead: true,
|
||||
buttonLoading: false,
|
||||
visible: this.value
|
||||
visible: this.value,
|
||||
dragData: {
|
||||
x: null,
|
||||
y: null,
|
||||
dragX: null,
|
||||
dragY: null,
|
||||
dragging: false
|
||||
},
|
||||
modalIndex: this.handleGetModalIndex(), // for Esc close the top modal
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -112,21 +146,46 @@
|
|||
`${prefixCls}-wrap`,
|
||||
{
|
||||
[`${prefixCls}-hidden`]: !this.wrapShow,
|
||||
[`${this.className}`]: !!this.className
|
||||
[`${this.className}`]: !!this.className,
|
||||
[`${prefixCls}-no-mask`]: !this.showMask
|
||||
}
|
||||
];
|
||||
},
|
||||
wrapStyles () {
|
||||
return {
|
||||
zIndex: this.modalIndex + this.zIndex
|
||||
};
|
||||
},
|
||||
maskClasses () {
|
||||
return `${prefixCls}-mask`;
|
||||
},
|
||||
classes () {
|
||||
return `${prefixCls}`;
|
||||
return [
|
||||
`${prefixCls}`,
|
||||
{
|
||||
[`${prefixCls}-fullscreen`]: this.fullscreen,
|
||||
[`${prefixCls}-fullscreen-no-header`]: this.fullscreen && !this.showHead,
|
||||
[`${prefixCls}-fullscreen-no-footer`]: this.fullscreen && this.footerHide
|
||||
}
|
||||
];
|
||||
},
|
||||
contentClasses () {
|
||||
return [
|
||||
`${prefixCls}-content`,
|
||||
{
|
||||
[`${prefixCls}-content-no-mask`]: !this.showMask,
|
||||
[`${prefixCls}-content-drag`]: this.draggable,
|
||||
[`${prefixCls}-content-dragging`]: this.draggable && this.dragData.dragging
|
||||
}
|
||||
];
|
||||
},
|
||||
mainStyles () {
|
||||
let style = {};
|
||||
|
||||
const width = parseInt(this.width);
|
||||
const styleWidth = {
|
||||
const styleWidth = this.dragData.x !== null ? {
|
||||
top: 0
|
||||
} : {
|
||||
width: width <= 100 ? `${width}%` : `${width}px`
|
||||
};
|
||||
|
||||
|
@ -136,6 +195,22 @@
|
|||
|
||||
return style;
|
||||
},
|
||||
contentStyles () {
|
||||
let style = {};
|
||||
|
||||
if (this.draggable) {
|
||||
if (this.dragData.x !== null) style.left = `${this.dragData.x}px`;
|
||||
if (this.dragData.y !== null) style.top = `${this.dragData.y}px`;
|
||||
const width = parseInt(this.width);
|
||||
const styleWidth = {
|
||||
width: width <= 100 ? `${width}%` : `${width}px`
|
||||
};
|
||||
|
||||
Object.assign(style, styleWidth);
|
||||
}
|
||||
|
||||
return style;
|
||||
},
|
||||
localeOkText () {
|
||||
if (this.okText === undefined) {
|
||||
return this.t('i.modal.okText');
|
||||
|
@ -149,6 +224,9 @@
|
|||
} else {
|
||||
return this.cancelText;
|
||||
}
|
||||
},
|
||||
showMask () {
|
||||
return this.draggable ? false : this.mask;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -157,15 +235,15 @@
|
|||
this.$emit('input', false);
|
||||
this.$emit('on-cancel');
|
||||
},
|
||||
mask () {
|
||||
if (this.maskClosable) {
|
||||
handleMask () {
|
||||
if (this.maskClosable && this.showMask) {
|
||||
this.close();
|
||||
}
|
||||
},
|
||||
handleWrapClick (event) {
|
||||
// use indexOf,do not use === ,because ivu-modal-wrap can have other custom className
|
||||
const className = event.target.getAttribute('class');
|
||||
if (className && className.indexOf(`${prefixCls}-wrap`) > -1) this.mask();
|
||||
if (className && className.indexOf(`${prefixCls}-wrap`) > -1) this.handleMask();
|
||||
},
|
||||
cancel () {
|
||||
this.close();
|
||||
|
@ -182,12 +260,74 @@
|
|||
EscClose (e) {
|
||||
if (this.visible && this.closable) {
|
||||
if (e.keyCode === 27) {
|
||||
this.close();
|
||||
const $Modals = findComponentsDownward(this.$root, 'Modal').filter(item => item.$data.visible && item.$props.closable);
|
||||
|
||||
const $TopModal = $Modals.sort((a, b) => {
|
||||
return a.$data.modalIndex < b.$data.modalIndex ? 1 : -1;
|
||||
})[0];
|
||||
|
||||
setTimeout(() => {
|
||||
$TopModal.close();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
},
|
||||
animationFinish() {
|
||||
this.$emit('on-hidden');
|
||||
},
|
||||
handleMoveStart (event) {
|
||||
if (!this.draggable) return false;
|
||||
|
||||
const $content = this.$refs.content;
|
||||
const rect = $content.getBoundingClientRect();
|
||||
this.dragData.x = rect.x;
|
||||
this.dragData.y = rect.y;
|
||||
|
||||
const distance = {
|
||||
x: event.clientX,
|
||||
y: event.clientY
|
||||
};
|
||||
|
||||
this.dragData.dragX = distance.x;
|
||||
this.dragData.dragY = distance.y;
|
||||
|
||||
this.dragData.dragging = true;
|
||||
|
||||
on(window, 'mousemove', this.handleMoveMove);
|
||||
on(window, 'mouseup', this.handleMoveEnd);
|
||||
},
|
||||
handleMoveMove (event) {
|
||||
if (!this.dragData.dragging) return false;
|
||||
|
||||
const distance = {
|
||||
x: event.clientX,
|
||||
y: event.clientY
|
||||
};
|
||||
|
||||
const diff_distance = {
|
||||
x: distance.x - this.dragData.dragX,
|
||||
y: distance.y - this.dragData.dragY
|
||||
};
|
||||
|
||||
this.dragData.x += diff_distance.x;
|
||||
this.dragData.y += diff_distance.y;
|
||||
|
||||
this.dragData.dragX = distance.x;
|
||||
this.dragData.dragY = distance.y;
|
||||
},
|
||||
handleMoveEnd () {
|
||||
this.dragData.dragging = false;
|
||||
off(window, 'mousemove', this.handleMoveMove);
|
||||
off(window, 'mouseup', this.handleMoveEnd);
|
||||
},
|
||||
handleGetModalIndex () {
|
||||
modalIncrease();
|
||||
return modalIndex;
|
||||
},
|
||||
handleClickModal () {
|
||||
if (this.draggable) {
|
||||
this.modalIndex = this.handleGetModalIndex();
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
@ -222,6 +362,8 @@
|
|||
this.removeScrollEffect();
|
||||
}, 300);
|
||||
} else {
|
||||
this.modalIndex = this.handleGetModalIndex();
|
||||
|
||||
if (this.timer) clearTimeout(this.timer);
|
||||
this.wrapShow = true;
|
||||
if (!this.scrollable) {
|
||||
|
|
|
@ -10,10 +10,10 @@ let noticeInstance;
|
|||
let name = 1;
|
||||
|
||||
const iconTypes = {
|
||||
'info': 'information-circled',
|
||||
'success': 'checkmark-circled',
|
||||
'warning': 'android-alert',
|
||||
'error': 'close-circled'
|
||||
'info': 'ios-information-circle',
|
||||
'success': 'ios-checkmark-circle',
|
||||
'warning': 'ios-alert',
|
||||
'error': 'ios-close-circle'
|
||||
};
|
||||
|
||||
function getNoticeInstance () {
|
||||
|
@ -50,18 +50,19 @@ function notice (type, options) {
|
|||
if (type == 'normal') {
|
||||
withIcon = false;
|
||||
content = `
|
||||
<div class="${prefixCls}-custom-content ${prefixCls}-with-normal${with_desc}">
|
||||
<div class="${prefixCls}-custom-content ${prefixCls}-with-normal ${with_desc}">
|
||||
<div class="${prefixCls}-title">${title}</div>
|
||||
<div class="${prefixCls}-desc">${desc}</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
const iconType = iconTypes[type];
|
||||
const outlineIcon = with_desc === '' ? '' : '-outline';
|
||||
withIcon = true;
|
||||
content = `
|
||||
<div class="${prefixCls}-custom-content ${prefixCls}-with-icon ${prefixCls}-with-${type}${with_desc}">
|
||||
<div class="${prefixCls}-custom-content ${prefixCls}-with-icon ${prefixCls}-with-${type} ${with_desc}">
|
||||
<span class="${prefixCls}-icon ${prefixCls}-icon-${type}">
|
||||
<i class="${iconPrefixCls} ${iconPrefixCls}-${iconType}"></i>
|
||||
<i class="${iconPrefixCls} ${iconPrefixCls}-${iconType}${outlineIcon}"></i>
|
||||
</span>
|
||||
<div class="${prefixCls}-title">${title}</div>
|
||||
<div class="${prefixCls}-desc">${desc}</div>
|
||||
|
@ -122,4 +123,4 @@ export default {
|
|||
noticeInstance = null;
|
||||
instance.destroy('ivu-notice');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div v-if="showSizer || showElevator" :class="optsClasses">
|
||||
<div v-if="showSizer" :class="sizerClasses">
|
||||
<i-select v-model="currentPageSize" :size="size" :placement="placement" @on-change="changeSize">
|
||||
<i-select v-model="currentPageSize" :size="size" :placement="placement" :transfer="transfer" @on-change="changeSize">
|
||||
<i-option v-for="item in pageSizeOpts" :key="item" :value="item" style="text-align:center;">{{ item }} {{ t('i.page.page') }}</i-option>
|
||||
</i-select>
|
||||
</div>
|
||||
|
@ -42,7 +42,8 @@
|
|||
pageSize: Number,
|
||||
allPages: Number,
|
||||
isSmall: Boolean,
|
||||
placement: String
|
||||
placement: String,
|
||||
transfer: Boolean
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
:title="t('i.page.prev')"
|
||||
:class="prevClasses"
|
||||
@click="prev">
|
||||
<a><i class="ivu-icon ivu-icon-ios-arrow-left"></i></a>
|
||||
<a><i class="ivu-icon ivu-icon-ios-arrow-back"></i></a>
|
||||
</li>
|
||||
<div :class="simplePagerClasses" :title="currentPage + '/' + allPages">
|
||||
<input
|
||||
|
@ -22,7 +22,7 @@
|
|||
:title="t('i.page.next')"
|
||||
:class="nextClasses"
|
||||
@click="next">
|
||||
<a><i class="ivu-icon ivu-icon-ios-arrow-right"></i></a>
|
||||
<a><i class="ivu-icon ivu-icon-ios-arrow-forward"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul :class="wrapClasses" :style="styles" v-else>
|
||||
|
@ -33,28 +33,31 @@
|
|||
:title="t('i.page.prev')"
|
||||
:class="prevClasses"
|
||||
@click="prev">
|
||||
<a><i class="ivu-icon ivu-icon-ios-arrow-left"></i></a>
|
||||
<a><template v-if="prevText !== ''">{{ prevText }}</template><i v-else class="ivu-icon ivu-icon-ios-arrow-back"></i></a>
|
||||
</li>
|
||||
<li title="1" :class="firstPageClasses" @click="changePage(1)"><a>1</a></li>
|
||||
<li :title="t('i.page.prev5')" v-if="currentPage - 3 > 1" :class="[prefixCls + '-item-jump-prev']" @click="fastPrev"><a><i class="ivu-icon ivu-icon-ios-arrow-left"></i></a></li>
|
||||
<li :title="t('i.page.prev5')" v-if="currentPage > 5" :class="[prefixCls + '-item-jump-prev']" @click="fastPrev"><a><i class="ivu-icon ivu-icon-ios-arrow-back"></i></a></li>
|
||||
<li :title="currentPage - 3" v-if="currentPage === 5" :class="[prefixCls + '-item']" @click="changePage(currentPage - 3)"><a>{{ currentPage - 3 }}</a></li>
|
||||
<li :title="currentPage - 2" v-if="currentPage - 2 > 1" :class="[prefixCls + '-item']" @click="changePage(currentPage - 2)"><a>{{ currentPage - 2 }}</a></li>
|
||||
<li :title="currentPage - 1" v-if="currentPage - 1 > 1" :class="[prefixCls + '-item']" @click="changePage(currentPage - 1)"><a>{{ currentPage - 1 }}</a></li>
|
||||
<li :title="currentPage" v-if="currentPage != 1 && currentPage != allPages" :class="[prefixCls + '-item',prefixCls + '-item-active']"><a>{{ currentPage }}</a></li>
|
||||
<li :title="currentPage + 1" v-if="currentPage + 1 < allPages" :class="[prefixCls + '-item']" @click="changePage(currentPage + 1)"><a>{{ currentPage + 1 }}</a></li>
|
||||
<li :title="currentPage + 2" v-if="currentPage + 2 < allPages" :class="[prefixCls + '-item']" @click="changePage(currentPage + 2)"><a>{{ currentPage + 2 }}</a></li>
|
||||
<li :title="t('i.page.next5')" v-if="currentPage + 3 < allPages" :class="[prefixCls + '-item-jump-next']" @click="fastNext"><a><i class="ivu-icon ivu-icon-ios-arrow-right"></i></a></li>
|
||||
<li :title="currentPage + 3" v-if="allPages - currentPage === 4" :class="[prefixCls + '-item']" @click="changePage(currentPage + 3)"><a>{{ currentPage + 3 }}</a></li>
|
||||
<li :title="t('i.page.next5')" v-if="allPages - currentPage >= 5" :class="[prefixCls + '-item-jump-next']" @click="fastNext"><a><i class="ivu-icon ivu-icon-ios-arrow-forward"></i></a></li>
|
||||
<li :title="allPages" v-if="allPages > 1" :class="lastPageClasses" @click="changePage(allPages)"><a>{{ allPages }}</a></li>
|
||||
<li
|
||||
:title="t('i.page.next')"
|
||||
:class="nextClasses"
|
||||
@click="next">
|
||||
<a><i class="ivu-icon ivu-icon-ios-arrow-right"></i></a>
|
||||
<a><template v-if="nextText !== ''">{{ nextText }}</template><i v-else class="ivu-icon ivu-icon-ios-arrow-forward"></i></a>
|
||||
</li>
|
||||
<Options
|
||||
:show-sizer="showSizer"
|
||||
:page-size="currentPageSize"
|
||||
:page-size-opts="pageSizeOpts"
|
||||
:placement="placement"
|
||||
:transfer="transfer"
|
||||
:show-elevator="showElevator"
|
||||
:_current.once="currentPage"
|
||||
:current="currentPage"
|
||||
|
@ -101,6 +104,12 @@
|
|||
},
|
||||
default: 'bottom'
|
||||
},
|
||||
transfer: {
|
||||
type: Boolean,
|
||||
default () {
|
||||
return !this.$IVIEW || this.$IVIEW.transfer === '' ? false : this.$IVIEW.transfer;
|
||||
}
|
||||
},
|
||||
size: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['small']);
|
||||
|
@ -127,6 +136,14 @@
|
|||
},
|
||||
styles: {
|
||||
type: Object
|
||||
},
|
||||
prevText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
nextText: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
|
@ -139,8 +156,8 @@
|
|||
watch: {
|
||||
total (val) {
|
||||
let maxPage = Math.ceil(val / this.currentPageSize);
|
||||
if (maxPage < this.currentPage && maxPage > 0) {
|
||||
this.currentPage = maxPage;
|
||||
if (maxPage < this.currentPage ) {
|
||||
this.currentPage = (maxPage === 0 ? 1 : maxPage);
|
||||
}
|
||||
},
|
||||
current (val) {
|
||||
|
@ -183,7 +200,8 @@
|
|||
return [
|
||||
`${prefixCls}-prev`,
|
||||
{
|
||||
[`${prefixCls}-disabled`]: this.currentPage === 1
|
||||
[`${prefixCls}-disabled`]: this.currentPage === 1,
|
||||
[`${prefixCls}-custom-text`]: this.prevText !== ''
|
||||
}
|
||||
];
|
||||
},
|
||||
|
@ -191,7 +209,8 @@
|
|||
return [
|
||||
`${prefixCls}-next`,
|
||||
{
|
||||
[`${prefixCls}-disabled`]: this.currentPage === this.allPages
|
||||
[`${prefixCls}-disabled`]: this.currentPage === this.allPages,
|
||||
[`${prefixCls}-custom-text`]: this.nextText !== ''
|
||||
}
|
||||
];
|
||||
},
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
:class="classes"
|
||||
@mouseenter="handleMouseenter"
|
||||
@mouseleave="handleMouseleave"
|
||||
v-clickoutside="handleClose">
|
||||
v-click-outside="handleClose">
|
||||
<div
|
||||
:class="[prefixCls + '-rel']"
|
||||
ref="reference"
|
||||
|
@ -27,7 +27,7 @@
|
|||
<div :class="[prefixCls + '-arrow']"></div>
|
||||
<div :class="[prefixCls + '-inner']" v-if="confirm">
|
||||
<div :class="[prefixCls + '-body']">
|
||||
<i class="ivu-icon ivu-icon-help-circled"></i>
|
||||
<i class="ivu-icon ivu-icon-ios-help-circle"></i>
|
||||
<div :class="[prefixCls + '-body-message']"><slot name="title">{{ title }}</slot></div>
|
||||
</div>
|
||||
<div :class="[prefixCls + '-footer']">
|
||||
|
@ -36,9 +36,9 @@
|
|||
</div>
|
||||
</div>
|
||||
<div :class="[prefixCls + '-inner']" v-if="!confirm">
|
||||
<div :class="[prefixCls + '-title']" v-if="showTitle" ref="title"><slot name="title"><div :class="[prefixCls + '-title-inner']">{{ title }}</div></slot></div>
|
||||
<div :class="[prefixCls + '-body']">
|
||||
<div :class="[prefixCls + '-body-content']"><slot name="content"><div :class="[prefixCls + '-body-content-inner']">{{ content }}</div></slot></div>
|
||||
<div :class="[prefixCls + '-title']" :style="contentPaddingStyle" v-if="showTitle" ref="title"><slot name="title"><div :class="[prefixCls + '-title-inner']">{{ title }}</div></slot></div>
|
||||
<div :class="[prefixCls + '-body']" :style="contentPaddingStyle">
|
||||
<div :class="contentClasses"><slot name="content"><div :class="[prefixCls + '-body-content-inner']">{{ content }}</div></slot></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -49,9 +49,10 @@
|
|||
<script>
|
||||
import Popper from '../base/popper';
|
||||
import iButton from '../button/button.vue';
|
||||
import clickoutside from '../../directives/clickoutside';
|
||||
import {directive as clickOutside} from 'v-click-outside-x';
|
||||
import TransferDom from '../../directives/transfer-dom';
|
||||
import { oneOf } from '../../utils/assist';
|
||||
import { transferIndex, transferIncrease } from '../../utils/transfer-queue';
|
||||
import Locale from '../../mixins/locale';
|
||||
|
||||
const prefixCls = 'ivu-poptip';
|
||||
|
@ -59,7 +60,7 @@
|
|||
export default {
|
||||
name: 'Poptip',
|
||||
mixins: [ Popper, Locale ],
|
||||
directives: { clickoutside, TransferDom },
|
||||
directives: { clickOutside, TransferDom },
|
||||
components: { iButton },
|
||||
props: {
|
||||
trigger: {
|
||||
|
@ -95,8 +96,21 @@
|
|||
type: String
|
||||
},
|
||||
transfer: {
|
||||
type: Boolean,
|
||||
default () {
|
||||
return !this.$IVIEW || this.$IVIEW.transfer === '' ? false : this.$IVIEW.transfer;
|
||||
}
|
||||
},
|
||||
popperClass: {
|
||||
type: String
|
||||
},
|
||||
wordWrap: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// default by css: 8px 16px
|
||||
padding: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data () {
|
||||
|
@ -105,6 +119,7 @@
|
|||
showTitle: true,
|
||||
isInput: false,
|
||||
disableCloseUnderTransfer: false, // transfer 模式下,点击 slot 也会触发关闭
|
||||
tIndex: this.handleGetIndex()
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -120,7 +135,8 @@
|
|||
return [
|
||||
`${prefixCls}-popper`,
|
||||
{
|
||||
[`${prefixCls}-confirm`]: this.transfer && this.confirm
|
||||
[`${prefixCls}-confirm`]: this.transfer && this.confirm,
|
||||
[`${this.popperClass}`]: !!this.popperClass
|
||||
}
|
||||
];
|
||||
},
|
||||
|
@ -130,6 +146,9 @@
|
|||
if (this.width) {
|
||||
style.width = `${this.width}px`;
|
||||
}
|
||||
|
||||
if (this.transfer) style['z-index'] = 1060 + this.tIndex;
|
||||
|
||||
return style;
|
||||
},
|
||||
localeOkText () {
|
||||
|
@ -145,7 +164,20 @@
|
|||
} else {
|
||||
return this.cancelText;
|
||||
}
|
||||
}
|
||||
},
|
||||
contentClasses () {
|
||||
return [
|
||||
`${prefixCls}-body-content`,
|
||||
{
|
||||
[`${prefixCls}-body-content-word-wrap`]: this.wordWrap
|
||||
}
|
||||
];
|
||||
},
|
||||
contentPaddingStyle () {
|
||||
const styles = {};
|
||||
if (this.padding !== '') styles['padding'] = this.padding;
|
||||
return styles;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClick () {
|
||||
|
@ -227,6 +259,13 @@
|
|||
}
|
||||
|
||||
return $children;
|
||||
},
|
||||
handleGetIndex () {
|
||||
transferIncrease();
|
||||
return transferIndex;
|
||||
},
|
||||
handleIndexIncrease () {
|
||||
this.tIndex = this.handleGetIndex();
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div :class="wrapClasses">
|
||||
<div :class="outerClasses">
|
||||
<div :class="innerClasses">
|
||||
<div :class="bgClasses" :style="bgStyle"></div>
|
||||
<div :class="bgClasses" :style="bgStyle"></div><div :class="successBgClasses" :style="successBgStyle"></div>
|
||||
</div>
|
||||
</div>
|
||||
<span v-if="!hideInfo" :class="textClasses">
|
||||
|
@ -24,12 +24,17 @@
|
|||
const prefixCls = 'ivu-progress';
|
||||
|
||||
export default {
|
||||
name: 'Progress',
|
||||
components: { Icon },
|
||||
props: {
|
||||
percent: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
successPercent: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
status: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['normal', 'active', 'wrong', 'success']);
|
||||
|
@ -62,10 +67,10 @@
|
|||
let type = '';
|
||||
switch (this.currentStatus) {
|
||||
case 'wrong':
|
||||
type = 'ios-close';
|
||||
type = 'ios-close-circle';
|
||||
break;
|
||||
case 'success':
|
||||
type = 'ios-checkmark';
|
||||
type = 'ios-checkmark-circle';
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -80,6 +85,15 @@
|
|||
height: `${this.strokeWidth}px`
|
||||
};
|
||||
},
|
||||
successBgStyle () {
|
||||
return this.vertical ? {
|
||||
height: `${this.successPercent}%`,
|
||||
width: `${this.strokeWidth}px`
|
||||
} : {
|
||||
width: `${this.successPercent}%`,
|
||||
height: `${this.strokeWidth}px`
|
||||
};
|
||||
},
|
||||
wrapClasses () {
|
||||
return [
|
||||
`${prefixCls}`,
|
||||
|
@ -105,6 +119,9 @@
|
|||
},
|
||||
bgClasses () {
|
||||
return `${prefixCls}-bg`;
|
||||
},
|
||||
successBgClasses () {
|
||||
return `${prefixCls}-success-bg`;
|
||||
}
|
||||
},
|
||||
created () {
|
||||
|
|
|
@ -24,6 +24,9 @@
|
|||
size: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['small', 'large', 'default']);
|
||||
},
|
||||
default () {
|
||||
return !this.$IVIEW || this.$IVIEW.size === '' ? 'default' : this.$IVIEW.size;
|
||||
}
|
||||
},
|
||||
type: {
|
||||
|
@ -67,7 +70,7 @@
|
|||
this.childrens = findComponentsDownward(this, 'Radio');
|
||||
if (this.childrens) {
|
||||
this.childrens.forEach(child => {
|
||||
child.currentValue = this.value === child.label;
|
||||
child.currentValue = this.currentValue === child.label;
|
||||
child.group = true;
|
||||
});
|
||||
}
|
||||
|
@ -82,8 +85,12 @@
|
|||
},
|
||||
watch: {
|
||||
value () {
|
||||
this.currentValue = this.value;
|
||||
this.updateValue();
|
||||
if(this.currentValue !== this.value){
|
||||
this.currentValue = this.value;
|
||||
this.$nextTick(()=>{
|
||||
this.updateValue();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -11,8 +11,7 @@
|
|||
@change="change"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur">
|
||||
</span>
|
||||
<slot>{{ label }}</slot>
|
||||
</span><slot>{{ label }}</slot>
|
||||
</label>
|
||||
</template>
|
||||
<script>
|
||||
|
@ -47,6 +46,9 @@
|
|||
size: {
|
||||
validator (value) {
|
||||
return oneOf(value, ['small', 'large', 'default']);
|
||||
},
|
||||
default () {
|
||||
return !this.$IVIEW || this.$IVIEW.size === '' ? 'default' : this.$IVIEW.size;
|
||||
}
|
||||
},
|
||||
name: {
|
||||
|
|
|
@ -5,8 +5,21 @@
|
|||
v-for="item in count"
|
||||
:class="starCls(item)"
|
||||
@mousemove="handleMousemove(item, $event)"
|
||||
:key="item"
|
||||
@click="handleClick(item)">
|
||||
<span :class="[prefixCls + '-star-content']" type="half"></span>
|
||||
<template v-if="!showCharacter">
|
||||
<span :class="[prefixCls + '-star-content']" type="half"></span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span :class="[prefixCls + '-star-first']" type="half">
|
||||
<template v-if="character !== ''">{{ character }}</template>
|
||||
<i v-else :class="iconClasses" type="half"></i>
|
||||
</span>
|
||||
<span :class="[prefixCls + '-star-second']">
|
||||
<template v-if="character !== ''">{{ character }}</template>
|
||||
<i v-else :class="iconClasses"></i>
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
<div :class="[prefixCls + '-text']" v-if="showText" v-show="currentValue > 0">
|
||||
<slot><span>{{ currentValue }}</span> <span v-if="currentValue <= 1">{{ t('i.rate.star') }}</span><span v-else>{{ t('i.rate.stars') }}</span></slot>
|
||||
|
@ -17,11 +30,14 @@
|
|||
import Locale from '../../mixins/locale';
|
||||
import Emitter from '../../mixins/emitter';
|
||||
|
||||
import Icon from '../icon/icon.vue';
|
||||
|
||||
const prefixCls = 'ivu-rate';
|
||||
|
||||
export default {
|
||||
name: 'Rate',
|
||||
mixins: [ Locale, Emitter ],
|
||||
components: { Icon },
|
||||
props: {
|
||||
count: {
|
||||
type: Number,
|
||||
|
@ -45,6 +61,22 @@
|
|||
},
|
||||
name: {
|
||||
type: String
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
character: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
customIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
|
@ -64,6 +96,18 @@
|
|||
[`${prefixCls}-disabled`]: this.disabled
|
||||
}
|
||||
];
|
||||
},
|
||||
iconClasses () {
|
||||
return [
|
||||
'ivu-icon',
|
||||
{
|
||||
[`ivu-icon-${this.icon}`]: this.icon !== '',
|
||||
[`${this.customIcon}`]: this.customIcon !== '',
|
||||
}
|
||||
];
|
||||
},
|
||||
showCharacter () {
|
||||
return this.character !== '' || this.icon !== '' || this.customIcon !== '';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@ -91,8 +135,9 @@
|
|||
}
|
||||
|
||||
return [
|
||||
`${prefixCls}-star`,
|
||||
{
|
||||
{
|
||||
[`${prefixCls}-star`]: !this.showCharacter,
|
||||
[`${prefixCls}-star-chart`]: this.showCharacter,
|
||||
[`${prefixCls}-star-full`]: (!isLast && full) || (isLast && !this.isHalf),
|
||||
[`${prefixCls}-star-half`]: isLast && this.isHalf,
|
||||
[`${prefixCls}-star-zero`]: !full
|
||||
|
@ -123,8 +168,13 @@
|
|||
},
|
||||
handleClick (value) {
|
||||
if (this.disabled) return;
|
||||
// value++;
|
||||
//value++;
|
||||
if (this.isHalf) value -= 0.5;
|
||||
|
||||
if(this.clearable && Math.abs(value - this.currentValue) < 0.01) {
|
||||
value = 0;
|
||||
}
|
||||
|
||||
this.currentValue = value;
|
||||
this.$emit('input', value);
|
||||
this.$emit('on-change', value);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div :class="wrapperClasses">
|
||||
<div :class="spinnerClasses">
|
||||
<Spin fix>
|
||||
<Icon type="load-c" size="18" :class="iconClasses"></Icon>
|
||||
<Icon type="ios-loading" size="18" :class="iconClasses"></Icon>
|
||||
<div v-if="text" :class="textClasses">{{text}}</div>
|
||||
</Spin>
|
||||
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
import Vue from 'vue';
|
||||
const isServer = Vue.prototype.$isServer;
|
||||
import { getStyle } from '../../utils/assist';
|
||||
const Popper = isServer ? function() {} : require('popper.js'); // eslint-disable-line
|
||||
const Popper = isServer ? function() {} : require('popper.js/dist/umd/popper.js'); // eslint-disable-line
|
||||
|
||||
import { transferIndex, transferIncrease } from '../../utils/transfer-queue';
|
||||
|
||||
export default {
|
||||
name: 'Drop',
|
||||
|
@ -16,18 +18,26 @@
|
|||
},
|
||||
className: {
|
||||
type: String
|
||||
},
|
||||
transfer: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
popper: null,
|
||||
width: ''
|
||||
width: '',
|
||||
popperStatus: false,
|
||||
tIndex: this.handleGetIndex()
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
styles () {
|
||||
let style = {};
|
||||
if (this.width) style.width = `${this.width}px`;
|
||||
if (this.width) style.minWidth = `${this.width}px`;
|
||||
|
||||
if (this.transfer) style['z-index'] = 1060 + this.tIndex;
|
||||
|
||||
return style;
|
||||
}
|
||||
},
|
||||
|
@ -37,18 +47,27 @@
|
|||
if (this.popper) {
|
||||
this.$nextTick(() => {
|
||||
this.popper.update();
|
||||
this.popperStatus = true;
|
||||
});
|
||||
} else {
|
||||
this.$nextTick(() => {
|
||||
this.popper = new Popper(this.$parent.$refs.reference, this.$el, {
|
||||
gpuAcceleration: false,
|
||||
placement: this.placement,
|
||||
boundariesPadding: 0,
|
||||
forceAbsolute: true,
|
||||
boundariesElement: 'body'
|
||||
});
|
||||
this.popper.onCreate(popper => {
|
||||
this.resetTransformOrigin(popper);
|
||||
modifiers: {
|
||||
computeStyle:{
|
||||
gpuAcceleration: false
|
||||
},
|
||||
preventOverflow :{
|
||||
boundariesElement: 'window'
|
||||
}
|
||||
},
|
||||
onCreate:()=>{
|
||||
this.resetTransformOrigin();
|
||||
this.$nextTick(this.popper.update());
|
||||
},
|
||||
onUpdate:()=>{
|
||||
this.resetTransformOrigin();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -56,24 +75,35 @@
|
|||
if (this.$parent.$options.name === 'iSelect') {
|
||||
this.width = parseInt(getStyle(this.$parent.$el, 'width'));
|
||||
}
|
||||
this.tIndex = this.handleGetIndex();
|
||||
},
|
||||
destroy () {
|
||||
if (this.popper) {
|
||||
this.resetTransformOrigin(this.popper);
|
||||
setTimeout(() => {
|
||||
if (this.popper) {
|
||||
if (this.popper && !this.popperStatus) {
|
||||
this.popper.destroy();
|
||||
this.popper = null;
|
||||
}
|
||||
this.popperStatus = false;
|
||||
}, 300);
|
||||
}
|
||||
},
|
||||
resetTransformOrigin(popper) {
|
||||
let placementMap = {top: 'bottom', bottom: 'top'};
|
||||
let placement = popper._popper.getAttribute('x-placement').split('-')[0];
|
||||
let origin = placementMap[placement];
|
||||
popper._popper.style.transformOrigin = `center ${ origin }`;
|
||||
}
|
||||
resetTransformOrigin() {
|
||||
// 不判断,Select 会报错,不知道为什么
|
||||
if (!this.popper) return;
|
||||
|
||||
let x_placement = this.popper.popper.getAttribute('x-placement');
|
||||
let placementStart = x_placement.split('-')[0];
|
||||
let placementEnd = x_placement.split('-')[1];
|
||||
const leftOrRight = x_placement === 'left' || x_placement === 'right';
|
||||
if(!leftOrRight){
|
||||
this.popper.popper.style.transformOrigin = placementStart==='bottom' || ( placementStart !== 'top' && placementEnd === 'start') ? 'center top' : 'center bottom';
|
||||
}
|
||||
},
|
||||
handleGetIndex () {
|
||||
transferIncrease();
|
||||
return transferIndex;
|
||||
},
|
||||
},
|
||||
created () {
|
||||
this.$on('on-update-popper', this.update);
|
||||
|
|
29
src/components/select/functional-options.vue
Normal file
29
src/components/select/functional-options.vue
Normal file
|
@ -0,0 +1,29 @@
|
|||
|
||||
<script>
|
||||
const returnArrayFn = () => [];
|
||||
|
||||
export default {
|
||||
props: {
|
||||
options: {
|
||||
type: Array,
|
||||
default: returnArrayFn
|
||||
},
|
||||
slotOptions: {
|
||||
type: Array,
|
||||
default: returnArrayFn
|
||||
},
|
||||
slotUpdateHook: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
},
|
||||
functional: true,
|
||||
render(h, {props, parent}){
|
||||
// to detect changes in the $slot children/options we do this hack
|
||||
// so we can trigger the parents computed properties and have everything reactive
|
||||
// although $slot.default is not
|
||||
if (props.slotOptions !== parent.$slots.default) props.slotUpdateHook();
|
||||
return props.options;
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -1,5 +1,11 @@
|
|||
<template>
|
||||
<li :class="classes" @click.stop="select" @mouseout.stop="blur" v-show="!hidden"><slot>{{ showLabel }}</slot></li>
|
||||
<li
|
||||
:class="classes"
|
||||
@click.stop="select"
|
||||
@touchend.stop="select"
|
||||
@mousedown.prevent
|
||||
@touchstart.prevent
|
||||
><slot>{{ showLabel }}</slot></li>
|
||||
</template>
|
||||
<script>
|
||||
import Emitter from '../../mixins/emitter';
|
||||
|
@ -22,15 +28,19 @@
|
|||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
selected: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isFocused: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
selected: false,
|
||||
index: 0, // for up and down to focus
|
||||
isFocus: false,
|
||||
hidden: false, // for search
|
||||
searchLabel: '', // the value is slot,only for search
|
||||
searchLabel: '', // the slot value (textContent)
|
||||
autoComplete: false
|
||||
};
|
||||
},
|
||||
|
@ -41,53 +51,34 @@
|
|||
{
|
||||
[`${prefixCls}-disabled`]: this.disabled,
|
||||
[`${prefixCls}-selected`]: this.selected && !this.autoComplete,
|
||||
[`${prefixCls}-focus`]: this.isFocus
|
||||
[`${prefixCls}-focus`]: this.isFocused
|
||||
}
|
||||
];
|
||||
},
|
||||
showLabel () {
|
||||
return (this.label) ? this.label : this.value;
|
||||
},
|
||||
optionLabel(){
|
||||
return this.label || (this.$el && this.$el.textContent);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
select () {
|
||||
if (this.disabled) {
|
||||
return false;
|
||||
}
|
||||
if (this.disabled) return false;
|
||||
|
||||
this.dispatch('iSelect', 'on-select-selected', this.value);
|
||||
this.dispatch('iSelect', 'on-select-selected', {
|
||||
value: this.value,
|
||||
label: this.optionLabel,
|
||||
});
|
||||
this.$emit('on-select-selected', {
|
||||
value: this.value,
|
||||
label: this.optionLabel,
|
||||
});
|
||||
},
|
||||
blur () {
|
||||
this.isFocus = false;
|
||||
},
|
||||
queryChange (val) {
|
||||
const parsedQuery = val.replace(/(\^|\(|\)|\[|\]|\$|\*|\+|\.|\?|\\|\{|\}|\|)/g, '\\$1');
|
||||
this.hidden = !new RegExp(parsedQuery, 'i').test(this.searchLabel);
|
||||
},
|
||||
// 在使用函数防抖后,设置 key 后,不更新组件了,导致SearchLabel 不更新 #1865
|
||||
updateSearchLabel () {
|
||||
this.searchLabel = this.$el.textContent;
|
||||
},
|
||||
onSelectClose(){
|
||||
this.isFocus = false;
|
||||
},
|
||||
onQueryChange(val){
|
||||
this.queryChange(val);
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.updateSearchLabel();
|
||||
this.dispatch('iSelect', 'append');
|
||||
this.$on('on-select-close', this.onSelectClose);
|
||||
this.$on('on-query-change',this.onQueryChange);
|
||||
|
||||
const Select = findComponentUpward(this, 'iSelect');
|
||||
if (Select) this.autoComplete = Select.autoComplete;
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.dispatch('iSelect', 'remove');
|
||||
this.$off('on-select-close', this.onSelectClose);
|
||||
this.$off('on-query-change',this.onQueryChange);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
201
src/components/select/select-head.vue
Normal file
201
src/components/select/select-head.vue
Normal file
|
@ -0,0 +1,201 @@
|
|||
<template>
|
||||
<div @click="onHeaderClick">
|
||||
<div class="ivu-tag ivu-tag-checked" v-for="item in selectedMultiple">
|
||||
<span class="ivu-tag-text">{{ item.label }}</span>
|
||||
<Icon type="ios-close" @click.native.stop="removeTag(item)"></Icon>
|
||||
</div>
|
||||
<span
|
||||
:class="singleDisplayClasses"
|
||||
v-show="singleDisplayValue"
|
||||
>{{ singleDisplayValue }}</span>
|
||||
<input
|
||||
:id="inputElementId"
|
||||
type="text"
|
||||
v-if="filterable"
|
||||
v-model="query"
|
||||
:disabled="disabled"
|
||||
:class="[prefixCls + '-input']"
|
||||
:placeholder="showPlaceholder ? localePlaceholder : ''"
|
||||
:style="inputStyle"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
@keydown="resetInputState"
|
||||
@keydown.delete="handleInputDelete"
|
||||
@focus="onInputFocus"
|
||||
@blur="onInputFocus"
|
||||
|
||||
ref="input">
|
||||
<Icon type="ios-close-circle" :class="[prefixCls + '-arrow']" v-if="resetSelect" @click.native.stop="onClear"></Icon>
|
||||
<Icon type="ios-arrow-down" :class="[prefixCls + '-arrow']" v-if="!resetSelect && !remote && !disabled"></Icon>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Icon from '../icon';
|
||||
import Emitter from '../../mixins/emitter';
|
||||
import Locale from '../../mixins/locale';
|
||||
|
||||
const prefixCls = 'ivu-select';
|
||||
|
||||
export default {
|
||||
name: 'iSelectHead',
|
||||
mixins: [ Emitter, Locale ],
|
||||
components: { Icon },
|
||||
props: {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
filterable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
remote: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
initialLabel: {
|
||||
type: [String, Number, Array],
|
||||
},
|
||||
values: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
clearable: {
|
||||
type: [Function, Boolean],
|
||||
default: false,
|
||||
},
|
||||
inputElementId: {
|
||||
type: String
|
||||
},
|
||||
placeholder: {
|
||||
type: String
|
||||
},
|
||||
queryProp: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
prefixCls: prefixCls,
|
||||
query: '',
|
||||
inputLength: 20,
|
||||
remoteInitialLabel: this.initialLabel,
|
||||
preventRemoteCall: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
singleDisplayClasses(){
|
||||
const {filterable, multiple, showPlaceholder} = this;
|
||||
return [{
|
||||
[prefixCls + '-placeholder']: showPlaceholder && !filterable,
|
||||
[prefixCls + '-selected-value']: !showPlaceholder && !multiple && !filterable,
|
||||
}];
|
||||
},
|
||||
singleDisplayValue(){
|
||||
if ((this.multiple && this.values.length > 0) || this.filterable) return '';
|
||||
return `${this.selectedSingle}` || this.localePlaceholder;
|
||||
},
|
||||
showPlaceholder () {
|
||||
let status = false;
|
||||
if (!this.multiple) {
|
||||
const value = this.values[0];
|
||||
if (typeof value === 'undefined' || String(value).trim() === ''){
|
||||
status = !this.remoteInitialLabel;
|
||||
}
|
||||
} else {
|
||||
if (!this.values.length > 0) {
|
||||
status = true;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
},
|
||||
resetSelect(){
|
||||
return !this.showPlaceholder && this.clearable;
|
||||
},
|
||||
inputStyle () {
|
||||
let style = {};
|
||||
|
||||
if (this.multiple) {
|
||||
if (this.showPlaceholder) {
|
||||
style.width = '100%';
|
||||
} else {
|
||||
style.width = `${this.inputLength}px`;
|
||||
}
|
||||
}
|
||||
|
||||
return style;
|
||||
},
|
||||
localePlaceholder () {
|
||||
if (this.placeholder === undefined) {
|
||||
return this.t('i.select.placeholder');
|
||||
} else {
|
||||
return this.placeholder;
|
||||
}
|
||||
},
|
||||
selectedSingle(){
|
||||
const selected = this.values[0];
|
||||
return selected ? selected.label : (this.remoteInitialLabel || '');
|
||||
},
|
||||
selectedMultiple(){
|
||||
return this.multiple ? this.values : [];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onInputFocus(e){
|
||||
this.$emit(e.type === 'focus' ? 'on-input-focus' : 'on-input-blur');
|
||||
},
|
||||
removeTag (value) {
|
||||
if (this.disabled) return false;
|
||||
this.dispatch('iSelect', 'on-select-selected', value);
|
||||
},
|
||||
resetInputState () {
|
||||
this.inputLength = this.$refs.input.value.length * 12 + 20;
|
||||
this.$emit('on-keydown');
|
||||
},
|
||||
handleInputDelete () {
|
||||
if (this.multiple && this.selectedMultiple.length && this.query === '') {
|
||||
this.removeTag(this.selectedMultiple[this.selectedMultiple.length - 1]);
|
||||
}
|
||||
},
|
||||
onHeaderClick(e){
|
||||
if (this.filterable && e.target === this.$el){
|
||||
this.$refs.input.focus();
|
||||
}
|
||||
},
|
||||
onClear(){
|
||||
this.$emit('on-clear');
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
values ([value]) {
|
||||
if (!this.filterable) return;
|
||||
this.preventRemoteCall = true;
|
||||
if (this.multiple){
|
||||
this.query = '';
|
||||
this.preventRemoteCall = false; // this should be after the query change setter above
|
||||
return;
|
||||
}
|
||||
// #982
|
||||
if (typeof value === 'undefined' || value === '' || value === null) this.query = '';
|
||||
else this.query = value.label;
|
||||
this.$nextTick(() => this.preventRemoteCall = false); // this should be after the query change setter above
|
||||
},
|
||||
query (val) {
|
||||
if (this.preventRemoteCall) {
|
||||
this.preventRemoteCall = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('on-query-change', val);
|
||||
},
|
||||
queryProp(query){
|
||||
if (query !== this.query) this.query = query;
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue