Modify the directory structure

Modify the directory structure
This commit is contained in:
梁灏 2016-10-28 10:09:07 +08:00
parent 31fbef10e4
commit 4b05d84ea2
175 changed files with 48 additions and 46 deletions

View file

@ -0,0 +1,126 @@
<template>
<div>
<div :class="classes" :style="styles">
<slot></slot>
</div>
</div>
</template>
<script>
const prefixCls = 'ivu-affix';
function getScroll(target, top) {
const prop = top ? 'pageYOffset' : 'pageXOffset';
const method = top ? 'scrollTop' : 'scrollLeft';
let ret = target[prop];
if (typeof ret !== 'number') {
ret = window.document.documentElement[method];
}
return ret;
}
function getOffset(element) {
const rect = element.getBoundingClientRect();
const scrollTop = getScroll(window, true);
const scrollLeft = getScroll(window);
const docEl = window.document.body;
const clientTop = docEl.clientTop || 0;
const clientLeft = docEl.clientLeft || 0;
return {
top: rect.top + scrollTop - clientTop,
left: rect.left + scrollLeft - clientLeft
}
}
export default {
props: {
offsetTop: {
type: Number,
default: 0
},
offsetBottom: {
type: Number
}
},
data () {
return {
affix: false,
styles: {}
}
},
computed: {
offsetType () {
let type = 'top';
if (this.offsetBottom >= 0) {
type = 'bottom';
}
return type;
},
classes () {
return [
{
[`${prefixCls}`]: this.affix
}
]
}
},
ready () {
window.addEventListener('scroll', this.handleScroll, false);
window.addEventListener('resize', this.handleScroll, false);
},
beforeDestroy () {
window.removeEventListener('scroll', this.handleScroll, false);
window.removeEventListener('resize', this.handleScroll, false);
},
methods: {
handleScroll () {
const affix = this.affix;
const scrollTop = getScroll(window, true);
const elOffset = getOffset(this.$el);
const windowHeight = window.innerHeight;
const elHeight = this.$el.getElementsByTagName('div')[0].offsetHeight;
// Fixed Top
if ((elOffset.top - this.offsetTop) < scrollTop && this.offsetType == 'top' && !affix) {
this.affix = true;
this.styles = {
top: `${this.offsetTop}px`,
left: `${elOffset.left}px`,
width: `${this.$el.offsetWidth}px`
};
this.$emit('on-change', true);
} else if ((elOffset.top - this.offsetTop) > scrollTop && this.offsetType == 'top' && affix) {
this.affix = false;
this.styles = null;
this.$emit('on-change', false);
}
// Fixed Bottom
if ((elOffset.top + this.offsetBottom + elHeight) > (scrollTop + windowHeight) && this.offsetType == 'bottom' && !affix) {
this.affix = true;
this.styles = {
bottom: `${this.offsetBottom}px`,
left: `${elOffset.left}px`,
width: `${this.$el.offsetWidth}px`
};
this.$emit('on-change', true);
} else if ((elOffset.top + this.offsetBottom + elHeight) < (scrollTop + windowHeight) && this.offsetType == 'bottom' && affix) {
this.affix = false;
this.styles = null;
this.$emit('on-change', false);
}
}
}
}
</script>

View file

@ -0,0 +1,2 @@
import Affix from './affix.vue';
export default Affix;

View file

@ -0,0 +1,101 @@
<template>
<div v-if="!closed" :class="wrapClasses" transition="fade">
<span :class="iconClasses" v-if="showIcon">
<slot name="icon">
<Icon :type="iconType"></Icon>
</slot>
</span>
<span :class="messageClasses"><slot></slot></span>
<span :class="descClasses" v-el:desc><slot name="desc"></slot></span>
<a :class="closeClasses" v-if="closable" @click="close">
<slot name="close">
<Icon type="ios-close-empty"></Icon>
</slot>
</a>
</div>
</template>
<script>
import Icon from '../icon';
import { oneOf } from '../../utils/assist';
const prefixCls = 'ivu-alert';
export default {
components: { Icon },
props: {
type: {
validator (value) {
return oneOf(value, ['success', 'info', 'warning', 'error']);
},
default: 'info'
},
closable: {
type: Boolean,
default: false
},
showIcon: {
type: Boolean,
default: false
}
},
data () {
return {
closed: false,
desc: false
}
},
computed: {
wrapClasses () {
return [
`${prefixCls}`,
`${prefixCls}-${this.type}`,
{
[`${prefixCls}-with-icon`]: this.showIcon,
[`${prefixCls}-with-desc`]: this.desc
}
]
},
messageClasses () {
return `${prefixCls}-message`;
},
descClasses () {
return `${prefixCls}-desc`;
},
closeClasses () {
return `${prefixCls}-close`;
},
iconClasses () {
return `${prefixCls}-icon`;
},
iconType () {
let type = '';
switch (this.type) {
case 'success':
type = 'checkmark-circled';
break;
case 'info':
type = 'information-circled';
break;
case 'warning':
type = 'android-alert';
break;
case 'error':
type = 'close-circled';
break;
}
return type;
}
},
methods: {
close (e) {
this.closed = true;
this.$emit('on-close', e);
}
},
compiled () {
this.desc = this.$els.desc.innerHTML != '';
}
}
</script>

View file

@ -0,0 +1,2 @@
import Alert from './alert.vue';
export default Alert;

View file

@ -0,0 +1,90 @@
<template>
<div :class="classes" :style="styles" @click="back">
<slot>
<div :class="innerClasses">
<i class="ivu-icon ivu-icon-chevron-up"></i>
</div>
</slot>
</div>
</template>
<script>
const prefixCls = 'ivu-back-top';
function getScroll(target, top) {
const prop = top ? 'pageYOffset' : 'pageXOffset';
const method = top ? 'scrollTop' : 'scrollLeft';
let ret = target[prop];
if (typeof ret !== 'number') {
ret = window.document.documentElement[method];
}
return ret;
}
export default {
props: {
height: {
type: Number,
default: 400
},
bottom: {
type: Number,
default: 30
},
right: {
type: Number,
default: 30
}
},
data () {
return {
backTop: false
}
},
ready () {
window.addEventListener('scroll', this.handleScroll, false);
window.addEventListener('resize', this.handleScroll, false);
},
beforeDestroy () {
window.removeEventListener('scroll', this.handleScroll, false);
window.removeEventListener('resize', this.handleScroll, false);
},
computed: {
classes () {
return [
`${prefixCls}`,
{
[`${prefixCls}-show`]: this.backTop
}
]
},
styles () {
return {
bottom: `${this.bottom}px`,
right: `${this.right}px`
}
},
innerClasses () {
return `${prefixCls}-inner`;
}
},
methods: {
handleScroll () {
const backTop = this.backTop;
const scrollTop = getScroll(window, true);
if (this.height <= scrollTop && !backTop) {
this.backTop = true;
} else if (this.height > scrollTop && backTop) {
this.backTop = false;
}
},
back () {
window.scrollTo(0, 0);
this.$emit('on-click');
}
}
}
</script>

View file

@ -0,0 +1,2 @@
import BackTop from './back-top.vue';
export default BackTop;

View file

@ -0,0 +1,77 @@
<template>
<span v-if="dot" :class="classes" v-el:badge>
<slot></slot>
<sup :class="dotClasses" v-show="badge"></sup>
</span>
<span v-else :class="classes" v-el:badge>
<slot></slot>
<sup v-if="count" :class="countClasses" v-show="badge">{{ finalCount }}</sup>
</span>
</template>
<script>
const prefixCls = 'ivu-badge';
export default {
props: {
count: [Number, String],
dot: {
type: Boolean,
default: false
},
overflowCount: {
type: [Number, String],
default: 99
},
class: String
},
computed: {
classes () {
return `${prefixCls}`;
},
dotClasses () {
return `${prefixCls}-dot`;
},
countClasses () {
return [
`${prefixCls}-count`,
{
[`${this.class}`]: !!this.class,
[`${prefixCls}-count-alone`]: this.alone
}
]
},
finalCount () {
return parseInt(this.count) >= parseInt(this.overflowCount) ? `${this.overflowCount}+` : this.count;
},
badge () {
let status = false;
if (this.count) {
status = !(parseInt(this.count) === 0);
}
if (this.dot) {
status = true;
if (this.count) {
if (parseInt(this.count) === 0) {
status = false;
}
}
}
return status;
}
},
data () {
return {
alone: false
}
},
compiled () {
const child_length = this.$els.badge.children.length;
if (child_length === 1) {
this.alone = true;
}
}
}
</script>

View file

@ -0,0 +1,2 @@
import Badge from './badge.vue';
export default Badge;

View file

@ -0,0 +1,37 @@
import Notification from './notification.vue';
import Vue from 'vue';
import { camelcaseToHyphen } from '../../../utils/assist';
Notification.newInstance = properties => {
const _props = properties || {};
let props = '';
Object.keys(_props).forEach(prop => {
props += ' :' + camelcaseToHyphen(prop) + '=' + prop;
});
const div = document.createElement('div');
div.innerHTML = `<notification${props}></notification>`;
document.body.appendChild(div);
const notification = new Vue({
el: div,
data: _props,
components: { Notification }
}).$children[0];
return {
notice (noticeProps) {
notification.add(noticeProps);
},
remove (key) {
notification.close(key);
},
component: notification,
destroy () {
document.body.removeChild(div);
}
}
};
export default Notification;

View file

@ -0,0 +1,93 @@
<template>
<div :class="classes" :style="style" :transition="transitionName">
<div :class="[`${baseClass}-content`]" v-el:content>{{{ content }}}</div>
<a :class="[`${baseClass}-close`]" @click="close" v-if="closable">
<i class="ivu-icon ivu-icon-ios-close-empty"></i>
</a>
</div>
</template>
<script>
export default {
props: {
prefixCls: {
type: String,
default: ''
},
duration: {
type: Number,
default: 1.5
},
content: {
type: String,
default: ''
},
style: {
type: Object,
default: function() {
return {
right: '50%'
}
}
},
closable: {
type: Boolean,
default: false
},
className: {
type: String
},
key: {
type: String,
required: true
},
onClose: {
type: Function
},
transitionName: {
type: String
}
},
computed: {
baseClass () {
return `${this.prefixCls}-notice`;
},
classes () {
return [
this.baseClass,
{
[`${this.className}`]: !!this.className,
[`${this.baseClass}-closable`]: this.closable
}
]
},
contentClasses () {
return `${this.baseClass}-content`;
}
},
methods: {
clearCloseTimer () {
if (this.closeTimer) {
clearTimeout(this.closeTimer);
this.closeTimer = null;
}
},
close () {
this.clearCloseTimer();
this.onClose();
this.$parent.close(this.key);
}
},
compiled () {
this.clearCloseTimer();
if (this.duration !== 0) {
this.closeTimer = setTimeout(() => {
this.close();
}, this.duration * 1000)
}
},
beforeDestroy () {
this.clearCloseTimer();
}
}
</script>

View file

