Commit 536eef86 by 姜雷

添加组件文件

parent ded4d376
<template>
<el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path" v-if="item.meta.title">
<span v-if="item.redirect==='noredirect'||index==levelList.length-1" class="no-redirect">{{item.meta.title}}</span>
<router-link v-else :to="item.redirect||item.path">{{item.meta.title}}</router-link>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</template>
<script>
export default {
created() {
this.getBreadcrumb()
},
data() {
return {
levelList: null
}
},
watch: {
$route() {
this.getBreadcrumb()
}
},
methods: {
getBreadcrumb() {
let matched = this.$route.matched.filter(item => item.name)
const first = matched[0]
if (first && first.name !== 'home') {
matched = [{ path: '/home', meta: { title: '首页' }}].concat(matched)
}
this.levelList = matched
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
display: inline-block;
font-size: 14px;
line-height: 50px;
margin-left: 10px;
.no-redirect {
color: #97a8be;
cursor: text;
}
}
</style>
<template>
<el-input
class="el-date-editor md-data-picker"
:class="'el-date-editor--' + type"
:readonly="!editable || readonly || type === 'dates'"
:disabled="pickerDisabled"
:size="pickerSize"
:name="name"
v-bind="firstInputId"
v-if="!ranged"
v-clickoutside="handleClose"
:placeholder="placeholder"
@focus="handleFocus"
@keydown.native="handleKeydown"
:value="displayValue"
@input="value => userInput = value"
@change="handleChange"
@mouseenter.native="handleMouseEnter"
@mouseleave.native="showClose = false"
:validateEvent="false"
ref="reference"
>
<i
slot="prefix"
class="md-date__icon"
:class="[showClose ? 'hide' : '']"
@click="handleFocus"
>
<img src="../../assets/images/picker/date_picker_icon-action.png" />
</i>
<i
slot="suffix"
class="el-input__icon"
@click="handleClickIcon"
:class="[showClose ? '' + clearIcon : '']"
v-if="haveTrigger"
>
</i>
</el-input>
<div
class="el-date-editor el-range-editor el-input__inner"
:class="[
'el-date-editor--' + type,
pickerSize ? `el-range-editor--${ pickerSize }` : '',
pickerDisabled ? 'is-disabled' : '',
pickerVisible ? 'is-active' : ''
]"
@click="handleRangeClick"
@mouseenter="handleMouseEnter"
@mouseleave="showClose = false"
@keydown="handleKeydown"
ref="reference"
v-clickoutside="handleClose"
v-else
>
<i :class="['el-input__icon', 'el-range__icon', triggerClass]"></i>
<input
autocomplete="off"
:placeholder="startPlaceholder"
:value="displayValue && displayValue[0]"
:disabled="pickerDisabled"
v-bind="firstInputId"
:readonly="!editable || readonly"
:name="name && name[0]"
@input="handleStartInput"
@change="handleStartChange"
@focus="handleFocus"
class="el-range-input"
>
<slot name="range-separator">
<span class="el-range-separator">{{ rangeSeparator }}</span>
</slot>
<input
autocomplete="off"
:placeholder="endPlaceholder"
:value="displayValue && displayValue[1]"
:disabled="pickerDisabled"
v-bind="secondInputId"
:readonly="!editable || readonly"
:name="name && name[1]"
@input="handleEndInput"
@change="handleEndChange"
@focus="handleFocus"
class="el-range-input"
>
<i
@click="handleClickIcon"
v-if="haveTrigger"
:class="[showClose ? '' + clearIcon : '']"
class="el-input__icon el-range__close-icon"
>
</i>
</div>
</template>
<script>
import { DatePicker } from 'element-ui';
export default {
name: 'date-picker',
extends: DatePicker,
};
</script>
<style lang="scss">
@import '../../assets/styles/variables.scss';
.md-data-picker {
&.el-input {
width: 100%;
}
.md-date__icon {
display: block;
width: 10px;
height: 10px;
margin-top: 7px;
}
.el-input__prefix {
left: 100%;
transform: translate(-22px);
}
.el-input__inner {
padding: 0 10px;
}
}
@media screen and (min-width: $bigScreenWidth) {
.md-data-picker {
.md-date__icon {
width: 22px;
height: 22px;
margin-top: 9px;
}
.el-input__prefix {
transform: translate(-30px);
}
.el-input__inner {
padding: 0 15px;
}
}
}
</style>
<template>
<div
class="date-range-pick"
v-if="noLabel"
>
<date-picker
class="data-range-item"
v-model="starttime"
format="yyyy-MM-dd HH:mm"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetime"
:picker-options="pickerOptions1"
default-time="00:00:00"
>
</date-picker>
<date-picker
class="data-range-item"
v-model="endtime"
format="yyyy-MM-dd HH:mm"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetime"
:picker-options="pickerOptions2"
default-time="00:00:00"
>
</date-picker>
</div>
<search-item
:label="label"
size="big"
v-else
>
<div class="date-range-pick">
<date-picker
class="data-range-item"
v-model="starttime"
format="yyyy-MM-dd HH:mm"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetime"
:picker-options="pickerOptions1"
default-time="00:00:00"
>
</date-picker>
<date-picker
class="data-range-item"
v-model="endtime"
format="yyyy-MM-dd HH:mm"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetime"
:picker-options="pickerOptions2"
default-time="00:00:00"
>
</date-picker>
</div>
</search-item>
</template>
<script>
export default {
name: 'date-range-picker',
props: {
label: {
type: String,
default: '时间范围',
},
noLabel: {
type: Boolean,
default: false,
},
startTime: {
type: String,
},
endTime: {
type: String,
},
},
data() {
return {
starttime: null,
endtime: null,
pickerOptions1: {
disabledDate: time => {
return this.endtime
? time.getTime() > new Date(this.endtime).getTime()
: false;
},
},
pickerOptions2: {
disabledDate: time => {
return this.starttime
? time.getTime() < new Date(this.starttime).getTime()
: false;
},
},
};
},
watch: {
starttime(newVal) {
this.$emit('input', [this.starttime, this.endtime]);
},
endtime(newVal) {
this.$emit('input', [this.starttime, this.endtime]);
},
},
};
</script>
<style lang="scss">
@import '../../assets/styles/variables.scss';
.date-range-pick {
display: flex;
justify-content: space-between;
.data-range-item.el-input {
width: 126px;
}
.el-input__prefix {
left: 110px;
}
.el-input__suffix {
right: -2px;
}
}
@media screen and (min-width: $bigScreenWidth) {
.date-range-pick {
.data-range-item.el-input {
width: 157px;
}
.el-input__prefix {
left: 130px;
}
.el-input__suffix {
right: 5px;
}
}
}
</style>
<template>
<transition name="el-zoom-in-top" @after-leave="$emit('dodestroy')">
<div
v-show="visible"
class="el-picker-panel el-date-range-picker el-popper"
:class="[{
'has-sidebar': $slots.sidebar || shortcuts,
'has-time': showTime
}, popperClass]">
<div class="el-picker-panel__body-wrapper">
<slot name="sidebar" class="el-picker-panel__sidebar"></slot>
<div class="el-picker-panel__sidebar" v-if="shortcuts">
<button
type="button"
class="el-picker-panel__shortcut"
v-for="(shortcut, key) in shortcuts"
:key="key"
@click="handleShortcutClick(shortcut)">{{shortcut.text}}</button>
</div>
<div class="el-picker-panel__body">
<div class="el-date-range-picker__time-header" v-if="showTime">
<span class="el-date-range-picker__editors-wrap">
<span class="el-date-range-picker__time-picker-wrap">
<el-input
size="small"
:disabled="rangeState.selecting"
ref="minInput"
:placeholder="t('el.datepicker.startDate')"
class="el-date-range-picker__editor"
:value="minVisibleDate"
@input.native="handleDateInput($event, 'min')"
@change.native="handleDateChange($event, 'min')" />
</span>
<span class="el-date-range-picker__time-picker-wrap" v-clickoutside="handleMinTimeClose">
<el-input
size="small"
:disabled="rangeState.selecting"
:placeholder="t('el.datepicker.startTime')"
class="el-date-range-picker__editor"
:value="minVisibleTime"
@focus="minTimePickerVisible = true"
@change.native="handleTimeChange($event, 'min')" />
<time-picker
ref="minTimePicker"
@pick="handleMinTimePick"
:time-arrow-control="arrowControl"
:visible="minTimePickerVisible"
@mounted="$refs.minTimePicker.format=timeFormat">
</time-picker>
</span>
</span>
<span class="el-icon-arrow-right"></span>
<span class="el-date-range-picker__editors-wrap is-right">
<span class="el-date-range-picker__time-picker-wrap">
<el-input
size="small"
:disabled="rangeState.selecting"
:placeholder="t('el.datepicker.endDate')"
class="el-date-range-picker__editor"
:value="maxVisibleDate"
:readonly="!minDate"
@input.native="handleDateInput($event, 'max')"
@change.native="handleDateChange($event, 'max')" />
</span>
<span class="el-date-range-picker__time-picker-wrap" v-clickoutside="handleMaxTimeClose">
<el-input
size="small"
:disabled="rangeState.selecting"
ref="maxInput"
:placeholder="t('el.datepicker.endTime')"
class="el-date-range-picker__editor"
:value="maxVisibleTime"
@focus="minDate && (maxTimePickerVisible = true)"
:readonly="!minDate"
@change.native="handleTimeChange($event, 'max')" />
<time-picker
ref="maxTimePicker"
@pick="handleMaxTimePick"
:time-arrow-control="arrowControl"
:visible="maxTimePickerVisible"
@mounted="$refs.maxTimePicker.format=timeFormat">
</time-picker>
</span>
</span>
</div>
<div class="el-picker-panel__content el-date-range-picker__content is-left">
<div class="el-date-range-picker__header">
<button
type="button"
@click="leftPrevYear"
class="el-picker-panel__icon-btn el-icon-d-arrow-left"></button>
<button
type="button"
@click="leftPrevMonth"
class="el-picker-panel__icon-btn el-icon-arrow-left"></button>
<button
type="button"
@click="leftNextYear"
v-if="unlinkPanels"
:disabled="!enableYearArrow"
:class="{ 'is-disabled': !enableYearArrow }"
class="el-picker-panel__icon-btn el-icon-d-arrow-right"></button>
<button
type="button"
@click="leftNextMonth"
v-if="unlinkPanels"
:disabled="!enableMonthArrow"
:class="{ 'is-disabled': !enableMonthArrow }"
class="el-picker-panel__icon-btn el-icon-arrow-right"></button>
<div>{{ leftLabel }}</div>
</div>
<date-table
selection-mode="range"
:date="leftDate"
:default-value="defaultValue"
:min-date="minDate"
:max-date="maxDate"
:range-state="rangeState"
:disabled-date="disabledDate"
@changerange="handleChangeRange"
:first-day-of-week="firstDayOfWeek"
@pick="handleRangePick">
</date-table>
</div>
<div class="el-picker-panel__content el-date-range-picker__content is-right">
<div class="el-date-range-picker__header">
<button
type="button"
@click="rightPrevYear"
v-if="unlinkPanels"
:disabled="!enableYearArrow"
:class="{ 'is-disabled': !enableYearArrow }"
class="el-picker-panel__icon-btn el-icon-d-arrow-left"></button>
<button
type="button"
@click="rightPrevMonth"
v-if="unlinkPanels"
:disabled="!enableMonthArrow"
:class="{ 'is-disabled': !enableMonthArrow }"
class="el-picker-panel__icon-btn el-icon-arrow-left"></button>
<button
type="button"
@click="rightNextYear"
class="el-picker-panel__icon-btn el-icon-d-arrow-right"></button>
<button
type="button"
@click="rightNextMonth"
class="el-picker-panel__icon-btn el-icon-arrow-right"></button>
<div>{{ rightLabel }}</div>
</div>
<date-table
selection-mode="range"
:date="rightDate"
:default-value="defaultValue"
:min-date="minDate"
:max-date="maxDate"
:range-state="rangeState"
:disabled-date="disabledDate"
@changerange="handleChangeRange"
:first-day-of-week="firstDayOfWeek"
@pick="handleRangePick">
</date-table>
</div>
</div>
</div>
<div class="el-picker-panel__footer" v-if="showTime">
<el-button
size="mini"
type="text"
class="el-picker-panel__link-btn"
@click="handleClear">
{{ t('el.datepicker.clear') }}
</el-button>
<el-button
plain
size="mini"
class="el-picker-panel__link-btn"
:disabled="btnDisabled"
@click="handleConfirm(false)">
{{ t('el.datepicker.confirm') }}
</el-button>
</div>
</div>
</transition>
</template>
<script type="text/babel">
import {
formatDate,
parseDate,
isDate,
modifyDate,
modifyTime,
modifyWithTimeString,
prevYear,
nextYear,
prevMonth,
nextMonth,
extractDateFormat,
extractTimeFormat
} from '../util';
import Clickoutside from 'element-ui/src/utils/clickoutside';
import Locale from 'element-ui/src/mixins/locale';
import TimePicker from './time';
import DateTable from '../basic/date-table';
import ElInput from 'element-ui/packages/input';
import ElButton from 'element-ui/packages/button';
const advanceDate = (date, amount) => {
return new Date(new Date(date).getTime() + amount);
};
const calcDefaultValue = (defaultValue) => {
if (Array.isArray(defaultValue)) {
return [new Date(defaultValue[0]), new Date(defaultValue[1])];
} else if (defaultValue) {
return [new Date(defaultValue), advanceDate(defaultValue, 24 * 60 * 60 * 1000)];
} else {
return [new Date(), advanceDate(Date.now(), 24 * 60 * 60 * 1000)];
}
};
export default {
mixins: [Locale],
directives: { Clickoutside },
computed: {
btnDisabled() {
return !(this.minDate && this.maxDate && !this.selecting && this.isValidValue([this.minDate, this.maxDate]));
},
leftLabel() {
return this.leftDate.getFullYear() + ' ' + this.t('el.datepicker.year') + ' ' + this.t(`el.datepicker.month${ this.leftDate.getMonth() + 1 }`);
},
rightLabel() {
return this.rightDate.getFullYear() + ' ' + this.t('el.datepicker.year') + ' ' + this.t(`el.datepicker.month${ this.rightDate.getMonth() + 1 }`);
},
leftYear() {
return this.leftDate.getFullYear();
},
leftMonth() {
return this.leftDate.getMonth();
},
leftMonthDate() {
return this.leftDate.getDate();
},
rightYear() {
return this.rightDate.getFullYear();
},
rightMonth() {
return this.rightDate.getMonth();
},
rightMonthDate() {
return this.rightDate.getDate();
},
minVisibleDate() {
return this.minDate ? formatDate(this.minDate, this.dateFormat) : '';
},
maxVisibleDate() {
return (this.maxDate || this.minDate) ? formatDate(this.maxDate || this.minDate, this.dateFormat) : '';
},
minVisibleTime() {
return this.minDate ? formatDate(this.minDate, this.timeFormat) : '';
},
maxVisibleTime() {
return (this.maxDate || this.minDate) ? formatDate(this.maxDate || this.minDate, this.timeFormat) : '';
},
timeFormat() {
if (this.format) {
return extractTimeFormat(this.format);
} else {
return 'HH:mm:ss';
}
},
dateFormat() {
if (this.format) {
return extractDateFormat(this.format);
} else {
return 'yyyy-MM-dd';
}
},
enableMonthArrow() {
const nextMonth = (this.leftMonth + 1) % 12;
const yearOffset = this.leftMonth + 1 >= 12 ? 1 : 0;
return this.unlinkPanels && new Date(this.leftYear + yearOffset, nextMonth) < new Date(this.rightYear, this.rightMonth);
},
enableYearArrow() {
return this.unlinkPanels && this.rightYear * 12 + this.rightMonth - (this.leftYear * 12 + this.leftMonth + 1) >= 12;
}
},
data() {
return {
popperClass: '',
value: [],
defaultValue: null,
defaultTime: null,
minDate: '',
maxDate: '',
leftDate: new Date(),
rightDate: nextMonth(new Date()),
rangeState: {
endDate: null,
selecting: false,
row: null,
column: null
},
showTime: false,
shortcuts: '',
visible: '',
disabledDate: '',
firstDayOfWeek: 7,
minTimePickerVisible: false,
maxTimePickerVisible: false,
format: '',
arrowControl: false,
unlinkPanels: false
};
},
watch: {
minDate(val) {
this.$nextTick(() => {
if (this.$refs.maxTimePicker && this.maxDate && this.maxDate < this.minDate) {
const format = 'HH:mm:ss';
this.$refs.maxTimePicker.selectableRange = [
[
parseDate(formatDate(this.minDate, format), format),
parseDate('23:59:59', format)
]
];
}
});
if (val && this.$refs.minTimePicker) {
this.$refs.minTimePicker.date = val;
this.$refs.minTimePicker.value = val;
}
},
maxDate(val) {
if (val && this.$refs.maxTimePicker) {
this.$refs.maxTimePicker.date = val;
this.$refs.maxTimePicker.value = val;
}
},
minTimePickerVisible(val) {
if (val) {
this.$nextTick(() => {
this.$refs.minTimePicker.date = this.minDate;
this.$refs.minTimePicker.value = this.minDate;
this.$refs.minTimePicker.adjustSpinners();
});
}
},
maxTimePickerVisible(val) {
if (val) {
this.$nextTick(() => {
this.$refs.maxTimePicker.date = this.maxDate;
this.$refs.maxTimePicker.value = this.maxDate;
this.$refs.maxTimePicker.adjustSpinners();
});
}
},
value(newVal) {
if (!newVal) {
this.minDate = null;
this.maxDate = null;
} else if (Array.isArray(newVal)) {
this.minDate = isDate(newVal[0]) ? new Date(newVal[0]) : null;
this.maxDate = isDate(newVal[1]) ? new Date(newVal[1]) : null;
if (this.minDate) {
this.leftDate = this.minDate;
if (this.unlinkPanels && this.maxDate) {
const minDateYear = this.minDate.getFullYear();
const minDateMonth = this.minDate.getMonth();
const maxDateYear = this.maxDate.getFullYear();
const maxDateMonth = this.maxDate.getMonth();
this.rightDate = minDateYear === maxDateYear && minDateMonth === maxDateMonth
? nextMonth(this.maxDate)
: this.maxDate;
} else {
this.rightDate = nextMonth(this.leftDate);
}
} else {
this.leftDate = calcDefaultValue(this.defaultValue)[0];
this.rightDate = nextMonth(this.leftDate);
}
}
},
defaultValue(val) {
if (!Array.isArray(this.value)) {
const [left, right] = calcDefaultValue(val);
this.leftDate = left;
this.rightDate = val && val[1] && this.unlinkPanels
? right
: nextMonth(this.leftDate);
}
}
},
methods: {
handleClear() {
this.minDate = null;
this.maxDate = null;
this.leftDate = calcDefaultValue(this.defaultValue)[0];
this.rightDate = nextMonth(this.leftDate);
this.$emit('pick', null);
},
handleChangeRange(val) {
this.minDate = val.minDate;
this.maxDate = val.maxDate;
this.rangeState = val.rangeState;
},
handleDateInput(event, type) {
const value = event.target.value;
if (value.length !== this.dateFormat.length) return;
const parsedValue = parseDate(value, this.dateFormat);
if (parsedValue) {
if (typeof this.disabledDate === 'function' &&
this.disabledDate(new Date(parsedValue))) {
return;
}
if (type === 'min') {
this.minDate = new Date(parsedValue);
this.leftDate = new Date(parsedValue);
this.rightDate = nextMonth(this.leftDate);
} else {
this.maxDate = new Date(parsedValue);
this.leftDate = prevMonth(parsedValue);
this.rightDate = new Date(parsedValue);
}
}
},
handleDateChange(event, type) {
const value = event.target.value;
const parsedValue = parseDate(value, this.dateFormat);
if (parsedValue) {
if (type === 'min') {
this.minDate = modifyDate(this.minDate, parsedValue.getFullYear(), parsedValue.getMonth(), parsedValue.getDate());
if (this.minDate > this.maxDate) {
this.maxDate = this.minDate;
}
} else {
this.maxDate = modifyDate(this.maxDate, parsedValue.getFullYear(), parsedValue.getMonth(), parsedValue.getDate());
if (this.maxDate < this.minDate) {
this.minDate = this.maxDate;
}
}
}
},
handleTimeChange(event, type) {
const value = event.target.value;
const parsedValue = parseDate(value, this.timeFormat);
if (parsedValue) {
if (type === 'min') {
this.minDate = modifyTime(this.minDate, parsedValue.getHours(), parsedValue.getMinutes(), parsedValue.getSeconds());
if (this.minDate > this.maxDate) {
this.maxDate = this.minDate;
}
this.$refs.minTimePicker.value = this.minDate;
this.minTimePickerVisible = false;
} else {
this.maxDate = modifyTime(this.maxDate, parsedValue.getHours(), parsedValue.getMinutes(), parsedValue.getSeconds());
if (this.maxDate < this.minDate) {
this.minDate = this.maxDate;
}
this.$refs.maxTimePicker.value = this.minDate;
this.maxTimePickerVisible = false;
}
}
},
handleRangePick(val, close = true) {
const defaultTime = this.defaultTime || [];
const minDate = modifyWithTimeString(val.minDate, defaultTime[0]);
const maxDate = modifyWithTimeString(val.maxDate, defaultTime[1]);
if (this.maxDate === maxDate && this.minDate === minDate) {
return;
}
this.onPick && this.onPick(val);
this.maxDate = maxDate;
this.minDate = minDate;
// workaround for https://github.com/ElemeFE/element/issues/7539, should remove this block when we don't have to care about Chromium 55 - 57
setTimeout(() => {
this.maxDate = maxDate;
this.minDate = minDate;
}, 10);
if (!close || this.showTime) return;
this.handleConfirm();
},
handleShortcutClick(shortcut) {
if (shortcut.onClick) {
shortcut.onClick(this);
}
},
handleMinTimePick(value, visible, first) {
this.minDate = this.minDate || new Date();
if (value) {
this.minDate = modifyTime(this.minDate, value.getHours(), value.getMinutes(), value.getSeconds());
}
if (!first) {
this.minTimePickerVisible = visible;
}
if (!this.maxDate || this.maxDate && this.maxDate.getTime() < this.minDate.getTime()) {
this.maxDate = new Date(this.minDate);
}
},
handleMinTimeClose() {
this.minTimePickerVisible = false;
},
handleMaxTimePick(value, visible, first) {
if (this.maxDate && value) {
this.maxDate = modifyTime(this.maxDate, value.getHours(), value.getMinutes(), value.getSeconds());
}
if (!first) {
this.maxTimePickerVisible = visible;
}
if (this.maxDate && this.minDate && this.minDate.getTime() > this.maxDate.getTime()) {
this.minDate = new Date(this.maxDate);
}
},
handleMaxTimeClose() {
this.maxTimePickerVisible = false;
},
// leftPrev*, rightNext* need to take care of `unlinkPanels`
leftPrevYear() {
this.leftDate = prevYear(this.leftDate);
if (!this.unlinkPanels) {
this.rightDate = nextMonth(this.leftDate);
}
},
leftPrevMonth() {
this.leftDate = prevMonth(this.leftDate);
if (!this.unlinkPanels) {
this.rightDate = nextMonth(this.leftDate);
}
},
rightNextYear() {
if (!this.unlinkPanels) {
this.leftDate = nextYear(this.leftDate);
this.rightDate = nextMonth(this.leftDate);
} else {
this.rightDate = nextYear(this.rightDate);
}
},
rightNextMonth() {
if (!this.unlinkPanels) {
this.leftDate = nextMonth(this.leftDate);
this.rightDate = nextMonth(this.leftDate);
} else {
this.rightDate = nextMonth(this.rightDate);
}
},
// leftNext*, rightPrev* are called when `unlinkPanels` is true
leftNextYear() {
this.leftDate = nextYear(this.leftDate);
},
leftNextMonth() {
this.leftDate = nextMonth(this.leftDate);
},
rightPrevYear() {
this.rightDate = prevYear(this.rightDate);
},
rightPrevMonth() {
this.rightDate = prevMonth(this.rightDate);
},
handleConfirm(visible = false) {
if (this.isValidValue([this.minDate, this.maxDate])) {
this.$emit('pick', [this.minDate, this.maxDate], visible);
}
},
isValidValue(value) {
return Array.isArray(value) &&
value && value[0] && value[1] &&
isDate(value[0]) && isDate(value[1]) &&
value[0].getTime() <= value[1].getTime() && (
typeof this.disabledDate === 'function'
? !this.disabledDate(value[0]) && !this.disabledDate(value[1])
: true
);
},
resetView() {
// NOTE: this is a hack to reset {min, max}Date on picker open.
// TODO: correct way of doing so is to refactor {min, max}Date to be dependent on value and internal selection state
// an alternative would be resetView whenever picker becomes visible, should also investigate date-panel's resetView
this.minDate = this.value && isDate(this.value[0]) ? new Date(this.value[0]) : null;
this.maxDate = this.value && isDate(this.value[0]) ? new Date(this.value[1]) : null;
}
},
components: { TimePicker, DateTable, ElInput, ElButton }
};
</script>
<template>
<transition name="el-zoom-in-top" @after-enter="handleEnter" @after-leave="handleLeave">
<div
v-show="visible"
class="el-picker-panel el-date-picker el-popper"
:class="[{
'has-sidebar': $slots.sidebar || shortcuts,
'has-time': showTime
}, popperClass]">
<div class="el-picker-panel__body-wrapper">
<slot name="sidebar" class="el-picker-panel__sidebar"></slot>
<div class="el-picker-panel__sidebar" v-if="shortcuts">
<button
type="button"
class="el-picker-panel__shortcut"
v-for="(shortcut, key) in shortcuts"
:key="key"
@click="handleShortcutClick(shortcut)">{{ shortcut.text }}</button>
</div>
<div class="el-picker-panel__body">
<div class="el-date-picker__time-header" v-if="showTime">
<span class="el-date-picker__editor-wrap">
<el-input
:placeholder="t('el.datepicker.selectDate')"
:value="visibleDate"
size="small"
@input="val => userInputDate = val"
@change="handleVisibleDateChange" />
</span>
<span class="el-date-picker__editor-wrap" v-clickoutside="handleTimePickClose">
<el-input
ref="input"
@focus="timePickerVisible = true"
:placeholder="t('el.datepicker.selectTime')"
:value="visibleTime"
size="small"
@input="val => userInputTime = val"
@change="handleVisibleTimeChange" />
<time-picker
ref="timepicker"
:time-arrow-control="arrowControl"
@pick="handleTimePick"
:visible="timePickerVisible"
@mounted="proxyTimePickerDataProperties">
</time-picker>
</span>
</div>
<div
class="el-date-picker__header"
:class="{ 'el-date-picker__header--bordered': currentView === 'year' || currentView === 'month' }"
v-show="currentView !== 'time'">
<button
type="button"
@click="prevYear"
:aria-label="t(`el.datepicker.prevYear`)"
class="el-picker-panel__icon-btn el-date-picker__prev-btn el-icon-d-arrow-left">
</button>
<button
type="button"
@click="prevMonth"
v-show="currentView === 'date'"
:aria-label="t(`el.datepicker.prevMonth`)"
class="el-picker-panel__icon-btn el-date-picker__prev-btn el-icon-arrow-left">
</button>
<span
@click="showYearPicker"
role="button"
class="el-date-picker__header-label">{{ yearLabel }}</span>
<span
@click="showMonthPicker"
v-show="currentView === 'date'"
role="button"
class="el-date-picker__header-label"
:class="{ active: currentView === 'month' }">{{t(`el.datepicker.month${ month + 1 }`)}}</span>
<button
type="button"
@click="nextYear"
:aria-label="t(`el.datepicker.nextYear`)"
class="el-picker-panel__icon-btn el-date-picker__next-btn el-icon-d-arrow-right">
</button>
<button
type="button"
@click="nextMonth"
v-show="currentView === 'date'"
:aria-label="t(`el.datepicker.nextMonth`)"
class="el-picker-panel__icon-btn el-date-picker__next-btn el-icon-arrow-right">
</button>
</div>
<div class="el-picker-panel__content">
<date-table
v-show="currentView === 'date'"
@pick="handleDatePick"
:selection-mode="selectionMode"
:first-day-of-week="firstDayOfWeek"
:value="value"
:default-value="defaultValue ? new Date(defaultValue) : null"
:date="date"
:disabled-date="disabledDate">
</date-table>
<year-table
v-show="currentView === 'year'"
@pick="handleYearPick"
:value="value"
:default-value="defaultValue ? new Date(defaultValue) : null"
:date="date"
:disabled-date="disabledDate">
</year-table>
<month-table
v-show="currentView === 'month'"
@pick="handleMonthPick"
:value="value"
:default-value="defaultValue ? new Date(defaultValue) : null"
:date="date"
:disabled-date="disabledDate">
</month-table>
</div>
</div>
</div>
<div
class="el-picker-panel__footer"
v-show="footerVisible && currentView === 'date'">
<el-button
size="mini"
type="text"
class="el-picker-panel__link-btn"
@click="changeToNow"
v-show="selectionMode !== 'dates'">
{{ t('el.datepicker.now') }}
</el-button>
<el-button
plain
size="mini"
class="el-picker-panel__link-btn"
@click="confirm">
{{ t('el.datepicker.confirm') }}
</el-button>
</div>
</div>
</transition>
</template>
<script type="text/babel">
import {
formatDate,
parseDate,
getWeekNumber,
isDate,
modifyDate,
modifyTime,
modifyWithTimeString,
clearMilliseconds,
clearTime,
prevYear,
nextYear,
prevMonth,
nextMonth,
changeYearMonthAndClampDate,
extractDateFormat,
extractTimeFormat
} from '../util';
import Clickoutside from 'element-ui/src/utils/clickoutside';
import Locale from 'element-ui/src/mixins/locale';
import ElInput from 'element-ui/packages/input';
import ElButton from 'element-ui/packages/button';
import TimePicker from './time';
import YearTable from '../basic/year-table';
import MonthTable from '../basic/month-table';
import DateTable from '../basic/date-table';
export default {
mixins: [Locale],
directives: { Clickoutside },
watch: {
showTime(val) {
/* istanbul ignore if */
if (!val) return;
this.$nextTick(_ => {
const inputElm = this.$refs.input.$el;
if (inputElm) {
this.pickerWidth = inputElm.getBoundingClientRect().width + 10;
}
});
},
value(val) {
if (this.selectionMode === 'dates' && this.value) return;
if (isDate(val)) {
this.date = new Date(val);
} else {
this.date = this.getDefaultValue();
}
},
defaultValue(val) {
if (!isDate(this.value)) {
this.date = val ? new Date(val) : new Date();
}
},
timePickerVisible(val) {
if (val) this.$nextTick(() => this.$refs.timepicker.adjustSpinners());
},
selectionMode(newVal) {
if (newVal === 'month') {
/* istanbul ignore next */
if (this.currentView !== 'year' || this.currentView !== 'month') {
this.currentView = 'month';
}
} else if (newVal === 'dates') {
this.currentView = 'date';
}
}
},
methods: {
proxyTimePickerDataProperties() {
const format = timeFormat => {this.$refs.timepicker.format = timeFormat;};
const value = value => {this.$refs.timepicker.value = value;};
const date = date => {this.$refs.timepicker.date = date;};
this.$watch('value', value);
this.$watch('date', date);
format(this.timeFormat);
value(this.value);
date(this.date);
},
handleClear() {
this.date = this.getDefaultValue();
this.$emit('pick', null);
},
emit(value, ...args) {
if (!value) {
this.$emit('pick', value, ...args);
} else if (Array.isArray(value)) {
const dates = value.map(date => this.showTime ? clearMilliseconds(date) : clearTime(date));
this.$emit('pick', dates, ...args);
} else {
this.$emit('pick', this.showTime ? clearMilliseconds(value) : clearTime(value), ...args);
}
this.userInputDate = null;
this.userInputTime = null;
},
// resetDate() {
// this.date = new Date(this.date);
// },
showMonthPicker() {
this.currentView = 'month';
},
showYearPicker() {
this.currentView = 'year';
},
// XXX: 没用到
// handleLabelClick() {
// if (this.currentView === 'date') {
// this.showMonthPicker();
// } else if (this.currentView === 'month') {
// this.showYearPicker();
// }
// },
prevMonth() {
this.date = prevMonth(this.date);
},
nextMonth() {
this.date = nextMonth(this.date);
},
prevYear() {
if (this.currentView === 'year') {
this.date = prevYear(this.date, 10);
} else {
this.date = prevYear(this.date);
}
},
nextYear() {
if (this.currentView === 'year') {
this.date = nextYear(this.date, 10);
} else {
this.date = nextYear(this.date);
}
},
handleShortcutClick(shortcut) {
if (shortcut.onClick) {
shortcut.onClick(this);
}
},
handleTimePick(value, visible, first) {
if (isDate(value)) {
const newDate = this.value
? modifyTime(this.value, value.getHours(), value.getMinutes(), value.getSeconds())
: modifyWithTimeString(this.getDefaultValue(), this.defaultTime);
this.date = newDate;
this.emit(this.date, true);
} else {
this.emit(value, true);
}
if (!first) {
this.timePickerVisible = visible;
}
},
handleTimePickClose() {
this.timePickerVisible = false;
},
handleMonthPick(month) {
if (this.selectionMode === 'month') {
this.date = modifyDate(this.date, this.year, month, 1);
this.emit(this.date);
} else {
this.date = changeYearMonthAndClampDate(this.date, this.year, month);
// TODO: should emit intermediate value ??
// this.emit(this.date);
this.currentView = 'date';
}
},
handleDatePick(value) {
if (this.selectionMode === 'day') {
this.date = this.value
? modifyDate(this.value, value.getFullYear(), value.getMonth(), value.getDate())
: modifyWithTimeString(value, this.defaultTime);
this.emit(this.date, this.showTime);
} else if (this.selectionMode === 'week') {
this.emit(value.date);
} else if (this.selectionMode === 'dates') {
this.emit(value, true); // set false to keep panel open
}
},
handleYearPick(year) {
if (this.selectionMode === 'year') {
this.date = modifyDate(this.date, year, 0, 1);
this.emit(this.date);
} else {
this.date = changeYearMonthAndClampDate(this.date, year, this.month);
// TODO: should emit intermediate value ??
// this.emit(this.date, true);
this.currentView = 'month';
}
},
changeToNow() {
// NOTE: not a permanent solution
// consider disable "now" button in the future
if (!this.disabledDate || !this.disabledDate(new Date())) {
this.date = new Date();
this.emit(this.date);
}
},
confirm() {
if (this.selectionMode === 'dates') {
this.emit(this.value);
} else {
// value were emitted in handle{Date,Time}Pick, nothing to update here
// deal with the scenario where: user opens the picker, then confirm without doing anything
const value = this.value
? this.value
: modifyWithTimeString(this.getDefaultValue(), this.defaultTime);
this.date = new Date(value); // refresh date
this.emit(value);
}
},
resetView() {
if (this.selectionMode === 'month') {
this.currentView = 'month';
} else if (this.selectionMode === 'year') {
this.currentView = 'year';
} else {
this.currentView = 'date';
}
},
handleEnter() {
document.body.addEventListener('keydown', this.handleKeydown);
},
handleLeave() {
this.$emit('dodestroy');
document.body.removeEventListener('keydown', this.handleKeydown);
},
handleKeydown(event) {
const keyCode = event.keyCode;
const list = [38, 40, 37, 39];
if (this.visible && !this.timePickerVisible) {
if (list.indexOf(keyCode) !== -1) {
this.handleKeyControl(keyCode);
event.stopPropagation();
event.preventDefault();
}
if (keyCode === 13 && this.userInputDate === null && this.userInputTime === null) { // Enter
this.emit(this.date, false);
}
}
},
handleKeyControl(keyCode) {
const mapping = {
'year': {
38: -4, 40: 4, 37: -1, 39: 1, offset: (date, step) => date.setFullYear(date.getFullYear() + step)
},
'month': {
38: -4, 40: 4, 37: -1, 39: 1, offset: (date, step) => date.setMonth(date.getMonth() + step)
},
'week': {
38: -1, 40: 1, 37: -1, 39: 1, offset: (date, step) => date.setDate(date.getDate() + step * 7)
},
'day': {
38: -7, 40: 7, 37: -1, 39: 1, offset: (date, step) => date.setDate(date.getDate() + step)
}
};
const mode = this.selectionMode;
const year = 3.1536e10;
const now = this.date.getTime();
const newDate = new Date(this.date.getTime());
while (Math.abs(now - newDate.getTime()) <= year) {
const map = mapping[mode];
map.offset(newDate, map[keyCode]);
if (typeof this.disabledDate === 'function' && this.disabledDate(newDate)) {
continue;
}
this.date = newDate;
this.$emit('pick', newDate, true);
break;
}
},
handleVisibleTimeChange(value) {
const time = parseDate(value, this.timeFormat);
if (time) {
this.date = modifyDate(time, this.year, this.month, this.monthDate);
this.userInputTime = null;
this.$refs.timepicker.value = this.date;
this.timePickerVisible = false;
this.emit(this.date, true);
}
},
handleVisibleDateChange(value) {
const date = parseDate(value, this.dateFormat);
if (date) {
if (typeof this.disabledDate === 'function' && this.disabledDate(date)) {
return;
}
this.date = modifyTime(date, this.date.getHours(), this.date.getMinutes(), this.date.getSeconds());
this.userInputDate = null;
this.resetView();
this.emit(this.date, true);
}
},
isValidValue(value) {
return value && !isNaN(value) && (
typeof this.disabledDate === 'function'
? !this.disabledDate(value)
: true
);
},
getDefaultValue() {
// if default-value is set, return it
// otherwise, return now (the moment this method gets called)
return this.defaultValue ? new Date(this.defaultValue) : new Date();
}
},
components: {
TimePicker, YearTable, MonthTable, DateTable, ElInput, ElButton
},
data() {
return {
popperClass: '',
date: new Date(),
value: '',
defaultValue: null, // use getDefaultValue() for time computation
defaultTime: null,
showTime: false,
selectionMode: 'day',
shortcuts: '',
visible: false,
currentView: 'date',
disabledDate: '',
firstDayOfWeek: 7,
showWeekNumber: false,
timePickerVisible: false,
format: '',
arrowControl: false,
userInputDate: null,
userInputTime: null
};
},
computed: {
year() {
return this.date.getFullYear();
},
month() {
return this.date.getMonth();
},
week() {
return getWeekNumber(this.date);
},
monthDate() {
return this.date.getDate();
},
footerVisible() {
return this.showTime || this.selectionMode === 'dates';
},
visibleTime() {
if (this.userInputTime !== null) {
return this.userInputTime;
} else {
return formatDate(this.value || this.defaultValue, this.timeFormat);
}
},
visibleDate() {
if (this.userInputDate !== null) {
return this.userInputDate;
} else {
return formatDate(this.value || this.defaultValue, this.dateFormat);
}
},
yearLabel() {
const yearTranslation = this.t('el.datepicker.year');
if (this.currentView === 'year') {
const startYear = Math.floor(this.year / 10) * 10;
if (yearTranslation) {
return startYear + ' ' + yearTranslation + ' - ' + (startYear + 9) + ' ' + yearTranslation;
}
return startYear + ' - ' + (startYear + 9);
}
return this.year + ' ' + yearTranslation;
},
timeFormat() {
if (this.format) {
return extractTimeFormat(this.format);
} else {
return 'HH:mm:ss';
}
},
dateFormat() {
if (this.format) {
return extractDateFormat(this.format);
} else {
return 'yyyy-MM-dd';
}
}
}
};
</script>
<template>
<transition
name="el-zoom-in-top"
@after-leave="$emit('dodestroy')">
<div
v-show="visible"
class="el-time-range-picker el-picker-panel el-popper"
:class="popperClass">
<div class="el-time-range-picker__content">
<div class="el-time-range-picker__cell">
<div class="el-time-range-picker__header">{{ t('el.datepicker.startTime') }}</div>
<div
:class="{ 'has-seconds': showSeconds, 'is-arrow': arrowControl }"
class="el-time-range-picker__body el-time-panel__content">
<time-spinner
ref="minSpinner"
:show-seconds="showSeconds"
:am-pm-mode="amPmMode"
@change="handleMinChange"
:arrow-control="arrowControl"
@select-range="setMinSelectionRange"
:date="minDate">
</time-spinner>
</div>
</div>
<div class="el-time-range-picker__cell">
<div class="el-time-range-picker__header">{{ t('el.datepicker.endTime') }}</div>
<div
:class="{ 'has-seconds': showSeconds, 'is-arrow': arrowControl }"
class="el-time-range-picker__body el-time-panel__content">
<time-spinner
ref="maxSpinner"
:show-seconds="showSeconds"
:am-pm-mode="amPmMode"
@change="handleMaxChange"
:arrow-control="arrowControl"
@select-range="setMaxSelectionRange"
:date="maxDate">
</time-spinner>
</div>
</div>
</div>
<div class="el-time-panel__footer">
<button
type="button"
class="el-time-panel__btn cancel"
@click="handleCancel()">{{ t('el.datepicker.cancel') }}</button>
<button
type="button"
class="el-time-panel__btn confirm"
@click="handleConfirm()"
:disabled="btnDisabled">{{ t('el.datepicker.confirm') }}</button>
</div>
</div>
</transition>
</template>
<script type="text/babel">
import {
parseDate,
limitTimeRange,
modifyDate,
clearMilliseconds,
timeWithinRange
} from '../util';
import Locale from 'element-ui/src/mixins/locale';
import TimeSpinner from '../basic/time-spinner';
const MIN_TIME = parseDate('00:00:00', 'HH:mm:ss');
const MAX_TIME = parseDate('23:59:59', 'HH:mm:ss');
const minTimeOfDay = function(date) {
return modifyDate(MIN_TIME, date.getFullYear(), date.getMonth(), date.getDate());
};
const maxTimeOfDay = function(date) {
return modifyDate(MAX_TIME, date.getFullYear(), date.getMonth(), date.getDate());
};
// increase time by amount of milliseconds, but within the range of day
const advanceTime = function(date, amount) {
return new Date(Math.min(date.getTime() + amount, maxTimeOfDay(date).getTime()));
};
export default {
mixins: [Locale],
components: { TimeSpinner },
computed: {
showSeconds() {
return (this.format || '').indexOf('ss') !== -1;
},
offset() {
return this.showSeconds ? 11 : 8;
},
spinner() {
return this.selectionRange[0] < this.offset ? this.$refs.minSpinner : this.$refs.maxSpinner;
},
btnDisabled() {
return this.minDate.getTime() > this.maxDate.getTime();
},
amPmMode() {
if ((this.format || '').indexOf('A') !== -1) return 'A';
if ((this.format || '').indexOf('a') !== -1) return 'a';
return '';
}
},
data() {
return {
popperClass: '',
minDate: new Date(),
maxDate: new Date(),
value: [],
oldValue: [new Date(), new Date()],
defaultValue: null,
format: 'HH:mm:ss',
visible: false,
selectionRange: [0, 2],
arrowControl: false
};
},
watch: {
value(value) {
if (Array.isArray(value)) {
this.minDate = new Date(value[0]);
this.maxDate = new Date(value[1]);
} else {
if (Array.isArray(this.defaultValue)) {
this.minDate = new Date(this.defaultValue[0]);
this.maxDate = new Date(this.defaultValue[1]);
} else if (this.defaultValue) {
this.minDate = new Date(this.defaultValue);
this.maxDate = advanceTime(new Date(this.defaultValue), 60 * 60 * 1000);
} else {
this.minDate = new Date();
this.maxDate = advanceTime(new Date(), 60 * 60 * 1000);
}
}
},
visible(val) {
if (val) {
this.oldValue = this.value;
this.$nextTick(() => this.$refs.minSpinner.emitSelectRange('hours'));
}
}
},
methods: {
handleClear() {
this.$emit('pick', null);
},
handleCancel() {
this.$emit('pick', this.oldValue);
},
handleMinChange(date) {
this.minDate = clearMilliseconds(date);
this.handleChange();
},
handleMaxChange(date) {
this.maxDate = clearMilliseconds(date);
this.handleChange();
},
handleChange() {
if (this.isValidValue([this.minDate, this.maxDate])) {
this.$refs.minSpinner.selectableRange = [[minTimeOfDay(this.minDate), this.maxDate]];
this.$refs.maxSpinner.selectableRange = [[this.minDate, maxTimeOfDay(this.maxDate)]];
this.$emit('pick', [this.minDate, this.maxDate], true);
}
},
setMinSelectionRange(start, end) {
this.$emit('select-range', start, end, 'min');
this.selectionRange = [start, end];
},
setMaxSelectionRange(start, end) {
this.$emit('select-range', start, end, 'max');
this.selectionRange = [start + this.offset, end + this.offset];
},
handleConfirm(visible = false) {
const minSelectableRange = this.$refs.minSpinner.selectableRange;
const maxSelectableRange = this.$refs.maxSpinner.selectableRange;
this.minDate = limitTimeRange(this.minDate, minSelectableRange, this.format);
this.maxDate = limitTimeRange(this.maxDate, maxSelectableRange, this.format);
this.$emit('pick', [this.minDate, this.maxDate], visible);
},
adjustSpinners() {
this.$refs.minSpinner.adjustSpinners();
this.$refs.maxSpinner.adjustSpinners();
},
changeSelectionRange(step) {
const list = this.showSeconds ? [0, 3, 6, 11, 14, 17] : [0, 3, 8, 11];
const mapping = ['hours', 'minutes'].concat(this.showSeconds ? ['seconds'] : []);
const index = list.indexOf(this.selectionRange[0]);
const next = (index + step + list.length) % list.length;
const half = list.length / 2;
if (next < half) {
this.$refs.minSpinner.emitSelectRange(mapping[next]);
} else {
this.$refs.maxSpinner.emitSelectRange(mapping[next - half]);
}
},
isValidValue(date) {
return Array.isArray(date) &&
timeWithinRange(this.minDate, this.$refs.minSpinner.selectableRange) &&
timeWithinRange(this.maxDate, this.$refs.maxSpinner.selectableRange);
},
handleKeydown(event) {
const keyCode = event.keyCode;
const mapping = { 38: -1, 40: 1, 37: -1, 39: 1 };
// Left or Right
if (keyCode === 37 || keyCode === 39) {
const step = mapping[keyCode];
this.changeSelectionRange(step);
event.preventDefault();
return;
}
// Up or Down
if (keyCode === 38 || keyCode === 40) {
const step = mapping[keyCode];
this.spinner.scrollDown(step);
event.preventDefault();
return;
}
}
}
};
</script>
<template>
<transition name="el-zoom-in-top" @before-enter="handleMenuEnter" @after-leave="$emit('dodestroy')">
<div
ref="popper"
v-show="visible"
:style="{ width: width + 'px' }"
:class="popperClass"
class="el-picker-panel time-select el-popper">
<el-scrollbar noresize wrap-class="el-picker-panel__content">
<div class="time-select-item"
v-for="item in items"
:class="{ selected: value === item.value, disabled: item.disabled, default: item.value === defaultValue }"
:disabled="item.disabled"
@click="handleClick(item)">{{ item.value }}</div>
</el-scrollbar>
</div>
</transition>
</template>
<script type="text/babel">
import ElScrollbar from 'element-ui/packages/scrollbar';
import scrollIntoView from 'element-ui/src/utils/scroll-into-view';
const parseTime = function(time) {
const values = (time || '').split(':');
if (values.length >= 2) {
const hours = parseInt(values[0], 10);
const minutes = parseInt(values[1], 10);
return {
hours,
minutes
};
}
/* istanbul ignore next */
return null;
};
const compareTime = function(time1, time2) {
const value1 = parseTime(time1);
const value2 = parseTime(time2);
const minutes1 = value1.minutes + value1.hours * 60;
const minutes2 = value2.minutes + value2.hours * 60;
if (minutes1 === minutes2) {
return 0;
}
return minutes1 > minutes2 ? 1 : -1;
};
const formatTime = function(time) {
return (time.hours < 10 ? '0' + time.hours : time.hours) + ':' + (time.minutes < 10 ? '0' + time.minutes : time.minutes);
};
const nextTime = function(time, step) {
const timeValue = parseTime(time);
const stepValue = parseTime(step);
const next = {
hours: timeValue.hours,
minutes: timeValue.minutes
};
next.minutes += stepValue.minutes;
next.hours += stepValue.hours;
next.hours += Math.floor(next.minutes / 60);
next.minutes = next.minutes % 60;
return formatTime(next);
};
export default {
components: { ElScrollbar },
watch: {
value(val) {
if (!val) return;
this.$nextTick(() => this.scrollToOption());
}
},
methods: {
handleClick(item) {
if (!item.disabled) {
this.$emit('pick', item.value);
}
},
handleClear() {
this.$emit('pick', null);
},
scrollToOption(selector = '.selected') {
const menu = this.$refs.popper.querySelector('.el-picker-panel__content');
scrollIntoView(menu, menu.querySelector(selector));
},
handleMenuEnter() {
const selected = this.items.map(item => item.value).indexOf(this.value) !== -1;
const hasDefault = this.items.map(item => item.value).indexOf(this.defaultValue) !== -1;
const option = (selected && '.selected') || (hasDefault && '.default') || '.time-select-item:not(.disabled)';
this.$nextTick(() => this.scrollToOption(option));
},
scrollDown(step) {
const items = this.items;
const length = items.length;
let total = items.length;
let index = items.map(item => item.value).indexOf(this.value);
while (total--) {
index = (index + step + length) % length;
if (!items[index].disabled) {
this.$emit('pick', items[index].value, true);
return;
}
}
},
isValidValue(date) {
return this.items.filter(item => !item.disabled).map(item => item.value).indexOf(date) !== -1;
},
handleKeydown(event) {
const keyCode = event.keyCode;
if (keyCode === 38 || keyCode === 40) {
const mapping = { 40: 1, 38: -1 };
const offset = mapping[keyCode.toString()];
this.scrollDown(offset);
event.stopPropagation();
return;
}
}
},
data() {
return {
popperClass: '',
start: '09:00',
end: '18:00',
step: '00:30',
value: '',
defaultValue: '',
visible: false,
minTime: '',
maxTime: '',
width: 0
};
},
computed: {
items() {
const start = this.start;
const end = this.end;
const step = this.step;
const result = [];
if (start && end && step) {
let current = start;
while (compareTime(current, end) <= 0) {
result.push({
value: current,
disabled: compareTime(current, this.minTime || '-1:-1') <= 0 ||
compareTime(current, this.maxTime || '100:100') >= 0
});
current = nextTime(current, step);
}
}
return result;
}
}
};
</script>
<template>
<transition name="el-zoom-in-top" @after-leave="$emit('dodestroy')">
<div
v-show="visible"
class="el-time-panel el-popper"
:class="popperClass">
<div class="el-time-panel__content" :class="{ 'has-seconds': showSeconds }">
<time-spinner
ref="spinner"
@change="handleChange"
:arrow-control="useArrow"
:show-seconds="showSeconds"
:am-pm-mode="amPmMode"
@select-range="setSelectionRange"
:date="date">
</time-spinner>
</div>
<div class="el-time-panel__footer">
<button
type="button"
class="el-time-panel__btn cancel"
@click="handleCancel">{{ t('el.datepicker.cancel') }}</button>
<button
type="button"
class="el-time-panel__btn"
:class="{confirm: !disabled}"
@click="handleConfirm()">{{ t('el.datepicker.confirm') }}</button>
</div>
</div>
</transition>
</template>
<script type="text/babel">
import { limitTimeRange, isDate, clearMilliseconds, timeWithinRange } from '../util';
import Locale from 'element-ui/src/mixins/locale';
import TimeSpinner from '../basic/time-spinner';
export default {
mixins: [Locale],
components: {
TimeSpinner
},
props: {
visible: Boolean,
timeArrowControl: Boolean
},
watch: {
visible(val) {
if (val) {
this.oldValue = this.value;
this.$nextTick(() => this.$refs.spinner.emitSelectRange('hours'));
} else {
this.needInitAdjust = true;
}
},
value(newVal) {
let date;
if (newVal instanceof Date) {
date = limitTimeRange(newVal, this.selectableRange, this.format);
} else if (!newVal) {
date = this.defaultValue ? new Date(this.defaultValue) : new Date();
}
this.date = date;
if (this.visible && this.needInitAdjust) {
this.$nextTick(_ => this.adjustSpinners());
this.needInitAdjust = false;
}
},
selectableRange(val) {
this.$refs.spinner.selectableRange = val;
},
defaultValue(val) {
if (!isDate(this.value)) {
this.date = val ? new Date(val) : new Date();
}
}
},
data() {
return {
popperClass: '',
format: 'HH:mm:ss',
value: '',
defaultValue: null,
date: new Date(),
oldValue: new Date(),
selectableRange: [],
selectionRange: [0, 2],
disabled: false,
arrowControl: false,
needInitAdjust: true
};
},
computed: {
showSeconds() {
return (this.format || '').indexOf('ss') !== -1;
},
useArrow() {
return this.arrowControl || this.timeArrowControl || false;
},
amPmMode() {
if ((this.format || '').indexOf('A') !== -1) return 'A';
if ((this.format || '').indexOf('a') !== -1) return 'a';
return '';
}
},
methods: {
handleCancel() {
this.$emit('pick', this.oldValue, false);
},
handleChange(date) {
// this.visible avoids edge cases, when use scrolls during panel closing animation
if (this.visible) {
this.date = clearMilliseconds(date);
// if date is out of range, do not emit
if (this.isValidValue(this.date)) {
this.$emit('pick', this.date, true);
}
}
},
setSelectionRange(start, end) {
this.$emit('select-range', start, end);
this.selectionRange = [start, end];
},
handleConfirm(visible = false, first) {
if (first) return;
const date = clearMilliseconds(limitTimeRange(this.date, this.selectableRange, this.format));
this.$emit('pick', date, visible, first);
},
handleKeydown(event) {
const keyCode = event.keyCode;
const mapping = { 38: -1, 40: 1, 37: -1, 39: 1 };
// Left or Right
if (keyCode === 37 || keyCode === 39) {
const step = mapping[keyCode];
this.changeSelectionRange(step);
event.preventDefault();
return;
}
// Up or Down
if (keyCode === 38 || keyCode === 40) {
const step = mapping[keyCode];
this.$refs.spinner.scrollDown(step);
event.preventDefault();
return;
}
},
isValidValue(date) {
return timeWithinRange(date, this.selectableRange, this.format);
},
adjustSpinners() {
return this.$refs.spinner.adjustSpinners();
},
changeSelectionRange(step) {
const list = [0, 3].concat(this.showSeconds ? [6] : []);
const mapping = ['hours', 'minutes'].concat(this.showSeconds ? ['seconds'] : []);
const index = list.indexOf(this.selectionRange[0]);
const next = (index + step + list.length) % list.length;
this.$refs.spinner.emitSelectRange(mapping[next]);
}
},
mounted() {
this.$nextTick(() => this.handleConfirm(true, true));
this.$emit('mounted');
}
};
</script>
<template>
<el-input
class="el-date-editor"
:class="'el-date-editor--' + type"
:readonly="!editable || readonly || type === 'dates'"
:disabled="pickerDisabled"
:size="pickerSize"
:name="name"
v-bind="firstInputId"
v-if="!ranged"
v-clickoutside="handleClose"
:placeholder="placeholder"
@focus="handleFocus"
@keydown.native="handleKeydown"
:value="displayValue"
@input="value => userInput = value"
@change="handleChange"
@mouseenter.native="handleMouseEnter"
@mouseleave.native="showClose = false"
:validateEvent="false"
ref="reference">
<i slot="prefix"
class="el-input__icon"
:class="triggerClass"
@click="handleFocus">
</i>
<i slot="suffix"
class="el-input__icon"
@click="handleClickIcon"
:class="[showClose ? '' + clearIcon : '']"
v-if="haveTrigger">
</i>
</el-input>
<div
class="el-date-editor el-range-editor el-input__inner"
:class="[
'el-date-editor--' + type,
pickerSize ? `el-range-editor--${ pickerSize }` : '',
pickerDisabled ? 'is-disabled' : '',
pickerVisible ? 'is-active' : ''
]"
@click="handleRangeClick"
@mouseenter="handleMouseEnter"
@mouseleave="showClose = false"
@keydown="handleKeydown"
ref="reference"
v-clickoutside="handleClose"
v-else>
<i :class="['el-input__icon', 'el-range__icon', triggerClass]"></i>
<input
autocomplete="off"
:placeholder="startPlaceholder"
:value="displayValue && displayValue[0]"
:disabled="pickerDisabled"
v-bind="firstInputId"
:readonly="!editable || readonly"
:name="name && name[0]"
@input="handleStartInput"
@change="handleStartChange"
@focus="handleFocus"
class="el-range-input">
<slot name="range-separator">
<span class="el-range-separator">{{ rangeSeparator }}</span>
</slot>
<input
autocomplete="off"
:placeholder="endPlaceholder"
:value="displayValue && displayValue[1]"
:disabled="pickerDisabled"
v-bind="secondInputId"
:readonly="!editable || readonly"
:name="name && name[1]"
@input="handleEndInput"
@change="handleEndChange"
@focus="handleFocus"
class="el-range-input">
<i
@click="handleClickIcon"
v-if="haveTrigger"
:class="[showClose ? '' + clearIcon : '']"
class="el-input__icon el-range__close-icon">
</i>
</div>
</template>
<script>
import Vue from 'vue';
import Clickoutside from 'element-ui/src/utils/clickoutside';
import { formatDate, parseDate, isDateObject, getWeekNumber } from './util';
import Popper from 'element-ui/src/utils/vue-popper';
import Emitter from 'element-ui/src/mixins/emitter';
import ElInput from 'element-ui/packages/input';
import merge from 'element-ui/src/utils/merge';
const NewPopper = {
props: {
appendToBody: Popper.props.appendToBody,
offset: Popper.props.offset,
boundariesPadding: Popper.props.boundariesPadding,
arrowOffset: Popper.props.arrowOffset
},
methods: Popper.methods,
data() {
return merge({ visibleArrow: true }, Popper.data);
},
beforeDestroy: Popper.beforeDestroy
};
const DEFAULT_FORMATS = {
date: 'yyyy-MM-dd',
month: 'yyyy-MM',
datetime: 'yyyy-MM-dd HH:mm:ss',
time: 'HH:mm:ss',
week: 'yyyywWW',
timerange: 'HH:mm:ss',
daterange: 'yyyy-MM-dd',
datetimerange: 'yyyy-MM-dd HH:mm:ss',
year: 'yyyy'
};
const HAVE_TRIGGER_TYPES = [
'date',
'datetime',
'time',
'time-select',
'week',
'month',
'year',
'daterange',
'timerange',
'datetimerange',
'dates'
];
const DATE_FORMATTER = function(value, format) {
if (format === 'timestamp') return value.getTime();
return formatDate(value, format);
};
const DATE_PARSER = function(text, format) {
if (format === 'timestamp') return new Date(Number(text));
return parseDate(text, format);
};
const RANGE_FORMATTER = function(value, format) {
if (Array.isArray(value) && value.length === 2) {
const start = value[0];
const end = value[1];
if (start && end) {
return [DATE_FORMATTER(start, format), DATE_FORMATTER(end, format)];
}
}
return '';
};
const RANGE_PARSER = function(array, format, separator) {
if (!Array.isArray(array)) {
array = array.split(separator);
}
if (array.length === 2) {
const range1 = array[0];
const range2 = array[1];
return [DATE_PARSER(range1, format), DATE_PARSER(range2, format)];
}
return [];
};
const TYPE_VALUE_RESOLVER_MAP = {
default: {
formatter(value) {
if (!value) return '';
return '' + value;
},
parser(text) {
if (text === undefined || text === '') return null;
return text;
}
},
week: {
formatter(value, format) {
let week = getWeekNumber(value);
let month = value.getMonth();
const trueDate = new Date(value);
if (week === 1 && month === 11) {
trueDate.setHours(0, 0, 0, 0);
trueDate.setDate(trueDate.getDate() + 3 - (trueDate.getDay() + 6) % 7);
}
let date = formatDate(trueDate, format);
date = /WW/.test(date)
? date.replace(/WW/, week < 10 ? '0' + week : week)
: date.replace(/W/, week);
return date;
},
parser(text) {
const array = (text || '').split('w');
if (array.length === 2) {
const year = Number(array[0]);
const month = Number(array[1]);
if (!isNaN(year) && !isNaN(month) && month < 54) {
return text;
}
}
return null;
}
},
date: {
formatter: DATE_FORMATTER,
parser: DATE_PARSER
},
datetime: {
formatter: DATE_FORMATTER,
parser: DATE_PARSER
},
daterange: {
formatter: RANGE_FORMATTER,
parser: RANGE_PARSER
},
datetimerange: {
formatter: RANGE_FORMATTER,
parser: RANGE_PARSER
},
timerange: {
formatter: RANGE_FORMATTER,
parser: RANGE_PARSER
},
time: {
formatter: DATE_FORMATTER,
parser: DATE_PARSER
},
month: {
formatter: DATE_FORMATTER,
parser: DATE_PARSER
},
year: {
formatter: DATE_FORMATTER,
parser: DATE_PARSER
},
number: {
formatter(value) {
if (!value) return '';
return '' + value;
},
parser(text) {
let result = Number(text);
if (!isNaN(text)) {
return result;
} else {
return null;
}
}
},
dates: {
formatter(value, format) {
return value.map(date => DATE_FORMATTER(date, format));
},
parser(value, format) {
return (typeof value === 'string' ? value.split(', ') : value)
.map(date => date instanceof Date ? date : DATE_PARSER(date, format));
}
}
};
const PLACEMENT_MAP = {
left: 'bottom-start',
center: 'bottom',
right: 'bottom-end'
};
const parseAsFormatAndType = (value, customFormat, type, rangeSeparator = '-') => {
if (!value) return null;
const parser = (
TYPE_VALUE_RESOLVER_MAP[type] ||
TYPE_VALUE_RESOLVER_MAP['default']
).parser;
const format = customFormat || DEFAULT_FORMATS[type];
return parser(value, format, rangeSeparator);
};
const formatAsFormatAndType = (value, customFormat, type) => {
if (!value) return null;
const formatter = (
TYPE_VALUE_RESOLVER_MAP[type] ||
TYPE_VALUE_RESOLVER_MAP['default']
).formatter;
const format = customFormat || DEFAULT_FORMATS[type];
return formatter(value, format);
};
/*
* Considers:
* 1. Date object
* 2. date string
* 3. array of 1 or 2
*/
const valueEquals = function(a, b) {
// considers Date object and string
const dateEquals = function(a, b) {
const aIsDate = a instanceof Date;
const bIsDate = b instanceof Date;
if (aIsDate && bIsDate) {
return a.getTime() === b.getTime();
}
if (!aIsDate && !bIsDate) {
return a === b;
}
return false;
};
const aIsArray = a instanceof Array;
const bIsArray = b instanceof Array;
if (aIsArray && bIsArray) {
if (a.length !== b.length) {
return false;
}
return a.every((item, index) => dateEquals(item, b[index]));
}
if (!aIsArray && !bIsArray) {
return dateEquals(a, b);
}
return false;
};
const isString = function(val) {
return typeof val === 'string' || val instanceof String;
};
const validator = function(val) {
// either: String, Array of String, null / undefined
return (
val === null ||
val === undefined ||
isString(val) ||
(Array.isArray(val) && val.length === 2 && val.every(isString))
);
};
export default {
mixins: [Emitter, NewPopper],
inject: {
elForm: {
default: ''
},
elFormItem: {
default: ''
}
},
props: {
size: String,
format: String,
valueFormat: String,
readonly: Boolean,
placeholder: String,
startPlaceholder: String,
endPlaceholder: String,
prefixIcon: String,
clearIcon: {
type: String,
default: 'el-icon-circle-close'
},
name: {
default: '',
validator
},
disabled: Boolean,
clearable: {
type: Boolean,
default: true
},
id: {
default: '',
validator
},
popperClass: String,
editable: {
type: Boolean,
default: true
},
align: {
type: String,
default: 'left'
},
value: {},
defaultValue: {},
defaultTime: {},
rangeSeparator: {
default: '-'
},
pickerOptions: {},
unlinkPanels: Boolean
},
components: { ElInput },
directives: { Clickoutside },
data() {
return {
pickerVisible: false,
showClose: false,
userInput: null,
valueOnOpen: null, // value when picker opens, used to determine whether to emit change
unwatchPickerOptions: null
};
},
watch: {
pickerVisible(val) {
if (this.readonly || this.pickerDisabled) return;
if (val) {
this.showPicker();
this.valueOnOpen = Array.isArray(this.value) ? [...this.value] : this.value;
} else {
this.hidePicker();
this.emitChange(this.value);
this.userInput = null;
this.dispatch('ElFormItem', 'el.form.blur');
this.$emit('blur', this);
this.blur();
}
},
parsedValue: {
immediate: true,
handler(val) {
if (this.picker) {
this.picker.value = val;
}
}
},
defaultValue(val) {
// NOTE: should eventually move to jsx style picker + panel ?
if (this.picker) {
this.picker.defaultValue = val;
}
},
value(val, oldVal) {
if (!valueEquals(val, oldVal) && !this.pickerVisible) {
this.dispatch('ElFormItem', 'el.form.change', val);
}
}
},
computed: {
ranged() {
return this.type.indexOf('range') > -1;
},
reference() {
const reference = this.$refs.reference;
return reference.$el || reference;
},
refInput() {
if (this.reference) {
return [].slice.call(this.reference.querySelectorAll('input'));
}
return [];
},
valueIsEmpty() {
const val = this.value;
if (Array.isArray(val)) {
for (let i = 0, len = val.length; i < len; i++) {
if (val[i]) {
return false;
}
}
} else {
if (val) {
return false;
}
}
return true;
},
triggerClass() {
return this.prefixIcon || (this.type.indexOf('time') !== -1 ? 'el-icon-time' : 'el-icon-date');
},
selectionMode() {
if (this.type === 'week') {
return 'week';
} else if (this.type === 'month') {
return 'month';
} else if (this.type === 'year') {
return 'year';
} else if (this.type === 'dates') {
return 'dates';
}
return 'day';
},
haveTrigger() {
if (typeof this.showTrigger !== 'undefined') {
return this.showTrigger;
}
return HAVE_TRIGGER_TYPES.indexOf(this.type) !== -1;
},
displayValue() {
const formattedValue = formatAsFormatAndType(this.parsedValue, this.format, this.type, this.rangeSeparator);
if (Array.isArray(this.userInput)) {
return [
this.userInput[0] || (formattedValue && formattedValue[0]) || '',
this.userInput[1] || (formattedValue && formattedValue[1]) || ''
];
} else if (this.userInput !== null) {
return this.userInput;
} else if (formattedValue) {
return this.type === 'dates'
? formattedValue.join(', ')
: formattedValue;
} else {
return '';
}
},
parsedValue() {
if (!this.value) return this.value; // component value is not set
if (this.type === 'time-select') return this.value; // time-select does not require parsing, this might change in next major version
const valueIsDateObject = isDateObject(this.value) || (Array.isArray(this.value) && this.value.every(isDateObject));
if (valueIsDateObject) {
return this.value;
}
if (this.valueFormat) {
return parseAsFormatAndType(this.value, this.valueFormat, this.type, this.rangeSeparator) || this.value;
}
// NOTE: deal with common but incorrect usage, should remove in next major version
// user might provide string / timestamp without value-format, coerce them into date (or array of date)
return Array.isArray(this.value) ? this.value.map(val => new Date(val)) : new Date(this.value);
},
_elFormItemSize() {
return (this.elFormItem || {}).elFormItemSize;
},
pickerSize() {
return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
},
pickerDisabled() {
return this.disabled || (this.elForm || {}).disabled;
},
firstInputId() {
const obj = {};
let id;
if (this.ranged) {
id = this.id && this.id[0];
} else {
id = this.id;
}
if (id) obj.id = id;
return obj;
},
secondInputId() {
const obj = {};
let id;
if (this.ranged) {
id = this.id && this.id[1];
}
if (id) obj.id = id;
return obj;
}
},
created() {
// vue-popper
this.popperOptions = {
boundariesPadding: 0,
gpuAcceleration: false
};
this.placement = PLACEMENT_MAP[this.align] || PLACEMENT_MAP.left;
this.$on('fieldReset', this.handleFieldReset);
},
methods: {
focus() {
if (!this.ranged) {
this.$refs.reference.focus();
} else {
this.handleFocus();
}
},
blur() {
this.refInput.forEach(input => input.blur());
},
// {parse, formatTo} Value deals maps component value with internal Date
parseValue(value) {
const isParsed = isDateObject(value) || (Array.isArray(value) && value.every(isDateObject));
if (this.valueFormat && !isParsed) {
return parseAsFormatAndType(value, this.valueFormat, this.type, this.rangeSeparator) || value;
} else {
return value;
}
},
formatToValue(date) {
const isFormattable = isDateObject(date) || (Array.isArray(date) && date.every(isDateObject));
if (this.valueFormat && isFormattable) {
return formatAsFormatAndType(date, this.valueFormat, this.type, this.rangeSeparator);
} else {
return date;
}
},
// {parse, formatTo} String deals with user input
parseString(value) {
const type = Array.isArray(value) ? this.type : this.type.replace('range', '');
return parseAsFormatAndType(value, this.format, type);
},
formatToString(value) {
const type = Array.isArray(value) ? this.type : this.type.replace('range', '');
return formatAsFormatAndType(value, this.format, type);
},
handleMouseEnter() {
if (this.readonly || this.pickerDisabled) return;
if (!this.valueIsEmpty && this.clearable) {
this.showClose = true;
}
},
handleChange() {
if (this.userInput) {
const value = this.parseString(this.displayValue);
if (value) {
this.picker.value = value;
if (this.isValidValue(value)) {
this.emitInput(value);
this.userInput = null;
}
}
}
if (this.userInput === '') {
this.emitInput(null);
this.emitChange(null);
this.userInput = null;
}
},
handleStartInput(event) {
if (this.userInput) {
this.userInput = [event.target.value, this.userInput[1]];
} else {
this.userInput = [event.target.value, null];
}
},
handleEndInput(event) {
if (this.userInput) {
this.userInput = [this.userInput[0], event.target.value];
} else {
this.userInput = [null, event.target.value];
}
},
handleStartChange(event) {
const value = this.parseString(this.userInput && this.userInput[0]);
if (value) {
this.userInput = [this.formatToString(value), this.displayValue[1]];
const newValue = [value, this.picker.value && this.picker.value[1]];
this.picker.value = newValue;
if (this.isValidValue(newValue)) {
this.emitInput(newValue);
this.userInput = null;
}
}
},
handleEndChange(event) {
const value = this.parseString(this.userInput && this.userInput[1]);
if (value) {
this.userInput = [this.displayValue[0], this.formatToString(value)];
const newValue = [this.picker.value && this.picker.value[0], value];
this.picker.value = newValue;
if (this.isValidValue(newValue)) {
this.emitInput(newValue);
this.userInput = null;
}
}
},
handleClickIcon(event) {
if (this.readonly || this.pickerDisabled) return;
if (this.showClose) {
this.valueOnOpen = this.value;
event.stopPropagation();
this.emitInput(null);
this.emitChange(null);
this.showClose = false;
if (this.picker && typeof this.picker.handleClear === 'function') {
this.picker.handleClear();
}
} else {
this.pickerVisible = !this.pickerVisible;
}
},
handleClose() {
if (!this.pickerVisible) return;
this.pickerVisible = false;
if (this.type === 'dates') {
// restore to former value
const oldValue = parseAsFormatAndType(this.valueOnOpen, this.valueFormat, this.type, this.rangeSeparator) || this.valueOnOpen;
this.emitInput(oldValue);
}
},
handleFieldReset(initialValue) {
this.userInput = initialValue === '' ? null : initialValue;
},
handleFocus() {
const type = this.type;
if (HAVE_TRIGGER_TYPES.indexOf(type) !== -1 && !this.pickerVisible) {
this.pickerVisible = true;
}
this.$emit('focus', this);
},
handleKeydown(event) {
const keyCode = event.keyCode;
// ESC
if (keyCode === 27) {
this.pickerVisible = false;
event.stopPropagation();
return;
}
// Tab
if (keyCode === 9) {
if (!this.ranged) {
this.handleChange();
this.pickerVisible = this.picker.visible = false;
this.blur();
event.stopPropagation();
} else {
// user may change focus between two input
setTimeout(() => {
if (this.refInput.indexOf(document.activeElement) === -1) {
this.pickerVisible = false;
this.blur();
event.stopPropagation();
}
}, 0);
}
return;
}
// Enter
if (keyCode === 13) {
if (this.userInput === '' || this.isValidValue(this.parseString(this.displayValue))) {
this.handleChange();
this.pickerVisible = this.picker.visible = false;
this.blur();
}
event.stopPropagation();
return;
}
// if user is typing, do not let picker handle key input
if (this.userInput) {
event.stopPropagation();
return;
}
// delegate other keys to panel
if (this.picker && this.picker.handleKeydown) {
this.picker.handleKeydown(event);
}
},
handleRangeClick() {
const type = this.type;
if (HAVE_TRIGGER_TYPES.indexOf(type) !== -1 && !this.pickerVisible) {
this.pickerVisible = true;
}
this.$emit('focus', this);
},
hidePicker() {
if (this.picker) {
this.picker.resetView && this.picker.resetView();
this.pickerVisible = this.picker.visible = false;
this.destroyPopper();
}
},
showPicker() {
if (this.$isServer) return;
if (!this.picker) {
this.mountPicker();
}
this.pickerVisible = this.picker.visible = true;
this.updatePopper();
this.picker.value = this.parsedValue;
this.picker.resetView && this.picker.resetView();
this.$nextTick(() => {
this.picker.adjustSpinners && this.picker.adjustSpinners();
});
},
mountPicker() {
this.picker = new Vue(this.panel).$mount();
this.picker.defaultValue = this.defaultValue;
this.picker.defaultTime = this.defaultTime;
this.picker.popperClass = this.popperClass;
this.popperElm = this.picker.$el;
this.picker.width = this.reference.getBoundingClientRect().width;
this.picker.showTime = this.type === 'datetime' || this.type === 'datetimerange';
this.picker.selectionMode = this.selectionMode;
this.picker.unlinkPanels = this.unlinkPanels;
this.picker.arrowControl = this.arrowControl || this.timeArrowControl || false;
this.$watch('format', (format) => {
this.picker.format = format;
});
const updateOptions = () => {
const options = this.pickerOptions;
if (options && options.selectableRange) {
let ranges = options.selectableRange;
const parser = TYPE_VALUE_RESOLVER_MAP.datetimerange.parser;
const format = DEFAULT_FORMATS.timerange;
ranges = Array.isArray(ranges) ? ranges : [ranges];
this.picker.selectableRange = ranges.map(range => parser(range, format, this.rangeSeparator));
}
for (const option in options) {
if (options.hasOwnProperty(option) &&
// 忽略 time-picker 的该配置项
option !== 'selectableRange') {
this.picker[option] = options[option];
}
}
// main format must prevail over undocumented pickerOptions.format
if (this.format) {
this.picker.format = this.format;
}
};
updateOptions();
this.unwatchPickerOptions = this.$watch('pickerOptions', () => updateOptions(), { deep: true });
this.$el.appendChild(this.picker.$el);
this.picker.resetView && this.picker.resetView();
this.picker.$on('dodestroy', this.doDestroy);
this.picker.$on('pick', (date = '', visible = false) => {
this.userInput = null;
this.pickerVisible = this.picker.visible = visible;
this.emitInput(date);
this.picker.resetView && this.picker.resetView();
});
this.picker.$on('select-range', (start, end, pos) => {
if (this.refInput.length === 0) return;
if (!pos || pos === 'min') {
this.refInput[0].setSelectionRange(start, end);
this.refInput[0].focus();
} else if (pos === 'max') {
this.refInput[1].setSelectionRange(start, end);
this.refInput[1].focus();
}
});
},
unmountPicker() {
if (this.picker) {
this.picker.$destroy();
this.picker.$off();
if (typeof this.unwatchPickerOptions === 'function') {
this.unwatchPickerOptions();
}
this.picker.$el.parentNode.removeChild(this.picker.$el);
}
},
emitChange(val) {
// determine user real change only
if (!valueEquals(val, this.valueOnOpen)) {
this.$emit('change', val);
this.dispatch('ElFormItem', 'el.form.change', val);
this.valueOnOpen = val;
}
},
emitInput(val) {
const formatted = this.formatToValue(val);
if (!valueEquals(this.value, formatted)) {
this.$emit('input', formatted);
}
},
isValidValue(value) {
if (!this.picker) {
this.mountPicker();
}
if (this.picker.isValidValue) {
return value && this.picker.isValidValue(value);
} else {
return true;
}
}
}
};
</script>
import dateUtil from 'element-ui/src/utils/date';
import { t } from 'element-ui/src/locale';
const weeks = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
const months = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
const getI18nSettings = () => {
return {
dayNamesShort: weeks.map(week => t(`el.datepicker.weeks.${ week }`)),
dayNames: weeks.map(week => t(`el.datepicker.weeks.${ week }`)),
monthNamesShort: months.map(month => t(`el.datepicker.months.${ month }`)),
monthNames: months.map((month, index) => t(`el.datepicker.month${ index + 1 }`)),
amPm: ['am', 'pm']
};
};
const newArray = function(start, end) {
let result = [];
for (let i = start; i <= end; i++) {
result.push(i);
}
return result;
};
export const toDate = function(date) {
return isDate(date) ? new Date(date) : null;
};
export const isDate = function(date) {
if (date === null || date === undefined) return false;
if (isNaN(new Date(date).getTime())) return false;
if (Array.isArray(date)) return false; // deal with `new Date([ new Date() ]) -> new Date()`
return true;
};
export const isDateObject = function(val) {
return val instanceof Date;
};
export const formatDate = function(date, format) {
date = toDate(date);
if (!date) return '';
return dateUtil.format(date, format || 'yyyy-MM-dd', getI18nSettings());
};
export const parseDate = function(string, format) {
return dateUtil.parse(string, format || 'yyyy-MM-dd', getI18nSettings());
};
export const getDayCountOfMonth = function(year, month) {
if (month === 3 || month === 5 || month === 8 || month === 10) {
return 30;
}
if (month === 1) {
if (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0) {
return 29;
} else {
return 28;
}
}
return 31;
};
export const getDayCountOfYear = function(year) {
const isLeapYear = year % 400 === 0 || (year % 100 !== 0 && year % 4 === 0);
return isLeapYear ? 366 : 365;
};
export const getFirstDayOfMonth = function(date) {
const temp = new Date(date.getTime());
temp.setDate(1);
return temp.getDay();
};
// see: https://stackoverflow.com/questions/3674539/incrementing-a-date-in-javascript
// {prev, next} Date should work for Daylight Saving Time
// Adding 24 * 60 * 60 * 1000 does not work in the above scenario
export const prevDate = function(date, amount = 1) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate() - amount);
};
export const nextDate = function(date, amount = 1) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + amount);
};
export const getStartDateOfMonth = function(year, month) {
const result = new Date(year, month, 1);
const day = result.getDay();
if (day === 0) {
return prevDate(result, 7);
} else {
return prevDate(result, day);
}
};
export const getWeekNumber = function(src) {
if (!isDate(src)) return null;
const date = new Date(src.getTime());
date.setHours(0, 0, 0, 0);
// Thursday in current week decides the year.
date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
// January 4 is always in week 1.
const week1 = new Date(date.getFullYear(), 0, 4);
// Adjust to Thursday in week 1 and count number of weeks from date to week 1.
// Rounding should be fine for Daylight Saving Time. Its shift should never be more than 12 hours.
return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000 - 3 + (week1.getDay() + 6) % 7) / 7);
};
export const getRangeHours = function(ranges) {
const hours = [];
let disabledHours = [];
(ranges || []).forEach(range => {
const value = range.map(date => date.getHours());
disabledHours = disabledHours.concat(newArray(value[0], value[1]));
});
if (disabledHours.length) {
for (let i = 0; i < 24; i++) {
hours[i] = disabledHours.indexOf(i) === -1;
}
} else {
for (let i = 0; i < 24; i++) {
hours[i] = false;
}
}
return hours;
};
function setRangeData(arr, start, end, value) {
for (let i = start; i < end; i++) {
arr[i] = value;
}
}
export const getRangeMinutes = function(ranges, hour) {
const minutes = new Array(60);
if (ranges.length > 0) {
ranges.forEach(range => {
const start = range[0];
const end = range[1];
const startHour = start.getHours();
const startMinute = start.getMinutes();
const endHour = end.getHours();
const endMinute = end.getMinutes();
if (startHour === hour && endHour !== hour) {
setRangeData(minutes, startMinute, 60, true);
} else if (startHour === hour && endHour === hour) {
setRangeData(minutes, startMinute, endMinute + 1, true);
} else if (startHour !== hour && endHour === hour) {
setRangeData(minutes, 0, endMinute + 1, true);
} else if (startHour < hour && endHour > hour) {
setRangeData(minutes, 0, 60, true);
}
});
} else {
setRangeData(minutes, 0, 60, true);
}
return minutes;
};
export const range = function(n) {
// see https://stackoverflow.com/questions/3746725/create-a-javascript-array-containing-1-n
return Array.apply(null, {length: n}).map((_, n) => n);
};
export const modifyDate = function(date, y, m, d) {
return new Date(y, m, d, date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
};
export const modifyTime = function(date, h, m, s) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), h, m, s, date.getMilliseconds());
};
export const modifyWithTimeString = (date, time) => {
if (date == null || !time) {
return date;
}
time = parseDate(time, 'HH:mm:ss');
return modifyTime(date, time.getHours(), time.getMinutes(), time.getSeconds());
};
export const clearTime = function(date) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
};
export const clearMilliseconds = function(date) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), 0);
};
export const limitTimeRange = function(date, ranges, format = 'HH:mm:ss') {
// TODO: refactory a more elegant solution
if (ranges.length === 0) return date;
const normalizeDate = date => dateUtil.parse(dateUtil.format(date, format), format);
const ndate = normalizeDate(date);
const nranges = ranges.map(range => range.map(normalizeDate));
if (nranges.some(nrange => ndate >= nrange[0] && ndate <= nrange[1])) return date;
let minDate = nranges[0][0];
let maxDate = nranges[0][0];
nranges.forEach(nrange => {
minDate = new Date(Math.min(nrange[0], minDate));
maxDate = new Date(Math.max(nrange[1], minDate));
});
const ret = ndate < minDate ? minDate : maxDate;
// preserve Year/Month/Date
return modifyDate(
ret,
date.getFullYear(),
date.getMonth(),
date.getDate()
);
};
export const timeWithinRange = function(date, selectableRange, format) {
const limitedDate = limitTimeRange(date, selectableRange, format);
return limitedDate.getTime() === date.getTime();
};
export const changeYearMonthAndClampDate = function(date, year, month) {
// clamp date to the number of days in `year`, `month`
// eg: (2010-1-31, 2010, 2) => 2010-2-28
const monthDate = Math.min(date.getDate(), getDayCountOfMonth(year, month));
return modifyDate(date, year, month, monthDate);
};
export const prevMonth = function(date) {
const year = date.getFullYear();
const month = date.getMonth();
return month === 0
? changeYearMonthAndClampDate(date, year - 1, 11)
: changeYearMonthAndClampDate(date, year, month - 1);
};
export const nextMonth = function(date) {
const year = date.getFullYear();
const month = date.getMonth();
return month === 11
? changeYearMonthAndClampDate(date, year + 1, 0)
: changeYearMonthAndClampDate(date, year, month + 1);
};
export const prevYear = function(date, amount = 1) {
const year = date.getFullYear();
const month = date.getMonth();
return changeYearMonthAndClampDate(date, year - amount, month);
};
export const nextYear = function(date, amount = 1) {
const year = date.getFullYear();
const month = date.getMonth();
return changeYearMonthAndClampDate(date, year + amount, month);
};
export const extractDateFormat = function(format) {
return format
.replace(/\W?m{1,2}|\W?ZZ/g, '')
.replace(/\W?h{1,2}|\W?s{1,3}|\W?a/gi, '')
.trim();
};
export const extractTimeFormat = function(format) {
return format
.replace(/\W?D{1,2}|\W?Do|\W?d{1,4}|\W?M{1,4}|\W?y{2,4}/g, '')
.trim();
};
<template>
<transition
name="dialog-fade"
@after-enter="afterEnter"
@after-leave="afterLeave"
>
<div
class="el-dialog__wrapper normal-dialog"
v-show="visible"
@click.self="handleWrapperClick"
>
<div
role="dialog"
aria-modal="true"
:aria-label="title || 'dialog'"
class="el-dialog"
:class="[{ 'is-fullscreen': fullscreen, 'el-dialog--center': center }, customClass]"
ref="dialog"
:style="style"
>
<div class="el-dialog__header">
<div class="el-dialog__title">
<slot name="title">
<span class="el-dialog__title">{{ title }}</span>
</slot>
</div>
<button
type="button"
class="el-dialog__headerbtn"
aria-label="Close"
v-if="showClose"
@click="handleClose"
>
<img
src="@/assets/images/dialog/close_icon.png"
alt=""
>
</button>
</div>
<div
class="el-dialog__body clearfix"
v-if="rendered"
>
<slot></slot>
</div>
<div
class="el-dialog__footer"
v-if="$slots.footer"
>
<slot name="footer"></slot>
</div>
</div>
</div>
</transition>
</template>
<script>
import { Dialog } from 'element-ui';
export default {
name: 'normal-dialog',
extends: Dialog,
};
</script>
<style lang="scss">
@import '../../assets/styles/variables.scss';
.el-dialog__wrapper {
.el-dialog__header {
position: relative;
height: 38px;
background-color: #4e82fb;
padding: 10px;
.el-dialog__title {
margin: 0px auto 0;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
line-height: 28px;
vertical-align: top;
font-size: 14px;
img {
width: 16px;
height: 16px;
margin-right: 20px;
}
}
}
.el-dialog__headerbtn {
position: absolute;
top: -20px;
right: -20px;
width: 39px;
height: 39px;
}
.el-dialog__body {
padding: 20px 40px;
}
.el-dialog__footer {
box-sizing: content-box;
height: 40px;
padding: 25px 10px 20px;
text-align: center;
}
}
@media screen and (min-width: $bigScreenWidth) {
.el-dialog__wrapper {
.el-dialog__header {
height: 96px;
padding: 20px 20px 10px;
.el-dialog__title {
margin: 14px auto 0;
font-size: 22px;
img {
width: 28px;
height: 28px;
margin-right: 20px;
}
}
}
.el-dialog__headerbtn {
top: -30px;
right: -30px;
width: 65px;
height: 65px;
}
.el-dialog__body {
padding: 30px 40px;
}
.el-dialog__footer {
padding: 50px 20px 40px;
}
}
}
</style>
<template>
<transition
name="dialog-fade"
@after-enter="afterEnter"
@after-leave="afterLeave"
>
<div
class="el-dialog__wrapper user-dialog"
v-show="visible"
@click.self="handleWrapperClick"
>
<div
role="dialog"
aria-modal="true"
:aria-label="title || 'dialog'"
class="el-dialog"
:class="[{ 'is-fullscreen': fullscreen, 'el-dialog--center': center }, customClass]"
ref="dialog"
:style="style"
>
<user-with-content
:customerId="customerId"
:visible='visible'
ref="userContent"
>
<button
type="button"
class="el-dialog__headerbtn"
aria-label="Close"
v-if="showClose"
@click="handleClose"
slot="closeBtn"
>
<img
src="@/assets/images/dialog/close_icon.png"
alt=""
>
</button>
<slot
name="title"
slot="title"
></slot>
<slot></slot>
<slot
name="footer"
slot="footer"
></slot>
</user-with-content>
</div>
</div>
</transition>
</template>
<script>
import { Dialog } from 'element-ui';
import UserInfo from '../UserCard/UserInfo';
export default {
name: 'user-dialog-inner',
extends: Dialog,
components: {
UserInfo,
},
props: {
customerId: Number,
},
// mounted() {
// let dialogWidth = 0;
// console.log(this.$refs.userContent.$el.offsetWidth);
// let rightDom = document.querySelector(
// '.SigleUserWithContent-inner .right-part'
// );
// dialogWidth += rightDom.offsetWidth;
// dialogWidth += rightDom.offsetLeft;
// console.log(dialogWidth);
// console.log(this.style);
// this.width = dialogWidth + 'px';
// },
};
</script>
<style lang="scss">
.user-dialog {
.el-dialog {
background: transparent;
box-shadow: none;
}
}
</style>
import UserDialogInner from './UserDialog';
export default {
name: 'user-dialog',
components: {
UserDialogInner,
},
props: {
customerId: Number,
visible: {
type: Boolean,
default: false,
},
title: {
type: String,
default: '',
},
width: String,
fullscreen: Boolean,
top: {
type: String,
default: '15vh',
},
modal: {
type: Boolean,
default: true,
},
modalAppendToBody: {
type: Boolean,
default: true,
},
appendToBody: {
type: Boolean,
default: false,
},
lockScroll: {
type: Boolean,
default: true,
},
customClass: {
type: String,
default: '',
},
closeOnClickModal: {
type: Boolean,
default: true,
},
closeOnPressEscape: {
type: Boolean,
default: true,
},
showClose: {
type: Boolean,
default: true,
},
beforeClose: Function,
center: {
type: Boolean,
default: false,
},
},
data() {
return {
wrapWidth: '50vw',
};
},
mounted() {
let dialogWidth = 0;
let rightDom = document.querySelector(
'.SigleUserWithContent-inner .right-part'
);
dialogWidth += rightDom.offsetWidth;
dialogWidth += rightDom.offsetLeft;
if (dialogWidth) {
this.wrapWidth = dialogWidth + 'px';
}
},
watch: {
visible(newVal) {
if (newVal && this.wrapWidth === '50vw') {
this.$nextTick(() => {
let dialogWidth = 0;
let rightDom = this.$el.querySelector(
'.SigleUserWithContent-inner .right-part'
);
dialogWidth += rightDom.offsetWidth;
dialogWidth += rightDom.offsetLeft;
this.wrapWidth = dialogWidth + 'px';
});
}
},
},
methods: {
updateVisible(val) {
this.$emit('update:visible', val);
},
},
render(h) {
const { width, wrapWidth } = this;
const updateVisible = {
on: {
'update:visible': this.updateVisible,
},
};
return width ? (
<UserDialogInner
{...updateVisible}
customerId={this.customerId}
visible={this.visible}
title={this.title}
fullscreen={this.fullscreen}
top={this.top}
modal={this.modal}
modalAppendToBody={this.modalAppendToBody}
appendToBody={this.appendToBody}
lockScroll={this.lockScroll}
customClass={this.customClass}
closeOnClickModal={this.closeOnClickModal}
closeOnPressEscape={this.closeOnPressEscape}
showClose={this.showClose}
beforeClose={this.beforeClose}
center={this.center}
width={this.width}>
<template slot="title">{this.$slots.title}</template>
{this.$slots.default}
<template slot="footer">{this.$slots.footer}</template>
</UserDialogInner>
) : (
<UserDialogInner
{...updateVisible}
customerId={this.customerId}
visible={this.visible}
title={this.title}
fullscreen={this.fullscreen}
top={this.top}
modal={this.modal}
modalAppendToBody={this.modalAppendToBody}
appendToBody={this.appendToBody}
lockScroll={this.lockScroll}
customClass={this.customClass}
closeOnClickModal={this.closeOnClickModal}
closeOnPressEscape={this.closeOnPressEscape}
showClose={this.showClose}
beforeClose={this.beforeClose}
center={this.center}
width={wrapWidth}>
<template slot="title">{this.$slots.title}</template>
{this.$slots.default}
<template slot="footer">{this.$slots.footer}</template>
</UserDialogInner>
);
},
};
import { Dialog } from 'element-ui';
import { listen, styler, pointer, value } from 'popmotion';
const DragDialog = {
name: 'drag-dialog',
extends: Dialog,
watch: {
visible(newVal) {
if (newVal) {
const dialogStyler = styler(this.$refs.dialog);
const dialogXY = value({ x: 0, y: 0 }, dialogStyler.set);
let elHeader = this.$refs.dialog.querySelector('.el-dialog__header');
listen(elHeader, 'mousedown touchstart').start(e => {
e.preventDefault();
pointer(dialogXY.get()).start(dialogXY);
// console.log(x,y)
});
listen(document, 'mouseup touchend').start(() => {
dialogXY.stop();
});
}
},
},
};
export default DragDialog;
<template>
<div class="FormArea">
<div class="FormArea-title">{{title}}</div>
<div class="FormArea-content">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'form-area',
props: {
title: {
type: String,
default: '题目',
},
},
};
</script>
<style lang="scss">
@import '../../assets/styles/variables.scss';
.FormArea {
.FormArea-title {
height: 18px;
line-height: 18px;
font-size: 12px;
color: #0177d5;
border-left: 2px solid #0177d5;
text-indent: 6px;
}
.FormArea-content {
padding: 10px 0 13px;
}
}
@media screen and (min-width: $bigScreenWidth) {
.FormArea {
.FormArea-title {
height: 30px;
line-height: 30px;
font-size: 22px;
text-indent: 12px;
}
.FormArea-content {
padding: 10px 0 20px;
}
}
}
</style>
<template>
<div class="FormItem">
<div class="FormItem-label">{{label}}</div>
<div :class="['FormItem-content', size]">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'form-item',
props: {
label: {
type: String,
default: '属性',
},
size: {
type: String,
default: 'normal',
},
},
};
</script>
<style lang="scss">
@import '../../assets/styles/variables.scss';
.FormItem {
display: flex;
font-size: 12px;
color: #333;
& + & {
margin-left: 25px;
}
.FormItem-label {
position: relative;
width: 57px;
text-align: justify;
text-align-last: justify;
&::after {
content: ':';
position: absolute;
right: -5px;
}
}
.FormItem-content {
margin-left: 10px;
&.big {
width: 210px;
}
&.normal {
width: 144px;
}
&small {
width: 120px;
}
}
}
@media screen and (min-width: $bigScreenWidth) {
.FormItem {
font-size: 18px;
& + & {
margin-left: 40px;
}
.FormItem-label {
width: 77px;
}
.FormItem-content {
margin-left: 20px;
&.big {
width: 240px;
}
&.normal {
width: 174px;
}
&small {
width: 150px;
}
}
}
}
</style>
<template>
<div class="FormLine">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'form-line',
};
</script>
<style lang="scss">
@import '../../assets/styles/variables.scss';
.FormLine {
height: 25px;
line-height: 25px;
display: flex;
& + & {
margin-top: 9px;
}
}
@media screen and (min-width: $bigScreenWidth) {
.FormLine {
height: 40px;
line-height: 40px;
& + & {
margin-top: 16px;
}
}
}
</style>
<template>
<div>
<svg t="1492500959545" @click="toggleClick" class="svg-icon hamburger" :class="{'is-active':isActive}" style="" viewBox="0 0 1024 1024"
version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1691" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64">
<path d="M966.8023 568.849776 57.196677 568.849776c-31.397081 0-56.850799-25.452695-56.850799-56.850799l0 0c0-31.397081 25.452695-56.849776 56.850799-56.849776l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.849776l0 0C1023.653099 543.397081 998.200404 568.849776 966.8023 568.849776z"
p-id="1692"></path>
<path d="M966.8023 881.527125 57.196677 881.527125c-31.397081 0-56.850799-25.452695-56.850799-56.849776l0 0c0-31.397081 25.452695-56.849776 56.850799-56.849776l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.849776l0 0C1023.653099 856.07443 998.200404 881.527125 966.8023 881.527125z"
p-id="1693"></path>
<path d="M966.8023 256.17345 57.196677 256.17345c-31.397081 0-56.850799-25.452695-56.850799-56.849776l0 0c0-31.397081 25.452695-56.850799 56.850799-56.850799l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.850799l0 0C1023.653099 230.720755 998.200404 256.17345 966.8023 256.17345z"
p-id="1694"></path>
</svg>
</div>
</template>
<script>
export default {
name: 'hamburger',
props: {
isActive: {
type: Boolean,
default: false
},
toggleClick: {
type: Function,
default: null
}
}
}
</script>
<style scoped>
.hamburger {
display: inline-block;
cursor: pointer;
width: 20px;
height: 20px;
transform: rotate(0deg);
transition: .38s;
transform-origin: 50% 50%;
}
.hamburger.is-active {
transform: rotate(90deg);
}
</style>
<template>
<div class="HeaderImgBox">
<img
v-if="imgUrl"
:src="imgUrl"
/>
<img
v-else
src="@/assets/images/user/defaultHeaderImg.png"
/>
</div>
</template>
<script>
export default {
name: 'header-img-box',
props: {
imgUrl: String,
},
};
</script>
<style lang="scss">
.HeaderImgBox {
width: 40px;
height: 40px;
}
</style>
<template>
<div class="img-icon">
<img :src="`/static/menu/${iconName}.png`" />
</div>
</template>
<script>
export default {
name: 'img-icon',
props: {
iconName: {
type: String,
},
},
};
</script>
<style lang="scss">
.img-icon {
width: 20px;
height: 20px;
overflow: hidden;
}
</style>
<template>
<div class="main-wrap">
<div :class="['main-wrap-inner',{'noFooter': !$slots.footer}]">
<div
v-if="$slots.filterItem || $slots.filterBtn"
class="search-box"
>
<div class="left-box">
<slot name="filterItem"></slot>
</div>
<div class="right-box">
<slot name="filterBtn"></slot>
</div>
</div>
<div class="table-wrap">
<slot></slot>
</div>
</div>
<div
v-if="$slots.footer"
class="footer-wrap"
>
<slot name="footer"></slot>
</div>
</div>
</template>
<script>
export default {
name: 'list-layout',
};
</script>
const defaults = {
title: null,
message: '',
type: '',
iconClass: '',
showInput: false,
showClose: true,
modalFade: true,
lockScroll: true,
closeOnClickModal: true,
closeOnPressEscape: true,
closeOnHashChange: true,
inputValue: null,
inputPlaceholder: '',
inputType: 'text',
inputPattern: null,
inputValidator: null,
inputErrorMessage: '',
showConfirmButton: true,
showCancelButton: false,
confirmButtonPosition: 'right',
confirmButtonHighlight: false,
cancelButtonHighlight: false,
confirmButtonText: '确定',
cancelButtonText: '取消',
confirmButtonClass: '',
cancelButtonClass: '',
customClass: '',
beforeClose: null,
dangerouslyUseHTMLString: false,
center: false,
roundButton: false,
distinguishCancelAndClose: false,
};
import Vue from 'vue';
import msgboxVue from './main.vue';
import merge from 'element-ui/src/utils/merge';
import { isVNode } from 'element-ui/src/utils/vdom';
const MessageBoxConstructor = Vue.extend(msgboxVue);
let currentMsg, instance;
let msgQueue = [];
const defaultCallback = action => {
if (currentMsg) {
let callback = currentMsg.callback;
if (typeof callback === 'function') {
if (instance.showInput) {
callback(instance.inputValue, action);
} else {
callback(action);
}
}
if (currentMsg.resolve) {
if (action === 'confirm') {
if (instance.showInput) {
currentMsg.resolve({ value: instance.inputValue, action });
} else {
currentMsg.resolve(action);
}
} else if (
currentMsg.reject &&
(action === 'cancel' || action === 'close')
) {
currentMsg.reject(action);
}
}
}
};
const initInstance = () => {
instance = new MessageBoxConstructor({
el: document.createElement('div'),
});
instance.callback = defaultCallback;
};
const showNextMsg = () => {
if (!instance) {
initInstance();
}
instance.action = '';
if (!instance.visible || instance.closeTimer) {
if (msgQueue.length > 0) {
currentMsg = msgQueue.shift();
let options = currentMsg.options;
for (let prop in options) {
if (options.hasOwnProperty(prop)) {
instance[prop] = options[prop];
}
}
if (options.callback === undefined) {
instance.callback = defaultCallback;
}
let oldCb = instance.callback;
instance.callback = (action, instance) => {
oldCb(action, instance);
showNextMsg();
};
if (isVNode(instance.message)) {
instance.$slots.default = [instance.message];
instance.message = null;
} else {
delete instance.$slots.default;
}
[
'modal',
'showClose',
'closeOnClickModal',
'closeOnPressEscape',
'closeOnHashChange',
].forEach(prop => {
if (instance[prop] === undefined) {
instance[prop] = true;
}
});
document.body.appendChild(instance.$el);
Vue.nextTick(() => {
instance.visible = true;
});
}
}
};
const MessageBox = function(options, callback) {
if (Vue.prototype.$isServer) return;
if (typeof options === 'string' || isVNode(options)) {
options = {
message: options,
};
if (typeof arguments[1] === 'string') {
options.title = arguments[1];
}
} else if (options.callback && !callback) {
callback = options.callback;
}
if (typeof Promise !== 'undefined') {
return new Promise((resolve, reject) => {
// eslint-disable-line
msgQueue.push({
options: merge({}, defaults, MessageBox.defaults, options),
callback: callback,
resolve: resolve,
reject: reject,
});
showNextMsg();
});
} else {
msgQueue.push({
options: merge({}, defaults, MessageBox.defaults, options),
callback: callback,
});
showNextMsg();
}
};
MessageBox.setDefaults = defaults => {
MessageBox.defaults = defaults;
};
MessageBox.alert = (message, title, options) => {
if (typeof title === 'object') {
options = title;
title = '';
} else if (title === undefined) {
title = '';
}
return MessageBox(
merge(
{
title: title,
message: message,
$type: 'alert',
closeOnPressEscape: false,
closeOnClickModal: false,
},
options
)
);
};
MessageBox.confirm = (message, title, options) => {
if (typeof title === 'object') {
options = title;
title = '';
} else if (title === undefined) {
title = '';
}
return MessageBox(
merge(
{
title: title,
message: message,
$type: 'confirm',
showCancelButton: true,
},
options
)
);
};
MessageBox.prompt = (message, title, options) => {
if (typeof title === 'object') {
options = title;
title = '';
} else if (title === undefined) {
title = '';
}
return MessageBox(
merge(
{
title: title,
message: message,
showCancelButton: true,
showInput: true,
$type: 'prompt',
},
options
)
);
};
MessageBox.success = (message, title, options) => {
if (typeof title === 'object') {
options = title;
title = '';
} else if (title === undefined) {
title = '';
}
return MessageBox(
merge(
{
title: title,
message: message,
$type: 'alert',
closeOnClickModal: false,
iconType: 'success',
},
options
)
);
};
MessageBox.info = (message, title, options) => {
if (typeof title === 'object') {
options = title;
title = '';
} else if (title === undefined) {
title = '';
}
return MessageBox(
merge(
{
title: title,
message: message,
$type: 'alert',
closeOnClickModal: false,
iconType: 'info',
},
options
)
);
};
MessageBox.close = () => {
instance.doClose();
instance.visible = false;
msgQueue = [];
currentMsg = null;
};
export default MessageBox;
export { MessageBox };
<template>
<transition name="msgbox-fade">
<div
class="el-message-box__wrapper md-message-box__wrapper"
tabindex="-1"
v-show="visible"
@click.self="handleWrapperClick"
role="dialog"
aria-modal="true"
:aria-label="title || 'dialog'"
>
<div
class="el-message-box"
:class="[customClass, center && 'el-message-box--center']"
>
<div class="el-message-box__header">
<div class="el-message-box__title-icon">
<img
v-if="iconType && iconType == 'success'"
src="@/assets/images/dialog/success_icon.png"
/>
<img
v-else
src="@/assets/images/dialog/info_icon.png"
/>
</div>
</div>
<div class="el-message-box__content">
<div
class="el-message-box__message"
v-if="message !== ''"
>
<slot>
<p v-if="!dangerouslyUseHTMLString">{{ message }}</p>
<p
v-else
v-html="message"
></p>
</slot>
</div>
<div
class="el-message-box__input"
v-show="showInput"
>
<el-input
v-model="inputValue"
:type="inputType"
@keydown.enter.native="handleInputEnter"
:placeholder="inputPlaceholder"
ref="input"
></el-input>
<div
class="el-message-box__errormsg"
:style="{ visibility: !!editorErrorMessage ? 'visible' : 'hidden' }"
>{{ editorErrorMessage }}</div>
</div>
</div>
<div class="el-message-box__btns">
<el-button
:loading="cancelButtonLoading"
:class="[ cancelButtonClasses ]"
v-if="showCancelButton"
:round="roundButton"
size="small"
@click.native="handleAction('cancel')"
@keydown.enter="handleAction('cancel')"
>
{{ cancelButtonText }}
</el-button>
<el-button
:loading="confirmButtonLoading"
ref="confirm"
:class="[ confirmButtonClasses ]"
v-show="showConfirmButton"
:round="roundButton"
size="small"
@click.native="handleAction('confirm')"
@keydown.enter="handleAction('confirm')"
>
{{ confirmButtonText }}
</el-button>
</div>
</div>
</div>
</transition>
</template>
<script>
import Dialog from 'element-ui/src/utils/aria-dialog';
import Popup from '../Popup/index.js';
import { addClass, removeClass } from 'element-ui/src/utils/dom';
let messageBox;
let typeMap = {
success: 'success',
info: 'info',
warning: 'warning',
error: 'error',
};
export default {
mixins: [Popup],
props: {
modal: {
default: true,
},
lockScroll: {
default: true,
},
showClose: {
type: Boolean,
default: true,
},
closeOnClickModal: {
default: true,
},
closeOnPressEscape: {
default: true,
},
closeOnHashChange: {
default: true,
},
center: {
default: false,
type: Boolean,
},
roundButton: {
default: false,
type: Boolean,
},
},
data() {
return {
uid: 1,
title: undefined,
message: '',
type: '',
iconClass: '',
customClass: '',
showInput: false,
inputValue: null,
inputPlaceholder: '',
inputType: 'text',
inputPattern: null,
inputValidator: null,
inputErrorMessage: '',
showConfirmButton: true,
showCancelButton: false,
action: '',
confirmButtonText: '',
cancelButtonText: '',
confirmButtonLoading: false,
cancelButtonLoading: false,
confirmButtonClass: '',
confirmButtonDisabled: false,
cancelButtonClass: '',
editorErrorMessage: null,
callback: null,
dangerouslyUseHTMLString: false,
focusAfterClosed: null,
isOnComposition: false,
distinguishCancelAndClose: false,
iconType: '',
};
},
computed: {
icon() {
const { type, iconClass } = this;
return (
iconClass || (type && typeMap[type] ? `el-icon-${typeMap[type]}` : '')
);
},
confirmButtonClasses() {
return `el-button--primary ${this.confirmButtonClass}`;
},
cancelButtonClasses() {
return `${this.cancelButtonClass}`;
},
},
watch: {
inputValue: {
immediate: true,
handler(val) {
this.$nextTick(_ => {
if (this.$type === 'prompt' && val !== null) {
this.validate();
}
});
},
},
visible(val) {
if (val) {
this.uid++;
if (
this.$type === 'alert' ||
this.$type === 'confirm' ||
this.$type === 'success' ||
this.$type === 'info'
) {
this.$nextTick(() => {
this.$refs.confirm.$el.focus();
});
}
this.focusAfterClosed = document.activeElement;
messageBox = new Dialog(
this.$el,
this.focusAfterClosed,
this.getFirstFocus()
);
}
// prompt
if (this.$type !== 'prompt') return;
if (val) {
setTimeout(() => {
if (this.$refs.input && this.$refs.input.$el) {
this.getInputElement().focus();
}
}, 500);
} else {
this.editorErrorMessage = '';
removeClass(this.getInputElement(), 'invalid');
}
},
},
mounted() {
this.$nextTick(() => {
if (this.closeOnHashChange) {
window.addEventListener('hashchange', this.close);
}
});
},
beforeDestroy() {
if (this.closeOnHashChange) {
window.removeEventListener('hashchange', this.close);
}
setTimeout(() => {
messageBox.closeDialog();
});
},
methods: {
getSafeClose() {
const currentId = this.uid;
return () => {
this.$nextTick(() => {
if (currentId === this.uid) this.doClose();
});
};
},
doClose() {
if (!this.visible) return;
this.visible = false;
this._closing = true;
this.onClose && this.onClose();
messageBox.closeDialog(); // 解绑
if (this.lockScroll) {
setTimeout(this.restoreBodyStyle, 200);
}
this.opened = false;
this.doAfterClose();
setTimeout(() => {
if (this.action) this.callback(this.action, this);
});
},
handleWrapperClick() {
if (this.closeOnClickModal) {
this.handleAction(this.distinguishCancelAndClose ? 'close' : 'cancel');
}
},
handleInputEnter() {
if (this.inputType !== 'textarea') {
return this.handleAction('confirm');
}
},
handleAction(action) {
if (this.$type === 'prompt' && action === 'confirm' && !this.validate()) {
return;
}
this.action = action;
if (typeof this.beforeClose === 'function') {
this.close = this.getSafeClose();
this.beforeClose(action, this, this.close);
} else {
this.doClose();
}
},
validate() {
if (this.$type === 'prompt') {
const inputPattern = this.inputPattern;
if (inputPattern && !inputPattern.test(this.inputValue || '')) {
this.editorErrorMessage = this.inputErrorMessage;
addClass(this.getInputElement(), 'invalid');
return false;
}
const inputValidator = this.inputValidator;
if (typeof inputValidator === 'function') {
const validateResult = inputValidator(this.inputValue);
if (validateResult === false) {
this.editorErrorMessage = this.inputErrorMessage;
addClass(this.getInputElement(), 'invalid');
return false;
}
if (typeof validateResult === 'string') {
this.editorErrorMessage = validateResult;
addClass(this.getInputElement(), 'invalid');
return false;
}
}
}
this.editorErrorMessage = '';
removeClass(this.getInputElement(), 'invalid');
return true;
},
getFirstFocus() {
const btn = this.$el.querySelector('.el-message-box__btns .el-button');
const title = this.$el.querySelector(
'.el-message-box__btns .el-message-box__title'
);
return btn || title;
},
getInputElement() {
const inputRefs = this.$refs.input.$refs;
return inputRefs.input || inputRefs.textarea;
},
},
};
</script>
<style lang="scss">
.el-message-box__wrapper.md-message-box__wrapper {
.el-message-box {
border: none;
border-radius: 8px;
box-shadow: 2px 2px 10px #666;
padding-bottom: 30px;
}
.el-message-box__header {
position: relative;
height: 110px;
background-color: #4e82fb;
.el-message-box__title-icon {
position: absolute;
width: 110px;
height: 110px;
top: 50px;
left: 50%;
transform: translate(-50%, 0);
}
}
.el-message-box__content {
box-sizing: content-box;
padding: 50px 20px 0;
height: 130px;
text-align: center;
font-size: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.el-message-box__btns {
text-align: center;
padding: 0;
button {
height: 40px;
line-height: 40px;
font-size: 18px;
padding: 0 18px;
}
}
}
</style>
<template>
<div :class="['el-pagination', {
'is-background': this.background,
'el-pagination--small': this.small
}]">
<total></total>
<sizes :pageSizes="this.pageSizes"></sizes>
<jumper></jumper>
<prev></prev>
<pager
:currentPage="this.internalCurrentPage"
:pageCount="this.internalPageCount"
:pagerCount="this.pagerCount"
@change="this.handleCurrentChange"
:disabled="this.disabled"
></pager>
<next></next>
</div>
</template>
<script>
import { Pagination } from 'element-ui';
import Pager from './pager.vue';
import Jumper from './jumper.js';
export default {
extends: Pagination,
components: {
MySlot: {
render(h) {
return this.$parent.$slots.default
? this.$parent.$slots.default[0]
: '';
},
},
Total: {
render(h) {
return typeof this.$parent.total === 'number' ? (
<span class="el-pagination__total">
总记录 {this.$parent.total}
<i class="pagination__pageNum">
{this.$parent.internalCurrentPage}
</i>
/{this.$parent.internalPageCount}
</span>
) : (
''
);
},
},
Jumper,
Pager,
},
};
</script>
<style lang="scss">
.md-pagination {
color: #333;
display: flex;
align-items: center;
.btn-next,
.btn-prev {
color: #477cfa;
}
span.el-pagination__total {
margin-right: 0;
.pagination__pageNum {
margin-left: 17px;
font-style: normal;
color: #477cfa;
}
}
span.el-pagination__sizes {
margin-right: 0;
margin-left: 12px;
}
span.el-pagination__jump {
margin-left: 12px;
margin-right: 17px;
span,
button {
height: 23px;
line-height: 23px;
}
button {
margin-left: 9px;
background-color: #1459fc;
color: #fff;
width: 38px;
padding: 0;
vertical-align: baseline;
}
}
}
</style>
import Pagination from './Pagination.vue';
export default {
name: 'md-pagination',
props: {
pagination: {
type: Object,
default: {
pageNum: 1,
pageSize: 10,
total: 0,
},
},
changePage: Function,
changeSizeHandle: Function,
},
render(h) {
return (
<Pagination
class="md-pagination"
currentPage={this.pagination.pageNum}
pageSize={this.pagination.pageSize}
total={this.pagination.total}
onCurrent-change={this.changePage}
onSize-change={this.changeSizeHandle}
/>
);
},
};
const Jumper = {
data() {
return {
oldValue: null,
};
},
watch: {
'$parent.internalPageSize'() {
this.$nextTick(() => {
this.$refs.input.$el.querySelector(
'input'
).value = this.$parent.internalCurrentPage;
});
},
},
methods: {
handleFocus(event) {
this.oldValue = event.target.value;
},
handleBlur({ target }) {
this.resetValueIfNeed(target.value);
this.reassignMaxValue(target.value);
},
handleKeyup({ keyCode, target }) {
if (keyCode === 13 && this.oldValue && target.value !== this.oldValue) {
this.handleChange(target.value);
}
},
handleChange(value) {
this.$parent.internalCurrentPage = this.$parent.getValidCurrentPage(
value
);
this.$parent.emitChange();
this.oldValue = null;
this.resetValueIfNeed(value);
},
resetValueIfNeed(value) {
const num = parseInt(value, 10);
if (!isNaN(num)) {
if (num < 1) {
this.$refs.input.setCurrentValue(1);
} else {
this.reassignMaxValue(value);
}
}
},
reassignMaxValue(value) {
const { internalPageCount } = this.$parent;
if (+value > internalPageCount) {
this.$refs.input.setCurrentValue(internalPageCount || 1);
}
},
},
render(h) {
return (
<span class="el-pagination__jump">
跳转至
<el-input
class="el-pagination__editor is-in-pagination"
min={1}
max={this.$parent.internalPageCount}
value={this.$parent.internalCurrentPage}
domPropsValue={this.$parent.internalCurrentPage}
type="number"
ref="input"
disabled={this.$parent.disabled}
nativeOnKeyup={this.handleKeyup}
onChange={this.handleChange}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
/>
<el-button>GO</el-button>
</span>
);
},
};
export default Jumper;
<template>
<ul
@click="onPagerClick"
class="el-pager md-pager"
>
<li
:class="{ active: currentPage === 1, disabled }"
v-if="pageCount > 0"
class="number"
>1</li>
<li
class="el-icon more btn-quickprev"
:class="[quickprevIconClass, { disabled }]"
v-if="showPrevMore"
@mouseenter="onMouseenter('left')"
@mouseleave="quickprevIconClass = 'el-icon-more'"
>
</li>
<li
v-for="pager in pagers"
:key="pager"
:class="{ active: currentPage === pager, disabled }"
class="number"
>{{ pager }}</li>
<li
class="el-icon more btn-quicknext"
:class="[quicknextIconClass, { disabled }]"
v-if="showNextMore"
@mouseenter="onMouseenter('right')"
@mouseleave="quicknextIconClass = 'el-icon-more'"
>
</li>
<li
:class="{ active: currentPage === pageCount, disabled }"
class="number"
v-if="pageCount > 1"
>{{ pageCount }}</li>
</ul>
</template>
<script type="text/babel">
/*eslint-disable */
export default {
name: 'ElPager',
props: {
currentPage: Number,
pageCount: Number,
pagerCount: Number,
disabled: Boolean,
},
watch: {
showPrevMore(val) {
if (!val) this.quickprevIconClass = 'el-icon-more';
},
showNextMore(val) {
if (!val) this.quicknextIconClass = 'el-icon-more';
},
},
methods: {
onPagerClick(event) {
const target = event.target;
if (target.tagName === 'UL' || this.disabled) {
return;
}
let newPage = Number(event.target.textContent);
const pageCount = this.pageCount;
const currentPage = this.currentPage;
const pagerCountOffset = this.pagerCount - 2;
if (target.className.indexOf('more') !== -1) {
if (target.className.indexOf('quickprev') !== -1) {
newPage = currentPage - pagerCountOffset;
} else if (target.className.indexOf('quicknext') !== -1) {
newPage = currentPage + pagerCountOffset;
}
}
/* istanbul ignore if */
if (!isNaN(newPage)) {
if (newPage < 1) {
newPage = 1;
}
if (newPage > pageCount) {
newPage = pageCount;
}
}
if (newPage !== currentPage) {
this.$emit('change', newPage);
}
},
onMouseenter(direction) {
if (this.disabled) return;
if (direction === 'left') {
this.quickprevIconClass = 'el-icon-d-arrow-left';
} else {
this.quicknextIconClass = 'el-icon-d-arrow-right';
}
},
},
computed: {
pagers() {
const pagerCount = this.pagerCount;
const halfPagerCount = (pagerCount - 1) / 2;
const currentPage = Number(this.currentPage);
const pageCount = Number(this.pageCount);
let showPrevMore = false;
let showNextMore = false;
if (pageCount > pagerCount) {
if (currentPage > pagerCount - halfPagerCount) {
showPrevMore = true;
}
if (currentPage < pageCount - halfPagerCount) {
showNextMore = true;
}
}
const array = [];
if (showPrevMore && !showNextMore) {
const startPage = pageCount - (pagerCount - 2);
for (let i = startPage; i < pageCount; i++) {
array.push(i);
}
} else if (!showPrevMore && showNextMore) {
for (let i = 2; i < pagerCount; i++) {
array.push(i);
}
} else if (showPrevMore && showNextMore) {
const offset = Math.floor(pagerCount / 2) - 1;
for (let i = currentPage - offset; i <= currentPage + offset; i++) {
array.push(i);
}
} else {
for (let i = 2; i < pageCount; i++) {
array.push(i);
}
}
this.showPrevMore = showPrevMore;
this.showNextMore = showNextMore;
return array;
},
},
data() {
return {
current: null,
showPrevMore: false,
showNextMore: false,
quicknextIconClass: 'el-icon-more',
quickprevIconClass: 'el-icon-more',
};
},
};
/*eslint-enable */
</script>
<style lang="scss">
.el-pager.md-pager {
color: #477cfa;
display: flex;
align-items: center;
height: 100%;
li {
height: 100%;
&.active {
background-color: #1459fc;
color: #fff;
}
&.btn-quickprev,
&.btn-quicknext {
color: #477cfa;
}
}
}
</style>
import Vue from 'vue';
import merge from 'element-ui/src/utils/merge';
import PopupManager from './popup-manager';
import getScrollBarWidth from 'element-ui/src/utils/scrollbar-width';
import { getStyle, addClass, removeClass, hasClass } from 'element-ui/src/utils/dom';
let idSeed = 1;
let scrollBarWidth;
const getDOM = function(dom) {
if (dom.nodeType === 3) {
dom = dom.nextElementSibling || dom.nextSibling;
getDOM(dom);
}
return dom;
};
export default {
props: {
visible: {
type: Boolean,
default: false
},
openDelay: {},
closeDelay: {},
zIndex: {},
modal: {
type: Boolean,
default: false
},
modalFade: {
type: Boolean,
default: true
},
modalClass: {},
modalAppendToBody: {
type: Boolean,
default: false
},
lockScroll: {
type: Boolean,
default: true
},
closeOnPressEscape: {
type: Boolean,
default: false
},
closeOnClickModal: {
type: Boolean,
default: false
}
},
beforeMount() {
this._popupId = 'popup-' + idSeed++;
PopupManager.register(this._popupId, this);
},
beforeDestroy() {
PopupManager.deregister(this._popupId);
PopupManager.closeModal(this._popupId);
this.restoreBodyStyle();
},
data() {
return {
opened: false,
bodyPaddingRight: null,
computedBodyPaddingRight: 0,
withoutHiddenClass: true,
rendered: false
};
},
watch: {
visible(val) {
if (val) {
if (this._opening) return;
if (!this.rendered) {
this.rendered = true;
Vue.nextTick(() => {
this.open();
});
} else {
this.open();
}
} else {
this.close();
}
}
},
methods: {
open(options) {
if (!this.rendered) {
this.rendered = true;
}
const props = merge({}, this.$props || this, options);
if (this._closeTimer) {
clearTimeout(this._closeTimer);
this._closeTimer = null;
}
clearTimeout(this._openTimer);
const openDelay = Number(props.openDelay);
if (openDelay > 0) {
this._openTimer = setTimeout(() => {
this._openTimer = null;
this.doOpen(props);
}, openDelay);
} else {
this.doOpen(props);
}
},
doOpen(props) {
if (this.$isServer) return;
if (this.willOpen && !this.willOpen()) return;
if (this.opened) return;
this._opening = true;
const dom = getDOM(this.$el);
const modal = props.modal;
const zIndex = props.zIndex;
if (zIndex) {
PopupManager.zIndex = zIndex;
}
if (modal) {
if (this._closing) {
PopupManager.closeModal(this._popupId);
this._closing = false;
}
PopupManager.openModal(this._popupId, PopupManager.nextZIndex(), this.modalAppendToBody ? undefined : dom, props.modalClass, props.modalFade);
if (props.lockScroll) {
this.withoutHiddenClass = !hasClass(document.body, 'el-popup-parent--hidden');
if (this.withoutHiddenClass) {
this.bodyPaddingRight = document.body.style.paddingRight;
this.computedBodyPaddingRight = parseInt(getStyle(document.body, 'paddingRight'), 10);
}
scrollBarWidth = getScrollBarWidth();
let bodyHasOverflow = document.documentElement.clientHeight < document.body.scrollHeight;
let bodyOverflowY = getStyle(document.body, 'overflowY');
if (scrollBarWidth > 0 && (bodyHasOverflow || bodyOverflowY === 'scroll') && this.withoutHiddenClass) {
document.body.style.paddingRight = this.computedBodyPaddingRight + scrollBarWidth + 'px';
}
addClass(document.body, 'el-popup-parent--hidden');
}
}
if (getComputedStyle(dom).position === 'static') {
dom.style.position = 'absolute';
}
dom.style.zIndex = PopupManager.nextZIndex();
this.opened = true;
this.onOpen && this.onOpen();
this.doAfterOpen();
},
doAfterOpen() {
this._opening = false;
},
close() {
if (this.willClose && !this.willClose()) return;
if (this._openTimer !== null) {
clearTimeout(this._openTimer);
this._openTimer = null;
}
clearTimeout(this._closeTimer);
const closeDelay = Number(this.closeDelay);
if (closeDelay > 0) {
this._closeTimer = setTimeout(() => {
this._closeTimer = null;
this.doClose();
}, closeDelay);
} else {
this.doClose();
}
},
doClose() {
this._closing = true;
this.onClose && this.onClose();
if (this.lockScroll) {
setTimeout(this.restoreBodyStyle, 200);
}
this.opened = false;
this.doAfterClose();
},
doAfterClose() {
PopupManager.closeModal(this._popupId);
this._closing = false;
},
restoreBodyStyle() {
if (this.modal && this.withoutHiddenClass) {
document.body.style.paddingRight = this.bodyPaddingRight;
removeClass(document.body, 'el-popup-parent--hidden');
}
this.withoutHiddenClass = true;
}
}
};
export {
PopupManager
};
import Vue from 'vue';
import { addClass, removeClass } from 'element-ui/src/utils/dom';
let hasModal = false;
let hasInitZIndex = false;
let zIndex = 5000;
const getModal = function() {
if (Vue.prototype.$isServer) return;
let modalDom = PopupManager.modalDom;
if (modalDom) {
hasModal = true;
} else {
hasModal = false;
modalDom = document.createElement('div');
PopupManager.modalDom = modalDom;
modalDom.addEventListener('touchmove', function(event) {
event.preventDefault();
event.stopPropagation();
});
modalDom.addEventListener('click', function() {
PopupManager.doOnModalClick && PopupManager.doOnModalClick();
});
}
return modalDom;
};
const instances = {};
const PopupManager = {
modalFade: true,
getInstance: function(id) {
return instances[id];
},
register: function(id, instance) {
if (id && instance) {
instances[id] = instance;
}
},
deregister: function(id) {
if (id) {
instances[id] = null;
delete instances[id];
}
},
nextZIndex: function() {
return PopupManager.zIndex++;
},
modalStack: [],
doOnModalClick: function() {
const topItem = PopupManager.modalStack[PopupManager.modalStack.length - 1];
if (!topItem) return;
const instance = PopupManager.getInstance(topItem.id);
if (instance && instance.closeOnClickModal) {
instance.close();
}
},
openModal: function(id, zIndex, dom, modalClass, modalFade) {
if (Vue.prototype.$isServer) return;
if (!id || zIndex === undefined) return;
this.modalFade = modalFade;
const modalStack = this.modalStack;
for (let i = 0, j = modalStack.length; i < j; i++) {
const item = modalStack[i];
if (item.id === id) {
return;
}
}
const modalDom = getModal();
addClass(modalDom, 'v-modal');
if (this.modalFade && !hasModal) {
addClass(modalDom, 'v-modal-enter');
}
if (modalClass) {
let classArr = modalClass.trim().split(/\s+/);
classArr.forEach(item => addClass(modalDom, item));
}
setTimeout(() => {
removeClass(modalDom, 'v-modal-enter');
}, 200);
if (dom && dom.parentNode && dom.parentNode.nodeType !== 11) {
dom.parentNode.appendChild(modalDom);
} else {
document.body.appendChild(modalDom);
}
if (zIndex) {
modalDom.style.zIndex = zIndex;
}
modalDom.tabIndex = 0;
modalDom.style.display = '';
this.modalStack.push({ id: id, zIndex: zIndex, modalClass: modalClass });
},
closeModal: function(id) {
const modalStack = this.modalStack;
const modalDom = getModal();
if (modalStack.length > 0) {
const topItem = modalStack[modalStack.length - 1];
if (topItem.id === id) {
if (topItem.modalClass) {
let classArr = topItem.modalClass.trim().split(/\s+/);
classArr.forEach(item => removeClass(modalDom, item));
}
modalStack.pop();
if (modalStack.length > 0) {
modalDom.style.zIndex = modalStack[modalStack.length - 1].zIndex;
}
} else {
for (let i = modalStack.length - 1; i >= 0; i--) {
if (modalStack[i].id === id) {
modalStack.splice(i, 1);
break;
}
}
}
}
if (modalStack.length === 0) {
if (this.modalFade) {
addClass(modalDom, 'v-modal-leave');
}
setTimeout(() => {
if (modalStack.length === 0) {
if (modalDom.parentNode) modalDom.parentNode.removeChild(modalDom);
modalDom.style.display = 'none';
PopupManager.modalDom = undefined;
}
removeClass(modalDom, 'v-modal-leave');
}, 200);
}
},
};
Object.defineProperty(PopupManager, 'zIndex', {
configurable: true,
get() {
if (!hasInitZIndex) {
zIndex = zIndex;
hasInitZIndex = true;
}
return zIndex;
},
set(value) {
zIndex = value;
},
});
const getTopPopup = function() {
if (Vue.prototype.$isServer) return;
if (PopupManager.modalStack.length > 0) {
const topPopup =
PopupManager.modalStack[PopupManager.modalStack.length - 1];
if (!topPopup) return;
const instance = PopupManager.getInstance(topPopup.id);
return instance;
}
};
if (!Vue.prototype.$isServer) {
// handle `esc` key when the popup is shown
window.addEventListener('keydown', function(event) {
if (event.keyCode === 27) {
const topPopup = getTopPopup();
if (topPopup && topPopup.closeOnPressEscape) {
topPopup.handleClose
? topPopup.handleClose()
: topPopup.handleAction
? topPopup.handleAction('cancel')
: topPopup.close();
}
}
});
}
export default PopupManager;
<template>
<div class="scroll-container" ref="scrollContainer" @wheel.prevent="handleScroll" >
<div class="scroll-wrapper" ref="scrollWrapper" :style="{top: top + 'px'}">
<slot></slot>
</div>
</div>
</template>
<script>
const delta = 15;
export default {
name: 'scrollBar',
data() {
return {
top: 0,
};
},
methods: {
handleScroll(e) {
const eventDelta = e.wheelDelta || -e.deltaY * 3;
const $container = this.$refs.scrollContainer;
const $containerHeight = $container.offsetHeight;
const $wrapper = this.$refs.scrollWrapper;
const $wrapperHeight = $wrapper.offsetHeight;
if (eventDelta > 0) {
this.top = Math.min(0, this.top + eventDelta);
} else {
if ($containerHeight - delta < $wrapperHeight) {
if (this.top < -($wrapperHeight - $containerHeight + delta)) {
this.top = this.top;
} else {
this.top = Math.max(
this.top + eventDelta,
$containerHeight - $wrapperHeight - delta
);
}
} else {
this.top = 0;
}
}
},
},
};
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.scroll-container {
position: relative;
width: 100%;
height: 100%;
.scroll-wrapper {
position: absolute;
width: 100% !important;
}
}
</style>
<template>
<div class="scroll-container" ref="scrollContainer" @wheel.prevent="handleScroll">
<div class="scroll-wrapper" ref="scrollWrapper" :style="{left: left + 'px'}">
<slot></slot>
</div>
</div>
</template>
<script>
const padding = 15 // tag's padding
export default {
name: 'scrollPane',
data() {
return {
left: 0
}
},
methods: {
handleScroll(e) {
const eventDelta = e.wheelDelta || -e.deltaY * 3
const $container = this.$refs.scrollContainer
const $containerWidth = $container.offsetWidth
const $wrapper = this.$refs.scrollWrapper
const $wrapperWidth = $wrapper.offsetWidth
if (eventDelta > 0) {
this.left = Math.min(0, this.left + eventDelta)
} else {
if ($containerWidth - padding < $wrapperWidth) {
if (this.left < -($wrapperWidth - $containerWidth + padding)) {
this.left = this.left
} else {
this.left = Math.max(this.left + eventDelta, $containerWidth - $wrapperWidth - padding)
}
} else {
this.left = 0
}
}
},
moveToTarget($target) {
const $container = this.$refs.scrollContainer
const $containerWidth = $container.offsetWidth
const $targetLeft = $target.offsetLeft
const $targetWidth = $target.offsetWidth
if ($targetLeft < -this.left) {
// tag in the left
this.left = -$targetLeft + padding
} else if ($targetLeft + padding > -this.left && $targetLeft + $targetWidth < -this.left + $containerWidth - padding) {
// tag in the current view
// eslint-disable-line
} else {
// tag in the right
this.left = -($targetLeft - ($containerWidth - $targetWidth) + padding)
}
}
}
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.scroll-container {
white-space: nowrap;
position: relative;
overflow: hidden;
width: 100%;
.scroll-wrapper {
position: absolute;
}
}
</style>
<template>
<div :class="['filter-item', {'oneline': size=='oneline'}]">
<div class="filter-item-label">{{label}}</div>
<div :class="`filter-item-input filter-item-input---${size}`">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'search-item',
props: {
label: {
type: String,
default: '查询条件',
},
size: {
type: String,
default: 'normal',
},
},
};
</script>
<style lang="scss">
@import '../../assets/styles/variables.scss';
.filter-item {
display: flex;
line-height: 22px;
margin-right: 10px;
margin-bottom: 12px;
font-size: 12px;
&.oneline {
width: 100%;
}
.filter-item-label {
margin-right: 6px;
width: 60px;
text-align: right;
}
.filter-item-input---big {
width: 262px;
}
.filter-item-input---normal {
width: 114px;
}
.filter-item-input---small {
width: 150px;
}
.date-range-pick {
.el-input__prefix {
transform: translate(-16px);
}
}
}
@media screen and (min-width: $bigScreenWidth) {
.filter-item {
line-height: 40px;
margin-right: 20px;
margin-bottom: 20px;
font-size: 16px;
.filter-item-label {
margin-right: 10px;
width: 72px;
}
.filter-item-input---datePicker {
width: 502px;
}
.filter-item-input---big {
width: 324px;
}
.filter-item-input---normal {
width: 200px;
}
.filter-item-input---small {
width: 150px;
}
.date-range-pick {
.el-input__prefix {
transform: translate(-26px);
}
}
}
}
</style>
<template>
<div class="slide" ref="slide">
<div class="slide-group" ref="slideGroup">
<slot>
</slot>
</div>
<div v-if="showDot" class="dots">
<span class="dot" :class="{active: currentPageIndex === index }" v-for="(item, index) in dots" :key="index"></span>
</div>
</div>
</template>
<script type="text/ecmascript-6">
import BScroll from 'better-scroll'
const COMPONENT_NAME = 'slide'
export default {
name: COMPONENT_NAME,
props: {
loop: {
type: Boolean,
default: false,
},
autoPlay: {
type: Boolean,
default: false,
},
interval: {
type: Number,
default: 4000
},
showDot: {
type: Boolean,
default: false,
},
click: {
type: Boolean,
default: true
},
threshold: {
type: Number,
default: 0.3
},
speed: {
type: Number,
default: 400
},
change: {
type: Function,
},
index: {
type: Number,
default: 0,
},
},
data() {
return {
dots: [],
currentPageIndex: 0
}
},
mounted() {
this.update()
window.addEventListener('resize', () => {
if (!this.slide || !this.slide.enabled) {
return
}
clearTimeout(this.resizeTimer)
this.resizeTimer = setTimeout(() => {
if (this.slide.isInTransition) {
this._onScrollEnd()
} else {
if (this.autoPlay) {
this._play()
}
}
this.refresh()
}, 60)
});
},
activated() {
if (!this.slide) {
return
}
this.slide.enable()
let pageIndex = this.slide.getCurrentPage().pageX
this.slide.goToPage(pageIndex, 0, 0)
this.currentPageIndex = pageIndex
if (this.autoPlay) {
this._play()
}
},
deactivated() {
this.slide.disable()
clearTimeout(this.timer)
},
beforeDestroy() {
this.slide.disable()
clearTimeout(this.timer)
},
methods: {
update() {
if (this.slide) {
this.slide.destroy()
}
this.$nextTick(() => {
this.init()
})
},
refresh() {
this._setSlideWidth(true)
this.slide.refresh()
},
prev() {
this.slide.prev()
},
next() {
this.slide.next()
},
init() {
clearTimeout(this.timer)
this.currentPageIndex = 0
this._setSlideWidth()
if (this.showDot) {
this._initDots()
}
this._initSlide()
if (this.autoPlay) {
this._play()
}
},
_setSlideWidth(isResize) {
this.children = this.$refs.slideGroup.children
let width = 0
let slideWidth = this.$refs.slide.clientWidth
for (let i = 0; i < this.children.length; i++) {
let child = this.children[i]
addClass(child, 'slide-item')
child.style.width = slideWidth + 'px'
width += slideWidth
}
if (this.loop && !isResize) {
width += 2 * slideWidth
}
this.$refs.slideGroup.style.width = width + 'px'
},
_initSlide() {
// console.log(this.threshold)
this.slide = new BScroll(this.$refs.slide, {
scrollX: true,
scrollY: false,
momentum: false,
snap: {
loop: this.loop,
threshold: this.threshold,
speed: this.speed
},
bounce: false,
click: this.click
})
this.slide.on('scrollEnd', this._onScrollEnd)
this.slide.on('touchEnd', () => {
if (this.autoPlay) {
this._play()
}
})
this.slide.on('beforeScrollStart', () => {
if (this.autoPlay) {
clearTimeout(this.timer)
}
})
},
_onScrollEnd() {
let pageIndex = this.slide.getCurrentPage().pageX
this.currentPageIndex = pageIndex;
if(this.change) {
this.change(pageIndex);
}
if (this.autoPlay) {
this._play()
}
},
_initDots() {
this.dots = new Array(this.children.length)
},
_play() {
clearTimeout(this.timer)
this.timer = setTimeout(() => {
this.slide.next()
}, this.interval)
}
},
watch: {
loop() {
this.update()
},
autoPlay() {
this.update()
},
speed() {
this.update()
},
threshold() {
this.update()
},
index(nVal) {
this.slide.goToPage(nVal);
this.currentPageIndex = nVal;
}
}
}
function hasClass(el, className) {
let reg = new RegExp('(^|\\s)' + className + '(\\s|$)')
return reg.test(el.className)
}
function addClass(el, className) {
if (hasClass(el, className)) {
return
}
let newClass = el.className.split(' ')
newClass.push(className)
el.className = newClass.join(' ')
}
</script>
<style lang="scss">
.slide {
position: relative;
min-height: 1px;
height: 100%;
.slide-group {
position: relative;
overflow: hidden;
white-space: nowrap;
height: 100%;
.slide-item {
height: 100%;
float: left;
box-sizing: border-box;
overflow: hidden;
a {
display: block;
width: 100%;
overflow: hidden;
text-decoration: none;
}
img {
display: block;
width: 100%;
}
}
}
.dots {
position: absolute;
right: 0;
left: 0;
bottom: 34px;
transform: translateZ(1px);
text-align: center;
font-size: 0;
.dot {
display: inline-block;
margin: 0 7px;
width: 12px;
height: 12px;
border-radius: 50%;
border: .5px solid #ee7e1e;
&.active {
border: none;
background: #fff;
}
}
}
}
</style>
\ No newline at end of file
<template>
<svg :class="svgClass" aria-hidden="true">
<use :xlink:href="iconName"></use>
</svg>
</template>
<script>
export default {
name: 'svg-icon',
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String
}
},
computed: {
iconName() {
return `#icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
}
}
}
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
<template>
<div
class="el-table md-table"
:class="[{
'el-table--fit': fit,
'el-table--striped': stripe,
'el-table--border': border || isGroup,
'el-table--hidden': isHidden,
'el-table--group': isGroup,
'el-table--fluid-height': maxHeight,
'el-table--scrollable-x': layout.scrollX,
'el-table--scrollable-y': layout.scrollY,
'el-table--enable-row-hover': !store.states.isComplex,
'el-table--enable-row-transition': (store.states.data || []).length !== 0 && (store.states.data || []).length < 100
}, tableSize ? `el-table--${ tableSize }` : '']"
@mouseleave="handleMouseLeave($event)"
>
<div
class="hidden-columns"
ref="hiddenColumns"
>
<slot></slot>
</div>
<div
v-if="showHeader"
v-mousewheel="handleHeaderFooterMousewheel"
class="el-table__header-wrapper"
ref="headerWrapper"
>
<table-header
ref="tableHeader"
:store="store"
:border="border"
:default-sort="defaultSort"
:style="{
width: layout.bodyWidth ? layout.bodyWidth + 'px' : ''
}"
>
</table-header>
</div>
<div
class="el-table__body-wrapper"
ref="bodyWrapper"
:class="[layout.scrollX ? `is-scrolling-${scrollPosition}` : 'is-scrolling-none']"
:style="[bodyHeight]"
>
<table-body
:context="context"
:store="store"
:stripe="stripe"
:row-class-name="rowClassName"
:row-style="rowStyle"
:highlight="highlightCurrentRow"
:style="{
width: bodyWidth
}"
>
</table-body>
<div
v-if="!data || data.length === 0"
class="el-table__empty-block"
ref="emptyBlock"
:style="{
width: bodyWidth
}"
>
<span class="el-table__empty-text">
<slot name="empty">{{ emptyText || t('el.table.emptyText') }}</slot>
</span>
</div>
<div
v-if="$slots.append"
class="el-table__append-wrapper"
ref="appendWrapper"
>
<slot name="append"></slot>
</div>
</div>
<div
v-if="showSummary"
v-show="data && data.length > 0"
v-mousewheel="handleHeaderFooterMousewheel"
class="el-table__footer-wrapper"
ref="footerWrapper"
>
<table-footer
:store="store"
:border="border"
:sum-text="sumText || t('el.table.sumText')"
:summary-method="summaryMethod"
:default-sort="defaultSort"
:style="{
width: layout.bodyWidth ? layout.bodyWidth + 'px' : ''
}"
>
</table-footer>
</div>
<div
v-if="fixedColumns.length > 0"
v-mousewheel="handleFixedMousewheel"
class="el-table__fixed"
ref="fixedWrapper"
:style="[{
width: layout.fixedWidth ? layout.fixedWidth + 'px' : ''
},
fixedHeight]"
>
<div
v-if="showHeader"
class="el-table__fixed-header-wrapper"
ref="fixedHeaderWrapper"
>
<table-header
ref="fixedTableHeader"
fixed="left"
:border="border"
:store="store"
:style="{
width: bodyWidth
}"
></table-header>
</div>
<div
class="el-table__fixed-body-wrapper"
ref="fixedBodyWrapper"
:style="[{
top: (layout.headerHeight + 4) + 'px'
},
fixedBodyHeight]"
>
<table-body
fixed="left"
:store="store"
:stripe="stripe"
:highlight="highlightCurrentRow"
:row-class-name="rowClassName"
:row-style="rowStyle"
:style="{
width: bodyWidth
}"
>
</table-body>
<div
v-if="$slots.append"
class="el-table__append-gutter"
:style="{
height: layout.appendHeight + 'px'
}"
></div>
</div>
<div
v-if="showSummary"
v-show="data && data.length > 0"
class="el-table__fixed-footer-wrapper"
ref="fixedFooterWrapper"
>
<table-footer
fixed="left"
:border="border"
:sum-text="sumText || t('el.table.sumText')"
:summary-method="summaryMethod"
:store="store"
:style="{
width: bodyWidth
}"
></table-footer>
</div>
</div>
<div
v-if="rightFixedColumns.length > 0"
v-mousewheel="handleFixedMousewheel"
class="el-table__fixed-right"
ref="rightFixedWrapper"
:style="[{
width: layout.rightFixedWidth ? layout.rightFixedWidth + 'px' : '',
right: layout.scrollY ? (border ? layout.gutterWidth : (layout.gutterWidth || 0)) + 'px' : ''
},
fixedHeight]"
>
<div
v-if="showHeader"
class="el-table__fixed-header-wrapper"
ref="rightFixedHeaderWrapper"
>
<table-header
ref="rightFixedTableHeader"
fixed="right"
:border="border"
:store="store"
:style="{
width: bodyWidth
}"
></table-header>
</div>
<div
class="el-table__fixed-body-wrapper"
ref="rightFixedBodyWrapper"
:style="[{
top: (layout.headerHeight + 4) + 'px'
},
fixedBodyHeight]"
>
<table-body
fixed="right"
:store="store"
:stripe="stripe"
:row-class-name="rowClassName"
:row-style="rowStyle"
:highlight="highlightCurrentRow"
:style="{
width: bodyWidth
}"
>
</table-body>
</div>
<div
v-if="showSummary"
v-show="data && data.length > 0"
class="el-table__fixed-footer-wrapper"
ref="rightFixedFooterWrapper"
>
<table-footer
fixed="right"
:border="border"
:sum-text="sumText || t('el.table.sumText')"
:summary-method="summaryMethod"
:store="store"
:style="{
width: bodyWidth
}"
></table-footer>
</div>
</div>
<div
v-if="rightFixedColumns.length > 0"
class="el-table__fixed-right-patch"
ref="rightFixedPatch"
:style="{
width: layout.scrollY ? layout.gutterWidth + 'px' : '0',
height: layout.headerHeight + 'px'
}"
></div>
<div
class="el-table__column-resize-proxy"
ref="resizeProxy"
v-show="resizeProxyVisible"
></div>
</div>
</template>
<script>
import { Table } from 'element-ui';
export default {
name: 'md-table-inner',
extends: Table,
};
</script>
<style lang="scss">
@import '../../assets/styles/variables.scss';
.md-table {
font-size: 12px;
background-color: #eee;
.el-table__fixed-header-wrapper,
.el-table__header-wrapper {
border-radius: 8px;
overflow: hidden;
margin-bottom: 4px;
z-index: 2;
}
th.gutter {
border-bottom: 1px solid #ebeef5;
}
thead {
tr th {
height: 30px;
background-color: #6895fe;
color: #fff;
font-size: 12px;
font-weight: normal;
padding: 0;
.cell {
text-align: center;
}
}
}
.el-table__row {
// margin-bottom: 4px;
// padding-bottom: 4px;
border-radius: 8px;
overflow: hidden;
}
th,
td {
padding: 0;
height: 36px;
}
}
@media screen and (min-width: $bigScreenWidth) {
.md-table {
font-size: 14px;
thead {
tr th {
height: 50px;
font-size: 16px;
}
}
th,
td {
height: 50px;
}
}
}
</style>
<script>
import { TableColumn } from 'element-ui';
export default {
name: 'md-table-column',
extends: TableColumn,
props: {
showOverflowTooltip: {
type: Boolean,
default: true,
},
},
};
</script>
import Table from './Table.vue';
export default {
name: 'md-table',
props: {
data: {
type: Array,
default: function() {
return [];
},
},
size: String,
width: [String, Number],
height: [String, Number],
maxHeight: [String, Number],
fit: {
type: Boolean,
default: true,
},
stripe: Boolean,
border: Boolean,
rowKey: [String, Function],
context: {},
showHeader: {
type: Boolean,
default: true,
},
showSummary: Boolean,
sumText: String,
summaryMethod: Function,
rowClassName: [String, Function],
rowStyle: [Object, Function],
cellClassName: [String, Function],
cellStyle: [Object, Function],
headerRowClassName: [String, Function],
headerRowStyle: [Object, Function],
headerCellClassName: [String, Function],
headerCellStyle: [Object, Function],
highlightCurrentRow: Boolean,
currentRowKey: [String, Number],
emptyText: String,
expandRowKeys: Array,
defaultExpandAll: Boolean,
defaultSort: Object,
tooltipEffect: String,
spanMethod: Function,
selectOnIndeterminate: {
type: Boolean,
default: true,
},
},
data() {
return {
maxWrapHeight: 0,
};
},
mounted() {
this.updateHeight();
addEventListener('resize', this.updateHeight);
},
destroyed() {
removeEventListener('resize', this.updateHeight);
},
methods: {
updateHeight() {
let tableWrapDom = document.querySelector('.table-wrap');
this.maxWrapHeight = tableWrapDom.offsetHeight;
},
},
render(h) {
const { maxHeight, maxWrapHeight } = this;
return maxHeight ? (
<Table
maxHeight={this.maxHeight}
data={this.data}
size={this.size}
width={this.width}
height={this.height}
fit={this.fit}
stripe={this.stripe}
border={this.border}
rowKey={this.rowKey}
context={this.context}
showHeader={this.showHeader}
showSummary={this.showSummary}
sumText={this.sumText}
summaryMethod={this.summaryMethod}
rowClassName={this.rowClassName}
rowStyle={this.rowStyle}
cellClassName={this.cellClassName}
cellStyle={this.cellStyle}
headerRowClassName={this.headerRowClassName}
headerRowStyle={this.headerRowStyle}
headerCellClassName={this.headerCellClassName}
headerCellStyle={this.headerCellStyle}
highlightCurrentRow={this.highlightCurrentRow}
currentRowKey={this.currentRowKey}
emptyText={this.emptyText}
expandRowKeys={this.expandRowKeys}
defaultExpandAll={this.defaultExpandAll}
defaultSort={this.defaultSort}
tooltipEffect={this.tooltipEffect}
spanMethod={this.spanMethod}
selectOnIndeterminate={this.selectOnIndeterminate}>
{this.$slots.default}
</Table>
) : maxWrapHeight ? (
<Table
maxHeight={this.maxWrapHeight}
data={this.data}
size={this.size}
width={this.width}
height={this.height}
fit={this.fit}
stripe={this.stripe}
border={this.border}
rowKey={this.rowKey}
context={this.context}
showHeader={this.showHeader}
showSummary={this.showSummary}
sumText={this.sumText}
summaryMethod={this.summaryMethod}
rowClassName={this.rowClassName}
rowStyle={this.rowStyle}
cellClassName={this.cellClassName}
cellStyle={this.cellStyle}
headerRowClassName={this.headerRowClassName}
headerRowStyle={this.headerRowStyle}
headerCellClassName={this.headerCellClassName}
headerCellStyle={this.headerCellStyle}
highlightCurrentRow={this.highlightCurrentRow}
currentRowKey={this.currentRowKey}
emptyText={this.emptyText}
expandRowKeys={this.expandRowKeys}
defaultExpandAll={this.defaultExpandAll}
defaultSort={this.defaultSort}
tooltipEffect={this.tooltipEffect}
spanMethod={this.spanMethod}
selectOnIndeterminate={this.selectOnIndeterminate}>
{this.$slots.default}
</Table>
) : null;
},
};
<template>
<div
class="UserInfo-Card-Wrap"
ref="CardWrap"
@mouseleave="offEditBar"
>
<div :class="['UserInfo-Card-EditBar',{'editBarVisible':editBarVisible},{'layoutRight':layoutType}]">
<div
class="EditBar-item"
@click="showDetail"
>
<div class="EditBar-item-inner">
<img src="../../assets/images/user/detail_icon.png" />
</div>
<span class="EditBar-item-inner-text">详情</span>
</div>
<div
class="EditBar-item"
@click="showEdit"
>
<div class="EditBar-item-inner">
<img src="../../assets/images/user/edit_icon.png" />
</div>
<span class="EditBar-item-inner-text">编辑</span>
</div>
<div
class="EditBar-item"
@click="showAccount"
>
<div class="EditBar-item-inner">
<img src="../../assets/images/user/account_icon.png" />
</div>
<span class="EditBar-item-inner-text">账户</span>
</div>
<div
class="EditBar-item"
@click="toggleLock"
>
<div class="EditBar-item-inner">
<img
v-if="state === '1'"
src="../../assets/images/user/lock_icon.png"
/>
<img
v-else-if="state === '2'"
src="../../assets/images/user/unlock_icon.png"
/>
</div>
<span class="EditBar-item-inner-text">{{state === '1'?'锁定':'解锁'}}</span>
</div>
<div class="EditBar-trans"></div>
</div>
<div :class="['UserInfo-Box', 'UserInfo-Card',{'hover':cardZIndex}]">
<div class="UserInfo-Title-Wrap">
<div class="UserInfo-Title">
<div class="UserInfo-IdTitle">ID:{{id}}</div>
<div
ref="HeaderImg"
@mouseenter="showEditBar"
>
<header-img-box
:class="['UserInfo-HeaderImg', {'hover':editBarVisible}]"
:imgUrl="customerHead"
></header-img-box>
</div>
<div class="UserInfo-Name-Box">
<div class="UserInfo-Name-Col">
<span>{{customerName}}</span>
<span>{{getTypeLabel(customerType)}}</span>
</div>
<div class="UserInfo-Name-Col">
<span>{{$formatePhone(customerPhone)}}</span>
<span>{{customerSex}}</span>
</div>
</div>
</div>
</div>
<div class="UserInfo-Content">
<div class="UserInfo-Content-Col UserInfo-State-Box">
<div
v-if="state === '1'"
class="UserInfo-State"
>
<div class="UserInfo-State-Icon">
<img src="@/assets/images/user/normal_icon.png" />
</div>
正常使用
</div>
<div
v-else
class="UserInfo-State"
>
<div class="UserInfo-State-Icon">
<img src="@/assets/images/user/locked_icon.png" />
</div>
已锁定
</div>
<div
v-if="isFirstRecharge"
class="UserInfo-State"
>
<div class="UserInfo-State-Icon">
<img src="@/assets/images/user/normal_icon.png" />
</div>
已充值
</div>
<div
v-else
class="UserInfo-State"
>
<div class="UserInfo-State-Icon">
<img src="@/assets/images/user/locked_icon.png" />
</div>
未充值
</div>
</div>
<div class="UserInfo-Content-Col">注册时间:{{$formatDate(new Date(createAt),'yyyy-MM-dd')}}</div>
<div class="UserInfo-Content-Col UserInfo-CampusName">{{areaName}}</div>
</div>
</div>
</div>
</template>
<script>
import getUserInfoLabelMixin from '@/mixins/user/getUserInfoLabel.js';
export default {
props: {
areaName: String,
createAt: String,
customerHead: String,
customerName: String,
customerPhone: String,
customerSex: String,
customerType: String,
id: Number,
isFirstRecharge: Number,
state: String,
showDetail: { type: Function, default: () => {} },
showEdit: { type: Function, default: () => {} },
showAccount: { type: Function, default: () => {} },
toggleLock: { type: Function, default: () => {} },
},
mixins: [getUserInfoLabelMixin],
data() {
return {
editBarVisible: false,
cardZIndex: false,
layoutType: 0, // 0 为默认左边弹出 1 为右边弹出
wrapOffsetLeft: 0,
};
},
mounted() {
let sideBarDom = document.querySelector('.scroll-container'); // 导航部分
let sideBarWidth = sideBarDom ? sideBarDom.offsetWidth : 0;
let el = this.$refs.CardWrap;
let wrapOffsetLeft = el.offsetLeft;
while ((el = el.offsetParent)) {
wrapOffsetLeft += el.offsetLeft;
}
console.log(wrapOffsetLeft, sideBarWidth + 60);
this.wrapOffsetLeft = wrapOffsetLeft;
if (this.wrapOffsetLeft < sideBarWidth + 60) {
this.layoutType = 1;
}
// this.offsetLeft = this.wrapOffsetLeft + 60;
},
methods: {
showEditBar() {
this.editBarVisible = true;
},
offEditBar() {
this.editBarVisible = false;
},
},
};
</script>
<style lang="scss">
@import '../../assets/styles/variables.scss';
.UserInfo-Card-Wrap {
width: 170px;
height: 174px;
display: flex;
align-items: flex-end;
margin-right: 14px;
margin-bottom: 14px;
position: relative;
.UserInfo-Card-EditBar {
position: absolute;
left: 0;
z-index: 2001;
width: 0px;
height: 158px;
background-color: #00c2ff;
box-shadow: 2px 2px 10px #333;
border-radius: 8px 0 0 8px;
transition: all 0.28s;
&.editBarVisible {
left: -36px;
width: 36px;
.EditBar-trans {
opacity: 1;
}
}
.EditBar-item {
height: 39px;
position: relative;
overflow: hidden;
cursor: pointer;
&:first-child {
border-radius: 8px 0 0 0;
}
&:hover {
background-color: #11a6e8;
}
.EditBar-item-inner {
width: 14px;
height: 14px;
position: absolute;
top: 6px;
left: 10px;
}
.EditBar-item-inner-text {
position: absolute;
font-size: 12px;
color: #fff;
width: 36px;
height: 14px;
bottom: 0;
text-align: center;
overflow: hidden;
}
}
.EditBar-trans {
opacity: 0;
position: absolute;
right: -13px;
top: 26px;
border-left: 13px solid #00c2ff;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
transition: all 0.28s;
}
}
.UserInfo-Card-EditBar.layoutRight {
left: 66px;
border-radius: 0 8px 8px 0;
&.editBarVisible {
.EditBar-trans {
right: 36px;
}
}
.EditBar-item {
&:first-child {
border-radius: 0 8px 0 0;
}
}
.EditBar-trans {
right: 0px;
transform: rotate(180deg);
}
}
.UserInfo-Card {
height: 158px;
border-radius: 0 8px 8px 8px;
box-shadow: 2px 2px 5px #bbbec2;
z-index: 2000;
&:hover {
box-shadow: 2px 2px 5px #999;
}
// &.hover {
// z-index: 2002;
// }
// .UserInfo-Title-Wrap {
// height: 150px;
// padding-top: 30px;
// }
.UserInfo-Title {
// margin-top: 30px;
border-radius: 0 8px 0 0;
}
.UserInfo-IdTitle {
position: absolute;
left: 0;
top: -16px;
width: 88px;
height: 16px;
line-height: 16px;
background-color: #4e82fb;
color: #fff;
text-indent: 10px;
border-radius: 20px 20px 0 0 / 40px 40px 0 0;
}
.UserInfo-Content {
height: 90px;
border-radius: 0 0 8px 8px;
}
.UserInfo-HeaderImg {
transition: all 0.28s;
}
.UserInfo-HeaderImg.hover {
border-color: #00c2ff;
}
}
}
@media screen and (min-width: $bigScreenWidth) {
.UserInfo-Card-Wrap {
width: 300px;
height: 310px;
margin-right: 20px;
margin-bottom: 20px;
.UserInfo-Card-EditBar {
height: 280px;
&.editBarVisible {
left: -60px;
width: 60px;
}
.EditBar-item {
height: 60px;
.EditBar-item-inner {
width: 26px;
height: 26px;
top: 12px;
left: 17px;
}
.EditBar-item-inner-text {
width: 60px;
bottom: 3px;
}
}
.EditBar-trans {
right: -25px;
top: 44px;
border-left: 25px solid #00c2ff;
border-top: 15px solid transparent;
border-bottom: 15px solid transparent;
}
}
.UserInfo-Card-EditBar.layoutRight {
left: 120px;
&.editBarVisible {
.EditBar-trans {
right: 60px;
}
}
.EditBar-trans {
right: 0px;
}
}
.UserInfo-Card {
height: 280px;
.UserInfo-IdTitle {
top: -30px;
width: 155px;
height: 30px;
line-height: 30px;
text-indent: 24px;
border-radius: 20px 20px 0 0 / 40px 40px 0 0;
}
.UserInfo-Content {
height: 160px;
}
}
}
}
</style>
<template>
<div
class="UserInfo-Box"
v-if="customerId || customerPhone"
>
<div class="UserInfo-Title">
<div class="UserInfo-IdTag">ID:{{customerBaseInfo.id}}</div>
<header-img-box
class="UserInfo-HeaderImg"
:imgUrl="customerBaseInfo.customerHead"
/>
<div class="UserInfo-Name-Box">
<div class="UserInfo-Name-Col">
<span>{{customerBaseInfo.customerName}}</span>
<span>{{getTypeLabel(customerBaseInfo.customerType)}}</span>
</div>
<div class="UserInfo-Name-Col">
<span>{{$formatePhone(customerBaseInfo.customerPhone)}}</span>
<span>{{getSexLabel(customerBaseInfo.customerSex)}}</span>
</div>
</div>
</div>
<div class="UserInfo-Content">
<div class="UserInfo-Content-Col UserInfo-State-Box">
<div
v-if="customerBaseInfo.state === '1'"
class="UserInfo-State"
>
<div class="UserInfo-State-Icon">
<img src="@/assets/images/user/normal_icon.png" />
</div>
正常使用
</div>
<div
v-else
class="UserInfo-State"
>
<div class="UserInfo-State-Icon">
<img src="@/assets/images/user/locked_icon.png" />
</div>
已锁定
</div>
<div
v-if="customerBaseInfo.isFirstRecharge"
class="UserInfo-State"
>
<div class="UserInfo-State-Icon">
<img src="@/assets/images/user/normal_icon.png" />
</div>
已充值
</div>
<div
v-else
class="UserInfo-State"
>
<div class="UserInfo-State-Icon">
<img src="@/assets/images/user/locked_icon.png" />
</div>
未充值
</div>
</div>
<div class="UserInfo-Content-Col">注册时间:{{$formatDate(customerBaseInfo.createAt,'yyyy-MM-dd')}}</div>
<div class="UserInfo-Content-Col UserInfo-CampusName">{{customerBaseInfo.areaName}}</div>
</div>
</div>
</template>
<script>
import headImg from '@/assets/images/demo/head_icon.png';
import UserBaseInfoMixin from '../../mixins/user/userBaseInfo.js';
import getUserInfoLabelMixin from '@/mixins/user/getUserInfoLabel.js';
export default {
props: {
customerId: Number,
customerPhone: String,
},
mixins: [UserBaseInfoMixin, getUserInfoLabelMixin],
};
</script>
<style lang="scss">
@import '../../assets/styles/variables.scss';
.UserInfo-Box {
width: 170px;
min-width: 170px;
height: 157px;
font-family: 'Microsoft YaHei';
font-size: 12px;
border-radius: 8px;
.UserInfo-Title {
height: 68px;
background-color: #4e82fb;
position: relative;
padding: 10px;
box-sizing: border-box;
display: flex;
.UserInfo-Name-Box {
align-self: center;
flex: 1;
color: #fff;
font-size: 12px;
line-height: 24px;
.UserInfo-Name-Col {
display: flex;
justify-content: space-between;
}
}
.UserInfo-HeaderImg {
width: 46px;
height: 46px;
border: 3px solid #a7c0fd;
border-radius: 50%;
margin-right: 8px;
overflow: hidden;
}
.UserInfo-IdTag {
border-radius: 0 8px 8px 0;
box-shadow: 0 0 10px #666;
position: absolute;
width: 70px;
height: 18px;
line-height: 18px;
text-indent: 5px;
left: 0;
bottom: -9px;
background-color: #ffd737;
font-size: 12px;
font-weight: bold;
color: #084e8a;
}
}
.UserInfo-Content {
height: 90px;
background-color: #fff;
padding: 10px;
.UserInfo-Content-Col {
line-height: 25px;
}
.UserInfo-State-Box {
margin-top: 2px;
display: flex;
.UserInfo-State {
display: flex;
margin-right: 10px;
align-items: center;
.UserInfo-State-Icon {
width: 12px;
height: 12px;
margin-right: 4px;
}
}
}
.UserInfo-CampusName {
line-height: 1;
margin-top: 0px;
}
}
}
@media screen and (min-width: $bigScreenWidth) {
.UserInfo-Box {
width: 300px;
min-width: 300px;
height: 300px;
font-size: 16px;
.UserInfo-Title {
height: 120px;
padding: 20px;
.UserInfo-Name-Box {
font-size: 18px;
line-height: 38px;
}
.UserInfo-HeaderImg {
width: 80px;
height: 80px;
border-width: 5px;
margin-right: 10px;
}
.UserInfo-IdTag {
width: 128px;
height: 30px;
line-height: 30px;
text-indent: 5px;
bottom: -20px;
font-size: 16px;
}
}
.UserInfo-Content {
height: 180px;
padding: 20px;
.UserInfo-Content-Col {
line-height: 38px;
}
.UserInfo-State-Box {
margin-top: 6px;
.UserInfo-State {
margin-right: 20px;
.UserInfo-State-Icon {
width: 20px;
height: 20px;
margin-right: 10px;
}
}
}
.UserInfo-CampusName {
line-height: 1;
margin-top: 10px;
}
}
}
}
</style>
<template>
<div class="SigleUserWithContent">
<div class="SigleUserWithContent-inner">
<UserInfo
class="left-part"
:customerId="customerId"
:customerPhone="customerPhone"
:visible="visible"
/>
<div class="right-part">
<div class="el-dialog__header">
<div class="connect-line"></div>
<div class="el-dialog__title">
<slot name="title"></slot>
</div>
<slot name="closeBtn"></slot>
</div>
<div class="el-dialog__body clearfix">
<slot></slot>
</div>
<div
class="el-dialog__footer"
v-if="$slots.footer"
>
<slot name="footer"></slot>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'user-with-content',
props: {
customerId: Number,
visible: {
type: Boolean,
default: true,
},
customerPhone: String,
},
};
</script>
<style lang="scss">
@import '../../assets/styles/variables.scss';
.SigleUserWithContent {
.SigleUserWithContent-inner {
display: flex;
background: transparent;
box-shadow: none;
.UserInfo-Box {
border-radius: 8px;
.UserInfo-Title {
border-radius: 8px 8px 0 0;
}
.UserInfo-Content {
border-radius: 0 0 8px 8px;
}
}
.left-part {
z-index: 2;
border-radius: 8px;
box-shadow: 2px 2px 10px #333;
}
.right-part {
flex: 1;
margin-left: 20px;
background-color: #fff;
border-radius: 0 8px 8px 8px;
.el-dialog__header {
border-radius: 0 8px 0 0;
}
}
}
.sigle-user__body {
display: flex;
padding: 0;
}
.el-dialog__header {
position: relative;
height: 38px;
background-color: #4e82fb;
padding: 10px;
.el-dialog__headerbtn {
position: absolute;
top: -20px;
right: -20px;
width: 39px;
height: 39px;
}
.connect-line {
position: absolute;
left: -50px;
top: 0;
border-top: 19px solid transparent;
border-right: 50px solid #4e82fb;
border-bottom: 19px solid transparent;
}
.el-dialog__title {
margin: 0px auto 0;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
line-height: 18px;
vertical-align: top;
font-size: 14px;
img {
width: 14px;
height: 14px;
margin-right: 20px;
}
}
}
.el-dialog__footer {
box-sizing: content-box;
text-align: center;
}
}
@media screen and (min-width: $bigScreenWidth) {
.SigleUserWithContent {
.SigleUserWithContent-inner {
.right-part {
margin-left: 25px;
}
}
.el-dialog__header {
height: 96px;
.el-dialog__headerbtn {
top: -30px;
right: -30px;
width: 65px;
height: 65px;
}
.connect-line {
left: -70px;
border-top-width: 48px;
border-right-width: 70px;
border-bottom-width: 48px;
}
.el-dialog__title {
margin: 14px auto 0;
font-size: 22px;
img {
width: 28px;
height: 28px;
margin-right: 20px;
}
}
}
.el-dialog__footer {
padding: 50px 20px 40px;
}
}
}
</style>
<template>
<el-select
clearable
filterable
:value="value"
@change="changeHandle"
v-if="noLabel"
>
<el-option
v-for="(item, index) in areaList"
:key="index"
:value="item.id"
:label="item.areaName"
></el-option>
</el-select>
<search-item
label="所属区域"
:size="size"
v-else
>
<el-select
clearable
filterable
:value="value"
@change="changeHandle"
>
<el-option
v-for="(item, index) in areaList"
:key="index"
:value="item.id"
:label="item.areaName"
></el-option>
</el-select>
</search-item>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
name: 'area-select',
props: {
size: String,
value: Number,
noLabel: {
type: Boolean,
default: false,
},
},
mounted() {
if (!this.areaList.length) {
// this.fetchAreaList();
}
},
computed: {
...mapGetters(['areaList']),
},
methods: {
...mapActions(['fetchAreaList']),
changeHandle(val) {
this.$emit('input', val);
},
},
};
</script>
<template>
<search-item
:label="label"
:size="size"
>
<el-select
clearable
filterable
:value="value"
@change="changeHandle"
>
<el-option
v-for="(item, index) in baseDataOptionsList"
:key="index"
:value="item.value"
:label="item.name"
></el-option>
</el-select>
</search-item>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import baseDataMixin from '../../mixins/base/BaseDataSelect.js';
import { selectTypeMapping } from '@/config';
export default {
name: 'base-data-select',
mixins: [baseDataMixin],
props: {
size: {
type: String,
default: 'normal',
},
value: {
type: String,
default: '',
},
label: {
type: String,
default: '',
},
},
data() {
return {
baseDataOptionsList: [],
};
},
created() {
if (this.label) {
this.getOptionsList(this.label).then(list => {
this.baseDataOptionsList = list;
});
}
},
};
</script>
<template>
<div class="editor-wrapper" ref="editor" style="text-align:left;margin-top: 20px;"></div>
</template>
<script>
import E from 'wangeditor';
import * as qiniu from 'qiniu-js';
import config from '@/config/index';
import { getUploadImgToken, getDelImgToken } from '@/api/mall/common';
export default {
name: 'editor',
props: {
onchange: Function,
config: Object,
},
data() {
return {
IMG_URL: config.IMG_URL,
editor: null,
};
},
methods: {
getImgToken(isReload) {
if (isReload) {
getUploadImgToken().then(res => {
console.log(res);
window.$imageToken = res.data;
this.uploadImg();
});
return;
}
if (window.hasToken) return;
window.hasToken = true;
getUploadImgToken().then(res => {
window.$imageToken = res.data;
});
},
uploadImg(files, insert) {
console.log(files);
// files 是 input 中选中的文件列表
// insert 是获取图片 url 后,插入到编辑器的方法
let file = files[0];
let key = null;
let putExtra = {
fname: '',
params: {},
mimeType: null,
};
let config = {
useCdnDomain: true,
region: qiniu.region.z2,
};
// 调用sdk上传接口获得相应的observable,控制上传和暂停
let observable = qiniu.upload(
file,
key,
window.$imageToken,
putExtra,
config
);
// 上传代码返回结果之后,将图片插入到编辑器中
let complete = response => {
insert(this.IMG_URL + response.key);
};
let _error = err => {
console.log(err);
if (err.isRequestError && err.code == 401) {
this.getImgToken(true);
} else {
console.log(err);
this.$refs.input.value = '';
}
};
let _next = res => {
// total: {
// loaded: number,已上传大小,单位为字节。
// total: number,本次上传的总量控制信息,单位为字节。
// percent: number,当前上传进度,范围:0~100。
// }
console.log(res.total.percent);
};
observable.subscribe({
next: this._next,
error: _error,
complete,
});
},
},
mounted() {
this.editor = new E(this.$refs.editor);
this.editor.customConfig = {
menus: [
'head', // 标题
'bold', // 粗体
'fontSize', // 字号
'fontName', // 字体
'italic', // 斜体
'underline', // 下划线
'strikeThrough', // 删除线
'foreColor', // 文字颜色
'backColor', // 背景颜色
// 'link', // 插入链接
// 'list', // 列表
'justify', // 对齐方式
'quote', // 引用
// 'emoticon', // 表情
'image', // 插入图片
// 'table', // 表格
// 'video', // 插入视频
// 'code', // 插入代码
'undo', // 撤销
'redo', // 重复
],
customUploadImg: this.uploadImg,
onchange: this.onchange,
zIndex: 300,
...this.config,
};
this.editor.create();
},
};
</script>
<style>
.editor-wrapper {
width: 80%;
}
.editor-wrapper img {
width: auto;
}
</style>
<template>
<search-item
label="赠送类型"
:size="size"
>
<el-select
clearable
filterable
:value="value"
@change="changeHandle"
>
<el-option
v-for="(item, index) in giveTypeList"
:key="index"
:value="item.value"
:label="item.name"
></el-option>
</el-select>
</search-item>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
name: 'give-select',
props: {
size: {
type: String,
default: '',
},
value: {
type: String,
default: '',
},
},
mounted() {
if (!this.giveTypeList.length) {
this.fetchGiveTypeList();
}
},
computed: {
...mapGetters(['giveTypeList']),
},
methods: {
...mapActions(['fetchGiveTypeList']),
changeHandle(val) {
this.$emit('input', val);
},
getTypeName(val){
let item = this.giveTypeList.find(({value}) => val === value);
return item?item.name: '';
}
},
};
</script>
<template>
<div class="image-uploader">
<div v-if="done" @mouseover="toogleDelIcon" @mouseout="toogleDelIcon" class="image-uploader-done">
<div v-if="showDel && canDelete" class="mask">
<svg @click="delImg" class="delimg icon">
<use xlink:href="#icon-delimg"></use>
</svg>
</div>
<img class="image-uploader-img" :src="url" />
<!-- <img class="del" src="@/assets/images/icon_delete.png" @click="delImg" /> -->
</div>
<label v-else class="image-uploader-pre">
<slot>
<svg class="icon image-uploader-icon" aria-hidden="true">
<use xlink:href="#icon-plus"></use>
</svg>
</slot>
<input ref='input' type="file" accept='image/*' @change="uploadImg" style="display: none;" />
</label>
</div>
</template>
<style lang="scss" scoped>
.image-uploader {
width: 123px;
height: 123px;
border: 1px solid #ccc;
.mask {
position: absolute;
z-index: 20;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
.image-uploader-pre,
.image-uploader-done {
display: block;
position: relative;
height: 100%;
.delimg {
width: 50px;
height: 50px;
position: absolute;
left: 50%;
top: 50%;
font-size: 50px;
fill: #999;
margin-left: -25px;
margin-top: -25px;
fill: #fff;
}
.image-uploader-img {
width: 100%;
}
}
.image-uploader-icon {
position: absolute;
display: block;
width: 50px;
height: 50px;
font-size: 50px;
left: 50%;
top: 50%;
margin-top: -25px;
margin-left: -25px;
fill: #999;
}
}
</style>
<script>
import * as qiniu from 'qiniu-js';
import { getDelImgToken } from '@/api/mall/common';
import { mapGetters } from 'vuex';
export default {
name: 'image-uploader',
data() {
return {
file: null,
fileName: '',
url: '',
domain: 'http://qiniu.dcrym.com/',
showDel: false,
};
},
props: {
value: {
// 图片地址
type: String,
default: '',
},
canDelete: {
// 是否显示删除按钮
type: Boolean,
default: true,
},
option: {
// 剪裁比例大小
type: Object,
default: () => ({
CropBox: {
width: 200,
height: 100,
},
}),
},
},
created() {
this.getImgToken();
this.setUrl(this.value);
},
computed: {
...mapGetters(['getToken', 'userInfo']),
done() {
return this.value ? true : false;
},
},
watch: {
value(newVal) {
this.setUrl(newVal);
},
},
deactivated() {
this.refreshData();
},
methods: {
setUrl(val) {
this.url = val ? val : '';
this.fileName = val ? val.replace(this.domain, '') : '';
},
toogleDelIcon() {
this.showDel = !this.showDel;
},
getImgToken(isReload) {
if (isReload) {
return this.$store.dispatch('getUploadImgToken').then(res => {
this.uploadImg(null, this.file);
return;
});
}
if (window.hasToken) return;
window.hasToken = true;
this.$store.dispatch('getUploadImgToken');
},
refreshData() {
this.file = null;
this.fileName = '';
this.url = '';
if (this.$refs.input && this.$refs.input.value) {
this.$refs.input.value = '';
}
},
uploadImg(e, blob) {
let file = blob ? blob : this.$refs.input.files[0];
this.file = file;
const id = this.userInfo.id;
const timeStemp = new Date().getTime();
let key = id + timeStemp;
let putExtra = {
fname: '',
params: {},
mimeType: null,
};
let config = {
useCdnDomain: true,
region: qiniu.region.z2,
};
// 调用sdk上传接口获得相应的observable,控制上传和暂停
let observable = qiniu.upload(file, key, this.getToken, putExtra, config);
observable.subscribe({
next: this._next,
error: this._error,
complete: this._complete,
});
},
delImg() {
getDelImgToken({
fileName: this.fileName,
})
.then(res => {
if (res.code === '0') {
this.$emit('input', '');
this.$nextTick(() => {
if (this.$refs.input) this.$refs.input.value = '';
});
} else {
console.log('删除错误:' + res.msg);
this.$message.error('删除错误!');
}
})
.catch(err => {
console.log('删除错误:' + err.msg);
});
},
_error(err) {
if (err.isRequestError && err.code == '401') {
this.getImgToken(true);
} else {
console.log(err);
this.$refs.input.value = '';
}
},
_next(res) {
// total: {
// loaded: number,已上传大小,单位为字节。
// total: number,本次上传的总量控制信息,单位为字节。
// percent: number,当前上传进度,范围:0~100。
// }
console.log(res.total.percent);
},
_complete(response) {
console.log(response);
this.$emit('input', this.domain + response.key);
this.fileName = response.key;
},
},
};
</script>
<script>
import { Select } from 'element-ui';
export default {
name: 'md-select',
extends: Select,
props: {
placeholder: {
type: String,
default() {
return '--- 请选择 ---';
},
},
},
};
</script>
<template>
<search-item
label="服务类型"
:size="size"
>
<el-select
clearable
filterable
:value="value"
@change="changeHandle"
>
<el-option
v-for="(item, index) in serviceTypeList"
:key="index"
:value="item.value"
:label="item.name"
></el-option>
</el-select>
</search-item>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
name: 'service-select',
props: {
size: {
type: String,
default: '',
},
value: {
type: Number,
default: null,
},
},
mounted() {
if (!this.serviceTypeList.length) {
this.fetchServiceTypeList();
}
},
computed: {
...mapGetters(['serviceTypeList']),
},
methods: {
...mapActions(['fetchServiceTypeList']),
changeHandle(val) {
if(!val){
this.$emit('input', null);
}else{
this.$emit('input', val);
}
},
},
};
</script>
<template>
<div class="KYandLS">
<div class="commonTitle">
<img :src="data.sectionPic" />
<span>
<span class="fontsize22 color666">查看全部</span>
<span class="iconfont icon-youjiantou1 color666"></span>
</span>
</div>
<div class="KYandLS_contentBigBox_wrap">
<div v-if="dataList.length" class="KYandLS_contentBigBox" ref="scrollWrap">
<div class="KYandLS_content bb1px" v-for="index in row" :key="index">
<div class="KYandLS_prdSmalBox" w-224-179 aspectratio v-for="(item) in dataList.slice((index-1) * 3, 3 * index)" :key='item.id' @tap="goNewPage({dataType: data.dataType, id: item.id})">
<div v-if="item.activityType" class="tag-icon">
<img src="@/images/tag-icon-index@2x.png" alt="">
</div>
<img aspectratio-content :src="item.coverPic" />
</div>
</div>
</div>
<div class="empty_block" v-else>
<img src="@/images/hasNoProduct@2x.png" alt="暂无商品">
</div>
</div>
</div>
</template>
<style lang="less">
.empty_block {
padding: 20px 0;
img {
width: 315px;
height: 256px;
margin: 0 auto;
}
}
.KYandLS {
.tag-icon {
position: absolute;
width: 54px;
height: 46px;
right: 0;
top: 0;
}
.KYandLS_contentBigBox_wrap {
width: 100%;
padding: 0 24px;
}
.KYandLS_content {
width: 100%;
display: flex;
background-color: #fff;
padding: 20px 0;
// justify-content: space-between;
.KYandLS_prdSmalBox {
width: 224px;
margin-right: 15px;
border: 1px solid #efeff4;
}
.KYandLS_prdSmalBox:last-child {
margin-right: 0;
}
}
}
</style>
<script>
import BScroll from 'better-scroll';
const list = [];
export default {
name: 'template-3',
props: {
data: {
type: null,
default: false,
},
dataList: {
type: Array,
default: () => list,
},
goAllPage: {
type: Function,
},
goNewPage: {
type: Function,
},
},
computed: {
row() {
const length = this.dataList.length;
return Math.ceil(length / 3);
},
},
mounted() {
},
};
</script>
<template>
<!-- <div>
hahaha{{schoolId}}
</div> -->
<div class="index">
<!-- <mainheader></mainheader> -->
<indexScroll class="index-content-wrap" ref='mainScrollWrap'>
<div class="index-content">
<!--轮播-->
<div class="banner LBbox">
<slider v-if="bannerData.length > 1" ref="slider" :autoPlay='true' :loop='true' :showDot='true'>
<div class="LBbox-item" v-for="item in bannerData" :key='item.id'>
<img :src="item.roundPic" height="220px" />
</div>
</slider>
<div v-else-if="bannerData.length == 1">
<img :src="bannerData[0].roundPic" height="220px" alt="" />
</div>
</div>
<!--四大类-->
<div class="fourPart positionR">
<div class="fourPart_inner">
<div class="textCenter">
<img id="testgopage" src="@/images/1@2x.png" />
<p class="">卖家中心</p>
</div>
<div class="textCenter">
<img src="@/images/2@2x.png" />
<p>配送员中心</p>
</div>
<div class="textCenter">
<img src="@/images/3@2x.png" />
<p>我的订单</p>
</div>
<div class="textCenter">
<img src="@/images/4@2x.png" />
<p>我的收藏</p>
</div>
</div>
</div>
<template v-for="blockItem in indexData">
<!--格子铺-->
<div :key="blockItem.id" class="GZ" v-if="blockItem.styleCode === 'TEMPLATE_1'">
<div class="commonTitle">
<img :src="blockItem.sectionPic" />
<span>
<div>
<span class="fontsize22 color666">查看全部</span>
<span class="iconfont icon-youjiantou1 color666"></span>
</div>
</span>
</div>
<div class="GZ_content clearFloat" v-if="gzData.length">
<div class="floatL GZ_imgSmallBox" v-for="(item) in gzData" :key='item.id' aspectratio w-340-272>
<img :src="item.coverPic" aspectratio-content />
<p class="title">
<span class="floatL gz-p-name ellipsis">{{item.productName}}</span>
<span class="floatR priceFont">&yen;{{item.price}}</span>
</p>
</div>
</div>
<div class="empty_block" v-else>
<img src="@/images/hasNoProduct@2x.png" alt="暂无商品">
</div>
</div>
<!--量版专区-->
<div :key="blockItem.id" class="LB" v-else-if="blockItem.styleCode === 'TEMPLATE_2'">
<div class="commonTitle">
<img :src="blockItem.sectionPic" />
<span>
<span class="fontsize22 color666">查看全部</span>
<span class="iconfont icon-youjiantou1 color666"></span>
</span>
</div>
<div class="LB_content" v-if="lfData.length">
<div class="LB_copimg borderBotmEB">
<div v-for="(item) in lfTop" :key='item.id' class="clearFloat LB_imgBox" aspectratio w-224-179>
<div v-if="item.activityType" class="tag-icon">
<img src="@/images/tag-icon-index@2x.png" alt="">
</div>
<img aspectratio-content :src="item.coverPic" />
<p class="LB_copimg_title">
<span class="floatL gz-p-name ellipsis">{{item.productName}}</span>
<span class="floatR priceFont">&yen;{{item.skuPrice}}</span>
</p>
</div>
</div>
<div class="LB_oneimg borderBotmEB" v-for="(item) in lfList" :key='item.id'>
<div class="LB_oneimg_box">
<!-- 左边图 -->
<div class="LB_oneimg_box_left" w-224-179 aspectratio>
<div v-if="item.activityType" class="tag-icon">
<img src="@/images/tag-icon-index@2x.png" alt="">
</div>
<img aspectratio-content :src="item.coverPic" />
</div>
<div class="LB_oneimg_box_right LB_singleContent">
<p class="colorDarkGry LB_oneimg_box_right_title">{{item.productName}}</p>
<p class="LB_oneimg_box_right_price">
<span class="price_box">
<span class="priceFont">&yen;{{item.skuPrice}}</span>
/
<span class="skuinfo color666 fontsize22 ellipsis">{{item.skuName}}</span>
</span>
<span class="sallcount color999 floatL fontsize22">销量{{item.saleCount}}</span>
</p>
<div class="LB_oneimg_box_right_xl">
<span class="shop-name ellipsis">{{item.shopName}}</span>
<!-- <span class="shop-name">店铺名字</span> -->
<span class="jindian">进店&nbsp;&nbsp;> &nbsp; </span>
</div>
</div>
</div>
</div>
</div>
<div class="empty_block" v-else>
<img src="@/images/hasNoProduct@2x.png" alt="暂无商品">
</div>
</div>
<!--酷饮/零食专区-->
<template-3 :key="blockItem.id" v-else-if="blockItem.styleCode === 'TEMPLATE_3' && tem3Data[blockItem.id]" :data="blockItem" :dataList="tem3Data[blockItem.id]"></template-3>
<!--店铺推荐-->
<div :key="blockItem.id" class="DP" v-else-if="blockItem.styleCode === 'TEMPLATE_4' && shopData.length">
<div class="commonTitle">
<img :src="blockItem.sectionPic" />
<span>
<span class="fontsize22 color666">查看全部</span>
<span class="iconfont icon-youjiantou1 color666"></span>
</span>
</div>
<div class="DP_content self-content-padded clearFloat">
<div class="DP_item_box" aspectratio w-340-272 v-for="(item) in shopData" :key="item.id">
<img aspectratio-content :src="item.coverPic" />
<p class="title ellipsis">{{item.shopName}}</p>
</div>
</div>
</div>
<!-- 足迹 -->
<!-- <div :key="blockItem.id" class="ZJ" v-else-if="blockItem.styleCode === 'TEMPLATE_5'">
<div class="commonTitle" style="background-color: #fff;">
<img :src="blockItem.sectionPic" />
</div>
<div class="ZJ_content" v-if="zjData.length">
<div class="ZJ_column1">
<v-touch class="ZJ_box" v-for="(item, index) in zjLeft" :key='index' @tap="goNewPage({dataType: dataType['zjData'], id: item.id})">
<img v-lazy="item.coverPic" />
<p class="title colorDarkGry fontsize26">{{item.productName}}</p>
<p>
<span class="priceFont fontsize38">&yen;{{item.skuPrice}}</span>
<span class="color999">月销
<span>{{item.saleCount}}</span>
</span>
</p>
</v-touch>
</div>
<div class="ZJ_column2">
<v-touch class="ZJ_box" v-for="(item, index) in zjRight" :key='index' @tap="goNewPage({dataType: dataType['zjData'], id: item.id})">
<img v-lazy="item.coverPic" />
<p class="title colorDarkGry fontsize26">{{item.productName}}</p>
<p>
<span class="priceFont fontsize38">&yen;{{item.skuPrice}}</span>
<span class="color999">月销
<span>{{item.saleCount}}</span>
</span>
</p>
</v-touch>
</div>
</div>
</div> -->
<!--广告-->
<!-- <div :key="blockItem.id" class="AD" v-else-if="blockItem.styleCode === 'TEMPLATE_6'">
<v-touch class="AD_box" @tap="goNewPage({dataType: blockItem.dataType, id: blockItem.sectionName, link: blockItem.jumpLink})">
<img v-lazy="adData.sectionPic" />
<div class="AD_title colorRed">广告</div>
</v-touch>
</div> -->
</template>
</div>
</indexScroll>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import indexScroll from '@/components/scroll';
import slider from '@/components/slider';
import template3 from './blockTmp3';
export default {
name: 'preview',
props: {
schoolId: String,
},
components: {
indexScroll,
slider,
[template3.name]: template3,
},
data() {
// return {
// dataType: {}, // 页面中区块数据请求类型 以区块名为key
// gzData: [], // 线下格子列表
// lfData: [], // 量贩专区列表
// adData: {}, // 广告
// tem3Data: {}, // 零食专区列表
// shopData: [], // 推荐店铺列表
// };
return {
userId: '', // 用户信息
campusId: '', // 校区信息
pullDownRefreshConfig: {
threshold: 90,
stop: 40,
txt: '刷新成功',
},
pullUpLoadConfig: {
threshold: 0,
txt: {
more: '上拉加载更多',
noMore: '噢吼!只有这么多了',
},
},
dataType: {}, // 页面中区块数据请求类型 以区块名为key
gzData: [], // 线下格子列表
lfData: [], // 量贩专区列表
adData: {}, // 广告
tem3Data: {}, // 零食专区列表
shopData: [], // 推荐店铺列表
zjData: [], // 足迹列表
zjParam: {},
zjPagination: {
id: '',
pageSize: 5,
}, // 足迹分页信息
};
},
computed: {
...mapGetters('operationStore/schoolStore', ['bannerData', 'indexData']),
lfTop() {
return this.lfData.filter((item, index) => index < 3);
},
lfList() {
return this.lfData.filter((item, index) => index > 2);
},
zjLeft() {
return this.zjData.filter((item, index) => !(index % 2));
},
zjRight() {
return this.zjData.filter((item, index) => !!(index % 2));
},
},
watch: {
schoolId(myNew, myOld) {
// console.log('观察');
// console.log(myNew);
this.initData();
},
},
created() {
// console.log("created模版预览")
this.initData();
},
methods: {
...mapActions('operationStore/schoolStore', [
'fetchIndexBannerData',
'fetchIndexData',
]),
initData() {
return new Promise(resolve => {
this.fetchIndexData({
campusId: this.schoolId,
}).then(indexDataList => {
console.log('indexDataList');
console.log(indexDataList);
this.indexData.map(blockItem => {
let param = blockItem.interfacePara;
param === null || param === ''
? (param = {})
: (param = eval('(' + param + ')'));
blockItem.styleCode === 'TEMPLATE_6'
? (this.adData = blockItem)
: this.getBlockData({ ...blockItem, param });
});
});
// console.log('校区ID');
// console.log(this.schoolId);
this.fetchIndexBannerData({
campusId: this.schoolId,
}).then(() => {});
});
},
getBlockData(blockItem) {
console.log('blockItem');
console.log(blockItem);
const name = blockItem.sectionName;
const sid = blockItem.dataInterface;
const code = blockItem.styleCode;
const dataType = blockItem.dataType;
const param = blockItem.param;
switch (code) {
case 'TEMPLATE_1':
this.dataType['gzData'] = dataType;
this.gzData = blockItem.data;
break;
case 'TEMPLATE_2':
this.dataType['lfData'] = dataType;
this.lfData = blockItem.data;
break;
case 'TEMPLATE_3':
this.dataType['tem3Data'] = dataType;
this.tem3Data[blockItem.id] = blockItem.data;
// console.log('tem3Data');
// console.log(tem3Data);
break;
case 'TEMPLATE_4':
this.dataType['shopData'] = dataType;
this.shopData = blockItem.data;
break;
// 足迹
default:
break;
}
},
},
mounted() {},
};
</script>
<style lang="less">
[aspectratio] {
position: relative;
}
[aspectratio]::before {
content: '';
display: block;
width: 1px;
margin-left: -1px;
height: 0;
}
[aspectratio-content] {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
}
[w-340-272] {
aspect-ratio: '340:272';
}
[w-224-179] {
aspect-ratio: '224:179';
}
.empty_block {
padding: 20px 0;
text-align: center;
img {
width: 315px;
height: 256px;
}
}
.ellipsis-lines(@n) {
overflow: hidden;
-webkit-line-clamp: @n;
-webkit-box-orient: vertical;
display: -webkit-box;
}
.index {
.tag-icon {
position: absolute;
width: 54px;
height: 46px;
right: 0;
top: 0;
z-index: 999;
}
.LBbox {
width: 100%;
height: 240px;
overflow: hidden;
.LBbox-item {
width: 100%;
height: 100%;
}
}
.fourPart {
position: relative;
height: 175px;
// .fourPart_inner_top {
// position: absolute;
// width: 100%;
// height: 58px;
// top: -57px;
// background-image: url('../../../images/banner-top.png');
// background-size: contain;
// }
.fourPart_inner {
width: 100%;
height: 100%;
padding: 0 34px 0;
display: flex;
justify-content: space-between;
align-items: center;
}
div {
img {
display: block;
margin: 0 auto;
width: 102px;
height: 102px;
margin-bottom: 10px;
}
p {
margin: 0;
font-size: 22px;
color: #333;
}
}
}
.GZ_imgBox {
background-color: #fff;
.GZ_box {
position: relative;
}
.GZ_inf {
position: absolute;
bottom: 0;
left: 0;
height: 30px;
line-height: 30px;
background-color: pink;
width: 100%;
font-size: 14px;
}
div {
img {
width: 94%;
display: block;
}
}
}
.GZ_content {
background-color: #fff;
margin: 20px 24px 10px;
display: flex;
flex-flow: wrap;
justify-content: space-between;
.GZ_imgSmallBox {
width: 260px;
margin-bottom: 20px;
border: 1px solid #efeff4;
}
.title {
position: absolute;
bottom: 0;
margin: 0;
width: 100%;
height: 50px;
line-height: 50px;
font-size: 22px;
background-color: rgba(255, 255, 255, 0.6);
padding: 0 16px;
display: flex;
span {
color: #333333;
}
.gz-p-name {
flex: 1;
}
.priceFont {
margin-right: 10px;
}
}
}
.commonTitle {
background-color: #ebebeb;
line-height: 80px;
height: 80px;
position: relative;
img {
position: absolute;
left: 50%;
margin-left: -155px;
width: 310px;
height: 80px;
}
span {
display: inline-block;
font-size: 22px;
.iconfont {
font-size: 30px;
}
}
& > span {
float: right;
padding-right: 24px;
}
}
.LB_content {
background-color: #fff;
margin: 20px 24px;
.LB_copimg {
display: flex;
padding-bottom: 20px;
.LB_copimg_title {
position: absolute;
bottom: 0;
margin: 0;
width: 100%;
height: 40px;
line-height: 40px;
font-size: 18px;
background-color: rgba(255, 255, 255, 0.6);
padding: 0 16px;
display: flex;
.gz-p-name {
flex: 1;
}
}
.LB_imgBox {
width: 224px;
margin-right: 15px;
border: 1px solid #efeff4;
}
.LB_imgBox:last-child {
margin: 0;
}
}
}
.LB_oneimg {
padding: 20px 0;
.LB_oneimg_box {
display: flex;
.LB_oneimg_box_left {
width: 224px;
height: 140px;
border: 1px solid #efeff4;
}
.LB_oneimg_box_right {
margin-left: 20px;
flex: 1;
height: 140px;
}
.LB_oneimg_box_right_title {
height: 60px;
line-height: 32px;
font-size: 18px;
margin: 6px 0 10px;
color: #1e1e1e;
.ellipsis-lines(2);
}
.LB_oneimg_box_right_price {
display: flex;
justify-content: space-between;
height: 40px;
line-height: 40px;
.price_box {
.skuinfo {
display: inline-block;
width: 180px;
vertical-align: middle;
}
}
.priceFont {
font-size: 24px;
color: #cd0000;
}
.sallcount {
font-size: 18px;
color: rgb(153, 153, 153);
}
}
.LB_oneimg_box_right_xl {
// height: 29px;
font-size: 18px;
display: flex;
// justify-content: space-between;
position: relative;
color: rgb(102, 102, 102);
.shop-name {
width: 310px;
color: rgb(153, 153, 153);
}
}
}
.LB_singleContent {
p:last-child {
span {
display: inline-block;
img {
width: 52px;
height: 39px;
}
}
}
}
}
.AD {
margin: 20px 0 30px 0;
.AD_box {
position: relative;
img {
display: block;
width: 100%;
height: 170px;
}
div {
position: absolute;
right: 0px;
top: 20px;
background-color: rgba(255, 255, 255, 0.6);
height: 42px;
line-height: 42px;
width: 90px;
text-align: center;
font-size: 26px;
}
}
}
.DP {
.DP_content {
width: 100%;
background-color: #fff;
padding: 20px 24px;
display: flex;
flex-flow: wrap;
justify-content: space-between;
.DP_item_box {
width: 260px;
margin-bottom: 20px;
border: 1px solid #efeff4;
.title {
position: absolute;
bottom: 0;
margin: 0;
width: 100%;
height: 46px;
line-height: 46px;
text-align: center;
background-color: rgba(255, 255, 255, 0.6);
color: #333333;
font-weight: bold;
font-size: 26px;
}
}
}
}
.ZJ {
background: #efeff4;
.commonTitle {
height: 32px;
display: flex;
align-items: center;
}
.commonTitle img {
width: 317px;
height: 32px;
}
.ZJ_content {
padding: 38px 24px;
display: flex;
justify-content: space-between;
.ZJ_column1,
.ZJ_column2,
.ZJ_box {
width: 341px;
border: 1px solid #efeff4;
}
.ZJ_box {
background-color: #fff;
margin-bottom: 20px;
overflow: hidden;
p:first-of-type {
margin-top: 15px;
font-size: 26px;
line-height: 32px;
max-height: 64px;
overflow: hidden;
color: #333;
}
p:last-child {
margin: 30px 0 22px;
font-size: 22px;
}
p {
padding: 0 15px;
}
img {
width: 100%;
}
.priceFont {
font-size: 38px;
color: #cd0000;
padding-right: 18px;
}
}
}
}
.sm_content {
padding-top: 70px !important;
background-color: rgba(51, 51, 51, 0.5);
position: fixed;
z-index: 99999;
width: 100%;
.sm_title {
padding-top: 5rem;
}
.sm_box {
background-color: #fff;
height: 12rem;
width: 12rem;
background-size: 100% 100%;
margin: 0 auto;
}
.sm_prdPos {
padding-top: 8.5rem;
}
}
.pullup-wrapper {
background: #efeff4;
}
.index-content-wrap {
flex: 1;
width: 100%;
height: 100%;
overflow: hidden;
}
}
.textRight {
width: 80px;
height: 80px;
position: absolute;
right: 0px;
bottom: -20px;
img {
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
bottom: 0px;
}
}
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment