Merge remote-tracking branch 'upstream/2.0' into 2.0

This commit is contained in:
TabEnter 2017-08-16 15:44:48 +08:00
commit f9508bf178
42 changed files with 960 additions and 240 deletions

View file

@ -0,0 +1,93 @@
<template>
<span :class="classes">
<img :src="src" v-if="src">
<Icon :type="icon" v-else-if="icon"></Icon>
<span ref="children" :class="[prefixCls + '-string']" :style="childrenStyle" v-else><slot></slot></span>
</span>
</template>
<script>
import Icon from '../icon';
import { oneOf } from '../../utils/assist';
const prefixCls = 'ivu-avatar';
export default {
name: 'Avatar',
components: { Icon },
props: {
shape: {
validator (value) {
return oneOf(value, ['circle', 'square']);
},
default: 'circle'
},
size: {
validator (value) {
return oneOf(value, ['small', 'large', 'default']);
},
default: 'default'
},
src: {
type: String
},
icon: {
type: String
}
},
data () {
return {
prefixCls: prefixCls,
scale: 1,
isSlotShow: false
};
},
computed: {
classes () {
return [
`${prefixCls}`,
`${prefixCls}-${this.shape}`,
`${prefixCls}-${this.size}`,
{
[`${prefixCls}-image`]: !!this.src,
[`${prefixCls}-icon`]: !!this.icon
}
];
},
childrenStyle () {
let style = {};
if (this.isSlotShow) {
style = {
msTransform: `scale(${this.scale})`,
WebkitTransform: `scale(${this.scale})`,
transform: `scale(${this.scale})`,
position: 'absolute',
display: 'inline-block',
left: `calc(50% - ${Math.round(this.$refs.children.offsetWidth / 2)}px)`
};
}
return style;
}
},
methods: {
setScale () {
this.isSlotShow = !this.src && !this.icon;
if (this.$slots.default) {
const 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;
} else {
this.scale = 1;
}
}
}
},
mounted () {
this.setScale();
},
updated () {
this.setScale();
}
};
</script>

View file

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

View file

@ -15,7 +15,7 @@
<script>
import Casitem from './casitem.vue';
import Emitter from '../../mixins/emitter';
import { findComponentUpward } from '../../utils/assist';
import { findComponentUpward, findComponentDownward } from '../../utils/assist';
let key = 1;
@ -67,7 +67,9 @@
if (fromUser) {
cascader.isLoadedChildren = true;
}
this.handleTriggerItem(item);
if (item.children.length) {
this.handleTriggerItem(item);
}
});
return;
}
@ -84,6 +86,14 @@
changeOnSelect: this.changeOnSelect,
fromInit: fromInit
});
// #1553
if (this.changeOnSelect) {
const Caspanel = findComponentDownward(this, 'Caspanel');
if (Caspanel) {
Caspanel.$emit('on-clear', true);
}
}
} else {
this.sublist = [];
this.dispatch('Cascader', 'on-result-change', {
@ -135,9 +145,16 @@
}
}
});
this.$on('on-clear', () => {
// deep for #1553
this.$on('on-clear', (deep = false) => {
this.sublist = [];
this.tmpItem = {};
if (deep) {
const Caspanel = findComponentDownward(this, 'Caspanel');
if (Caspanel) {
Caspanel.$emit('on-clear', true);
}
}
});
}
};

View file