@ -0,0 +1,92 @@
<template>
<div :class="classes" :style="style">
<Notice v-for="notice in notices"
:prefix-cls="prefixCls"
:style="notice.style"
:content="notice.content"
:duration="notice.duration"
:closable="notice.closable"
:key="notice.key"
:transition-name="notice.transitionName"
:on-close="notice.onClose">
</Notice>
</div>
</template>
<script>
import Notice from './notice.vue';
const prefixCls = 'ivu-notification';
let seed = 0;
const now = Date.now();
function getUuid () {
return 'ivuNotification_' + now + '_' + (seed++);
}
export default {
components: { Notice },
props: {
prefixCls: {
type: String,
default: prefixCls
},
style: {
type: Object,
default: function () {
return {
top: '65px',
left: '50%'
}
}
},
content: {
type: String
},
className: {
type: String
}
},
data () {
return {
notices: []
}
},
computed: {
classes () {
return [
`${this.prefixCls}`,
{
[`${this.className}`]: !!this.className
}
]
}
},
methods: {
add (notice) {
const key = notice.key || getUuid();
let _notice = Object.assign({
style: {
right: '50%'
},
content: '',
duration: 1.5,
closable: false,
key: key
}, notice);
this.notices.push(_notice);
},
close (key) {
const notices = this.notices;
for (let i = 0; i < notices.length; i++) {
if (notices[i].key === key) {
this.notices.splice(i, 1);
break;
}
}
}
}
}
</script>

View file

@ -0,0 +1,101 @@
/**
* https://github.com/freeze-component/vue-popper
* */
import Popper from 'popper.js';
export default {
props: {
placement: {
type: String,
default: 'bottom'
},
boundariesPadding: {
type: Number,
default: 5
},
reference: Object,
popper: Object,
offset: {
default: 0
},
value: Boolean,
transition: String,
options: {
type: Object,
default () {
return {
gpuAcceleration: false,
boundariesElement: 'body'
}
}
},
visible: {
type: Boolean,
default: false
}
},
watch: {
value: {
immediate: true,
handler(val) {
this.visible = val;
this.$emit('input', val);
}
},
visible(val) {
val ? this.updatePopper() : this.destroyPopper();
this.$emit('input', val);
}
},
methods: {
createPopper() {
if (!/^(top|bottom|left|right)(-start|-end)?$/g.test(this.placement)) {
return;
}
const options = this.options;
const popper = this.popper || this.$els.popper;
const reference = this.reference || this.$els.reference;
if (!popper || !reference) return;
if (this.popperJS && this.popperJS.hasOwnProperty('destroy')) {
this.popperJS.destroy();
}
options.placement = this.placement;
options.offset = this.offset;
this.popperJS = new Popper(reference, popper, options);
this.popperJS.onCreate(popper => {
this.resetTransformOrigin(popper);
this.$nextTick(this.updatePopper);
this.$emit('created', this);
});
},
updatePopper() {
this.popperJS ? this.popperJS.update() : this.createPopper();
},
doDestroy() {
if (this.visible) return;
this.popperJS.destroy();
this.popperJS = null;
},
destroyPopper() {
if (this.popperJS) {
this.resetTransformOrigin(this.popperJS);
}
},
resetTransformOrigin(popper) {
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`;
}
},
beforeDestroy() {
if (this.popperJS) {
this.popperJS.destroy();
}
}
};

View file

@ -0,0 +1,36 @@
<template>
<span>
<a v-if="href" :href="href" :class="linkClasses">
<slot></slot>
</a>
<span v-else :class="linkClasses">
<slot></slot>
</span>
<span :class="separatorClasses">
<slot name="separator">{{{ separator }}}</slot>
</span>
</span>
</template>
<script>
const prefixCls = 'ivu-breadcrumb-item';
export default {
props: {
href: {
type: String
},
separator: {
type: String,
default: '/'
}
},
computed: {
linkClasses () {
return `${prefixCls}-link`;
},
separatorClasses () {
return `${prefixCls}-separator`;
}
}
}
</script>

View file

@ -0,0 +1,37 @@
<template>
<div :class="classes">
<slot></slot>
</div>
</template>
<script>
const prefixCls = 'ivu-breadcrumb';
export default {
props: {
separator: {
type: String,
default: '/'
}
},
computed: {
classes () {
return `${prefixCls}`;
}
},
compiled () {
this.updateChildren();
},
methods: {
updateChildren () {
this.$children.forEach((child) => {
child.separator = this.separator;
});
}
},
watch: {
separator () {
this.updateChildren();
}
}
}
</script>

View file

@ -0,0 +1,5 @@
import Breadcrumb from './breadcrumb.vue';
import BreadcrumbItem from './breadcrumb-item.vue';
Breadcrumb.Item = BreadcrumbItem;
export default Breadcrumb;

View file

@ -0,0 +1,30 @@
<template>
<div :class="classes">
<slot></slot>
</div>
</template>
<script>
import { oneOf } from '../../utils/assist';
const prefixCls = 'ivu-btn-group';
export default {
props: {
size: {
validator (value) {
return oneOf(value, ['small', 'large']);
}
},
},
computed: {
classes () {
return [
`${prefixCls}`,
{
[`${prefixCls}-${this.size}`]: !!this.size
}
]
}
}
}
</script>

View file

@ -0,0 +1,68 @@
<template>
<button :type="htmlType" :class="classes" :disabled="disabled">
<i :class="loadingIconClasses" v-if="loading"></i>
<i :class="typeIconClasses" v-if="icon && !loading"></i>
<slot></slot>
</button>
</template>
<script>
import Icon from '../icon';
import { oneOf } from '../../utils/assist';
const prefixCls = 'ivu-btn';
const iconPrefixCls = 'ivu-icon';
export default {
components: { Icon },
props: {
type: {
validator (value) {
return oneOf(value, ['primary', 'ghost']);
}
},
shape: {
validator (value) {
return oneOf(value, ['circle', 'circle-outline']);
}
},
size: {
validator (value) {
return oneOf(value, ['small', 'large']);
}
},
loading: Boolean,
disabled: Boolean,
htmlType: {
default: 'button',
validator (value) {
return oneOf(value, ['button', 'submit', 'reset']);
}
},
icon: String
},
computed: {
classes () {
return [
`${prefixCls}`,
{
[`${prefixCls}-${this.type}`]: !!this.type,
[`${prefixCls}-${this.shape}`]: !!this.shape,
[`${prefixCls}-${this.size}`]: !!this.size,
[`${prefixCls}-loading`]: this.loading != null && this.loading
}
]
},
loadingIconClasses () {
return `${iconPrefixCls} ivu-load-loop ${iconPrefixCls}-load-c`;
},
typeIconClasses () {
return [
`${iconPrefixCls}`,
{
[`${iconPrefixCls}-${this.icon}`]: !!this.icon
}
]
}
}
}
</script>

View file

@ -0,0 +1,5 @@
import Button from './button.vue';
import ButtonGroup from './button-group.vue';
Button.Group = ButtonGroup;
export default Button;

View file

@ -0,0 +1,58 @@
<template>
<div :class="classes">
<div :class="headClasses" v-if="showHead" v-el:head><slot name="title"></slot></div>
<div :class="extraClasses" v-if="showExtra" v-el:extra><slot name="extra"></slot></div>
<div :class="bodyClasses"><slot></slot></div>
</div>
</template>
<script>
const prefixCls = 'ivu-card';
export default {
props: {
bordered: {
type: Boolean,
default: true
},
disHover: {
type: Boolean,
default: false
},
shadow: {
type: Boolean,
default: false
}
},
data () {
return {
showHead: true,
showExtra: true
}
},
computed: {
classes () {
return [
`${prefixCls}`,
{
[`${prefixCls}-bordered`]: this.bordered && !this.shadow,
[`${prefixCls}-dis-hover`]: this.disHover || this.shadow,
[`${prefixCls}-shadow`]: this.shadow
}
]
},
headClasses () {
return `${prefixCls}-head`;
},
extraClasses () {
return `${prefixCls}-extra`;
},
bodyClasses () {
return `${prefixCls}-body`;
}
},
compiled () {
this.showHead = this.$els.head.innerHTML != '';
this.showExtra = this.$els.extra.innerHTML != '';
}
}
</script>

View file

@ -0,0 +1,2 @@
import Card from './card.vue';
export default Card;

View file

@ -0,0 +1,52 @@
<template>
<div :class="classes">
<slot></slot>
</div>
</template>
<script>
const prefixCls = 'ivu-checkbox-group';
export default {
props: {
model: {
type: Array,
default: []
}
},
computed: {
classes () {
return `${prefixCls}`;
}
},
compiled () {
this.updateModel(true);
},
methods: {
updateModel (update) {
const model = this.model;
this.$children.forEach((child) => {
child.model = model;
if (update) {
child.selected = model.indexOf(child.value) >= 0;
child.group = true;
}
});
},
change (data) {
this.model = data;
this.$emit('on-change', data);
}
},
watch: {
model (val, oldVal) {
if (val == oldVal) {
this.updateModel();
} else {
this.updateModel(true);
}
}
}
}
</script>

View file

@ -0,0 +1,104 @@
<template>
<label :class="wrapClasses">
<span :class="checkboxClasses">
<span :class="innerClasses"></span>
<input
v-if="group"
type="checkbox"
:class="inputClasses"
:disabled="disabled"
:value="value"
v-model="model"
@change="change">
<input
v-if="!group"
type="checkbox"
:class="inputClasses"
:disabled="disabled"
v-model="checked"
@change="change">
</span>
<slot><span>{{ value }}</span></slot>
</label>
</template>
<script>
const prefixCls = 'ivu-checkbox';
export default {
props: {
disabled: {
type: Boolean,
default: false
},
value: {
type: [String, Number, Boolean]
},
checked: {
type: Boolean,
default: false
}
},
data () {
return {
model: [],
selected: false,
group: false
}
},
computed: {
wrapClasses () {
return [
`${prefixCls}-wrapper`,
{
[`${prefixCls}-group-item`]: this.group,
[`${prefixCls}-wrapper-checked`]: this.selected,
[`${prefixCls}-wrapper-disabled`]: this.disabled
}
]
},
checkboxClasses () {
return [
`${prefixCls}`,
{
[`${prefixCls}-checked`]: this.selected,
[`${prefixCls}-disabled`]: this.disabled
}
]
},
innerClasses () {
return `${prefixCls}-inner`;
},
inputClasses () {
return `${prefixCls}-input`;
}
},
ready () {
if (!this.group) {
this.updateModel();
}
},
methods: {
change (event) {
if (this.disabled) {
return false;
}
this.selected = event.target.checked;
if (this.group) {
this.$parent.change(this.model);
} else {
this.$emit('on-change', this.checked);
}
},
updateModel () {
this.selected = this.checked;
}
},
watch: {
checked () {
this.updateModel();
}
}
}
</script>

View file

@ -0,0 +1,5 @@
import Checkbox from './checkbox.vue';
import CheckboxGroup from './checkbox-group.vue';
Checkbox.Group = CheckboxGroup;
export default Checkbox;

View file

@ -0,0 +1,83 @@
<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"/>
</svg>
<div :class="innerClasses">
<slot></slot>
</div>
</div>
</template>
<script>
import { oneOf } from '../../utils/assist';
const prefixCls = 'ivu-chart-circle';
export default {
props: {
percent: {
type: Number,
default: 0
},
size: {
type: Number,
default: 120
},
strokeWidth: {
type: Number,
default: 6
},
strokeColor: {
type: String,
default: '#2db7f5'
},
strokeLinecap: {
validator (value) {
return oneOf(value, ['square', 'round']);
},
default: 'round'
},
trailWidth: {
type: Number,
default: 5
},
trailColor: {
type: String,
default: '#eaeef2'
}
},
computed: {
circleSize () {
return {
width: `${this.size}px`,
height: `${this.size}px`
};
},
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}`;
},
len () {
return Math.PI * 2 * this.radius;
},
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'
}
},
wrapClasses () {
return `${prefixCls}`;
},
innerClasses () {
return `${prefixCls}-inner`;
}
}
}
</script>

