添加 cl-calendar 组件
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cool-unix",
|
||||
"version": "8.0.22",
|
||||
"version": "8.0.23",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build-ui": "node ./uni_modules/cool-ui/scripts/generate-types.js",
|
||||
|
||||
@@ -214,6 +214,12 @@
|
||||
"navigationBarTitleText": "Upload 文件上传"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "form/calendar",
|
||||
"style": {
|
||||
"navigationBarTitleText": "Calendar 日历"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "layout/flex",
|
||||
"style": {
|
||||
|
||||
41
pages/demo/form/calendar.uvue
Normal file
41
pages/demo/form/calendar.uvue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<cl-page>
|
||||
<view class="p-3">
|
||||
<demo-item :label="t('选择器')">
|
||||
<cl-calendar-select v-model="date"></cl-calendar-select>
|
||||
</demo-item>
|
||||
|
||||
<demo-item :label="t('范围选择器')">
|
||||
<cl-calendar-select v-model:date="dateArr" mode="range"></cl-calendar-select>
|
||||
</demo-item>
|
||||
|
||||
<demo-item :label="t('禁用部分日期')">
|
||||
<cl-calendar-select
|
||||
v-model="date"
|
||||
:disabled-date="disabledDate"
|
||||
></cl-calendar-select>
|
||||
</demo-item>
|
||||
|
||||
<demo-item :label="t('日历面板')">
|
||||
<cl-calendar v-model="date"></cl-calendar>
|
||||
</demo-item>
|
||||
</view>
|
||||
</cl-page>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import { t } from "@/locale";
|
||||
import DemoItem from "../components/item.uvue";
|
||||
import { dayUts } from "@/cool";
|
||||
|
||||
const date = ref<string | null>(dayUts().format("YYYY-MM-DD"));
|
||||
|
||||
const dateArr = ref<string[]>(["2025-09-02", "2025-09-09"]);
|
||||
|
||||
const disabledDate = ref<string[]>([
|
||||
dayUts().add(2, "day").format("YYYY-MM-DD"),
|
||||
dayUts().add(4, "day").format("YYYY-MM-DD"),
|
||||
dayUts().add(6, "day").format("YYYY-MM-DD")
|
||||
]);
|
||||
</script>
|
||||
@@ -226,6 +226,11 @@ const data = computed<Item[]>(() => {
|
||||
label: t("文件上传"),
|
||||
icon: "file-upload-line",
|
||||
path: "/pages/demo/form/upload"
|
||||
},
|
||||
{
|
||||
label: t("日历"),
|
||||
icon: "calendar-line",
|
||||
path: "/pages/demo/form/calendar"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -327,6 +332,11 @@ const data = computed<Item[]>(() => {
|
||||
label: t("树形结构"),
|
||||
icon: "node-tree",
|
||||
path: "/pages/demo/data/tree"
|
||||
},
|
||||
{
|
||||
label: t("日历"),
|
||||
icon: "calendar-line",
|
||||
path: "/pages/demo/data/calendar"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
12
types/uni-app.d.ts
vendored
12
types/uni-app.d.ts
vendored
@@ -430,6 +430,17 @@ declare const onUnhandledRejection: (
|
||||
|
||||
declare const onUnload: (hook: () => any, target?: ComponentInternalInstance | null) => void;
|
||||
|
||||
declare interface DOMRect {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
left: number;
|
||||
top: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
}
|
||||
|
||||
declare interface UniElement {
|
||||
$vm: ComponentPublicInstance;
|
||||
id: string;
|
||||
@@ -446,6 +457,7 @@ declare interface UniElement {
|
||||
success?: (res: { tempFilePath: string }) => void;
|
||||
fail?: (err: { errCode: number; errMsg: string }) => void;
|
||||
}): void;
|
||||
getBoundingClientRectAsync(): Promise<DOMRect>;
|
||||
getDrawableContext(): DrawableContext;
|
||||
animate(
|
||||
keyframes: UniAnimationKeyframe | UniAnimationKeyframe[],
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
<template>
|
||||
<cl-select-trigger
|
||||
v-if="showTrigger"
|
||||
:pt="{
|
||||
className: pt.trigger?.className,
|
||||
icon: pt.trigger?.icon
|
||||
}"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
:focus="popupRef?.isOpen"
|
||||
:text="text"
|
||||
@open="open()"
|
||||
@clear="clear"
|
||||
></cl-select-trigger>
|
||||
|
||||
<cl-popup
|
||||
ref="popupRef"
|
||||
v-model="visible"
|
||||
:title="title"
|
||||
:pt="{
|
||||
className: pt.popup?.className,
|
||||
header: pt.popup?.header,
|
||||
container: pt.popup?.container,
|
||||
mask: pt.popup?.mask,
|
||||
draw: pt.popup?.draw
|
||||
}"
|
||||
>
|
||||
<view class="cl-select-popup" @touchmove.stop>
|
||||
<slot name="prepend"></slot>
|
||||
|
||||
<view class="cl-select-popup__picker">
|
||||
<cl-calendar
|
||||
v-model="value"
|
||||
v-model:date="date"
|
||||
:mode="mode"
|
||||
:disabled-date="disabledDate"
|
||||
></cl-calendar>
|
||||
</view>
|
||||
|
||||
<slot name="append"></slot>
|
||||
|
||||
<view class="cl-select-popup__op">
|
||||
<cl-button
|
||||
v-if="showCancel"
|
||||
size="large"
|
||||
text
|
||||
border
|
||||
type="light"
|
||||
:pt="{
|
||||
className: 'flex-1 !rounded-xl h-[80rpx]'
|
||||
}"
|
||||
@tap="close"
|
||||
>{{ cancelText }}</cl-button
|
||||
>
|
||||
<cl-button
|
||||
v-if="showConfirm"
|
||||
size="large"
|
||||
:pt="{
|
||||
className: 'flex-1 !rounded-xl h-[80rpx]'
|
||||
}"
|
||||
@tap="confirm"
|
||||
>{{ confirmText }}</cl-button
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
</cl-popup>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, type PropType } from "vue";
|
||||
import type { ClCalendarMode } from "../../types";
|
||||
import { isEmpty, parsePt } from "@/cool";
|
||||
import type { ClSelectTriggerPassThrough } from "../cl-select-trigger/props";
|
||||
import type { ClPopupPassThrough } from "../cl-popup/props";
|
||||
import { t } from "@/locale";
|
||||
|
||||
defineOptions({
|
||||
name: "cl-calendar-select"
|
||||
});
|
||||
|
||||
defineSlots<{
|
||||
prepend(): any;
|
||||
append(): any;
|
||||
}>();
|
||||
|
||||
// 组件属性定义
|
||||
const props = defineProps({
|
||||
// 透传样式配置
|
||||
pt: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// 选择器的值
|
||||
modelValue: {
|
||||
type: String as PropType<string | null>,
|
||||
default: null
|
||||
},
|
||||
// 多个日期
|
||||
date: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => []
|
||||
},
|
||||
// 日期选择模式
|
||||
mode: {
|
||||
type: String as PropType<ClCalendarMode>,
|
||||
default: "single"
|
||||
},
|
||||
// 禁用的日期
|
||||
disabledDate: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => []
|
||||
},
|
||||
// 选择器标题
|
||||
title: {
|
||||
type: String,
|
||||
default: () => t("请选择")
|
||||
},
|
||||
// 选择器占位符
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: () => t("请选择")
|
||||
},
|
||||
// 是否显示选择器触发器
|
||||
showTrigger: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否禁用选择器
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 分隔符
|
||||
splitor: {
|
||||
type: String,
|
||||
default: () => t(" 至 ")
|
||||
},
|
||||
// 确认按钮文本
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: () => t("确定")
|
||||
},
|
||||
// 是否显示确认按钮
|
||||
showConfirm: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 取消按钮文本
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: () => t("取消")
|
||||
},
|
||||
// 是否显示取消按钮
|
||||
showCancel: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits(["update:modelValue", "update:date", "change"]);
|
||||
|
||||
// 弹出层引用
|
||||
const popupRef = ref<ClPopupComponentPublicInstance | null>(null);
|
||||
|
||||
// 透传样式类型定义
|
||||
type PassThrough = {
|
||||
trigger?: ClSelectTriggerPassThrough;
|
||||
popup?: ClPopupPassThrough;
|
||||
};
|
||||
|
||||
// 解析透传样式配置
|
||||
const pt = computed(() => parsePt<PassThrough>(props.pt));
|
||||
|
||||
// 当前选中的值
|
||||
const value = ref<string | null>(null);
|
||||
|
||||
// 当前选中的日期
|
||||
const date = ref<string[]>([]);
|
||||
|
||||
// 显示文本
|
||||
const text = computed(() => {
|
||||
return props.mode == "single" ? (props.modelValue ?? "") : props.date.join(props.splitor);
|
||||
});
|
||||
|
||||
// 选择器显示状态
|
||||
const visible = ref(false);
|
||||
|
||||
// 选择回调函数
|
||||
let callback: ((value: string | string[]) => void) | null = null;
|
||||
|
||||
// 打开选择器
|
||||
function open(cb: ((value: string | string[]) => void) | null = null) {
|
||||
visible.value = true;
|
||||
|
||||
// 单选日期
|
||||
value.value = props.modelValue;
|
||||
|
||||
// 多个日期
|
||||
date.value = props.date;
|
||||
|
||||
// 回调
|
||||
callback = cb;
|
||||
}
|
||||
|
||||
// 关闭选择器
|
||||
function close() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
// 清空选择器
|
||||
function clear() {
|
||||
value.value = null;
|
||||
date.value = [] as string[];
|
||||
|
||||
emit("update:modelValue", value.value);
|
||||
emit("update:date", date.value);
|
||||
}
|
||||
|
||||
// 确认选择
|
||||
function confirm() {
|
||||
if (props.mode == "single") {
|
||||
if (value.value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit("update:modelValue", value.value);
|
||||
emit("change", value.value);
|
||||
|
||||
// 触发回调
|
||||
if (callback != null) {
|
||||
callback!(value.value);
|
||||
}
|
||||
} else {
|
||||
if (isEmpty(date.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit("update:date", date.value);
|
||||
emit("change", date.value);
|
||||
|
||||
// 触发回调
|
||||
if (callback != null) {
|
||||
callback!(date.value);
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭选择器
|
||||
close();
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
close
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-select {
|
||||
&-popup {
|
||||
&__picker {
|
||||
@apply p-3 pt-0;
|
||||
}
|
||||
|
||||
&__op {
|
||||
@apply flex flex-row items-center justify-center;
|
||||
padding: 24rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
732
uni_modules/cool-ui/components/cl-calendar/cl-calendar.uvue
Normal file
732
uni_modules/cool-ui/components/cl-calendar/cl-calendar.uvue
Normal file
@@ -0,0 +1,732 @@
|
||||
<template>
|
||||
<!-- 日历组件主容器 -->
|
||||
<view class="cl-calendar" :class="[pt.className]">
|
||||
<!-- 年月选择器弹窗 -->
|
||||
<calendar-picker
|
||||
:year="currentYear"
|
||||
:month="currentMonth"
|
||||
:ref="refs.set('picker')"
|
||||
@change="onYearMonthChange"
|
||||
></calendar-picker>
|
||||
|
||||
<!-- 头部导航栏 -->
|
||||
<view class="cl-calendar__header" v-if="showHeader">
|
||||
<!-- 上一月按钮 -->
|
||||
<view
|
||||
class="cl-calendar__header-prev"
|
||||
:class="{ 'is-dark': isDark }"
|
||||
@tap.stop="gotoPrevMonth"
|
||||
>
|
||||
<cl-icon name="arrow-left-s-line"></cl-icon>
|
||||
</view>
|
||||
|
||||
<!-- 当前年月显示区域 -->
|
||||
<view class="cl-calendar__header-date" @tap="refs.open('picker')">
|
||||
<slot name="current-date">
|
||||
<cl-text :pt="{ className: 'text-lg' }">{{
|
||||
$t(`{year}年{month}月`, { year: currentYear, month: currentMonth })
|
||||
}}</cl-text>
|
||||
</slot>
|
||||
</view>
|
||||
|
||||
<!-- 下一月按钮 -->
|
||||
<view
|
||||
class="cl-calendar__header-next"
|
||||
:class="{ 'is-dark': isDark }"
|
||||
@tap.stop="gotoNextMonth"
|
||||
>
|
||||
<cl-icon name="arrow-right-s-line"></cl-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 星期标题行 -->
|
||||
<view class="cl-calendar__weeks" :style="{ gap: `${cellGap}px` }">
|
||||
<view class="cl-calendar__weeks-item" v-for="weekName in weekLabels" :key="weekName">
|
||||
<cl-text>{{ weekName }}</cl-text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 日期网格容器 -->
|
||||
<view
|
||||
class="cl-calendar__view"
|
||||
ref="calendarViewRef"
|
||||
:style="{ height: `${viewHeight}px`, gap: `${cellGap}px` }"
|
||||
@tap="onTap"
|
||||
>
|
||||
<!-- Web端使用DOM渲染 -->
|
||||
<!-- #ifndef APP -->
|
||||
<view
|
||||
class="cl-calendar__view-row"
|
||||
:style="{ gap: `${cellGap}px` }"
|
||||
v-for="(weekRow, rowIndex) in dateMatrix"
|
||||
:key="rowIndex"
|
||||
>
|
||||
<view
|
||||
class="cl-calendar__view-cell"
|
||||
v-for="(dateCell, cellIndex) in weekRow"
|
||||
:key="cellIndex"
|
||||
:class="{
|
||||
'is-selected': dateCell.isSelected,
|
||||
'is-range': dateCell.isRange,
|
||||
'is-hide': dateCell.isHide,
|
||||
'is-disabled': dateCell.isDisabled,
|
||||
'is-today': dateCell.isToday,
|
||||
'is-other-month': !dateCell.isCurrentMonth
|
||||
}"
|
||||
:style="{ height: cellHeight + 'px' }"
|
||||
@click.stop="selectDateCell(dateCell)"
|
||||
>
|
||||
<cl-text :color="getCellColor(dateCell)" :size="`${fontSize}px`">{{
|
||||
dateCell.date
|
||||
}}</cl-text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, nextTick, onMounted, ref, watch, type PropType } from "vue";
|
||||
import { ctx, dayUts, first, isDark, parsePt, useRefs } from "@/cool";
|
||||
import CalendarPicker from "./picker.uvue";
|
||||
import { $t, t } from "@/locale";
|
||||
import type { ClCalendarMode } from "../../types";
|
||||
|
||||
defineOptions({
|
||||
name: "cl-calendar"
|
||||
});
|
||||
|
||||
// 日期单元格数据结构
|
||||
type DateCell = {
|
||||
date: string; // 显示的日期数字
|
||||
isCurrentMonth: boolean; // 是否属于当前显示月份
|
||||
isToday: boolean; // 是否为今天
|
||||
isSelected: boolean; // 是否被选中
|
||||
isRange: boolean; // 是否在选择范围内
|
||||
fullDate: string; // 完整日期格式 YYYY-MM-DD
|
||||
isDisabled: boolean; // 是否被禁用
|
||||
isHide: boolean; // 是否隐藏显示
|
||||
};
|
||||
|
||||
// 组件属性定义
|
||||
const props = defineProps({
|
||||
// 透传样式配置
|
||||
pt: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// 是否显示头部导航栏
|
||||
showHeader: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 日期选择模式:单选/多选/范围选择
|
||||
mode: {
|
||||
type: String as PropType<ClCalendarMode>,
|
||||
default: "single"
|
||||
},
|
||||
// 当前选中的日期值(单选模式)
|
||||
modelValue: {
|
||||
type: String as PropType<string | null>,
|
||||
default: null
|
||||
},
|
||||
// 选中的日期数组(多选/范围模式)
|
||||
date: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => []
|
||||
},
|
||||
// 是否显示其他月份的日期
|
||||
showOtherMonth: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 禁用的日期列表
|
||||
disabledDate: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => []
|
||||
},
|
||||
// 单元格高度
|
||||
cellHeight: {
|
||||
type: Number,
|
||||
default: 60
|
||||
},
|
||||
// 单元格间距
|
||||
cellGap: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 字体大小
|
||||
fontSize: {
|
||||
type: Number,
|
||||
default: 14
|
||||
},
|
||||
// 当前月日期颜色
|
||||
currentMonthColor: {
|
||||
type: String,
|
||||
default: () => ctx.color["surface-700"] as string
|
||||
},
|
||||
// 其他月日期颜色
|
||||
otherMonthColor: {
|
||||
type: String,
|
||||
default: () => ctx.color["surface-300"] as string
|
||||
},
|
||||
// 今天日期颜色
|
||||
todayColor: {
|
||||
type: String,
|
||||
default: "#ff6b6b"
|
||||
},
|
||||
// 选中日期文字颜色
|
||||
selectedTextColor: {
|
||||
type: String,
|
||||
default: "#ffffff"
|
||||
},
|
||||
// 选中日期背景色
|
||||
selectedBgColor: {
|
||||
type: String,
|
||||
default: () => ctx.color["primary-500"] as string
|
||||
},
|
||||
// 范围选择背景色
|
||||
rangeBgColor: {
|
||||
type: String,
|
||||
default: () => ctx.color["primary-100"] as string
|
||||
},
|
||||
// 禁用的日期颜色
|
||||
disabledColor: {
|
||||
type: String,
|
||||
default: () => ctx.color["surface-300"] as string
|
||||
}
|
||||
});
|
||||
|
||||
// 事件发射器定义
|
||||
const emit = defineEmits(["update:modelValue", "update:date", "change"]);
|
||||
|
||||
// 透传样式属性类型
|
||||
type PassThrough = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
// 解析透传样式配置
|
||||
const pt = computed(() => parsePt<PassThrough>(props.pt));
|
||||
|
||||
// 组件引用管理器
|
||||
const refs = useRefs();
|
||||
|
||||
// 日历视图DOM元素引用
|
||||
const calendarViewRef = ref<UniElement | null>(null);
|
||||
|
||||
// 当前显示的年份
|
||||
const currentYear = ref(0);
|
||||
|
||||
// 当前显示的月份
|
||||
const currentMonth = ref(0);
|
||||
|
||||
// 视图高度
|
||||
const viewHeight = computed(() => {
|
||||
return props.cellHeight * 6 + "px";
|
||||
});
|
||||
|
||||
// 单元格宽度
|
||||
const cellWidth = ref(0);
|
||||
|
||||
// 星期标签数组
|
||||
const weekLabels = computed(() => {
|
||||
return [t("日"), t("一"), t("二"), t("三"), t("四"), t("五"), t("六")];
|
||||
});
|
||||
|
||||
// 日历日期矩阵数据(6行7列)
|
||||
const dateMatrix = ref<DateCell[][]>([]);
|
||||
|
||||
// 当前选中的日期列表
|
||||
const selectedDates = ref<string[]>([]);
|
||||
|
||||
/**
|
||||
* 获取日历视图元素的位置信息
|
||||
*/
|
||||
async function getViewRect(): Promise<DOMRect | null> {
|
||||
return calendarViewRef.value!.getBoundingClientRectAsync();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定日期是否被选中
|
||||
* @param dateStr 日期字符串 YYYY-MM-DD
|
||||
*/
|
||||
function isDateSelected(dateStr: string): boolean {
|
||||
if (props.mode == "single") {
|
||||
// 单选模式:检查是否为唯一选中日期
|
||||
return selectedDates.value[0] == dateStr;
|
||||
} else {
|
||||
// 多选/范围模式:检查是否在选中列表中
|
||||
return selectedDates.value.includes(dateStr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定日期是否被禁用
|
||||
* @param dateStr 日期字符串 YYYY-MM-DD
|
||||
*/
|
||||
function isDateDisabled(dateStr: string): boolean {
|
||||
return props.disabledDate.includes(dateStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定日期是否在选择范围内(不包括端点)
|
||||
* @param dateStr 日期字符串 YYYY-MM-DD
|
||||
*/
|
||||
function isDateInRange(dateStr: string): boolean {
|
||||
// 仅范围选择模式且已选择两个端点时才有范围
|
||||
if (props.mode != "range" || selectedDates.value.length != 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const [startDate, endDate] = selectedDates.value;
|
||||
const currentDate = dayUts(dateStr);
|
||||
return currentDate.isAfter(startDate) && currentDate.isBefore(endDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算并生成日历矩阵数据
|
||||
* 生成6行7列共42个日期,包含上月末尾和下月开头的日期
|
||||
*/
|
||||
function calculateDateMatrix() {
|
||||
const weekRows: DateCell[][] = [];
|
||||
const todayStr = dayUts().format("YYYY-MM-DD"); // 今天的日期字符串
|
||||
|
||||
// 获取当前月第一天
|
||||
const monthFirstDay = dayUts(`${currentYear.value}-${currentMonth.value}-01`);
|
||||
const firstDayWeekIndex = monthFirstDay.getDay(); // 第一天是星期几 (0=周日, 6=周六)
|
||||
|
||||
// 计算日历显示的起始日期(可能是上个月的日期)
|
||||
const calendarStartDate = monthFirstDay.subtract(firstDayWeekIndex, "day");
|
||||
|
||||
// 生成6周的日期数据(6行 × 7列 = 42天)
|
||||
let iterateDate = calendarStartDate;
|
||||
for (let weekIndex = 0; weekIndex < 6; weekIndex++) {
|
||||
const weekDates: DateCell[] = [];
|
||||
|
||||
for (let dayIndex = 0; dayIndex < 7; dayIndex++) {
|
||||
const fullDateStr = iterateDate.format("YYYY-MM-DD");
|
||||
const nativeDate = iterateDate.toDate();
|
||||
const dayNumber = nativeDate.getDate();
|
||||
|
||||
// 判断是否属于当前显示月份
|
||||
const belongsToCurrentMonth =
|
||||
nativeDate.getMonth() + 1 == currentMonth.value &&
|
||||
nativeDate.getFullYear() == currentYear.value;
|
||||
|
||||
// 构建日期单元格数据
|
||||
const dateCell = {
|
||||
date: `${dayNumber}`,
|
||||
isCurrentMonth: belongsToCurrentMonth,
|
||||
isToday: fullDateStr == todayStr,
|
||||
isSelected: isDateSelected(fullDateStr),
|
||||
isRange: isDateInRange(fullDateStr),
|
||||
fullDate: fullDateStr,
|
||||
isDisabled: isDateDisabled(fullDateStr),
|
||||
isHide: false
|
||||
} as DateCell;
|
||||
|
||||
// 根据配置决定是否隐藏相邻月份的日期
|
||||
if (!props.showOtherMonth && !belongsToCurrentMonth) {
|
||||
dateCell.isHide = true;
|
||||
}
|
||||
|
||||
weekDates.push(dateCell);
|
||||
iterateDate = iterateDate.add(1, "day"); // 移动到下一天
|
||||
}
|
||||
|
||||
weekRows.push(weekDates);
|
||||
}
|
||||
|
||||
dateMatrix.value = weekRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用Canvas绘制日历(仅APP端)
|
||||
* Web端使用DOM渲染,APP端使用Canvas提升性能
|
||||
*/
|
||||
async function renderCalendarCanvas() {
|
||||
// #ifdef APP
|
||||
await nextTick(); // 等待DOM更新完成
|
||||
|
||||
const canvasContext = calendarViewRef.value!.getDrawableContext();
|
||||
|
||||
if (canvasContext == null) return;
|
||||
|
||||
canvasContext!.reset(); // 清空画布
|
||||
|
||||
/**
|
||||
* 绘制单个日期单元格
|
||||
* @param dateCell 日期单元格数据
|
||||
* @param colIndex 列索引 (0-6)
|
||||
* @param rowIndex 行索引 (0-5)
|
||||
*/
|
||||
function drawSingleCell(dateCell: DateCell, colIndex: number, rowIndex: number) {
|
||||
// 计算单元格位置
|
||||
const cellX = colIndex * cellWidth.value;
|
||||
const cellY = rowIndex * props.cellHeight;
|
||||
const centerX = cellX + cellWidth.value / 2;
|
||||
const centerY = cellY + props.cellHeight / 2;
|
||||
|
||||
// 绘制背景(选中状态或范围状态)
|
||||
if (dateCell.isSelected || dateCell.isRange) {
|
||||
const padding = props.cellGap; // 使用间距作为内边距
|
||||
const bgX = cellX + padding;
|
||||
const bgY = cellY + padding;
|
||||
const bgWidth = cellWidth.value - padding * 2;
|
||||
const bgHeight = props.cellHeight - padding * 2;
|
||||
|
||||
// 设置背景颜色
|
||||
if (dateCell.isSelected) {
|
||||
canvasContext!.fillStyle = props.selectedBgColor;
|
||||
}
|
||||
if (dateCell.isRange) {
|
||||
canvasContext!.fillStyle = props.rangeBgColor;
|
||||
}
|
||||
|
||||
canvasContext!.fillRect(bgX, bgY, bgWidth, bgHeight); // 绘制背景矩形
|
||||
}
|
||||
|
||||
// 设置文字样式
|
||||
canvasContext!.font = `${props.fontSize}px sans-serif`;
|
||||
canvasContext!.textAlign = "center";
|
||||
|
||||
// 根据状态设置文字颜色
|
||||
if (dateCell.isSelected) {
|
||||
canvasContext!.fillStyle = props.selectedTextColor;
|
||||
} else if (dateCell.isToday) {
|
||||
canvasContext!.fillStyle = props.todayColor;
|
||||
} else if (dateCell.isCurrentMonth) {
|
||||
canvasContext!.fillStyle = props.currentMonthColor;
|
||||
} else {
|
||||
canvasContext!.fillStyle = props.otherMonthColor;
|
||||
}
|
||||
|
||||
// 绘制日期数字(垂直居中对齐)
|
||||
const textOffsetY = (props.fontSize / 2) * 0.7;
|
||||
canvasContext!.fillText(dateCell.date.toString(), centerX, centerY + textOffsetY);
|
||||
}
|
||||
|
||||
// 获取容器尺寸信息
|
||||
const viewRect = await getViewRect();
|
||||
|
||||
if (viewRect == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算单元格宽度(总宽度除以7列)
|
||||
const cellSize = viewRect.width / 7;
|
||||
|
||||
// 更新渲染配置
|
||||
cellWidth.value = cellSize;
|
||||
|
||||
// 遍历日期矩阵进行绘制
|
||||
for (let rowIndex = 0; rowIndex < dateMatrix.value.length; rowIndex++) {
|
||||
const weekRow = dateMatrix.value[rowIndex];
|
||||
for (let colIndex = 0; colIndex < weekRow.length; colIndex++) {
|
||||
const dateCell = weekRow[colIndex];
|
||||
drawSingleCell(dateCell, colIndex, rowIndex);
|
||||
}
|
||||
}
|
||||
|
||||
canvasContext!.update(); // 更新画布显示
|
||||
// #endif
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理日期单元格选择逻辑
|
||||
* @param dateCell 被点击的日期单元格
|
||||
*/
|
||||
function selectDateCell(dateCell: DateCell) {
|
||||
// 隐藏或禁用的日期不可选择
|
||||
if (dateCell.isHide || dateCell.isDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (props.mode == "single") {
|
||||
// 单选模式:直接替换选中日期
|
||||
selectedDates.value = [dateCell.fullDate];
|
||||
emit("update:modelValue", dateCell.fullDate);
|
||||
} else if (props.mode == "multiple") {
|
||||
// 多选模式:切换选中状态
|
||||
const existingIndex = selectedDates.value.indexOf(dateCell.fullDate);
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
// 已选中则移除
|
||||
selectedDates.value.splice(existingIndex, 1);
|
||||
} else {
|
||||
// 未选中则添加
|
||||
selectedDates.value.push(dateCell.fullDate);
|
||||
}
|
||||
} else {
|
||||
// 范围选择模式
|
||||
if (selectedDates.value.length == 0) {
|
||||
// 第一次点击:设置起始日期
|
||||
selectedDates.value = [dateCell.fullDate];
|
||||
} else if (selectedDates.value.length == 1) {
|
||||
// 第二次点击:设置结束日期
|
||||
const startDate = dayUts(selectedDates.value[0]);
|
||||
const endDate = dayUts(dateCell.fullDate);
|
||||
|
||||
if (endDate.isBefore(startDate)) {
|
||||
// 结束日期早于开始日期时自动交换
|
||||
selectedDates.value = [dateCell.fullDate, selectedDates.value[0]];
|
||||
} else {
|
||||
selectedDates.value = [selectedDates.value[0], dateCell.fullDate];
|
||||
}
|
||||
} else {
|
||||
// 已有范围时重新开始选择
|
||||
selectedDates.value = [dateCell.fullDate];
|
||||
}
|
||||
}
|
||||
|
||||
// 发射更新事件
|
||||
emit("update:date", [...selectedDates.value]);
|
||||
emit("change", selectedDates.value);
|
||||
|
||||
// 重新计算日历数据并重绘
|
||||
calculateDateMatrix();
|
||||
renderCalendarCanvas();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单元格字体颜色
|
||||
* @param dateCell 日期单元格数据
|
||||
* @returns 字体颜色
|
||||
*/
|
||||
function getCellColor(dateCell: DateCell): string {
|
||||
// 禁用的日期颜色
|
||||
if (dateCell.isDisabled) {
|
||||
return props.disabledColor;
|
||||
}
|
||||
|
||||
// 选中的日期文字颜色
|
||||
if (dateCell.isSelected) {
|
||||
return props.selectedTextColor;
|
||||
}
|
||||
|
||||
// 今天日期颜色
|
||||
if (dateCell.isToday) {
|
||||
return props.todayColor;
|
||||
}
|
||||
|
||||
// 当前月份日期颜色
|
||||
if (dateCell.isCurrentMonth) {
|
||||
return props.currentMonthColor;
|
||||
}
|
||||
|
||||
// 其他月份日期颜色
|
||||
return props.otherMonthColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理年月选择器的变化事件
|
||||
* @param yearMonthArray [年份, 月份] 数组
|
||||
*/
|
||||
function onYearMonthChange(yearMonthArray: number[]) {
|
||||
console.log("年月选择器变化:", yearMonthArray);
|
||||
currentYear.value = yearMonthArray[0];
|
||||
currentMonth.value = yearMonthArray[1];
|
||||
|
||||
// 重新计算日历数据并重绘
|
||||
calculateDateMatrix();
|
||||
renderCalendarCanvas();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理点击事件(APP端Canvas点击检测)
|
||||
*/
|
||||
async function onTap(e: UniPointerEvent) {
|
||||
// 获取容器位置信息
|
||||
const viewRect = await getViewRect();
|
||||
|
||||
if (viewRect == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算触摸点相对于容器的坐标
|
||||
const relativeX = e.clientX - viewRect.left;
|
||||
const relativeY = e.clientY - viewRect.top;
|
||||
|
||||
// 根据坐标计算对应的行列索引
|
||||
const columnIndex = Math.floor(relativeX / cellWidth.value);
|
||||
const rowIndex = Math.floor(relativeY / props.cellHeight);
|
||||
|
||||
// 边界检查:确保索引在有效范围内
|
||||
if (
|
||||
rowIndex < 0 ||
|
||||
rowIndex >= dateMatrix.value.length ||
|
||||
columnIndex < 0 ||
|
||||
columnIndex >= 7
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetDateCell = dateMatrix.value[rowIndex][columnIndex];
|
||||
selectDateCell(targetDateCell);
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换到上一个月
|
||||
*/
|
||||
function gotoPrevMonth() {
|
||||
const [newYear, newMonth] = dayUts(`${currentYear.value}-${currentMonth.value}-01`)
|
||||
.subtract(1, "month")
|
||||
.toArray();
|
||||
|
||||
currentYear.value = newYear;
|
||||
currentMonth.value = newMonth;
|
||||
|
||||
// 重新计算并渲染日历
|
||||
calculateDateMatrix();
|
||||
renderCalendarCanvas();
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换到下一个月
|
||||
*/
|
||||
function gotoNextMonth() {
|
||||
const [newYear, newMonth] = dayUts(`${currentYear.value}-${currentMonth.value}-01`)
|
||||
.add(1, "month")
|
||||
.toArray();
|
||||
|
||||
currentYear.value = newYear;
|
||||
currentMonth.value = newMonth;
|
||||
|
||||
// 重新计算并渲染日历
|
||||
calculateDateMatrix();
|
||||
renderCalendarCanvas();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析选中日期
|
||||
*/
|
||||
function parseDate() {
|
||||
// 根据选择模式初始化选中日期
|
||||
if (props.mode == "single") {
|
||||
selectedDates.value = props.modelValue != null ? [props.modelValue] : [];
|
||||
} else {
|
||||
selectedDates.value = [...props.date];
|
||||
}
|
||||
|
||||
// 获取初始显示日期(优先使用选中日期,否则使用当前日期)
|
||||
const initialDate = first(selectedDates.value);
|
||||
const [initialYear, initialMonth] = dayUts(initialDate).toArray();
|
||||
|
||||
currentYear.value = initialYear;
|
||||
currentMonth.value = initialMonth;
|
||||
|
||||
// 计算初始日历数据
|
||||
calculateDateMatrix();
|
||||
|
||||
// 渲染日历视图
|
||||
renderCalendarCanvas();
|
||||
}
|
||||
|
||||
// 组件挂载时的初始化逻辑
|
||||
onMounted(() => {
|
||||
// 监听单选模式的值变化
|
||||
watch(
|
||||
computed(() => props.modelValue ?? ""),
|
||||
(newValue: string) => {
|
||||
selectedDates.value = [newValue];
|
||||
parseDate();
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
// 监听多选/范围模式的值变化
|
||||
watch(
|
||||
computed(() => props.date),
|
||||
(newDateArray: string[]) => {
|
||||
selectedDates.value = [...newDateArray];
|
||||
parseDate();
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 日历组件主容器 */
|
||||
.cl-calendar {
|
||||
@apply relative;
|
||||
|
||||
/* 头部导航栏样式 */
|
||||
&__header {
|
||||
@apply flex flex-row items-center justify-between p-3 w-full;
|
||||
|
||||
/* 上一月/下一月按钮样式 */
|
||||
&-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;
|
||||
}
|
||||
}
|
||||
|
||||
/* 星期标题行样式 */
|
||||
&__weeks {
|
||||
@apply flex flex-row;
|
||||
|
||||
/* 单个星期标题样式 */
|
||||
&-item {
|
||||
@apply flex flex-row items-center justify-center flex-1;
|
||||
height: 80rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* 日期网格容器样式 */
|
||||
&__view {
|
||||
@apply w-full;
|
||||
|
||||
/* Web端DOM渲染样式 */
|
||||
// #ifndef APP
|
||||
/* 日期行样式 */
|
||||
&-row {
|
||||
@apply flex flex-row;
|
||||
}
|
||||
|
||||
/* 日期单元格样式 */
|
||||
&-cell {
|
||||
@apply flex-1 flex flex-col items-center justify-center relative;
|
||||
height: 80rpx;
|
||||
|
||||
/* 选中状态样式 */
|
||||
&.is-selected {
|
||||
background-color: v-bind("props.selectedBgColor");
|
||||
}
|
||||
|
||||
/* 范围选择背景样式 */
|
||||
&.is-range {
|
||||
background-color: v-bind("props.rangeBgColor");
|
||||
}
|
||||
|
||||
/* 隐藏状态(相邻月份日期) */
|
||||
&.is-hide {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* 禁用状态 */
|
||||
&.is-disabled {
|
||||
@apply opacity-50;
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
</style>
|
||||
273
uni_modules/cool-ui/components/cl-calendar/picker.uvue
Normal file
273
uni_modules/cool-ui/components/cl-calendar/picker.uvue
Normal file
@@ -0,0 +1,273 @@
|
||||
<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>
|
||||
@@ -198,3 +198,5 @@ export type ClTreeNodeInfo = {
|
||||
parent?: ClTreeItem;
|
||||
index: number;
|
||||
};
|
||||
|
||||
export type ClCalendarMode = "single" | "multiple" | "range";
|
||||
|
||||
Reference in New Issue
Block a user