274 lines
5.5 KiB
Plaintext
274 lines
5.5 KiB
Plaintext
<template>
|
||
<view
|
||
class="cl-calendar-picker"
|
||
:class="{
|
||
'is-dark': isDark
|
||
}"
|
||
v-if="visible"
|
||
>
|
||
<view class="cl-calendar-picker__header">
|
||
<view
|
||
class="cl-calendar-picker__prev"
|
||
:class="{
|
||
'is-dark': isDark
|
||
}"
|
||
@tap="prev"
|
||
>
|
||
<cl-icon name="arrow-left-double-line"></cl-icon>
|
||
</view>
|
||
|
||
<view class="cl-calendar-picker__date" @tap="toMode('year')">
|
||
<cl-text
|
||
:pt="{
|
||
className: 'text-lg'
|
||
}"
|
||
>
|
||
{{ title }}
|
||
</cl-text>
|
||
</view>
|
||
|
||
<view
|
||
class="cl-calendar-picker__next"
|
||
:class="{
|
||
'is-dark': isDark
|
||
}"
|
||
@tap="next"
|
||
>
|
||
<cl-icon name="arrow-right-double-line"></cl-icon>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="cl-calendar-picker__list">
|
||
<view
|
||
class="cl-calendar-picker__item"
|
||
v-for="item in list"
|
||
:key="item.value"
|
||
@tap="select(item.value)"
|
||
>
|
||
<cl-text
|
||
:pt="{
|
||
className: parseClass([[item.value == value, 'text-primary-500']])
|
||
}"
|
||
>{{ item.label }}</cl-text
|
||
>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { first, isDark, last, parseClass } from "@/cool";
|
||
import { t } from "@/locale";
|
||
import { computed, ref } from "vue";
|
||
|
||
defineOptions({
|
||
name: "cl-calendar-picker"
|
||
});
|
||
|
||
// 定义日历选择器的条目类型
|
||
type Item = {
|
||
label: string; // 显示的标签,如"1月"、"2024"
|
||
value: number; // 对应的数值,如1、2024
|
||
};
|
||
|
||
// 定义组件接收的属性:年份和月份,均为数字类型,默认值为0
|
||
const props = defineProps({
|
||
year: {
|
||
type: Number,
|
||
default: 0
|
||
},
|
||
month: {
|
||
type: Number,
|
||
default: 0
|
||
}
|
||
});
|
||
|
||
// 定义组件可触发的事件,这里只定义了"change"事件
|
||
const emit = defineEmits(["change"]);
|
||
|
||
// 当前选择的模式,"year"表示选择年份,"month"表示选择月份,默认是"month"
|
||
const mode = ref<"year" | "month">("month");
|
||
|
||
// 当前选中的年份
|
||
const year = ref(0);
|
||
|
||
// 当前选中的月份
|
||
const month = ref(0);
|
||
|
||
// 当前年份选择面板的起始年份(如2020-2029,则startYear为2020)
|
||
const startYear = ref(0);
|
||
|
||
// 当前选中的值,若为月份模式则为月份,否则为年份
|
||
const value = computed(() => {
|
||
return mode.value == "month" ? month.value : year.value;
|
||
});
|
||
|
||
// 计算可供选择的列表:
|
||
// - 若为月份模式,返回1-12月
|
||
// - 若为年份模式,返回以startYear为起点的连续10年
|
||
const list = computed(() => {
|
||
if (mode.value == "month") {
|
||
return [
|
||
{ label: t("1月"), value: 1 },
|
||
{ label: t("2月"), value: 2 },
|
||
{ label: t("3月"), value: 3 },
|
||
{ label: t("4月"), value: 4 },
|
||
{ label: t("5月"), value: 5 },
|
||
{ label: t("6月"), value: 6 },
|
||
{ label: t("7月"), value: 7 },
|
||
{ label: t("8月"), value: 8 },
|
||
{ label: t("9月"), value: 9 },
|
||
{ label: t("10月"), value: 10 },
|
||
{ label: t("11月"), value: 11 },
|
||
{ label: t("12月"), value: 12 }
|
||
] as Item[];
|
||
} else {
|
||
const years: Item[] = [];
|
||
// 生成10个连续年份
|
||
for (let i = 0; i < 10; i++) {
|
||
years.push({
|
||
label: `${startYear.value + i}`,
|
||
value: startYear.value + i
|
||
});
|
||
}
|
||
return years;
|
||
}
|
||
});
|
||
|
||
// 计算标题内容:
|
||
// - 月份模式下显示“xxxx年”
|
||
// - 年份模式下显示“起始年 - 结束年”
|
||
const title = computed(() => {
|
||
return mode.value == "month"
|
||
? `${year.value}年`
|
||
: `${first(list.value)?.label} - ${last(list.value)?.label}`;
|
||
});
|
||
|
||
// 控制选择器弹窗的显示与隐藏
|
||
const visible = ref(false);
|
||
|
||
/**
|
||
* 打开选择器,并初始化年份、月份、起始年份
|
||
*/
|
||
function open() {
|
||
visible.value = true;
|
||
|
||
// 初始化当前年份和月份为传入的props
|
||
year.value = props.year;
|
||
month.value = props.month;
|
||
|
||
// 计算当前年份所在的十年区间的起始年份
|
||
startYear.value = Math.floor(year.value / 10) * 10;
|
||
}
|
||
|
||
/**
|
||
* 关闭选择器
|
||
*/
|
||
function close() {
|
||
visible.value = false;
|
||
}
|
||
|
||
/**
|
||
* 切换选择模式(年份/月份)
|
||
* @param val "year" 或 "month"
|
||
*/
|
||
function toMode(val: "year" | "month") {
|
||
mode.value = val;
|
||
}
|
||
|
||
/**
|
||
* 选择某个值(年份或月份)
|
||
* @param val 选中的值
|
||
*/
|
||
function select(val: number) {
|
||
if (mode.value == "month") {
|
||
// 选择月份后,关闭弹窗并触发change事件
|
||
month.value = val;
|
||
close();
|
||
emit("change", [year.value, month.value]);
|
||
} else {
|
||
// 选择年份后,切换到月份选择模式
|
||
year.value = val;
|
||
toMode("month");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 切换到上一个区间
|
||
* - 月份模式下,年份减1
|
||
* - 年份模式下,起始年份减10
|
||
*/
|
||
function prev() {
|
||
if (mode.value == "month") {
|
||
year.value -= 1;
|
||
} else {
|
||
startYear.value -= 10;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 切换到下一个区间
|
||
* - 月份模式下,年份加1
|
||
* - 年份模式下,起始年份加10
|
||
*/
|
||
function next() {
|
||
if (mode.value == "month") {
|
||
year.value += 1;
|
||
} else {
|
||
startYear.value += 10;
|
||
}
|
||
}
|
||
|
||
defineExpose({
|
||
open,
|
||
close
|
||
});
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.cl-calendar-picker {
|
||
@apply flex flex-col absolute left-0 top-0 w-full h-full bg-white z-10;
|
||
|
||
&__header {
|
||
@apply flex flex-row items-center justify-between w-full p-3;
|
||
}
|
||
|
||
&__prev,
|
||
&__next {
|
||
@apply flex flex-row items-center justify-center rounded-full bg-surface-100;
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
|
||
&.is-dark {
|
||
@apply bg-surface-700;
|
||
}
|
||
}
|
||
|
||
&__date {
|
||
@apply flex flex-row items-center justify-center;
|
||
}
|
||
|
||
&__list {
|
||
@apply flex flex-row flex-wrap;
|
||
}
|
||
|
||
&__item {
|
||
@apply flex flex-row items-center justify-center;
|
||
height: 100rpx;
|
||
width: 25%;
|
||
|
||
&-bg {
|
||
@apply px-4 py-2;
|
||
|
||
&.is-active {
|
||
@apply bg-primary-500 rounded-xl;
|
||
}
|
||
}
|
||
}
|
||
|
||
&.is-dark {
|
||
@apply bg-surface-800;
|
||
}
|
||
}
|
||
</style>
|