服务器下发业务、监控策略、脚本策略每次打开弹窗请求数据 ;交换机监控策略引用旧策略列表分页功能修改

自定义日期时间区间组件
This commit is contained in:
康冉冉
2026-02-13 15:19:16 +08:00
parent e307b90317
commit ab37536df5
6 changed files with 1036 additions and 44 deletions
@@ -0,0 +1,892 @@
<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)"
@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
};
},
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 [];
}
},
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.updatePanelPosition, true);
window.addEventListener('resize', this.updatePanelPosition);
this.$nextTick(() => {
setTimeout(() => {
this.updatePanelPosition();
},500);
});
} else {
document.removeEventListener('click', this.handleClickOutside);
document.removeEventListener('scroll', this.updatePanelPosition, true);
window.removeEventListener('resize', this.updatePanelPosition);
}
}
},
mounted() {
this.generateCalendar();
},
beforeDestroy() {
document.removeEventListener('click', this.handleClickOutside);
document.removeEventListener('scroll', this.updatePanelPosition, true);
window.removeEventListener('resize', this.updatePanelPosition);
},
methods: {
// 更新面板位置
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 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();
this.days = [];
// 添加上个月的天数
for (let i = 0; i < firstDayWeek; i++) {
const day = prevMonthDays - firstDayWeek + i + 1;
this.days.push({
date: day,
month: 'prev',
fullDate: new Date(year, month - 1, day)
});
}
// 添加当月天数
for (let i = 1; i <= daysInMonth; i++) {
this.days.push({
date: i,
month: 'current',
fullDate: new Date(year, month, i)
});
}
// 添加下个月的天数
const totalCells = 42; // 6行 * 7天
const nextMonthDay = 1;
while (this.days.length < totalCells) {
this.days.push({
date: nextMonthDay + (this.days.length - daysInMonth - firstDayWeek),
month: 'next',
fullDate: new Date(year, month + 1, nextMonthDay + (this.days.length - daysInMonth - firstDayWeek))
});
}
},
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 : new Date(0,0,0,this.defaultTime[0].split(':').map(Number)[0],this.defaultTime[0].split(':').map(Number)[1],this.defaultTime[0].split(':').map(Number)[2]);
this.endTime = this.endTime ? this.endTime : new Date(0,0,0,this.defaultTime[1].split(':').map(Number)[0],this.defaultTime[1].split(':').map(Number)[1],this.defaultTime[1].split(':').map(Number)[2]);
}
},
getDayClass(day) {
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');
}
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;
}
},
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.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>