@ -36,7 +36,15 @@
default: false
},
value: {
type: Boolean,
type: [String, Number, Boolean],
default: false
},
trueValue: {
type: [String, Number, Boolean],
default: true
},
falseValue: {
type: [String, Number, Boolean],
default: false
},
label: {
@ -102,21 +110,26 @@
const checked = event.target.checked;
this.currentValue = checked;
this.$emit('input', checked);
let value = checked ? this.trueValue : this.falseValue;
this.$emit('input', value);
if (this.group) {
this.parent.change(this.model);
} else {
this.$emit('on-change', checked);
this.dispatch('FormItem', 'on-form-change', checked);
this.$emit('on-change', value);
this.dispatch('FormItem', 'on-form-change', value);
}
},
updateModel () {
this.currentValue = this.value;
this.currentValue = this.value === this.trueValue;
}
},
watch: {
value () {
value (val) {
if (val !== this.trueValue && val !== this.falseValue) {
throw 'Value should be trueValue or falseValue.';
}
this.updateModel();
}
}

View file

@ -0,0 +1,90 @@
<template>
<Dropdown trigger="click" :transfer="transfer" :placement="placement">
<div :class="wrapClasses">
<i class="ivu-icon ivu-icon-arrow-down-b ivu-input-icon ivu-input-icon-normal"></i>
<div :class="inputClasses">
<div :class="[prefixCls + '-color']" style="background-color: rgb(32, 160, 255);"></div>
</div>
</div>
<Dropdown-menu slot="list">
<p>常用于各种自定义下拉内容的场景</p>
<div style="text-align: right;margin:10px;">
<Button type="primary">关闭</Button>
</div>
</Dropdown-menu>
</Dropdown>
</template>
<script>
import Dropdown from '../dropdown/dropdown.vue';
import DropdownMenu from '../dropdown/dropdown-menu.vue';
import { oneOf } from '../../utils/assist';
const prefixCls = 'ivu-color-picker';
const inputPrefixCls = 'ivu-input';
export default {
name: 'ColorPicker',
components: { Dropdown, DropdownMenu },
props: {
value: {
type: String
},
alpha: {
type: Boolean,
default: false
},
format: {
validator (value) {
return oneOf(value, ['hsl', 'hsv', 'hex', 'rgb']);
}
},
disabled: {
type: Boolean,
default: false
},
size: {
validator (value) {
return oneOf(value, ['small', 'large', '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']);
},
default: 'bottom'
},
transfer: {
type: Boolean,
default: false
}
},
data () {
return {
prefixCls: prefixCls,
currentValue: this.value
};
},
computed: {
wrapClasses () {
return [
`${prefixCls}-rel`,
`${inputPrefixCls}-wrapper`,
`${inputPrefixCls}-wrapper-${this.size}`
];
},
inputClasses () {
return [
`${prefixCls}-input`,
`${inputPrefixCls}`,
`${inputPrefixCls}-${this.size}`,
{
[`${inputPrefixCls}-disabled`]: this.disabled
}
];
}
},
methods: {
}
};
</script>

View file

View file

@ -0,0 +1,2 @@
import ColorPicker from './color-picker.vue';
export default ColorPicker;

View file

@ -1,18 +1,18 @@
<template>
<div :class="classes">
<div :class="[prefixCls+ '-list']" ref="hours">
<ul :class="[prefixCls + '-ul']" @click="handleClickHours">
<li :class="getCellCls(item)" v-for="(item, index) in hoursList" v-show="!item.hide" :index="index">{{ formatTime(item.text) }}</li>
<ul :class="[prefixCls + '-ul']">
<li :class="getCellCls(item)" v-for="item in hoursList" v-show="!item.hide" @click="handleClick('hours', item)">{{ formatTime(item.text) }}</li>
</ul>
</div>
<div :class="[prefixCls+ '-list']" ref="minutes">
<ul :class="[prefixCls + '-ul']" @click="handleClickMinutes">
<li :class="getCellCls(item)" v-for="(item, index) in minutesList" v-show="!item.hide" :index="index">{{ formatTime(item.text) }}</li>
<ul :class="[prefixCls + '-ul']">
<li :class="getCellCls(item)" v-for="item in minutesList" v-show="!item.hide" @click="handleClick('minutes', item)">{{ formatTime(item.text) }}</li>
</ul>
</div>
<div :class="[prefixCls+ '-list']" v-show="showSeconds" ref="seconds">
<ul :class="[prefixCls + '-ul']" @click="handleClickSeconds">
<li :class="getCellCls(item)" v-for="(item, index) in secondsList" v-show="!item.hide" :index="index">{{ formatTime(item.text) }}</li>
<ul :class="[prefixCls + '-ul']">
<li :class="getCellCls(item)" v-for="item in secondsList" v-show="!item.hide" @click="handleClick('seconds', item)">{{ formatTime(item.text) }}</li>
</ul>
</div>
</div>
@ -41,10 +41,15 @@
showSeconds: {
type: Boolean,
default: true
},
steps: {
type: Array,
default: () => []
}
},
data () {
return {
spinerSteps: [1, 1, 1].map((one, i) => Math.abs(this.steps[i]) || one),
prefixCls: prefixCls,
compiled: false
};
@ -60,6 +65,7 @@
},
hoursList () {
let hours = [];
const step = this.spinerSteps[0];
const hour_tmpl = {
text: 0,
selected: false,
@ -67,7 +73,7 @@
hide: false
};
for (let i = 0; i < 24; i++) {
for (let i = 0; i < 24; i += step) {
const hour = deepCopy(hour_tmpl);
hour.text = i;
@ -83,6 +89,7 @@
},
minutesList () {
let minutes = [];
const step = this.spinerSteps[1];
const minute_tmpl = {
text: 0,
selected: false,
@ -90,7 +97,7 @@
hide: false
};
for (let i = 0; i < 60; i++) {
for (let i = 0; i < 60; i += step) {
const minute = deepCopy(minute_tmpl);
minute.text = i;
@ -101,11 +108,11 @@
if (this.minutes === i) minute.selected = true;
minutes.push(minute);
}
return minutes;
},
secondsList () {
let seconds = [];
const step = this.spinerSteps[2];
const second_tmpl = {
text: 0,
selected: false,
@ -113,7 +120,7 @@
hide: false
};
for (let i = 0; i < 60; i++) {
for (let i = 0; i < 60; i += step) {
const second = deepCopy(second_tmpl);
second.text = i;
@ -138,24 +145,11 @@
}
];
},
handleClickHours (event) {
this.handleClick('hours', event);
},
handleClickMinutes (event) {
this.handleClick('minutes', event);
},
handleClickSeconds (event) {
this.handleClick('seconds', event);
},
handleClick (type, event) {
const target = event.target;
if (target.tagName === 'LI') {
const cell = this[`${type}List`][parseInt(event.target.getAttribute('index'))];
if (cell.disabled) return;
const data = {};
data[type] = cell.text;
this.$emit('on-change', data);
}
handleClick (type, cell) {
if (cell.disabled) return;
const data = {};
data[type] = cell.text;
this.$emit('on-change', data);
this.$emit('on-pick-click');
},
scroll (type, index) {
@ -183,20 +177,24 @@
},
formatTime (text) {
return text < 10 ? '0' + text : text;
},
getItemIndex(type, val){
const item = this[`${type}List`].find(obj => obj.text == val);
return this[`${type}List`].indexOf(item);
}
},
watch: {
hours (val) {
if (!this.compiled) return;
this.scroll('hours', val);
this.scroll('hours', this.getItemIndex('hours', val));
},
minutes (val) {
if (!this.compiled) return;
this.scroll('minutes', val);
this.scroll('minutes', this.getItemIndex('minutes', val));
},
seconds (val) {
if (!this.compiled) return;
this.scroll('seconds', val);
this.scroll('seconds', this.getItemIndex('seconds', val));
}
},
mounted () {
@ -204,4 +202,4 @@
this.$nextTick(() => this.compiled = true);
}
};
</script>
</script>

View file

@ -6,6 +6,7 @@
<time-spinner
ref="timeSpinner"
:show-seconds="showSeconds"
:steps="steps"
:hours="hours"
:minutes="minutes"
:seconds="seconds"
@ -39,6 +40,12 @@
name: 'TimePicker',
mixins: [ Mixin, Locale ],
components: { TimeSpinner, Confirm },
props: {
steps: {
type: Array,
default: () => []
}
},
data () {
return {
prefixCls: prefixCls,
@ -113,4 +120,4 @@
if (this.$parent && this.$parent.$options.name === 'DatePicker') this.showDate = true;
}
};
</script>
</script>

View file

@ -32,7 +32,6 @@
</div>
</template>
<script>
import Vue from 'vue';
import iInput from '../../components/input/input.vue';
import Drop from '../../components/select/dropdown.vue';
import clickoutside from '../../directives/clickoutside';
@ -397,7 +396,7 @@
let isConfirm = this.confirm;
const type = this.type;
this.picker = new Vue(this.panel).$mount(this.$refs.picker);
this.picker = this.Panel.$mount(this.$refs.picker);
if (type === 'datetime' || type === 'datetimerange') {
isConfirm = true;
this.picker.showTime = true;
@ -459,7 +458,7 @@
).formatter;
let newDate = formatter(date, format);
if (type === 'daterange' || type === 'timerange') {
if (type === 'daterange' || type === 'timerange' || type === 'datetimerange') {
newDate = [newDate.split(RANGE_SEPARATOR)[0], newDate.split(RANGE_SEPARATOR)[1]];
}
return newDate;

View file

@ -1,3 +1,4 @@
import Vue from 'vue';
import Picker from '../picker.vue';
import DatePanel from '../panel/date.vue';
import DateRangePanel from '../panel/date-range.vue';
@ -31,6 +32,7 @@ export default {
}
}
this.panel = getPanel(this.type);
const panel = getPanel(this.type);
this.Panel = new Vue(panel);
}
};

