Files
maintenance_system/src/views/ueScreen/components/date.vue

255 lines
5.6 KiB
Vue
Raw Normal View History

2025-11-15 16:40:35 +08:00
<template>
<div class="year-month-picker">
<!-- 年份选择器 -->
<div class="picker-group">
<div class="picker-input" @click="isYearOpen = !isYearOpen" :class="{ 'open': isYearOpen }">
<span class="value">{{ selectedYear }}</span>
<span class="arrow"></span>
</div>
<ul class="options" v-show="isYearOpen">
<li v-for="year in years" :key="year" :class="{ 'selected': year === selectedYear }"
@click="handleYearSelect(year)">
{{ year }}
</li>
</ul>
</div>
<!-- 月份选择器 -->
<div class="picker-group">
<div class="picker-input" @click="isMonthOpen = !isMonthOpen" :class="{ 'open': isMonthOpen }">
<span class="value">{{ selectedMonth }}</span>
<span class="arrow"></span>
</div>
<ul class="options" v-show="isMonthOpen">
<li v-for="month in 12" :key="month" :class="{ 'selected': month === selectedMonth }"
@click="handleMonthSelect(month)">
{{ month }}
</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue';
const props = defineProps({
year: {
type: Number,
required: true,
},
month: {
type: Number,
required: true,
},
startYear: {
type: Number,
default: 2000,
},
endYear: {
type: Number,
default: new Date().getFullYear(),
},
disabled: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['update:year', 'update:month', 'change']);
// 计算年份列表
const years = computed(() => {
const yearList = [];
for (let y = props.startYear; y <= props.endYear; y++) {
yearList.push(y);
}
return yearList;
});
// 内部状态
const selectedYear = ref(props.year);
const selectedMonth = ref(props.month);
const isYearOpen = ref(false);
const isMonthOpen = ref(false);
// 监听props变化同步到内部状态
watch(
() => [props.year, props.month],
([newYear, newMonth]) => {
selectedYear.value = newYear;
selectedMonth.value = newMonth;
},
{ immediate: true }
);
// 当内部值变化时,通知父组件
const notifyParent = () => {
emit('update:year', selectedYear.value);
emit('update:month', selectedMonth.value);
emit('change', { year: selectedYear.value, month: selectedMonth.value });
};
// 处理年份选择
const handleYearSelect = (year) => {
selectedYear.value = year;
isYearOpen.value = false;
notifyParent();
};
// 处理月份选择
const handleMonthSelect = (month) => {
selectedMonth.value = month;
isMonthOpen.value = false;
notifyParent();
};
// 点击外部关闭下拉框
const handleClickOutside = (event) => {
if (!event.target.closest('.year-month-picker')) {
isYearOpen.value = false;
isMonthOpen.value = false;
}
};
// 挂载时添加事件监听
onMounted(() => {
document.addEventListener('click', handleClickOutside);
// 验证初始值
if (!years.value.includes(selectedYear.value)) {
selectedYear.value = Math.max(props.startYear, Math.min(selectedYear.value, props.endYear));
}
selectedMonth.value = Math.max(1, Math.min(selectedMonth.value, 12));
notifyParent(); // 确保初始值正确通知
});
// 卸载时移除事件监听,防止内存泄漏
onBeforeUnmount(() => {
document.removeEventListener('click', handleClickOutside);
});
</script>
<style scoped lang="scss">
$vm_base: 1920;
$vh_base: 1080;
// 计算vw
@function vw($px) {
@return calc(($px / $vm_base) * 100vw);
}
// 计算vh
@function vh($px) {
@return calc(($px / $vh_base) * 100vh);
}
.year-month-picker {
display: inline-flex;
border-radius: vw(8);
font-size: vw(14);
background-color: transparent;
box-shadow: 0 vh(2) vh(8) rgba(0, 0, 0, 0.08);
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
border: vw(1) solid #c0c4cc;
}
.year-month-picker:hover {
border-color: #909399;
box-shadow: 0 vh(4) vh(12) rgba(0, 0, 0, 0.1);
}
.picker-group {
position: relative;
}
.picker-input {
width: fit-content;
display: flex;
gap: vw(8);
justify-content: space-between;
align-items: center;
padding: vh(8) vw(16);
cursor: pointer;
user-select: none;
border-right: vw(1) solid #e9e9e9;
}
/* 移除最后一个输入框的右边框 */
.picker-group:last-child .picker-input {
border-right: none;
}
.picker-input .value {
color: #fff;
}
.picker-input.open .arrow {
transform: rotate(180deg);
}
.arrow {
display: inline-block;
width: 0;
height: 0;
border-style: solid;
border-width: vw(6) vw(5) 0 vw(5);
border-color: #909399 transparent transparent transparent;
transition: transform 0.2s ease-in-out;
}
/* 美化后的下拉选项窗口 */
.options {
position: absolute;
top: 110%;
left: 0;
right: 0;
max-height: vh(200); /* 限制最大高度并可滚动 */
overflow-y: auto;
background-color: rgba(0, 0, 0, 0.6);
border-radius: vw(4);
box-shadow: 0 vh(4) vh(12) rgba(0, 0, 0, 0.15); /* 更明显的阴影 */
list-style: none;
z-index: 10;
padding: vh(4) 0;
margin: 0;
border: vw(1) solid #ebeef5;
}
.options li {
padding: vh(10) vw(16);
cursor: pointer;
transition: background-color 0.2s;
white-space: nowrap;
color: #fff;
}
.options li:hover {
background-color: #f5f7fa;
color: #1890ff;
}
.options li.selected {
// background-color: #e6f7ff;
color: #1890ff;
font-weight: 500;
}
/* 美化滚动条 (WebKit浏览器) */
.options::-webkit-scrollbar {
width: vw(6);
}
.options::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: vw(10);
}
.options::-webkit-scrollbar-thumb {
background: #c9c9c9;
border-radius: vw(10);
}
.options::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
</style>