基于elementPlus二次封装的季度选择器组件

360 阅读3分钟

对于一些可能会用到的组件进行封装,为了方便以后用的时候直接使用。 quarter.vue


<template>
	<el-popover ref="quarterRef" popper-class="popper-quarter" placement="bottom-start" trigger="click" :width="props.popoverWidth" :hide-after="10">
		<template #reference>
			<el-input
				ref="quarterValueRef"
				:model-value="quarter"
				:style="{ width: `${props.width}px` }"
				:placeholder="props.placeholder"
				:clearable="props.clearable"
				:size="props.size"
				@input="handleChange"
				@clear="handleClear"
			>
				<template #prefix>
					<el-icon :size="props.size" class="el-input__icon"><Calendar /></el-icon>
				</template>
			</el-input>
		</template>
		<template #default>
			<div class="quarterList">
				<div class="quarter-header">
					<div class="quarter-header-left" @click="handleLastYear">
						<el-icon :size="props.size" class="yearClass"><DArrowLeft /></el-icon>
					</div>
					<div class="quarter-header-center" @click="handleChangeYear">
						<span class="yearClass">{{ yearRangeName }}</span>
					</div>
					<div class="quarter-header-right" @click="handleNextYear">
						<el-icon :size="props.size" class="yearClass"><DArrowRight /></el-icon>
					</div>
				</div>
				<div class="quarter-content">
					<div
						@click="handleClickItem(item.value)"
						:style="{ width: type === 'quarter' ? '50%' : '25%' }"
						class="quarter-content-item"
						v-for="item in contentList"
						:key="item.value"
					>
						{{ item.label }}
					</div>
				</div>
			</div>
		</template>
	</el-popover>
</template>
<script setup lang="ts">
const props = defineProps({
	modelValue: {
		type: String,
		required: true,
		default: '',
	},
	placeholder: {
		type: String,
		default: '请选择季度',
	},
	clearable: {
		type: Boolean,
		default: false,
	},
	// input组件的宽度
	width: {
		type: Number,
		default: 240,
	},
	// 弹出框的宽度
	popoverWidth: {
		type: Number,
		default: 240,
	},
	size: {
		type: String,
		default: 'default', //'large' | 'default' | 'small'
	},
	//formmat的值为 'YYYYQQ' || 'YYYYCQ' 默认为'YYYYQQ'
	//'YYYYQQ' 季度格式 2024-Q1
	//'YYYYCQ' 中文季度格式2024-第一季度
	formmat: {
		type: String,
		default: 'YYYYQQ',
	},
	//value_formmat的值为 'YYYYQQ' || 'YYYY-QQ' || 'YYYY/QQ' 默认为'YYYY-QQ'
	//'YYYYQQ' 实际值为 '2024Q1'
	//'YYYY-QQ' 实际值为 '2024-Q1'
	//'YYYY/QQ' 实际值为 '2024/Q1'
	value_formmat: {
		type: String,
		default: 'YYYY-QQ',
	},
	//分隔符默认为'-' 可自定义 比如'/'
	splitter: {
		type: String,
		default: '-',
	},
});
// 'update:modelValue',
const emit = defineEmits(['change']);
const quarterRef = ref();
const quarterValueRef = ref();
const quarter = ref(props.modelValue); // 季度值
const currentYear = ref(new Date().getFullYear()); //当前年份
const yearRangeName = ref(''); // 年份范围
const type = ref('quarter'); // 类型 默认'quarter' 季度,点击年时为 'year'
interface ContentList {
	label: string;
	value: number | string;
}
const contentList = ref<ContentList[]>([]); // 内容列表选项
onMounted(() => {
	// yearRangeName.value = currentYear.value + '年';
	yearRangeName.value = currentYear.value.toString();
	getType();
});
/**
 * 获取类型信息,并根据类型更新内容列表和年份范围名称
 *
 * @returns 无返回值
 */
const getType = () => {
	if (type.value === 'quarter') {
		contentList.value = [
			{ label: '第一季度', value: 'Q1' },
			{ label: '第二季度', value: 'Q2' },
			{ label: '第三季度', value: 'Q3' },
			{ label: '第四季度', value: 'Q4' },
		];
		// yearRangeName.value = currentYear.value + '年';
		yearRangeName.value = currentYear.value.toString();
	} else {
		contentList.value = getCurrentYearsList();
		// yearRangeName.value = contentList.value[0].value + '年' + ' ~ ' + contentList.value[contentList.value.length - 1].value + '年';
		yearRangeName.value = contentList.value[0].value + ' ~ ' + contentList.value[contentList.value.length - 1].value;
	}
};
//点击上一年
const handleLastYear = () => {
	if (type.value === 'year') {
		currentYear.value -= 10;
	} else {
		currentYear.value -= 1;
	}
	getType();
};
//点击下一年
const handleNextYear = () => {
	if (type.value === 'year') {
		currentYear.value -= 10;
	} else {
		currentYear.value += 1;
	}
	getType();
};
//点击年时触发选择年份列表
const handleChangeYear = () => {
	type.value = 'year';
	getType();
};
//获取年份列表
const getCurrentYearsList = () => {
	// 初始化年份列表
	const yearsList = [];
	// 添加前5年的年份
	for (let i = 4; i > 0; i--) {
		yearsList.push({
			label: (currentYear.value - i).toString(),
			value: currentYear.value - i,
		});
	}
	// 添加当前年份
	yearsList.push({
		label: currentYear.value.toString(),
		value: currentYear.value,
	});
	// 添加后5年的年份
	for (let i = 1; i <= 5; i++) {
		yearsList.push({
			label: (currentYear.value + i).toString(),
			value: currentYear.value + i,
		});
	}

	return yearsList;
};
//获取季度格式化后显示的值
const getQuarterFormmat = (val: any) => {
	let resultVal = '';
	if (props.formmat === 'YYYYQQ') {
		resultVal = currentYear.value + props.splitter + val;
	}
	if (props.formmat === 'YYYYCQ') {
		resultVal = currentYear.value + props.splitter + contentList.value.filter((item) => item.value === val)[0].label;
	}
	return resultVal;
};