View file

@ -1,3 +1,4 @@
import Vue from 'vue';
import Picker from '../picker.vue';
import TimePanel from '../panel/time.vue';
import TimeRangePanel from '../panel/time-range.vue';
@ -21,6 +22,10 @@ export default {
},
default: 'time'
},
steps: {
type: Array,
default: () => []
},
value: {}
},
created () {
@ -31,6 +36,11 @@ export default {
this.currentValue = '';
}
}
this.panel = getPanel(this.type);
const Panel = Vue.extend(getPanel(this.type));
this.Panel = new Panel({
propsData: {
steps: this.steps
}
});
}
};
};

View file

@ -1,6 +1,6 @@
<template>
<div :class="classes">
<label :class="[prefixCls + '-label']" :style="labelStyles" v-if="label"><slot name="label">{{ label }}</slot></label>
<label :class="[prefixCls + '-label']" :style="labelStyles" v-if="label || $slots.label"><slot name="label">{{ label }}</slot></label>
<div :class="[prefixCls + '-content']" :style="contentStyles">
<slot></slot>
<transition name="fade">
@ -177,6 +177,7 @@
callback(this.validateMessage);
});
this.validateDisabled = false;
},
resetField () {
this.validateState = '';

View file

@ -74,7 +74,7 @@
},
size: {
validator (value) {
return oneOf(value, ['small', 'large']);
return oneOf(value, ['small', 'large', 'default']);
}
},
placeholder: {

View file

@ -135,6 +135,12 @@
};
},
watch: {
total (val) {
let maxPage = Math.ceil(val / this.currentPageSize);
if (maxPage < this.currentPage && maxPage > 0) {
this.currentPage = maxPage;
}
},
current (val) {
this.currentPage = val;
},
@ -208,6 +214,7 @@
changePage (page) {
if (this.currentPage != page) {
this.currentPage = page;
this.$emit('update:current', page);
this.$emit('on-change', page);
}
},

View file

@ -22,7 +22,15 @@
mixins: [ Emitter ],
props: {
value: {
type: Boolean,
type: [String, Number, Boolean],
default: false
},
trueValue: {
type: [String, Number, Boolean],
default: true
},
falseValue: {
type: [String, Number, Boolean],
default: false
},
label: {
@ -83,7 +91,9 @@
const checked = event.target.checked;
this.currentValue = checked;
this.$emit('input', checked);
let value = checked ? this.trueValue : this.falseValue;
this.$emit('input', value);
if (this.group && this.label !== undefined) {
this.parent.change({
@ -92,16 +102,19 @@
});
}
if (!this.group) {
this.$emit('on-change', checked);
this.dispatch('FormItem', 'on-form-change', checked);
this.$emit('on-change', value);
this.dispatch('FormItem', 'on-form-change', value);
}
},
updateValue () {
this.currentValue = this.value;
this.currentValue = this.value === this.trueValue;
}
},
watch: {
value () {
value (val) {
if (val !== this.trueValue && val !== this.falseValue) {
throw 'Value should be trueValue or falseValue.';
}
this.updateValue();
}
}

View file

@ -41,7 +41,7 @@ const csv = {
_getDownloadUrl (text) {
const BOM = '\uFEFF';
// Add BOM to text for open in excel correctly
if (window.Blob && window.URL && window.URL.createObjectURL && !has('Safari')) {
if (window.Blob && window.URL && window.URL.createObjectURL) {
const csvData = new Blob([BOM + text], { type: 'text/csv' });
return URL.createObjectURL(csvData);
} else {
@ -66,7 +66,6 @@ const csv = {
const link = document.createElement('a');
link.download = filename;
link.href = this._getDownloadUrl(text);
link.target = '_blank';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);

View file

@ -71,7 +71,7 @@
// init checked status
function reverseChecked(data) {
if (!data.nodeKey) data.nodeKey = key++;
if (data.children) {
if (data.children && data.children.length) {
let checkedLength = 0;
data.children.forEach(node => {
if (node.children) node = reverseChecked(node);