View file

@ -0,0 +1,2 @@
import Circle from './circle.vue';
export default Circle;

View file

@ -0,0 +1,98 @@
<template>
<div :class="classes">
<slot></slot>
</div>
</template>
<script>
const prefixCls = 'ivu-collapse';
export default {
props: {
accordion: {
type: Boolean,
default: false
},
activeKey: {
type: [Array, String]
}
},
computed: {
classes () {
return `${prefixCls}`;
}
},
compiled () {
this.setActive();
},
methods: {
setActive () {
const activeKey = this.getActiveKey();
this.$children.forEach((child, index) => {
const key = child.key || index.toString();
let isActive = false;
if (self.accordion) {
isActive = activeKey === key;
} else {
isActive = activeKey.indexOf(key) > -1;
}
child.isActive = isActive;
child.index = index;
})
},
getActiveKey () {
let activeKey = this.activeKey || [];
const accordion = this.accordion;
if (!Array.isArray(activeKey)) {
activeKey = [activeKey];
}
if (accordion && activeKey.length > 1) {
activeKey = [activeKey[0]];
}
for (let i = 0; i < activeKey.length; i++) {
activeKey[i] = activeKey[i].toString();
}
return activeKey;
},
toggle (data) {
const key = data.key.toString();
let newActiveKey = [];
if (this.accordion) {
if (!data.isActive) {
newActiveKey.push(key);
}
} else {
let activeKey = this.getActiveKey();
const keyIndex = activeKey.indexOf(key);
if (data.isActive) {
if (keyIndex > -1) {
activeKey.splice(keyIndex, 1);
}
} else {
if (keyIndex < 0) {
activeKey.push(key);
}
}
newActiveKey = activeKey;
}
this.activeKey = newActiveKey;
this.$emit('on-change', newActiveKey);
}
},
watch: {
activeKey () {
this.setActive();
}
}
}
</script>

View file

@ -0,0 +1,5 @@
import Collapse from './collapse.vue';
import Panel from './panel.vue';
Collapse.Panel = Panel;
export default Collapse;

View file

@ -0,0 +1,57 @@
<template>
<div :class="itemClasses">
<div :class="headerClasses" @click="toggle">
<Icon type="arrow-right-b"></Icon>
<slot></slot>
</div>
<div :class="concentClasses" v-show="isActive">
<div :class="boxClasses"><slot name="content"></slot></div>
</div>
</div>
</template>
<script>
import Icon from '../icon';
const prefixCls = 'ivu-collapse';
export default {
components: { Icon },
props: {
key: {
type: String
}
},
data () {
return {
index: 0, // use index for default when key is null
isActive: false
}
},
computed: {
itemClasses () {
return [
`${prefixCls}-item`,
{
[`${prefixCls}-item-active`]: this.isActive
}
]
},
headerClasses () {
return `${prefixCls}-header`;
},
concentClasses () {
return `${prefixCls}-content`;
},
boxClasses () {
return `${prefixCls}-content-box`;
}
},
methods: {
toggle () {
this.$parent.toggle({
key: this.key || this.index,
isActive: this.isActive
});
}
}
}
</script>

View file

@ -0,0 +1,27 @@
<template>
<i :class="classes" :style="styles"></i>
</template>
<script>
const prefixCls = 'ivu-icon';
export default {
props: {
type: String,
size: [Number, String]
},
computed: {
classes () {
return `${prefixCls} ${prefixCls}-${this.type}`
},
styles () {
if (!!this.size) {
return {
'font-size': `${this.size}px`
}
} else {
return {}
}
}
}
}
</script>

View file

@ -0,0 +1,2 @@
import Icon from './icon.vue';
export default Icon;

View file

@ -0,0 +1,2 @@
import InputNumber from './input-number.vue';
export default InputNumber;

View file

@ -0,0 +1,242 @@
<template>
<div :class="wrapClasses">
<div :class="handlerClasses">
<a
@click="up"
@mouse.down="preventDefault"
:class="upClasses">
<span :class="innerUpClasses" @click="preventDefault"></span>
</a>
<a
@click="down"
@mouse.down="preventDefault"
:class="downClasses">
<span :class="innerDownClasses" @click="preventDefault"></span>
</a>
</div>
<div :class="inputWrapClasses">
<input
:class="inputClasses"
:disabled="disabled"
autoComplete="off"
@focus="focus"
@blur="blur"
@keydown.stop="keyDown"
@change="change"
:value="value">
</div>
</div>
</template>
<script>
import { oneOf } from '../../utils/assist';
const prefixCls = 'ivu-input-number';
const iconPrefixCls = 'ivu-icon';
function isValueNumber (value) {
return (/(^-?[0-9]+\.{1}\d+$)|(^-?[1-9][0-9]*$)/).test(value + '');
}
function addNum (num1, num2) {
var sq1, sq2, m;
try {
sq1 = num1.toString().split(".")[1].length;
}
catch (e) {
sq1 = 0;
}
try {
sq2 = num2.toString().split(".")[1].length;
}
catch (e) {
sq2 = 0;
}
// if (sq1 === 0 || sq2 === 0) {
// return num1 + num2;
// } else {
// m = Math.pow(10, Math.max(sq1, sq2));
// return (num1 * m + num2 * m) / m;
// }
m = Math.pow(10, Math.max(sq1, sq2));
return (num1 * m + num2 * m) / m;
}
export default {
props: {
max: {
type: Number,
default: Infinity
},
min: {
type: Number,
default: -Infinity
},
step: {
type: Number,
default: 1
},
value: {
type: Number,
default: 1
},
size: {
validator (value) {
return oneOf(value, ['small', 'large']);
}
},
disabled: {
type: Boolean,
default: false
}
},
data () {
return {
focused: false,
upDisabled: false,
downDisabled: false
}
},
computed: {
wrapClasses () {
return [
`${prefixCls}`,
{
[`${prefixCls}-${this.size}`]: !!this.size,
[`${prefixCls}-disabled`]: this.disabled,
[`${prefixCls}-focused`]: this.focused
}
]
},
handlerClasses () {
return `${prefixCls}-handler-wrap`;
},
upClasses () {
return [
`${prefixCls}-handler`,
`${prefixCls}-handler-up`,
{
[`${prefixCls}-handler-up-disabled`]: this.upDisabled
}
]
},
innerUpClasses () {
return `${prefixCls}-handler-up-inner ${iconPrefixCls} ${iconPrefixCls}-ios-arrow-up`;
},
downClasses () {
return [
`${prefixCls}-handler`,
`${prefixCls}-handler-down`,
{
[`${prefixCls}-handler-down-disabled`]: this.downDisabled
}
]
},
innerDownClasses () {
return `${prefixCls}-handler-down-inner ${iconPrefixCls} ${iconPrefixCls}-ios-arrow-down`;
},
inputWrapClasses () {
return `${prefixCls}-input-wrap`;
},
inputClasses () {
return `${prefixCls}-input`;
}
},
methods: {
preventDefault (e) {
e.preventDefault();
},
up () {
if (this.upDisabled) {
return false;
}
this.changeStep('up');
},
down () {
if (this.downDisabled) {
return false;
}
this.changeStep('down');
},
changeStep (type) {
if (this.disabled) {
return false;
}
let val = Number(this.value);
const step = Number(this.step);
if (isNaN(val)) {
return false;
}
if (type == 'up') {
val = addNum(val, step);
} else if (type == 'down') {
val = addNum(val, -step);
}
this.setValue(val);
},
setValue (val) {
this.$nextTick(() => {
this.value = val;
});
this.$emit('on-change', val);
},
focus () {
this.focused = true;
},
blur () {
this.focused = false;
},
keyDown (e) {
if (e.keyCode === 38) {
e.preventDefault();
this.up()
} else if (e.keyCode === 40) {
e.preventDefault();
this.down()
}
},
change (event) {
let val = event.target.value.trim();
const max = this.max;
const min = this.min;
if (isValueNumber(val)) {
val = Number(val);
this.value = val;
if (val > max) {
this.setValue(max);
} else if (val < min) {
this.setValue(min);
} else {
this.setValue(val);
}
} else {
event.target.value = this.value;
}
},
changeVal (val) {
if (isValueNumber(val) || val === 0) {
val = Number(val);
const step = this.step;
this.upDisabled = val + step > this.max;
this.downDisabled = val - step < this.min;
} else {
this.upDisabled = true;
this.downDisabled = true;
}
}
},
ready () {
this.changeVal(this.value);
},
watch: {
value (val) {
this.changeVal(val);
}
}
}
</script>

View file

@ -0,0 +1,2 @@
import Input from './input.vue';
export default Input;

View file

@ -0,0 +1,49 @@
<template>
<input
:class="classes"
:type="type"
:placeholder="placeholder"
:name="name"
v-model="value">
</template>
<script>
import { oneOf } from '../../utils/assist';
const prefixCls = 'ivu-input';
export default {
props: {
type: {
type: String,
default: 'text'
},
value: {
type: [String, Number],
default: '',
twoWay: true
},
placeholder: String,
name: String,
size: {
validator (value) {
return oneOf(value, ['small', 'large']);
}
}
},
data () {
return {
}
},
computed: {
classes () {
return [
`${prefixCls}`,
{
[`${prefixCls}-${this.size}`]: !!this.size
}
]
}
}
}
</script>

View file

@ -0,0 +1,36 @@
<template>
<div :class="classes">
<slot></slot>
</div>
</template>
<script>
import { oneOf } from '../../utils/assist';
const prefixCls = 'ivu-col';
export default {
props: {
span: [Number, String],
order: [Number, String],
offset: [Number, String],
push: [Number, String],
pull: [Number, String],
className: String
},
computed: {
classes () {
return [
`${prefixCls}`,
{
[`${prefixCls}-span-${this.span}`]: this.span,
[`${prefixCls}-order-${this.order}`]: this.order,
[`${prefixCls}-offset-${this.offset}`]: this.offset,
[`${prefixCls}-push-${this.push}`]: this.push,
[`${prefixCls}-pull-${this.pull}`]: this.pull,
[`${this.className}`]: !!this.className
}
]
}
}
}
</script>

View file

@ -0,0 +1,4 @@
import Row from './row.vue';
import Col from './col.vue';
export { Row, Col };

View file

@ -0,0 +1,44 @@
<template>
<div :class="classes">
<slot></slot>
</div>
</template>
<script>
import { oneOf } from '../../utils/assist';
const prefixCls = 'ivu-row';
export default {
props: {
type: {
validator (value) {
return oneOf(value, ['flex']);
}
},
align: {
validator (value) {
return oneOf(value, ['top', 'middle', 'bottom']);
}
},
justify: {
validator (value) {
return oneOf(value, ['start', 'end', 'center', 'space-around', 'space-between']);
}
},
className: String
},
computed: {
classes () {
return [
`${prefixCls}`,
{
[`${prefixCls}-${this.type}`]: !!this.type,
[`${prefixCls}-${this.type}-${this.align}`]: !!this.align,
[`${prefixCls}-${this.type}-${this.justify}`]: !!this.justify,
[`${this.className}`]: !!this.className
}
]
}
}
}
</script>

View file

