Files
maintenance_system/src/views/ueScreen/components/date.vue
2025-11-15 16:40:35 +08:00

255 lines
5.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>