//点击内容列表项
const handleClickItem = (item: any) => {
	if (type.value === 'quarter') {
		quarter.value = getQuarterFormmat(item);
		// quarter.value = currentYear.value + '-' + item;
		// emit('update:modelValue', quarter.value);
		nextTick(() => {
			quarterRef.value?.hide();
			quarterValueRef.value?.blur();
		});
	}
	if (type.value === 'year') {
		currentYear.value = item;
		type.value = 'quarter';
		getType();
	}
};
//获取季度格式化后的值
const getQuarterValue = (val: String) => {
	let parts = val.split(props.splitter);
	let transVal = '';
	switch (parts[1]) {
		case '第一季度':
			transVal = 'Q1';
			break;
		case '第二季度':
			transVal = 'Q1';
			break;
		case '第三季度':
			transVal = 'Q1';
			break;
		case '第四季度':
			transVal = 'Q1';
			break;
		default:
			transVal = parts[1];
			break;
	}
	let resultVal = '';
	if (props.value_formmat === 'YYYYQQ') {
		resultVal = parts[0] + transVal;
	}
	if (props.value_formmat === 'YYYY-QQ') {
		resultVal = parts[0] + '-' + transVal;
	}
	if (props.value_formmat === 'YYYY/QQ') {
		resultVal = parts[0] + '/' + transVal;
	}
	return resultVal;
};
//输入框内的值发生改变时触发
const handleChange = (event: any) => {
	// emit('update:modelValue', getQuarterValue(event));
	emit('change', getQuarterValue(event));
	setTimeout(() => {
		quarterRef.value?.hide();
		quarterValueRef.value?.blur();
	}, 10);
};
// 数据监听----------------------------------------------------
watch(
	() => quarter.value,
	(newVal) => {
		handleChange(newVal);
	},
	{
		deep: true,
		immediate: true,
	}
);
//清空输入框时关闭下拉框
const handleClear = () => {
	quarter.value = '';
	setTimeout(() => {
		// quarterRef.value?.hide(); //不需要了,因为失去焦点时会自动关闭
		quarterValueRef.value?.blur();
	}, 0);
	//nextTick不生效的
	// nextTick(() => {
	// 	quarterValueRef.value?.blur();
	// 	quarterRef.value?.hide();
	// });
};
</script>

<style lang="scss" scoped>
// .popper-quarter {
// 	padding: 0 !important;
// 	box-sizing: border-box;
// 	min-width: 240px !important;
// }
.quarterList {
	width: 100%;
	min-width: 200px;
}
.quarter-header {
	height: 42px;
	display: flex;
	justify-content: space-between;
	align-items: center;
	padding: 12px 12px;
	box-sizing: border-box;
	border-bottom: 1px solid #e4e7ed;
	font-size: 16px;
	font-weight: 600;

	.quarter-header-left,
	.quarter-header-center,
	.quarter-header-right {
		cursor: pointer;
		.yearClass {
			vertical-align: middle;
		}
	}
}
.quarter-content {
	display: flex;
	justify-content: flex-start;
	flex-wrap: wrap;
	align-items: center;
	margin: 15px 15px 0;
	box-sizing: border-box;
	.quarter-content-item {
		padding: 0px 0px 20px;
		box-sizing: border-box;
		text-align: center;
		font-size: 14px;
		cursor: pointer;
		&:hover {
			color: #409eff;
		}
	}
}
</style>


父组件使用 parent.vue 1.先引入

const Quarter = defineAsyncComponent(() => import('./quarter.vue'));
const quarter = ref('2024-Q4'); //季度
const getQuearter = (val) => {
	console.log(quarter.value, val, '-----------------');
};

2.使用

<Quarter v-model="quarter" :width="240" size="default" @change="getQuearter" :clearable="true" />

3.加全局样式控制显示样式

.popper-quarter {
	padding: 0 !important;
	box-sizing: border-box;
	min-width: 240px !important;
}

文档说明

属性名说明类型默认
v-model绑定值string''
placeholder非选择时的占位内容string''
clearable是否显示清除按钮booleanfalse
width组件展示的宽度number240
popoverWidth选中弹出框展示的宽度number240
size输入框尺寸'large' / 'default' / 'small''default'
formmat展示内容格式化'YYYYQQ' / 'YYYYCQ''YYYYQQ'
value_formmat绑定值的格式化'YYYYQQ' / 'YYYY-QQ' / 'YYYY/QQ''YYYY-QQ'
splitter分隔符string(如'/'或'-')'-'