@ -0,0 +1,110 @@
import LoadingBar from './loading-bar';
let loadingBarInstance;
let color = 'primary';
let failedColor = 'error';
let height = 2;
let timer;
function getLoadingBarInstance () {
loadingBarInstance = loadingBarInstance || LoadingBar.newInstance({
color: color,
failedColor: failedColor,
height: height
});
return loadingBarInstance;
}
function update(options) {
let instance = getLoadingBarInstance();
instance.update(options);
}
function hide() {
setTimeout(() => {
update({
show: false
});
setTimeout(() => {
update({
percent: 0
});
}, 200)
}, 800);
}
function clearTimer() {
if (timer) {
clearInterval(timer);
timer = null;
}
}
export default {
start () {
let percent = 0;
update({
percent: percent,
status: 'success',
show: true
});
timer = setInterval(() => {
percent += Math.floor(Math.random () * 3 + 5);
if (percent > 95) {
clearTimer();
}
update({
percent: percent,
status: 'success',
show: true
});
}, 200);
},
update (percent) {
clearTimer();
update({
percent: percent,
status: 'success',
show: true
});
},
finish () {
clearTimer();
update({
percent: 100,
status: 'success',
show: true
});
hide();
},
error () {
clearTimer();
update({
percent: 100,
status: 'error',
show: true
});
hide();
},
config (options) {
if (options.color) {
color = options.color;
}
if (options.failedColor) {
failedColor = options.failedColor;
}
if (options.height) {
height = options.height;
}
},
destroy () {
clearTimer();
let instance = getLoadingBarInstance();
loadingBarInstance = null;
instance.destroy();
}
}

View file

@ -0,0 +1,42 @@
import LoadingBar from './loading-bar.vue';
import Vue from 'vue';
import { camelcaseToHyphen } from '../../utils/assist';
LoadingBar.newInstance = properties => {
const _props = properties || {};
let props = '';
Object.keys(_props).forEach(prop => {
props += ' :' + camelcaseToHyphen(prop) + '=' + prop;
});
const div = document.createElement('div');
div.innerHTML = `<loading-bar${props}></loading-bar>`;
document.body.appendChild(div);
const loading_bar = new Vue({
el: div,
data: _props,
components: { LoadingBar }
}).$children[0];
return {
update (options) {
if ('percent' in options) {
loading_bar.percent = options.percent;
}
if (options.status) {
loading_bar.status = options.status;
}
if ('show' in options) {
loading_bar.show = options.show;
}
},
component: loading_bar,
destroy () {
document.body.removeChild(div);
}
}
};
export default LoadingBar;

View file

@ -0,0 +1,77 @@
<template>
<div :class="classes" :style="outerStyles" v-show="show" transition="fade">
<div :class="innerClasses" :style="styles"></div>
</div>
</template>
<script>
import { oneOf } from '../../utils/assist';
const prefixCls = 'ivu-loading-bar';
export default {
props: {
percent: {
type: Number,
default: 0
},
color: {
type: String,
default: 'primary'
},
failedColor: {
type: String,
default: 'error'
},
height: {
type: Number,
default: 2
},
status: {
type: String,
validator (value) {
return oneOf(value, ['success', 'error']);
},
default: 'success'
},
show: {
type: Boolean,
default: false
}
},
computed: {
classes () {
return `${prefixCls}`;
},
innerClasses () {
return [
`${prefixCls}-inner`,
{
[`${prefixCls}-inner-color-primary`]: this.color === 'primary' && this.status === 'success',
[`${prefixCls}-inner-failed-color-error`]: this.failedColor === 'error' && this.status === 'error'
}
]
},
outerStyles () {
return {
height: `${this.height}px`
}
},
styles () {
let style = {
width: `${this.percent}%`,
height: `${this.height}px`
};
if (this.color !== 'primary' && this.status === 'success') {
style.backgroundColor = this.color;
}
if (this.failedColor !== 'error' && this.status === 'error') {
style.backgroundColor = this.failedColor;
}
return style;
}
}
}
</script>

View file

@ -0,0 +1,97 @@
import Notification from '../base/notification';
const prefixCls = 'ivu-message';
const iconPrefixCls = 'ivu-icon';
const prefixKey = 'ivu_message_key_';
let defaultDuration = 1.5;
let top;
let messageInstance;
let key = 1;
const iconTypes = {
'info': 'information-circled',
'success': 'checkmark-circled',
'warning': 'android-alert',
'error': 'close-circled',
'loading': 'load-c'
};
function getMessageInstance () {
messageInstance = messageInstance || Notification.newInstance({
prefixCls: prefixCls,
style: {
top: `${top}px`
}
});
return messageInstance;
}
function notice (content, duration = defaultDuration, type, onClose) {
if (!onClose) {
onClose = function () {
}
}
const iconType = iconTypes[type];
// if loading
const loadCls = type === 'loading' ? ' ivu-load-loop' : '';
let instance = getMessageInstance();
instance.notice({
key: `${prefixKey}${key}`,
duration: duration,
style: {},
transitionName: 'move-up',
content: `
<div class="${prefixCls}-custom-content ${prefixCls}-${type}">
<i class="${iconPrefixCls} ${iconPrefixCls}-${iconType}${loadCls}"></i>
<span>${content}</span>
</div>
`,
onClose: onClose
});
// 用于手动消除
return (function () {
let target = key++;
return function () {
instance.remove(`${prefixKey}${target}`);
}
})();
}
export default {
info (content, duration, onClose) {
return notice(content, duration, 'info', onClose);
},
success (content, duration, onClose) {
return notice(content, duration, 'success', onClose);
},
warning (content, duration, onClose) {
return notice(content, duration, 'warning', onClose);
},
error (content, duration, onClose) {
return notice(content, duration, 'error', onClose);
},
loading (content, duration, onClose) {
return notice(content, duration, 'loading', onClose);
},
config (options) {
if (options.top) {
top = options.top;
}
if (options.duration) {
defaultDuration = options.duration;
}
},
destroy () {
let instance = getMessageInstance();
messageInstance = null;
instance.destroy();
}
}

View file

@ -0,0 +1,169 @@
import Vue from 'vue';
import Modal from './modal.vue';
import Icon from '../icon/icon.vue';
import Button from '../button/button.vue';
import { camelcaseToHyphen } from '../../utils/assist';
const prefixCls = 'ivu-modal-confirm';
Modal.newInstance = properties => {
const _props = properties || {};
let props = '';
Object.keys(_props).forEach(prop => {
props += ' :' + camelcaseToHyphen(prop) + '=' + prop;
});
const div = document.createElement('div');
div.innerHTML = `
<Modal${props} :visible.sync="visible" :width="width">
<div class="${prefixCls}">
<div class="${prefixCls}-head">
<div :class="iconTypeCls"><i :class="iconNameCls"></i></div>
<div class="${prefixCls}-head-title">{{{ title }}}</div>
</div>
<div class="${prefixCls}-body">
{{{ body }}}
</div>
<div class="${prefixCls}-footer">
<Button type="ghost" size="large" v-if="showCancel" @click="cancel">{{ cancelText }}</Button>
<Button type="primary" size="large" :loading="buttonLoading" @click="ok">{{ okText }}</Button>
</div>
</div>
</Modal>
`;
document.body.appendChild(div);
const modal = new Vue({
el: div,
components: { Modal, Button, Icon },
data: Object.assign(_props, {
visible: false,
width: 416,
title: '',
body: '',
iconType: '',
iconName: '',
okText: '确定',
cancelText: '取消',
showCancel: false,
loading: false,
buttonLoading: false
}),
computed: {
iconTypeCls () {
return [
`${prefixCls}-head-icon`,
`${prefixCls}-head-icon-${this.iconType}`
]
},
iconNameCls () {
return [
'ivu-icon',
`ivu-icon-${this.iconName}`
]
}
},
methods: {
cancel () {
this.visible = false;
this.buttonLoading = false;
this.onCancel();
this.remove();
},
ok () {
if (this.loading) {
this.buttonLoading = true;
} else {
this.visible = false;
this.remove();
}
this.onOk();
},
remove () {
setTimeout(() => {
this.destroy();
}, 300);
},
destroy () {
this.$destroy();
document.body.removeChild(div);
this.onRemove();
},
onOk () {},
onCancel () {},
onRemove () {}
}
}).$children[0];
return {
show (props) {
modal.$parent.showCancel = props.showCancel;
modal.$parent.iconType = props.icon;
switch (props.icon) {
case 'info':
modal.$parent.iconName = 'information-circled';
break;
case 'success':
modal.$parent.iconName = 'checkmark-circled';
break;
case 'warning':
modal.$parent.iconName = 'android-alert';
break;
case 'error':
modal.$parent.iconName = 'close-circled';
break;
case 'confirm':
modal.$parent.iconName = 'help-circled';
break;
}
if ('width' in props) {
modal.$parent.width = props.width;
}
if ('title' in props) {
modal.$parent.title = props.title;
}
if ('content' in props) {
modal.$parent.body = props.content;
}
if ('okText' in props) {
modal.$parent.okText = props.okText;
}
if ('cancelText' in props) {
modal.$parent.cancelText = props.cancelText;
}
if ('onCancel' in props) {
modal.$parent.onCancel = props.onCancel;
}
if ('onOk' in props) {
modal.$parent.onOk = props.onOk;
}
// async for ok
if ('loading' in props) {
modal.$parent.loading = props.loading;
}
// notice when component destroy
modal.$parent.onRemove = props.onRemove;
modal.visible = true;
},
remove () {
modal.visible = false;
modal.$parent.buttonLoading = false;
modal.$parent.remove();
},
component: modal
}
};
export default Modal;

View file

@ -0,0 +1,65 @@
import Modal from './confirm';
let modalInstance;
function getModalInstance () {
modalInstance = modalInstance || Modal.newInstance({
closable: false,
maskClosable: false,
footerHide: true
});
return modalInstance;
}
function confirm (options) {
let instance = getModalInstance();
options.onRemove = function () {
modalInstance = null;
};
instance.show(options);
}
Modal.info = function (props = {}) {
props.icon = 'info';
props.showCancel = false;
return confirm(props);
};
Modal.success = function (props = {}) {
props.icon = 'success';
props.showCancel = false;
return confirm(props);
};
Modal.warning = function (props = {}) {
props.icon = 'warning';
props.showCancel = false;
return confirm(props);
};
Modal.error = function (props = {}) {
props.icon = 'error';
props.showCancel = false;
return confirm(props);
};
Modal.confirm = function (props = {}) {
props.icon = 'confirm';
props.showCancel = true;
return confirm(props);
};
Modal.remove = function () {
if (!modalInstance) { // at loading status, remove after Cancel
return false;
}
const instance = getModalInstance();
instance.remove();
};
export default Modal;

View file

