1、优化批量流量图形展示页面Echarts图表X轴数据缩进逻辑,提升展示效果 2、添加代码注释,移除冗余代码,优化代码体积与执行效率 3、优化自定义日期时间区间组件,完善功能与视觉表现 4、优化路由匹配逻辑,解决单页面适配多路由场景下的 keep-alive 缓存错乱问题
1046 lines
27 KiB
Vue
1046 lines
27 KiB
Vue
<template>
|
||
<div class="custom-datetime-range-picker">
|
||
<!-- 输入框 -->
|
||
<div
|
||
class="el-input_parent el-input el-input--suffix"
|
||
:class="{'is-focus': showPanel, 'is-disabled': disabled}"
|
||
@click="togglePanel"
|
||
ref="triggerRef"
|
||
>
|
||
<i class="el-input_dateIcon el-input__icon el-range__icon el-icon-time"></i>
|
||
<input
|
||
class="el-range-input el-input_first"
|
||
type="text"
|
||
readonly
|
||
:placeholder="startPlaceholder"
|
||
:value="displayValue[0]"
|
||
:disabled="disabled"
|
||
>
|
||
<span class="disInlineBlock textAlignCenter" style="width: 30px;">{{rangeSeparator}}</span>
|
||
<input
|
||
class="el-range-input el-input_second"
|
||
type="text"
|
||
readonly
|
||
:placeholder="endPlaceholder"
|
||
:value="displayValue[1]"
|
||
:disabled="disabled"
|
||
>
|
||
<span class="el-input__suffix" style="right: 5px!important;">
|
||
<span class="el-input__suffix-inner">
|
||
<i
|
||
class="el-input__icon"
|
||
:class="{'el-icon-circle-close': clearable && displayValue.length > 0}"
|
||
@click.stop="clearSelection"
|
||
></i>
|
||
</span>
|
||
</span>
|
||
</div>
|
||
|
||
<!-- 日期时间选择面板 -->
|
||
<transition name="el-zoom-in-top">
|
||
<div v-if="showPanel" class="el-picker-panel time-range-panel" :style="panelStyle" ref="panelRef">
|
||
<div class="panel-body">
|
||
<!-- 快捷选项 -->
|
||
<template v-if="pickerOptions.shortcuts && pickerOptions.shortcuts.length > 0">
|
||
<div class="quick-panel">
|
||
<button
|
||
type="button"
|
||
class="el-picker-panel__shortcut"
|
||
v-for="(shortcut, key) in pickerOptions.shortcuts"
|
||
:key="key"
|
||
@click="handleShortcutClick(shortcut)">{{shortcut.text}}</button>
|
||
</div>
|
||
<!-- 中间分隔线 -->
|
||
<div class="panel-divider"></div>
|
||
</template>
|
||
<!-- 左侧:日期选择区域 -->
|
||
<div class="date-panel">
|
||
<!-- 开始结束时间选择 -->
|
||
<div v-if="dateType === 'datetimerange'" style="padding: 8px 0 5px;">
|
||
<el-time-picker v-model="startTime" :disabled="!(startDate && endDate)" size="small" style="width: 49%; margin-right: 1%;" placeholder="开始时间" />
|
||
<el-time-picker v-model="endTime" :disabled="!(startDate && endDate)" size="small" style="width: 49%; margin-left: 1%;" placeholder="结束时间" />
|
||
</div>
|
||
<div style="width: 100%;height: 1px; background-color: #e4e7ed;"></div>
|
||
<!-- 头部:年月切换 -->
|
||
<div class="panel-header">
|
||
<button
|
||
type="button"
|
||
class="icon-btn prev-year-btn"
|
||
@click="prevYear"
|
||
>
|
||
<i class="el-icon-d-arrow-left"></i>
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="icon-btn prev-month-btn"
|
||
@click="prevMonth"
|
||
>
|
||
<i class="el-icon-arrow-left"></i>
|
||
</button>
|
||
<span class="header-label">
|
||
{{ currentMonth }}年{{ currentDate }}月
|
||
</span>
|
||
<button
|
||
type="button"
|
||
class="icon-btn next-month-btn"
|
||
@click="nextMonth"
|
||
>
|
||
<i class="el-icon-arrow-right"></i>
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="icon-btn next-year-btn"
|
||
@click="nextYear"
|
||
>
|
||
<i class="el-icon-d-arrow-right"></i>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 周几标题 -->
|
||
<div class="weekdays">
|
||
<div v-for="day in weekdays" :key="day" class="weekday">{{ day }}</div>
|
||
</div>
|
||
|
||
<!-- 日历表格 -->
|
||
<div class="calendar">
|
||
<div
|
||
v-for="(day, index) in days"
|
||
:key="index"
|
||
class="day-cell"
|
||
:class="getDayClass(day, index)"
|
||
@click="selectDate(day)"
|
||
>
|
||
<div class="day-content">
|
||
{{ day.date }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 面板底部 -->
|
||
<div class="panel-footer">
|
||
<div class="footer-actions w100" style="display: block;">
|
||
<button
|
||
type="button"
|
||
class="footer-btn clear-btn"
|
||
@click="clearSelection"
|
||
>
|
||
清空
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="el-button is-plain el-button--mini"
|
||
:disabled="!(startDate && endDate)"
|
||
@click="applySelection"
|
||
>
|
||
确定
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</transition>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: 'CustomDateTimeRangePicker',
|
||
props: {
|
||
dateType: {
|
||
type: String,
|
||
default: 'datetimerange'
|
||
},
|
||
value: {
|
||
type: Array,
|
||
default: () => []
|
||
},
|
||
startPlaceholder: {
|
||
type: String,
|
||
default: '开始日期'
|
||
},
|
||
endPlaceholder: {
|
||
type: String,
|
||
default: '结束日期'
|
||
},
|
||
rangeSeparator: {
|
||
type: String,
|
||
default: '至'
|
||
},
|
||
disabled: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
clearable: {
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
format: {
|
||
type: String,
|
||
default: 'YYYY-MM-DD HH:mm:ss'
|
||
},
|
||
defaultTime: {
|
||
type: Array,
|
||
default: () => ['00:00:00', '00:00:00']
|
||
},
|
||
pickerOptions: {
|
||
type: Object,
|
||
default: () => ({shortcuts: []})
|
||
},
|
||
placement: {
|
||
type: String,
|
||
default: 'bottom-start',
|
||
validator: (value) => ['bottom-start', 'bottom-end', 'top-start', 'top-end'].includes(value)
|
||
},
|
||
offset: {
|
||
type: Number,
|
||
default: 4 // 面板与触发器的距离
|
||
}
|
||
},
|
||
data() {
|
||
const now = new Date();
|
||
return {
|
||
showPanel: false,
|
||
currentDate: now.getMonth() + 1,
|
||
currentMonth: now.getFullYear(),
|
||
weekdays: ['日', '一', '二', '三', '四', '五', '六'],
|
||
days: [],
|
||
startDate: null,
|
||
endDate: null,
|
||
startTime: '',
|
||
endTime: '',
|
||
panelStyle: {opacity: 0},
|
||
actualPlacement: this.placement,
|
||
updatePanelPositionDebounced: null, // 防抖函数引用
|
||
calendarCache: {}, // 日历数据缓存
|
||
focusedDayIndex: -1, // 当前焦点日期的索引
|
||
isKeyboardNavigating: false // 是否正在使用键盘导航
|
||
};
|
||
},
|
||
computed: {
|
||
displayValue() {
|
||
if (this.value && this.value.length === 2 && this.value[0] && this.value[1]) {
|
||
const start = this.formatDateTime(this.value[0]);
|
||
const end = this.formatDateTime(this.value[1]);
|
||
// return `${start} 至 ${end}`;
|
||
return [start, end]
|
||
}
|
||
return [];
|
||
},
|
||
// 缓存键,用于判断是否需要重新生成日历
|
||
calendarCacheKey() {
|
||
return `${this.currentMonth}-${this.currentDate}`;
|
||
}
|
||
},
|
||
|
||
watch: {
|
||
value: {
|
||
immediate: true,
|
||
handler(val) {
|
||
if (val && val.length === 2 && val[0] && val[1]) {
|
||
const start = new Date(val[0]);
|
||
const end = new Date(val[1]);
|
||
this.startDate = start;
|
||
this.endDate = end;
|
||
if (this.dateType === 'datetimerange') {
|
||
this.startTime = start;
|
||
this.endTime = end;
|
||
}
|
||
// 同步月份显示
|
||
this.currentMonth = start.getFullYear();
|
||
this.currentDate = start.getMonth() + 1;
|
||
} else {
|
||
this.startDate = null;
|
||
this.endDate = null;
|
||
}
|
||
}
|
||
},
|
||
showPanel(val) {
|
||
if (val) {
|
||
this.generateCalendar();
|
||
document.addEventListener('click', this.handleClickOutside);
|
||
document.addEventListener('scroll', this.updatePanelPositionDebounced, true);
|
||
window.addEventListener('resize', this.updatePanelPositionDebounced);
|
||
document.addEventListener('keydown', this.handleKeydown);
|
||
// 初始化焦点到第一个当前月份的日期
|
||
this.$nextTick(() => {
|
||
this.focusFirstCurrentMonthDay();
|
||
setTimeout(() => {
|
||
this.updatePanelPosition();
|
||
},500);
|
||
});
|
||
} else {
|
||
document.removeEventListener('click', this.handleClickOutside);
|
||
document.removeEventListener('scroll', this.updatePanelPositionDebounced, true);
|
||
window.removeEventListener('resize', this.updatePanelPositionDebounced);
|
||
document.removeEventListener('keydown', this.handleKeydown);
|
||
this.focusedDayIndex = -1;
|
||
this.isKeyboardNavigating = false;
|
||
}
|
||
}
|
||
},
|
||
mounted() {
|
||
this.generateCalendar();
|
||
// 创建防抖函数
|
||
this.updatePanelPositionDebounced = this.debounce(this.updatePanelPosition, 100);
|
||
},
|
||
beforeDestroy() {
|
||
document.removeEventListener('click', this.handleClickOutside);
|
||
document.removeEventListener('scroll', this.updatePanelPositionDebounced, true);
|
||
window.removeEventListener('resize', this.updatePanelPositionDebounced);
|
||
document.removeEventListener('keydown', this.handleKeydown);
|
||
},
|
||
methods: {
|
||
// 简单的防抖函数实现
|
||
debounce(func, wait) {
|
||
let timeout;
|
||
return function executedFunction(...args) {
|
||
const later = () => {
|
||
clearTimeout(timeout);
|
||
func.apply(this, args);
|
||
};
|
||
clearTimeout(timeout);
|
||
timeout = setTimeout(later, wait);
|
||
};
|
||
},
|
||
|
||
// 解析时间字符串为Date对象
|
||
parseTimeString(timeStr) {
|
||
if (!timeStr) return new Date(0, 0, 0, 0, 0, 0);
|
||
const parts = timeStr.split(':').map(Number);
|
||
return new Date(0, 0, 0, parts[0] || 0, parts[1] || 0, parts[2] || 0);
|
||
},
|
||
|
||
// 更新面板位置
|
||
updatePanelPosition() {
|
||
if (!this.showPanel || !this.$refs.triggerRef || !this.$refs.panelRef) {
|
||
return;
|
||
}
|
||
|
||
const trigger = this.$refs.triggerRef;
|
||
const panel = this.$refs.panelRef;
|
||
const viewport = {
|
||
width: window.innerWidth,
|
||
height: window.innerHeight
|
||
};
|
||
|
||
// 获取触发器位置
|
||
const triggerRect = trigger.getBoundingClientRect();
|
||
|
||
// 计算面板基本尺寸
|
||
const panelRect = panel.getBoundingClientRect();
|
||
|
||
// 应用最终位置
|
||
if ((viewport.height - panelRect.height - triggerRect.top - 8) > 0) {
|
||
this.panelStyle = {top: `100%`, opacity: 1};
|
||
} else {
|
||
this.panelStyle = {top: `${Math.round(viewport.height - panelRect.height - triggerRect.top - 8)}px`, opacity: 1};
|
||
}
|
||
},
|
||
// 快捷选项点击事件
|
||
handleShortcutClick(shortcut) {
|
||
if (shortcut.onClick) {
|
||
shortcut.onClick(this);
|
||
}
|
||
},
|
||
generateCalendar() {
|
||
// 检查缓存
|
||
const cacheKey = this.calendarCacheKey;
|
||
if (this.calendarCache[cacheKey]) {
|
||
this.days = this.calendarCache[cacheKey];
|
||
return;
|
||
}
|
||
|
||
const year = this.currentMonth;
|
||
const month = this.currentDate - 1;
|
||
const firstDayOfMonth = new Date(year, month, 1);
|
||
const firstDayWeek = firstDayOfMonth.getDay();
|
||
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
||
const prevMonthDays = new Date(year, month, 0).getDate();
|
||
|
||
const days = [];
|
||
|
||
// 添加上个月的天数
|
||
for (let i = 0; i < firstDayWeek; i++) {
|
||
const day = prevMonthDays - firstDayWeek + i + 1;
|
||
days.push({
|
||
date: day,
|
||
month: 'prev',
|
||
fullDate: new Date(year, month - 1, day)
|
||
});
|
||
}
|
||
|
||
// 添加当月天数
|
||
for (let i = 1; i <= daysInMonth; i++) {
|
||
days.push({
|
||
date: i,
|
||
month: 'current',
|
||
fullDate: new Date(year, month, i)
|
||
});
|
||
}
|
||
|
||
// 添加下个月的天数
|
||
const totalCells = 42; // 6行 * 7天
|
||
const nextMonthDay = 1;
|
||
while (days.length < totalCells) {
|
||
days.push({
|
||
date: nextMonthDay + (days.length - daysInMonth - firstDayWeek),
|
||
month: 'next',
|
||
fullDate: new Date(year, month + 1, nextMonthDay + (days.length - daysInMonth - firstDayWeek))
|
||
});
|
||
}
|
||
|
||
// 更新数据和缓存
|
||
this.days = days;
|
||
this.calendarCache[cacheKey] = days;
|
||
|
||
// 限制缓存大小,避免内存泄漏
|
||
const cacheKeys = Object.keys(this.calendarCache);
|
||
if (cacheKeys.length > 10) {
|
||
delete this.calendarCache[cacheKeys[0]];
|
||
}
|
||
},
|
||
|
||
selectDate(day) {
|
||
if (day.month !== 'current') {
|
||
if (day.month === 'prev') {
|
||
this.prevMonth();
|
||
} else {
|
||
this.nextMonth();
|
||
}
|
||
this.$nextTick(() => {
|
||
this.generateCalendar();
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (!this.startDate) {
|
||
this.startDate = day.fullDate;
|
||
} else if (!this.endDate) {
|
||
this.endDate = day.fullDate;
|
||
// 确保结束时间不早于开始时间
|
||
if (this.endDate < this.startDate) {
|
||
[this.startDate, this.endDate] = [this.endDate, this.startDate];
|
||
}
|
||
} else {
|
||
this.startDate = day.fullDate;
|
||
this.endDate = null;
|
||
}
|
||
if (this.startDate && this.endDate && this.dateType === 'datetimerange') {
|
||
this.startTime = this.startTime ? this.startTime : this.parseTimeString(this.defaultTime[0]);
|
||
this.endTime = this.endTime ? this.endTime : this.parseTimeString(this.defaultTime[1]);
|
||
}
|
||
},
|
||
|
||
getDayClass(day, index) {
|
||
const classes = [];
|
||
|
||
if (day.month !== 'current') {
|
||
classes.push('other-month');
|
||
}
|
||
|
||
if (this.isToday(day.fullDate)) {
|
||
classes.push('today');
|
||
}
|
||
|
||
if (this.startDate && this.isSameDay(day.fullDate, this.startDate)) {
|
||
classes.push('start-date');
|
||
}
|
||
|
||
if (this.endDate && this.isSameDay(day.fullDate, this.endDate)) {
|
||
classes.push('end-date');
|
||
}
|
||
|
||
if (this.isInRange(day.fullDate)) {
|
||
classes.push('in-range');
|
||
}
|
||
|
||
// 添加焦点样式
|
||
if (this.isKeyboardNavigating && index === this.focusedDayIndex) {
|
||
classes.push('focused');
|
||
}
|
||
|
||
return classes;
|
||
},
|
||
|
||
isSameDay(date1, date2) {
|
||
if (!date1 || !date2) return false;
|
||
return date1.getDate() === date2.getDate() &&
|
||
date1.getMonth() === date2.getMonth() &&
|
||
date1.getFullYear() === date2.getFullYear();
|
||
},
|
||
|
||
isInRange(date) {
|
||
if (!this.startDate || !this.endDate) return false;
|
||
const start = new Date(this.startDate);
|
||
const end = new Date(this.endDate);
|
||
start.setHours(0, 0, 0, 0);
|
||
end.setHours(23, 59, 59, 999);
|
||
return date >= start && date <= end;
|
||
},
|
||
|
||
isToday(date) {
|
||
const today = new Date();
|
||
return date.getDate() === today.getDate() &&
|
||
date.getMonth() === today.getMonth() &&
|
||
date.getFullYear() === today.getFullYear();
|
||
},
|
||
applySelection() {
|
||
if (this.startDate && this.endDate) {
|
||
const start = new Date(this.startDate);
|
||
const end = new Date(this.endDate);
|
||
if (this.dateType === 'datetimerange') {
|
||
start.setHours(
|
||
this.startTime.getHours(),
|
||
this.startTime.getMinutes(),
|
||
this.startTime.getSeconds()
|
||
);
|
||
end.setHours(
|
||
this.endTime.getHours(),
|
||
this.endTime.getMinutes(),
|
||
this.endTime.getSeconds()
|
||
);
|
||
}
|
||
if (start > end) {
|
||
this.$message.error('开始时间不能晚于结束时间');
|
||
return;
|
||
}
|
||
let startFormat = this.formatDateTime(start);
|
||
let endFormat = this.formatDateTime(end);
|
||
this.$emit('change', [startFormat, endFormat]);
|
||
this.$emit('input', [startFormat, endFormat]);
|
||
this.showPanel = false;
|
||
}
|
||
},
|
||
|
||
clearSelection() {
|
||
this.startDate = null;
|
||
this.endDate = null;
|
||
this.startTime = '';
|
||
this.endTime = '';
|
||
this.$emit('change', []);
|
||
this.$emit('input', []);
|
||
if (this.showPanel) {
|
||
this.closePanel();
|
||
}
|
||
},
|
||
|
||
closePanel() {
|
||
this.showPanel = false;
|
||
},
|
||
|
||
togglePanel() {
|
||
if (this.disabled) return;
|
||
this.showPanel = !this.showPanel;
|
||
},
|
||
|
||
prevMonth() {
|
||
this.currentDate--;
|
||
if (this.currentDate < 1) {
|
||
this.currentDate = 12;
|
||
this.currentMonth--;
|
||
}
|
||
this.generateCalendar();
|
||
},
|
||
|
||
nextMonth() {
|
||
this.currentDate++;
|
||
if (this.currentDate > 12) {
|
||
this.currentDate = 1;
|
||
this.currentMonth++;
|
||
}
|
||
this.generateCalendar();
|
||
},
|
||
|
||
prevYear() {
|
||
this.currentMonth--;
|
||
this.generateCalendar();
|
||
},
|
||
|
||
nextYear() {
|
||
this.currentMonth++;
|
||
this.generateCalendar();
|
||
},
|
||
|
||
handleClickOutside(event) {
|
||
if (!this.$el.contains(event.target)) {
|
||
this.showPanel = false;
|
||
}
|
||
},
|
||
|
||
// 键盘事件处理
|
||
handleKeydown(event) {
|
||
if (!this.showPanel) return;
|
||
|
||
this.isKeyboardNavigating = true;
|
||
|
||
switch(event.key) {
|
||
case 'ArrowLeft':
|
||
event.preventDefault();
|
||
this.moveFocus(-1); // 向左移动
|
||
break;
|
||
case 'ArrowRight':
|
||
event.preventDefault();
|
||
this.moveFocus(1); // 向右移动
|
||
break;
|
||
case 'ArrowUp':
|
||
event.preventDefault();
|
||
this.moveFocus(-7); // 向上移动
|
||
break;
|
||
case 'ArrowDown':
|
||
event.preventDefault();
|
||
this.moveFocus(7); // 向下移动
|
||
break;
|
||
case 'Enter':
|
||
event.preventDefault();
|
||
this.selectFocusedDay();
|
||
break;
|
||
case 'Escape':
|
||
event.preventDefault();
|
||
this.closePanel();
|
||
break;
|
||
case 'Tab':
|
||
// 允许Tab键正常导航
|
||
this.isKeyboardNavigating = false;
|
||
break;
|
||
}
|
||
},
|
||
|
||
// 聚焦到第一个当前月份的日期
|
||
focusFirstCurrentMonthDay() {
|
||
const firstCurrentMonthIndex = this.days.findIndex(day => day.month === 'current');
|
||
if (firstCurrentMonthIndex !== -1) {
|
||
this.focusedDayIndex = firstCurrentMonthIndex;
|
||
} else {
|
||
this.focusedDayIndex = 0;
|
||
}
|
||
},
|
||
|
||
// 移动焦点
|
||
moveFocus(delta) {
|
||
if (this.days.length === 0) return;
|
||
|
||
let newIndex = this.focusedDayIndex + delta;
|
||
|
||
// 边界检查
|
||
if (newIndex < 0) {
|
||
// 移动到上个月
|
||
this.prevMonth();
|
||
this.$nextTick(() => {
|
||
newIndex = this.days.length - 7 + (newIndex % 7);
|
||
if (newIndex < 0) newIndex = 0;
|
||
this.focusedDayIndex = newIndex;
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (newIndex >= this.days.length) {
|
||
// 移动到下个月
|
||
this.nextMonth();
|
||
this.$nextTick(() => {
|
||
newIndex = newIndex % 7;
|
||
this.focusedDayIndex = newIndex;
|
||
});
|
||
return;
|
||
}
|
||
|
||
this.focusedDayIndex = newIndex;
|
||
},
|
||
|
||
// 选择当前焦点的日期
|
||
selectFocusedDay() {
|
||
if (this.focusedDayIndex >= 0 && this.focusedDayIndex < this.days.length) {
|
||
const day = this.days[this.focusedDayIndex];
|
||
this.selectDate(day);
|
||
}
|
||
},
|
||
|
||
formatDateTime(date) {
|
||
if (!date) return '';
|
||
const d = new Date(date);
|
||
const year = d.getFullYear();
|
||
const month = (d.getMonth() + 1).toString().padStart(2, '0');
|
||
const day = d.getDate().toString().padStart(2, '0');
|
||
const hour = d.getHours().toString().padStart(2, '0');
|
||
const minute = d.getMinutes().toString().padStart(2, '0');
|
||
|
||
if (this.format === 'YYYY-MM-DD HH:mm:ss' || this.format === 'yyyy-MM-dd HH:mm:ss') {
|
||
const second = d.getSeconds().toString().padStart(2, '0');
|
||
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
|
||
} else if (this.format === 'YYYY-MM-DD' || this.format === 'yyyy-MM-dd') {
|
||
return `${year}-${month}-${day}`;
|
||
} else {
|
||
if (this.dateType === 'datetimerange') {
|
||
const second = d.getSeconds().toString().padStart(2, '0');
|
||
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
|
||
} else if (this.dateType === 'daterange') {
|
||
return `${year}-${month}-${day} ${hour}:${minute}`;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.custom-datetime-range-picker {
|
||
position: relative;
|
||
display: inline-block;
|
||
width: 100%;
|
||
}
|
||
/* 输入框前面的按钮*/
|
||
.el-input_dateIcon {
|
||
position: absolute;
|
||
}
|
||
.el-range__icon {
|
||
font-size: 14px;
|
||
color: #c0c4cc;
|
||
float: left;
|
||
line-height: 32px;
|
||
}
|
||
/* 输入框样式 */
|
||
.el-input_parent {
|
||
position: relative;
|
||
font-size: 14px;
|
||
display: inline-block;
|
||
width: 100%;
|
||
height: 36px;
|
||
border-radius: 4px;
|
||
border: 1px solid #dcdfe6;
|
||
padding: 1px 0;
|
||
}
|
||
.el-input_parent:hover {
|
||
border-color: #c0c4cc;
|
||
}
|
||
|
||
.el-input_parent.is-focus {
|
||
border-color: #409eff;
|
||
}
|
||
|
||
.el-input_parent.is-disabled {
|
||
background-color: #f5f7fa;
|
||
border-color: #e4e7ed;
|
||
color: #c0c4cc;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.el-input_first {
|
||
padding: 0 0 0 25px;
|
||
}
|
||
.el-input_second {
|
||
padding: 0 30px 0 0;
|
||
}
|
||
.el-range-input {
|
||
-webkit-appearance: none;
|
||
background-color: #fff;
|
||
background-image: none;
|
||
border-radius: 4px;
|
||
border: none;
|
||
box-sizing: border-box;
|
||
color: #606266;
|
||
display: inline-block;
|
||
font-size: inherit;
|
||
height: 100%;
|
||
line-height: 100%;
|
||
outline: none;
|
||
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||
width: calc((100% - 30px) / 2);
|
||
cursor: pointer;
|
||
text-align: center;
|
||
}
|
||
|
||
.el-input__suffix {
|
||
display: none;
|
||
position: absolute;
|
||
height: 100%;
|
||
right: 5px!important;
|
||
top: 0;
|
||
text-align: center;
|
||
color: #c0c4cc;
|
||
transition: all 0.3s;
|
||
pointer-events: none;
|
||
}
|
||
/*.el-input:hover .el-input__suffix {*/
|
||
/* display: block;*/
|
||
/*}*/
|
||
|
||
.el-input_parent:hover .el-input__suffix {
|
||
display: block;
|
||
}
|
||
|
||
.el-input__suffix-inner {
|
||
pointer-events: all;
|
||
}
|
||
|
||
.el-input__icon {
|
||
height: 100%;
|
||
width: 25px;
|
||
text-align: center;
|
||
transition: all 0.3s;
|
||
line-height: 100%;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.el-input__icon:hover {
|
||
color: #409eff;
|
||
}
|
||
|
||
.el-icon-close:hover {
|
||
color: #f56c6c;
|
||
}
|
||
|
||
/* 面板容器 */
|
||
.time-range-panel {
|
||
position: absolute;
|
||
top: 100%;
|
||
right: 0;
|
||
z-index: 2001;
|
||
margin-top: 5px;
|
||
background: #fff;
|
||
border: 1px solid #e4e7ed;
|
||
border-radius: 4px;
|
||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.panel-body {
|
||
display: flex;
|
||
min-height: 350px;
|
||
}
|
||
/* 快捷选项面板 */
|
||
.quick-panel {
|
||
width: 110px;
|
||
padding-top: 6px;
|
||
}
|
||
|
||
/* 左侧日期面板 */
|
||
.date-panel {
|
||
/*flex: 1;*/
|
||
width: 320px;
|
||
padding: 0 20px 20px;
|
||
border-right: 1px solid #e4e7ed;
|
||
}
|
||
|
||
.panel-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 20px;
|
||
padding: 0 4px;
|
||
}
|
||
|
||
.icon-btn {
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
color: #303133;
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 4px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.2s;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.icon-btn:hover {
|
||
background-color: #f5f7fa;
|
||
color: #409eff;
|
||
}
|
||
|
||
.header-label {
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
color: #303133;
|
||
flex: 1;
|
||
text-align: center;
|
||
cursor: pointer;
|
||
padding: 4px 8px;
|
||
border-radius: 4px;
|
||
transition: background-color 0.2s;
|
||
}
|
||
|
||
.header-label:hover {
|
||
background-color: #f5f7fa;
|
||
}
|
||
|
||
/* 周几标题 */
|
||
.weekdays {
|
||
display: grid;
|
||
grid-template-columns: repeat(7, 1fr);
|
||
gap: 4px;
|
||
margin-bottom: 12px;
|
||
padding: 0 2px;
|
||
}
|
||
|
||
.weekday {
|
||
text-align: center;
|
||
font-size: 12px;
|
||
color: #606266;
|
||
height: 24px;
|
||
line-height: 24px;
|
||
}
|
||
|
||
/* 日历表格 */
|
||
.calendar {
|
||
display: grid;
|
||
grid-template-columns: repeat(7, 1fr);
|
||
grid-template-rows: repeat(6, 1fr);
|
||
gap: 4px;
|
||
}
|
||
|
||
.day-cell {
|
||
height: 36px;
|
||
position: relative;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
|
||
.day-content {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
transition: all 0.2s;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.day-cell:hover .day-content {
|
||
background-color: #f2f6fc;
|
||
color: #409eff;
|
||
}
|
||
|
||
.day-cell.today .day-content {
|
||
color: #409eff;
|
||
font-weight: 500;
|
||
border: 1px solid #409eff;
|
||
}
|
||
|
||
.day-cell.other-month .day-content {
|
||
color: #c0c4cc;
|
||
}
|
||
|
||
.day-cell.start-date .day-content,
|
||
.day-cell.end-date .day-content {
|
||
background: #409eff;
|
||
color: white;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.day-cell.focused .day-content {
|
||
box-shadow: 0 0 0 2px #409eff;
|
||
outline: none;
|
||
z-index: 2;
|
||
}
|
||
|
||
.day-cell.in-range:before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
background: #f0f9ff;
|
||
z-index: 0;
|
||
}
|
||
|
||
.day-cell.start-date.in-range:before {
|
||
left: 50%;
|
||
}
|
||
|
||
.day-cell.end-date.in-range:before {
|
||
right: 50%;
|
||
}
|
||
|
||
/* 中间分隔线 */
|
||
.panel-divider {
|
||
width: 1px;
|
||
background-color: #e4e7ed;
|
||
/*margin: 20px 0;*/
|
||
}
|
||
|
||
/* 面板底部 */
|
||
.panel-footer {
|
||
padding: 4px;
|
||
border-top: 1px solid #e4e7ed;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.footer-btn {
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
font-weight: bold;
|
||
padding: 6px 12px;
|
||
border-radius: 4px;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.clear-btn {
|
||
color: #409eff;
|
||
}
|
||
|
||
.clear-btn:hover {
|
||
color: #66b1ff;
|
||
background: transparent;
|
||
}
|
||
|
||
.footer-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
/* Element UI 按钮样式 */
|
||
.el-button {
|
||
display: inline-block;
|
||
line-height: 1;
|
||
white-space: nowrap;
|
||
cursor: pointer;
|
||
background: #fff;
|
||
border: 1px solid #dcdfe6;
|
||
color: #606266;
|
||
text-align: center;
|
||
box-sizing: border-box;
|
||
outline: none;
|
||
margin: 0;
|
||
transition: 0.1s;
|
||
font-weight: 500;
|
||
padding: 7px 15px;
|
||
font-size: 12px;
|
||
border-radius: 4px;
|
||
min-width: 60px;
|
||
}
|
||
|
||
.el-button--mini {
|
||
padding: 7px 15px;
|
||
font-size: 12px;
|
||
min-width: 50px;
|
||
}
|
||
|
||
.is-plain:hover {
|
||
color: #409eff;
|
||
background: #fff;
|
||
border-color: #409eff;
|
||
}
|
||
|
||
.is-plain:disabled, .is-plain:disabled:hover {
|
||
cursor: not-allowed;
|
||
background: #fff;
|
||
border-color: #ebeef5;
|
||
color: #c0c4cc;
|
||
}
|
||
::v-deep .el-input.is-disabled .el-range-input {
|
||
background: #F5F7FA!important;
|
||
border-color: #e4e7ed!important;
|
||
color: #c0c4cc;
|
||
}
|
||
</style>
|