@ -0,0 +1,205 @@
<template>
<div :class="wrapClasses">
<div :class="maskClasses" v-show="visible" @click="mask" transition="fade"></div>
<div :class="classes" :style="styles" v-show="visible" transition="ease">
<div :class="[`${prefixCls}-content`]">
<a :class="[`${prefixCls}-close`]" v-if="closable" @click="close">
<slot name="close">
<Icon type="ios-close-empty"></Icon>
</slot>
</a>
<div :class="[`${prefixCls}-header`]" v-if="showHead" v-el:head><slot name="header"><p>{{ title }}</p></slot></div>
<div :class="[`${prefixCls}-body`]"><slot></slot></div>
<div :class="[`${prefixCls}-footer`]" v-if="!footerHide">
<slot name="footer">
<Button type="ghost" size="large" @click="cancel">{{ cancelText }}</Button>
<Button type="primary" size="large" :loading="buttonLoading" @click="ok">{{ okText }}</Button>
</slot>
</div>
</div>
</div>
</div>
</template>
<script>
import Icon from '../icon';
import Button from '../button';
import { getScrollBarSize } from '../../utils/assist';
const prefixCls = 'ivu-modal';
export default {
components: { Icon, Button },
props: {
visible: {
type: Boolean,
default: false
},
closable: {
type: Boolean,
default: true
},
maskClosable: {
type: Boolean,
default: true
},
title: {
type: String
},
width: {
type: [Number, String],
default: 520
},
okText: {
type: String,
default: '确定'
},
cancelText: {
type: String,
default: '取消'
},
loading: {
type: Boolean,
default: false
},
style: {
type: Object
},
className: {
type: String
},
// for instance
footerHide: {
type: Boolean,
default: false
}
},
data () {
return {
prefixCls: prefixCls,
wrapShow: false,
showHead: true,
buttonLoading: false
}
},
computed: {
wrapClasses () {
return [
`${prefixCls}-wrap`,
{
[`${prefixCls}-hidden`]: !this.wrapShow,
[`${this.className}`]: !!this.className
}
]
},
maskClasses () {
return `${prefixCls}-mask`;
},
classes () {
return `${prefixCls}`;
},
styles () {
let style = {};
const styleWidth = {
width: `${this.width}px`
};
const customStyle = !!this.style ? this.style : {};
Object.assign(style, styleWidth, customStyle);
return style;
}
},
methods: {
close () {
this.visible = false;
this.$emit('on-cancel');
},
mask () {
if (this.maskClosable) {
this.close();
}
},
cancel () {
this.close();
},
ok () {
if (this.loading) {
this.buttonLoading = true;
} else {
this.visible = false;
}
this.$emit('on-ok');
},
EscClose (e) {
if (this.visible && this.closable) {
if (e.keyCode === 27) {
this.close()
}
}
},
checkScrollBar () {
let fullWindowWidth = window.innerWidth;
if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8
const documentElementRect = document.documentElement.getBoundingClientRect();
fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left);
}
this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth;
if (this.bodyIsOverflowing) {
this.scrollBarWidth = getScrollBarSize();
}
},
setScrollBar () {
if (this.bodyIsOverflowing && this.scrollBarWidth !== undefined) {
document.body.style.paddingRight = `${this.scrollBarWidth}px`;
}
},
resetScrollBar () {
document.body.style.paddingRight = '';
},
addScrollEffect () {
this.checkScrollBar();
this.setScrollBar();
document.body.style.overflow = 'hidden';
},
removeScrollEffect() {
document.body.style.overflow = '';
this.resetScrollBar();
}
},
ready () {
if (this.visible) {
this.wrapShow = true;
}
let showHead = true;
if (this.$els.head.innerHTML == '<p></p>' && !this.title) {
showHead = false;
}
this.showHead = showHead;
// ESC close
document.addEventListener('keydown', this.EscClose);
},
beforeDestroy () {
document.removeEventListener('keydown', this.EscClose);
},
watch: {
visible (val) {
if (val === false) {
this.buttonLoading = false;
setTimeout(() => {
this.wrapShow = false;
}, 300);
this.removeScrollEffect();
} else {
this.wrapShow = true;
this.addScrollEffect();
}
}
}
}
</script>

View file

@ -0,0 +1,115 @@
import Notification from '../base/notification';
const prefixCls = 'ivu-notice';
const iconPrefixCls = 'ivu-icon';
const prefixKey = 'ivu_notice_key_';
let top = 24;
let defaultDuration = 4.5;
let noticeInstance;
let key = 1;
const iconTypes = {
'info': 'information-circled',
'success': 'checkmark-circled',
'warning': 'android-alert',
'error': 'close-circled'
};
function getNoticeInstance () {
noticeInstance = noticeInstance || Notification.newInstance({
prefixCls: prefixCls,
style: {
top: `${top}px`,
right: 0
}
});
return noticeInstance;
}
function notice (type, options) {
const title = options.title || '';
const desc = options.desc || '';
const noticeKey = options.key || `${prefixKey}${key}`;
const onClose = options.onClose || function () {};
// todo const btn = options.btn || null;
const duration = (options.duration === 0) ? 0 : options.duration || defaultDuration;
key++;
let instance = getNoticeInstance();
let content;
if (type == 'normal') {
content = `
<div class="${prefixCls}-custom-content">
<div class="${prefixCls}-title">${title}</div>
<div class="${prefixCls}-desc">${desc}</div>
</div>
`;
} else {
const iconType = iconTypes[type];
content = `
<div class="${prefixCls}-custom-content ${prefixCls}-with-icon">
<span class="${prefixCls}-icon ${prefixCls}-icon-${type}">
<i class="${iconPrefixCls} ${iconPrefixCls}-${iconType}"></i>
</span>
<div class="${prefixCls}-title">${title}</div>
<div class="${prefixCls}-desc">${desc}</div>
</div>
`;
}
instance.notice({
key: noticeKey.toString(),
duration: duration,
style: {},
transitionName: 'move-right',
content: content,
onClose: onClose,
closable: true
});
}
export default {
open (options) {
return notice('normal', options);
},
info (options) {
return notice('info', options);
},
success (options) {
return notice('success', options);
},
warning (options) {
return notice('warning', options);
},
error (options) {
return notice('error', options);
},
config (options) {
if (options.top) {
top = options.top;
}
if (options.duration || options.duration === 0) {
defaultDuration = options.duration;
}
},
close (key) {
if (key) {
key = key.toString();
if (noticeInstance) {
noticeInstance.remove(key);
}
} else {
return false;
}
},
destroy () {
let instance = getNoticeInstance();
noticeInstance = null;
instance.destroy();
}
}

View file

@ -0,0 +1,2 @@
import Page from './page.vue';
export default Page;

View file

@ -0,0 +1,87 @@
<template>
<div v-if="showSizer || showElevator" :class="optsClasses">
<div v-if="showSizer" :class="sizerClasses">
<i-select :model.sync="pageSize" :size="size" @on-change="changeSize">
<i-option v-for="item in pageSizeOpts" :value="item" style="text-align:center;">{{ item }} /</i-option>
</i-select>
</div>
<div v-if="showElevator" :class="ElevatorClasses">
跳至
<input type="text" :value="_current" @keyup.enter="changePage">
</div>
</div>
</template>
<script>
import iSelect from '../../components/select/select.vue';
import iOption from '../../components/select/option.vue';
const prefixCls = 'ivu-page';
function isValueNumber (value) {
return (/^[1-9][0-9]*$/).test(value + '');
}
export default {
components: { iSelect, iOption },
props: {
pageSizeOpts: Array,
showSizer: Boolean,
showElevator: Boolean,
current: Number,
_current: Number,
pageSize: Number,
allPages: Number,
isSmall: Boolean
},
computed: {
size () {
return this.isSmall ? 'small' : 'default';
},
optsClasses () {
return [
`${prefixCls}-options`
]
},
sizerClasses () {
return [
`${prefixCls}-options-sizer`
]
},
ElevatorClasses () {
return [
`${prefixCls}-options-elevator`
]
}
},
methods: {
changeSize () {
this.$emit('on-size', this.pageSize);
},
changePage (event) {
let val = event.target.value.trim();
let page = 0;
if (isValueNumber(val)) {
val = Number(val);
if (val != this.current) {
const allPages = this.allPages;
if (val > allPages) {
page = allPages;
} else {
page = val;
}
}
} else {
page = 1;
}
if (page) {
this.$emit('on-page', page);
event.target.value = page;
}
}
}
}
</script>

View file

@ -0,0 +1,238 @@
<template>
<ul :class="simpleWrapClasses" v-if="simple">
<li
title="上一页"
:class="prevClasses"
@click="prev">
<a><i class="ivu-icon ivu-icon-ios-arrow-left"></i></a>
</li>
<div :class="simplePagerClasses" :title="current + '/' + allPages">
<input
type="text"
:value="current"
@keydown="keyDown"
@keyup="keyUp"
@change="keyUp">
<span>/</span>
{{ allPages }}
</div>
<li
title="下一页"
:class="nextClasses"
@click="next">
<a><i class="ivu-icon ivu-icon-ios-arrow-right"></i></a>
</li>
</ul>
<ul :class="wrapClasses" v-else>
<span :class="[`${prefixCls}-total`]" v-if="showTotal">
<slot> {{ total }} </slot>
</span>
<li
title="上一页"
:class="prevClasses"
@click="prev">
<a><i class="ivu-icon ivu-icon-ios-arrow-left"></i></a>
</li>
<li title="第一页" :class="[`${prefixCls}-item`,{[`${prefixCls}-item-active`]: current == 1}]" @click="changePage(1)"><a>1</a></li>
<li title="向前 5 页" v-if="current - 3 > 1" :class="[`${prefixCls}-item-jump-prev`]" @click="fastPrev"><a><i class="ivu-icon ivu-icon-ios-arrow-left"></i></a></li>
<li :title="current - 2" v-if="current - 2 > 1" :class="[`${prefixCls}-item`]" @click="changePage(current - 2)"><a>{{ current - 2 }}</a></li>
<li :title="current - 1" v-if="current - 1 > 1" :class="[`${prefixCls}-item`]" @click="changePage(current - 1)"><a>{{ current - 1 }}</a></li>
<li :title="current" v-if="current != 1 && current != allPages" :class="[`${prefixCls}-item`,`${prefixCls}-item-active`]"><a>{{ current }}</a></li>
<li :title="current + 1" v-if="current + 1 < allPages" :class="[`${prefixCls}-item`]" @click="changePage(current + 1)"><a>{{ current + 1 }}</a></li>
<li :title="current + 2" v-if="current + 2 < allPages" :class="[`${prefixCls}-item`]" @click="changePage(current + 2)"><a>{{ current + 2 }}</a></li>
<li title="向后 5 页" v-if="current + 3 < allPages" :class="[`${prefixCls}-item-jump-next`]" @click="fastNext"><a><i class="ivu-icon ivu-icon-ios-arrow-right"></i></a></li>
<li :title="'最后一页:' + allPages" v-if="allPages > 1" :class="[`${prefixCls}-item`, {[`${prefixCls}-item-active`]: current == allPages}]" @click="changePage(allPages)"><a>{{ allPages }}</a></li>
<li
title="下一页"
:class="nextClasses"
@click="next">
<a><i class="ivu-icon ivu-icon-ios-arrow-right"></i></a>
</li>
<Options
:show-sizer="showSizer"
:page-size="pageSize"
:page-size-opts="pageSizeOpts"
:show-elevator="showElevator"
:_current.once="current"
:current.sync="current"
:all-pages="allPages"
:is-small="isSmall"
@on-size="onSize"
@on-page="onPage">
</Options>
</ul>
</template>
<script>
import { oneOf } from '../../utils/assist';
import Options from './options.vue';
const prefixCls = 'ivu-page';
export default {
components: { Options },
props: {
current: {
type: Number,
default: 1
},
total: {
type: Number,
default: 0
},
pageSize: {
type: Number,
default: 10
},
pageSizeOpts: {
type: Array,
default () {
return [10, 20, 30, 40]
}
},
size: {
validator (value) {
return oneOf(value, ['small']);
}
},
simple: {
type: Boolean,
default: false
},
showTotal: {
type: Boolean,
default: false
},
showElevator: {
type: Boolean,
default: false
},
showSizer: {
type: Boolean,
default: false
}
},
data () {
return {
prefixCls: prefixCls
}
},
computed: {
isSmall () {
return !!this.size;
},
allPages () {
const allPage = Math.ceil(this.total / this.pageSize);
return (allPage === 0) ? 1 : allPage;
},
simpleWrapClasses () {
return [
`${prefixCls}`,
`${prefixCls}-simple`
]
},
simplePagerClasses () {
return `${prefixCls}-simple-pager`;
},
wrapClasses () {
return [
`${prefixCls}`,
{
'mini': !!this.size
}
]
},
prevClasses () {
return [
`${prefixCls}-prev`,
{
[`${prefixCls}-disabled`]: this.current == 1
}
]
},
nextClasses () {
return [
`${prefixCls}-next`,
{
[`${prefixCls}-disabled`]: this.current == this.allPages
}
]
}
},
methods: {
changePage (page) {
if (this.current != page) {
this.current = page;
this.$emit('on-change', page);
}
},
prev () {
const current = this.current;
if (current <= 1) {
return false;
}
this.changePage(current - 1);
},
next () {
const current = this.current;
if (current >= this.allPages) {
return false;
}
this.changePage(current + 1);
},
fastPrev () {
const page = this.current - 5;
if (page > 0) {
this.changePage(page);
} else {
this.changePage(1);
}
},
fastNext () {
const page = this.current + 5;
if (page > this.allPages) {
this.changePage(this.allPages);
} else {
this.changePage(page);
}
},
onSize (pageSize) {
this.pageSize = pageSize;
this.changePage(1);
},
onPage (page) {
this.changePage(page);
},
keyDown (e) {
const key = e.keyCode;
const condition = (key >= 48 && key <= 57) || key == 8 || key == 37 || key == 39;
if (!condition) {
e.preventDefault();
}
},
keyUp (e) {
const key = e.keyCode;
const val = parseInt(e.target.value);
if (key === 38) {
this.prev()
} else if (key === 40) {
this.next()
} else if (key == 13) {
let page = 1;
if (val > this.allPages) {
page = this.allPages;
} else if (val <= 0) {
page = 1;
} else {
page = val;
}
e.target.value = page;
this.changePage(page);
}
}
}
}
</script>

View file

@ -0,0 +1,3 @@
import Poptip from './poptip.vue';
export default Poptip;

View file

@ -0,0 +1,170 @@
<template>
<div
:class="classes"
@mouseenter="handleMouseenter"
@mouseleave="handleMouseleave"
v-clickoutside="handleClose">
<div
:class="[`${prefixCls}-rel`]"
v-el:reference
@click="handleClick"
@mousedown="handleFocus"
@mouseup="handleBlur">
<slot></slot>
</div>
<div :class="[`${prefixCls}-popper`]" :style="styles" transition="fade" v-el:popper v-show="visible">
<div :class="[`${prefixCls}-content`]">
<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>
<div :class="[`${prefixCls}-body-message`]"><slot name="title">{{ title }}</slot></div>
</div>
<div :class="[`${prefixCls}-footer`]">
<Button type="ghost" size="small" @click="cancel">{{ cancelText }}</Button>
<Button type="primary" size="small" @click="ok">{{ okText }}</Button>
</div>
</div>
<div :class="[`${prefixCls}-inner`]" v-if="!confirm">
<div :class="[`${prefixCls}-title`]" v-if="showTitle" v-el:title><slot name="title">{{ title }}</slot></div>
<div :class="[`${prefixCls}-body`]">
<div :class="[`${prefixCls}-body-content`]"><slot name="content">{{ content }}</slot></div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Popper from '../base/popper';
import Button from '../button/button.vue';
import clickoutside from '../../directives/clickoutside';
import { oneOf } from '../../utils/assist';
const prefixCls = 'ivu-poptip';
export default {
mixins: [Popper],
directives: { clickoutside },
components: { Button },
props: {
trigger: {
validator (value) {
return oneOf(value, ['click', 'focus', 'hover']);
},
default: 'click'
},
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']);
},
default: 'top'
},
title: {
type: [String, Number]
},
content: {
type: [String, Number],
default: ''
},
width: {
type: [String, Number]
},
confirm: {
type: Boolean,
default: false
},
okText: {
type: String,
default: '确定'
},
cancelText: {
type: String,
default: '取消'
}
},
data () {
return {
prefixCls: prefixCls,
showTitle: true
}
},
computed: {
classes () {
return [
`${prefixCls}`,
{
[`${prefixCls}-confirm`]: this.confirm
}
]
},
styles () {
let style = {};
if (!!this.width) {
style.width = `${this.width}px`;
}
return style;
}
},
methods: {
handleClick () {
if (this.confirm) {
this.visible = !this.visible;
return true;
}
if (this.trigger !== 'click') {
return false;
}
this.visible = !this.visible;
},
handleClose () {
if (this.confirm) {
this.visible = false;
return true;
}
if (this.trigger !== 'click') {
return false;
}
this.visible = false;
},
handleFocus () {
if (this.trigger !== 'focus' || this.confirm) {
return false;
}
this.visible = true;
},
handleBlur () {
if (this.trigger !== 'focus' || this.confirm) {
return false;
}
this.visible = false;
},
handleMouseenter () {
if (this.trigger !== 'hover' || this.confirm) {
return false;
}
this.visible = true;
},
handleMouseleave () {
if (this.trigger !== 'hover' || this.confirm) {
return false;
}
this.visible = false;
},
cancel () {
this.visible = false;
this.$emit('on-cancel');
},
ok () {
this.visible = false;
this.$emit('on-ok');
}
},
ready () {
if (!this.confirm) {
this.showTitle = this.$els.title.innerHTML != '';
}
}
}
</script>

View file

@ -0,0 +1,2 @@
import Progress from './progress.vue';
export default Progress;

View file

@ -0,0 +1,121 @@
<template>
<div :class="wrapClasses">
<div :class="outerClasses">
<div :class="innerClasses">
<div :class="bgClasses" :style="bgStyle"></div>
</div>
</div>
<span v-if="!hideInfo" :class="textClasses">
<slot>
<span v-if="isStatus" :class="textInnerClasses">
<Icon :type="statusIcon"></Icon>
</span>
<span v-else :class="textInnerClasses">
{{ percent }}%
</span>
</slot>
</span>
</div>
</template>
<script>
import Icon from '../icon';
import { oneOf } from '../../utils/assist';
const prefixCls = 'ivu-progress';
export default {
components: { Icon },
props: {
percent: {
type: Number,
default: 0
},
status: {
validator (value) {
return oneOf(value, ['normal', 'active', 'wrong', 'success']);
},
default: 'normal'
},
hideInfo: {
type: Boolean,
default: false
},
strokeWidth: {
type: Number,
default: 10
}
},
computed: {
isStatus () {
return this.status == 'wrong' || this.status == 'success';
},
statusIcon () {
let type = '';
switch (this.status) {
case 'wrong':
type = 'ios-close';
break;
case 'success':
type = 'ios-checkmark';
break;
}
return type;
},
bgStyle () {
return {
width: `${this.percent}%`,
height: `${this.strokeWidth}px`
}
},
wrapClasses () {
return [
`${prefixCls}`,
`${prefixCls}-${this.status}`,
{
[`${prefixCls}-show-info`]: !this.hideInfo,
}
]
},
textClasses () {
return `${prefixCls}-text`;
},
textInnerClasses () {
return `${prefixCls}-text-inner`;
},
outerClasses () {
return `${prefixCls}-outer`;
},
innerClasses () {
return `${prefixCls}-inner`;
},
bgClasses () {
return `${prefixCls}-bg`;
}
},
compiled () {
this.handleStatus();
},
methods: {
handleStatus (isDown) {
if (isDown) {
this.status = 'normal';
} else {
if (parseInt(this.percent, 10) == 100) {
this.status = 'success';
}
}
}
},
watch: {
percent (val, oldVal) {
if (val < oldVal) {
this.handleStatus(true);
} else {
this.handleStatus();
}
}
}
}
</script>

View file

@ -0,0 +1,5 @@
import Radio from './radio.vue';
import RadioGroup from './radio-group.vue';
Radio.Group = RadioGroup;
export default Radio;

View file

@ -0,0 +1,62 @@
<template>
<div :class="classes">
<slot></slot>
</div>
</template>
<script>
import { oneOf } from '../../utils/assist';
const prefixCls = 'ivu-radio-group';
export default {
props: {
model: {
type: [String, Number],
default: ''
},
size: {
validator (value) {
return oneOf(value, ['small', 'large']);
}
},
type: {
validator (value) {
return oneOf(value, ['button']);
}
}
},
computed: {
classes () {
return [
`${prefixCls}`,
{
[`${prefixCls}-${this.size}`]: !!this.size,
[`${prefixCls}-${this.type}`]: !!this.type
}
]
}
},
compiled () {
this.updateModel();
},
methods: {
updateModel () {
const model = this.model;
this.$children.forEach((child) => {
child.selected = model == child.value;
child.group = true;
});
},
change (data) {
this.model = data.value;
this.updateModel();
this.$emit('on-change', data.value);
}
},
watch: {
model () {
this.updateModel()
}
}
}
</script>

View file

@ -0,0 +1,96 @@
<template>
<label :class="wrapClasses">
<span :class="radioClasses">
<span :class="innerClasses"></span>
<input
type="radio"
:class="inputClasses"
:disabled="disabled"
:checked="selected"
@change="change">
</span>
<slot>{{ value }}</slot>
</label>
</template>
<script>
const prefixCls = 'ivu-radio';
export default {
props: {
checked: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
value: {
type: [String, Number]
}
},
data () {
return {
selected: false,
group: false
}
},
computed: {
wrapClasses () {
return [
`${prefixCls}-wrapper`,
{
[`${prefixCls}-group-item`]: this.group,
[`${prefixCls}-wrapper-checked`]: this.selected,
[`${prefixCls}-wrapper-disabled`]: this.disabled
}
]
},
radioClasses () {
return [
`${prefixCls}`,
{
[`${prefixCls}-checked`]: this.selected,
[`${prefixCls}-disabled`]: this.disabled
}
]
},
innerClasses () {
return `${prefixCls}-inner`;
},
inputClasses () {
return `${prefixCls}-input`;
}
},
ready () {
if (!this.group) {
this.updateModel();
}
},
methods: {
change (event) {
if (this.disabled) {
return false;
}
this.selected = event.target.checked;
this.checked = this.selected;
if (this.group && this.checked) {
this.$parent.change({
value: this.value,
checked: this.checked
});
}
},
updateModel () {
this.selected = this.checked;
}
},
watch: {
checked () {
this.updateModel();
}
}
}
</script>

View file

@ -0,0 +1,59 @@
<template>
<div class="ivu-select-dropdown"><slot></slot></div>
</template>
<script>
import Popper from 'popper.js';
export default {
data () {
return {
popper: null
}
},
methods: {
update () {
if (this.popper) {
this.$nextTick(() => {
this.popper.update();
});
} else {
this.$nextTick(() => {
this.popper = new Popper(this.$parent.$els.reference, this.$el, {
gpuAcceleration: false,
placement: 'bottom-start',
boundariesPadding: 0,
forceAbsolute: true
});
this.popper.onCreate(popper => {
this.resetTransformOrigin(popper);
});
});
}
},
destroy () {
if (this.popper) {
this.resetTransformOrigin(this.popper);
setTimeout(() => {
this.popper.destroy();
this.popper = null;
}, 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 }`;
}
},
ready () {
this.$on('on-update-popper', this.update);
this.$on('on-destroy-popper', this.destroy);
},
beforeDestroy () {
if (this.popper) {
this.popper.destroy();
}
}
}
</script>

View file

@ -0,0 +1,5 @@
import Select from './select.vue';
import Option from './option.vue';
import OptionGroup from './option-group.vue';
export { Select, Option, OptionGroup };

View file

@ -0,0 +1,25 @@
<template>
<li :class="[`${prefixCls}-wrap`]">
<div :class="[`${prefixCls}-title`]">{{ label }}</div>
<ul>
<li :class="[`${prefixCls}`]"><slot></slot></li>
</ul>
</li>
</template>
<script>
const prefixCls = 'ivu-select-group';
export default {
props: {
label: {
type: String,
default: ''
}
},
data () {
return {
prefixCls: prefixCls
}
}
}
</script>

View file

@ -0,0 +1,73 @@
<template>
<li :class="classes" @click.stop="select" @mouseout.stop="blur" v-show="!hidden"><slot>{{ showLabel }}</slot></li>
</template>
<script>
const prefixCls = 'ivu-select-item';
export default {
props: {
value: {
type: [String, Number],
required: true
},
label: {
type: [String, Number]
},
disabled: {
type: Boolean,
default: false
}
},
componentName: 'select-item',
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
}
},
computed: {
classes () {
return [
`${prefixCls}`,
{
[`${prefixCls}-disabled`]: this.disabled,
[`${prefixCls}-selected`]: this.selected,
[`${prefixCls}-focus`]: this.isFocus
}
]
},
showLabel () {
return (!!this.label) ? this.label : this.value;
}
},
methods: {
select () {
if (this.disabled) {
return false;
}
this.$dispatch('on-select-selected', this.value);
},
blur () {
this.isFocus = false;
},
queryChange (val) {
this.hidden = !new RegExp(val, 'i').test(this.searchLabel);
}
},
ready () {
this.searchLabel = this.$el.innerHTML;
},
events: {
'on-select-close' () {
this.isFocus = false;
},
'on-query-change' (val) {
this.queryChange(val);
}
}
}
</script>

View file

@ -0,0 +1,511 @@
<template>
<div :class="classes" v-clickoutside="handleClose">
<div
:class="[`${prefixCls}-selection`]"
v-el:reference
@click="toggleMenu">
<div class="ivu-tag" v-for="item in selectedMultiple">
<span class="ivu-tag-text">{{ item.label }}</span>
<Icon type="ios-close-empty" @click.stop="removeTag($index)"></Icon>
</div>
<span :class="[`${prefixCls}-placeholder`]" v-show="showPlaceholder && !filterable">{{ placeholder }}</span>
<span :class="[`${prefixCls}-selected-value`]" v-show="!showPlaceholder && !multiple && !filterable">{{ selectedSingle }}</span>
<input
type="text"
v-if="filterable"
v-model="query"
:class="[`${prefixCls}-input`]"
:placeholder="showPlaceholder ? placeholder : ''"
:style="inputStyle"
@blur="handleBlur"
@keydown="resetInputState"
@keydown.delete="handleInputDelete"
v-el:input>
<Icon type="ios-close" :class="[`${prefixCls}-arrow`]" v-show="showCloseIcon" @click.stop="clearSingleSelect"></Icon>
<Icon type="arrow-down-b" :class="[`${prefixCls}-arrow`]"></Icon>
</div>
<Dropdown v-show="visible" transition="slide-up" v-ref:dropdown>
<ul v-show="notFound" :class="[`${prefixCls}-not-found`]"><li>{{ notFoundText }}</li></ul>
<ul v-else :class="[`${prefixCls}-dropdown-list`]"><slot></slot></ul>
</Dropdown>
</div>
</template>
<script>
import Icon from '../icon';
import Dropdown from './dropdown.vue';
import clickoutside from '../../directives/clickoutside';
import { oneOf } from '../../utils/assist';
const prefixCls = 'ivu-select';
export default {
components: { Icon, Dropdown },
directives: { clickoutside },
props: {
model: {
type: [String, Number, Array],
default: ''
},
multiple: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
clearable: {
type: Boolean,
default: false
},
placeholder: {
type: String,
default: '请选择'
},
filterable: {
type: Boolean,
default: false
},
filterMethod: {
type: Function
},
size: {
validator (value) {
return oneOf(value, ['small', 'large', 'default']);
}
},
labelInValue: {
type: Boolean,
default: false
},
notFoundText: {
type: String,
default: '无匹配数据'
}
},
data () {
return {
prefixCls: prefixCls,
visible: false,
options: [],
optionInstances: [],
selectedSingle: '', // label
selectedMultiple: [],
focusIndex: 0,
query: '',
inputLength: 20,
notFound: false
}
},
computed: {
classes () {
return [
`${prefixCls}`,
{
[`${prefixCls}-visible`]: this.visible,
[`${prefixCls}-disabled`]: this.disabled,
[`${prefixCls}-multiple`]: this.multiple,
[`${prefixCls}-single`]: !this.multiple,
[`${prefixCls}-show-clear`]: this.showCloseIcon,
[`${prefixCls}-${this.size}`]: !!this.size
}
]
},
showPlaceholder () {
let status = false;
if ((typeof this.model) === 'string') {
if (this.model === '') {
status = true;
}
} else if (Array.isArray(this.model)) {
if (!this.model.length) {
status = true;
}
}
return status;
},
showCloseIcon () {
return !this.multiple && this.clearable && !this.showPlaceholder;
},
inputStyle () {
let style = {};
if (this.multiple) {
if (this.showPlaceholder) {
style.width = '100%';
} else {
style.width = `${this.inputLength}px`;
}
}
return style;
}
},
methods: {
toggleMenu () {
if (this.disabled) {
return false;
}
this.visible = !this.visible;
},
hideMenu () {
this.visible = false;
this.focusIndex = 0;
this.$broadcast('on-select-close');
},
// find option component
findChild (cb) {
const find = function (child) {
const name = child.$options.componentName;
if (name) {
cb(child);
} else if (child.$children.length) {
child.$children.forEach((innerChild) => {
find(innerChild, cb);
});
}
};
if (this.optionInstances.length) {
this.optionInstances.forEach((child) => {
find(child);
})
} else {
this.$children.forEach((child) => {
find(child);
});
}
},
updateOptions (init) {
let options = [];
let index = 1;
this.findChild((child) => {
options.push({
value: child.value,
label: (child.label === undefined) ? child.$el.innerHTML : child.label
});
child.index = index++;
if (init) {
this.optionInstances.push(child);
}
});
this.options = options;
if (init) {
this.updateSingleSelected(true);
this.updateMultipleSelected(true);
}
},
updateSingleSelected (init = false) {
const type = typeof this.model;
if (type === 'string' || type === 'number') {
for (let i = 0; i < this.options.length; i++) {
if (this.model === this.options[i].value) {
this.selectedSingle = this.options[i].label;
break;
}
}
}
this.toggleSingleSelected(this.model, init);
},
clearSingleSelect () {
if (this.showCloseIcon) {
this.findChild((child) => {
child.selected = false;
});
this.model = '';
if (this.filterable) {
this.query = '';
}
}
},
updateMultipleSelected (init = false) {
if (this.multiple && Array.isArray(this.model)) {
let selected = [];
for (let i = 0; i < this.model.length; i++) {
const model = this.model[i];
for (let j = 0; j < this.options.length; j++) {
const option = this.options[j];
if (model === option.value) {
selected.push({
value: option.value,
label: option.label
})
}
}
}
this.selectedMultiple = selected;
}
this.toggleMultipleSelected(this.model, init);
},
removeTag (index) {
if (this.disabled) {
return false;
}
this.model.splice(index, 1);
if (this.filterable && this.visible) {
this.$els.input.focus();
}
this.$broadcast('on-update-popper');
},
// to select option for single
toggleSingleSelected (value, init = false) {
if (!this.multiple) {
let label = '';
this.findChild((child) => {
if (child.value === value) {
child.selected = true;
label = (child.label === undefined) ? child.$el.innerHTML : child.label;
} else {
child.selected = false;
}
});
this.hideMenu();
if (!init) {
if (this.labelInValue) {
this.$emit('on-change', {
value: value,
label: label
});
} else {
this.$emit('on-change', value);
}
}
}
},
// to select option for multiple
toggleMultipleSelected (value, init = false) {
if (this.multiple) {
let hybridValue = [];
for (let i = 0; i < value.length; i++) {
hybridValue.push({
value: value[i]
});
}
this.findChild((child) => {
const index = value.indexOf(child.value);
if (index >= 0) {
child.selected = true;
hybridValue[index].label = (child.label === undefined) ? child.$el.innerHTML : child.label;
} else {
child.selected = false;
}
});
if (!init) {
if (this.labelInValue) {
this.$emit('on-change', hybridValue);
} else {
this.$emit('on-change', value);
}
}
}
},
handleClose () {
this.hideMenu();
},
handleKeydown (e) {
if (this.visible) {
const keyCode = e.keyCode;
// Esc slide-up
if (keyCode === 27) {
e.preventDefault();
this.hideMenu();
}
// next
if (keyCode === 40) {
e.preventDefault();
this.navigateOptions('next');
}
// prev
if (keyCode === 38) {
e.preventDefault();
this.navigateOptions('prev');
}
// enter
if (keyCode === 13) {
e.preventDefault();
this.findChild((child) => {
if (child.isFocus) {
child.select();
}
});
}
}
},
navigateOptions (direction) {
if (direction === 'next') {
const next = this.focusIndex + 1;
this.focusIndex = (this.focusIndex === this.options.length) ? 1 : next;
} else if (direction === 'prev') {
const prev = this.focusIndex - 1;
this.focusIndex = (this.focusIndex <= 1) ? this.options.length : prev;
}
let child_status = {
disabled: false,
hidden: false
};
let find_deep = false; // can next find allowed
this.findChild((child) => {
if (child.index === this.focusIndex) {
child_status.disabled = child.disabled;
child_status.hidden = child.hidden;
if (!child.disabled && !child.hidden) {
child.isFocus = true;
}
} else {
child.isFocus = false;
}
if (!child.hidden && !child.disabled) {
find_deep = true;
}
});
this.resetScrollTop();
if ((child_status.disabled || child_status.hidden) && find_deep) {
this.navigateOptions(direction);
}
},
resetScrollTop () {
const index = this.focusIndex - 1;
let bottomOverflowDistance = this.optionInstances[index].$el.getBoundingClientRect().bottom - this.$refs.dropdown.$el.getBoundingClientRect().bottom;
let topOverflowDistance = this.optionInstances[index].$el.getBoundingClientRect().top - this.$refs.dropdown.$el.getBoundingClientRect().top;
if (bottomOverflowDistance > 0) {
this.$refs.dropdown.$el.scrollTop += bottomOverflowDistance;
}
if (topOverflowDistance < 0) {
this.$refs.dropdown.$el.scrollTop += topOverflowDistance;
}
},
handleBlur () {
setTimeout(() => {
const model = this.model;
if (this.multiple) {
} else {
if (model !== '') {
this.findChild((child) => {
if (child.value === model) {
this.query = child.searchLabel;
}
});
}
}
}, 300);
},
resetInputState () {
this.inputLength = this.$els.input.value.length * 12 + 20;
},
handleInputDelete () {
if (this.multiple && this.model.length && this.query === '') {
this.removeTag(this.model.length - 1);
}
}
},
ready () {
this.updateOptions(true);
document.addEventListener('keydown', this.handleKeydown);
},
beforeDestroy () {
document.removeEventListener('keydown', this.handleKeydown);
},
watch: {
model () {
if (this.multiple) {
this.updateMultipleSelected();
} else {
this.updateSingleSelected();
}
},
visible (val) {
if (val) {
if (this.multiple && this.filterable) {
this.$els.input.focus();
}
this.$broadcast('on-update-popper');
} else {
if (this.filterable) {
this.$els.input.blur();
}
this.$broadcast('on-destroy-popper');
}
},
query (val) {
this.$broadcast('on-query-change', val);
let is_hidden = true;
this.$nextTick(() => {
this.findChild((child) => {
if (!child.hidden) {
is_hidden = false;
}
});
this.notFound = is_hidden;
});
}
},
events: {
'on-select-selected' (value) {
if (this.model === value) {
this.hideMenu();
} else {
if (this.multiple) {
const index = this.model.indexOf(value);
if (index >= 0) {
this.removeTag(index);
} else {
this.model.push(value);
this.$broadcast('on-update-popper');
}
if (this.filterable) {
this.query = '';
this.$els.input.focus();
}
} else {
this.model = value;
if (this.filterable) {
this.findChild((child) => {
if (child.value === value) {
this.query = child.searchLabel;
}
});
}
}
}
}
}
}
</script>

View file

@ -0,0 +1,2 @@
import Spin from './spin.vue';
export default Spin;

View file

@ -0,0 +1,60 @@
<template>
<div :class="classes" transition="fade">
<div :class="mainClasses">
<span :class="dotClasses"></span>
<div :class="textClasses" v-el:text><slot></slot></div>
</div>
</div>
</template>
<script>
import { oneOf } from '../../utils/assist';
const prefixCls = 'ivu-spin';
export default {
props: {
size: {
validator (value) {
return oneOf(value, ['small', 'large']);
}
},
fix: {
type: Boolean,
default: false
}
},
data () {
return {
showText: false
}
},
computed: {
classes () {
return [
`${prefixCls}`,
{
[`${prefixCls}-${this.size}`]: !!this.size,
[`${prefixCls}-fix`]: this.fix,
[`${prefixCls}-show-text`]: this.showText,
}
]
},
mainClasses () {
return `${prefixCls}-main`;
},
dotClasses () {
return `${prefixCls}-dot`;
},
textClasses () {
return `${prefixCls}-text`;
}
},
compiled () {
const text = this.$els.text.innerHTML;
if (text != '') {
this.showText = true;
}
}
}
</script>

View file

@ -0,0 +1,5 @@
import Steps from './steps.vue';
import Step from './step.vue';
Steps.Step = Step;
export default Steps;

View file

@ -0,0 +1,94 @@
<template>
<div :class="wrapClasses" :style="styles">
<div :class="[`${prefixCls}-tail`]"><i></i></div>
<div :class="[`${prefixCls}-head`]">
<div :class="[`${prefixCls}-head-inner`]">
<span v-if="!icon && status != 'finish' && status != 'error'">{{ stepNumber }}</span>
<span v-else :class="iconClasses"></span>
</div>
</div>
<div :class="[`${prefixCls}-main`]">
<div :class="[`${prefixCls}-title`]">{{ title }}</div>
<div v-if="content" :class="[`${prefixCls}-content`]">{{ content }}</div>
</div>
</div>
</template>
<script>
import { oneOf } from '../../utils/assist';
const prefixCls = 'ivu-steps';
const iconPrefixCls = 'ivu-icon';
export default {
props: {
status: {
validator (value) {
return oneOf(value, ['wait', 'process', 'finish', 'error']);
}
},
title: {
type: String,
default: ''
},
content: {
type: String
},
icon: {
type: String
}
},
data () {
return {
prefixCls: prefixCls,
stepNumber: '',
nextError: false,
total: 1
}
},
computed: {
wrapClasses () {
return [
`${prefixCls}-item`,
`${prefixCls}-status-${this.status}`,
{
[`${prefixCls}-custom`]: !!this.icon,
[`${prefixCls}-next-error`]: this.nextError
}
]
},
iconClasses () {
let icon = '';
if (!!this.icon) {
icon = this.icon;
} else {
if (this.status == 'finish') {
icon = 'ios-checkmark-empty';
} else if (this.status == 'error') {
icon = 'ios-close-empty';
}
}
return [
`${prefixCls}-icon`,
`${iconPrefixCls}`,
{
[`${iconPrefixCls}-${icon}`]: icon != ''
}
]
},
styles () {
return {
width: `${1/this.total*100}%`
}
}
},
watch: {
status () {
if (this.status == 'error') {
this.$parent.setNextError();
}
}
}
}
</script>

View file

@ -0,0 +1,107 @@
<template>
<div :class="classes">
<slot></slot>
</div>
</template>
<script>
import { oneOf } from '../../utils/assist';
const prefixCls = 'ivu-steps';
export default {
props: {
current: {
type: Number,
default: 0
},
status: {
validator (value) {
return oneOf(value, ['wait', 'process', 'finish', 'error']);
},
default: 'process'
},
size: {
validator (value) {
return oneOf(value, ['small']);
}
},
direction: {
validator (value) {
return oneOf(value, ['horizontal', 'vertical']);
},
default: 'horizontal'
}
},
computed: {
classes () {
return [
`${prefixCls}`,
`${prefixCls}-${this.direction}`,
{
[`${prefixCls}-${this.size}`]: !!this.size
}
]
}
},
ready () {
this.updateChildProps(true);
this.setNextError();
this.updateCurrent(true);
},
methods: {
updateChildProps (isInit) {
const total = this.$children.length;
this.$children.forEach((child, index) => {
child.stepNumber = index + 1;
if (this.direction === 'horizontal') {
child.total = total;
}
// status,,
// todo error,current
if (!(isInit && child.status)) {
if (index == this.current) {
if (this.status != 'error') {
child.status = 'process';
}
} else if (index < this.current) {
child.status = 'finish';
} else {
child.status = 'wait';
}
}
if (child.status != 'error' && index != 0) {
this.$children[index - 1].nextError = false;
}
});
},
setNextError () {
this.$children.forEach((child, index) => {
if (child.status == 'error' && index != 0) {
this.$children[index - 1].nextError = true;
}
});
},
updateCurrent (isInit) {
if (isInit) {
const current_status = this.$children[this.current].status;
if (!current_status) {
this.$children[this.current].status = this.status;
}
} else {
this.$children[this.current].status = this.status;
}
}
},
watch: {
current () {
this.updateChildProps();
},
status () {
this.updateCurrent();
}
}
}
</script>

View file

@ -0,0 +1,2 @@
import Switch from './switch.vue';
export default Switch;

View file

@ -0,0 +1,56 @@
<template>
<span :class="wrapClasses" @click="toggle">
<span :class="innerClasses">
<slot name="open" v-if="checked"></slot>
<slot name="close" v-if="!checked"></slot>
</span>
</span>
</template>
<script>
import { oneOf } from '../../utils/assist';
const prefixCls = 'ivu-switch';
export default {
props: {
checked: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
size: {
validator (value) {
return oneOf(value, ['small']);
}
}
},
computed: {
wrapClasses () {
return [
`${prefixCls}`,
{
[`${prefixCls}-checked`]: this.checked,
[`${prefixCls}-disabled`]: this.disabled,
[`${prefixCls}-${this.size}`]: !!this.size
}
]
},
innerClasses () {
return `${prefixCls}-inner`;
}
},
methods: {
toggle () {
if (this.disabled) {
return false;
}
this.checked = !this.checked;
this.$emit('on-change', this.checked);
}
}
}
</script>

View file

@ -0,0 +1,2 @@
import Tag from './tag.vue';
export default Tag;

View file

@ -0,0 +1,51 @@
<template>
<div v-if="!closed" :class="classes" transition="fade">
<span :class="textClasses"><slot></slot></span>
<Icon v-if="closable" type="ios-close-empty" @click="close"></Icon>
</div>
</template>
<script>
import Icon from '../icon';
import { oneOf } from '../../utils/assist';
const prefixCls = 'ivu-tag';
export default {
components: { Icon },
props: {
closable: {
type: Boolean,
default: false
},
color: {
validator (value) {
return oneOf(value, ['blue', 'green', 'red', 'yellow']);
}
}
},
data () {
return {
closed: false
}
},
computed: {
classes () {
return [
`${prefixCls}`,
{
[`${prefixCls}-${this.color}`]: !!this.color
}
]
},
textClasses () {
return `${prefixCls}-text`;
}
},
methods: {
close (e) {
this.closed = true;
this.$emit('on-close', e);
}
}
}
</script>

View file

@ -0,0 +1,5 @@
import Timeline from './timeline.vue';
import TimelineItem from './timeline-item.vue';
Timeline.Item = TimelineItem;
export default Timeline;

View file

@ -0,0 +1,65 @@
<template>
<li :class="itemClasses">
<div :class="tailClasses"></div>
<div :class="headClasses" :style="customColor" v-el:dot><slot name="dot"></slot></div>
<div :class="contentClasses">
<slot></slot>
</div>
</li>
</template>
<script>
const prefixCls = 'ivu-timeline';
export default {
props: {
color: {
type: String,
default: 'blue'
}
},
data () {
return {
dot: false
}
},
ready () {
this.dot = this.$els.dot.innerHTML.length ? true : false;
},
computed: {
itemClasses () {
return `${prefixCls}-item`;
},
tailClasses () {
return `${prefixCls}-item-tail`;
},
headClasses () {
return [
`${prefixCls}-item-head`,
{
[`${prefixCls}-item-head-custom`]: this.dot,
[`${prefixCls}-item-head-${this.color}`]: this.headColorShow
}
]
},
headColorShow () {
return this.color == 'blue' || this.color == 'red' || this.color == 'green';
},
customColor () {
let style = {};
if (this.color) {
if (!this.headColorShow) {
style = {
'color': this.color,
'border-color': this.color
}
}
}
return style;
},
contentClasses () {
return `${prefixCls}-item-content`;
}
}
}
</script>

View file

@ -0,0 +1,27 @@
<template>
<ul :class="classes">
<slot></slot>
</ul>
</template>
<script>
const prefixCls = 'ivu-timeline';
export default {
props: {
pending: {
type: Boolean,
default: false
}
},
computed: {
classes () {
return [
`${prefixCls}`,
{
[`${prefixCls}-pending`]: this.pending
}
]
}
}
}
</script>

View file

@ -0,0 +1,3 @@
import Tooltip from './tooltip.vue';
export default Tooltip;

View file

@ -0,0 +1,59 @@
<template>
<div :class="[`${prefixCls}`]" @mouseenter="handleShowPopper" @mouseleave="handleClosePopper">
<div :class="[`${prefixCls}-rel`]" v-el:reference>
<slot></slot>
</div>
<div :class="[`${prefixCls}-popper`]" transition="fade" v-el:popper v-show="!disabled && visible">
<div :class="[`${prefixCls}-content`]">
<div :class="[`${prefixCls}-arrow`]"></div>
<div :class="[`${prefixCls}-inner`]"><slot name="content">{{ content }}</slot></div>
</div>
</div>
</div>
</template>
<script>
import Popper from '../base/popper';
import { oneOf } from '../../utils/assist';
const prefixCls = 'ivu-tooltip';
export default {
mixins: [Popper],
props: {
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']);
},
default: 'bottom'
},
content: {
type: [String, Number],
default: ''
},
delay: {
type: Number,
default: 0
},
disabled: {
type: Boolean,
default: false
}
},
data () {
return {
prefixCls: prefixCls
}
},
methods: {
handleShowPopper() {
this.timeout = setTimeout(() => {
this.visible = true;
}, this.delay);
},
handleClosePopper() {
clearTimeout(this.timeout);
this.visible = false;
}
}
}
</script>