uview-pro 0.5.16 → 0.5.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,124 +1,22 @@
1
1
  <template>
2
- <view v-if="props.isPage" class="u-calendar" :class="props.customClass" :style="$u.toStyle(customStyle)">
3
- <!-- <view class="u-calendar__header">
4
- <view class="u-calendar__header__text" v-if="!slots.tooltip">
5
- {{ toolTip }}
6
- </view>
7
- <slot v-else name="tooltip" />
8
- </view> -->
9
- <view class="u-calendar__action u-flex u-row-center">
10
- <view class="u-calendar__action__icon">
11
- <u-icon
12
- v-if="changeYear"
13
- name="arrow-left-double"
14
- :color="yearArrowColor"
15
- @click="changeYearHandler(0)"
16
- ></u-icon>
17
- </view>
18
- <view class="u-calendar__action__icon">
19
- <u-icon
20
- v-if="changeMonth"
21
- name="arrow-left"
22
- :color="monthArrowColor"
23
- @click="changeMonthHandler(0)"
24
- ></u-icon>
25
- </view>
26
- <view class="u-calendar__action__text">{{ showTitle }}</view>
27
- <view class="u-calendar__action__icon">
28
- <u-icon
29
- v-if="changeMonth"
30
- name="arrow-right"
31
- :color="monthArrowColor"
32
- @click="changeMonthHandler(1)"
33
- ></u-icon>
34
- </view>
35
- <view class="u-calendar__action__icon">
36
- <u-icon
37
- v-if="changeYear"
38
- name="arrow-right-double"
39
- :color="yearArrowColor"
40
- @click="changeYearHandler(1)"
41
- ></u-icon>
42
- </view>
43
- </view>
44
- <view class="u-calendar__week-day">
45
- <view class="u-calendar__week-day__text" v-for="(item, index) in weekDayZh" :key="index">{{ item }}</view>
46
- </view>
47
- <view class="u-calendar__content">
48
- <!-- 前置空白部分 -->
49
- <block v-for="(item, index) in weekdayArr" :key="index">
50
- <view class="u-calendar__content__item"></view>
51
- </block>
52
- <view
53
- class="u-calendar__content__item"
54
- :class="{
55
- 'u-hover-class': openDisAbled(year, month, index + 1),
56
- 'u-calendar__content--start-date':
57
- (mode == 'range' && startDate == `${year}-${month}-${index + 1}`) || mode == 'date',
58
- 'u-calendar__content--end-date':
59
- (mode == 'range' && endDate == `${year}-${month}-${index + 1}`) || mode == 'date'
60
- }"
61
- :style="{ backgroundColor: getColor(index, 1) }"
62
- v-for="(item, index) in daysArr"
63
- :key="index"
64
- @tap="dateClick(index)"
65
- >
66
- <view class="u-calendar__content__item__inner" :style="{ color: getColor(index, 2) }">
67
- <view>{{ index + 1 }}</view>
68
- </view>
69
- <view
70
- class="u-calendar__content__item__tips"
71
- :style="{ color: activeColor }"
72
- v-if="mode == 'range' && startDate == `${year}-${month}-${index + 1}` && startDate != endDate"
73
- >
74
- {{ startText }}
75
- </view>
76
- <view
77
- class="u-calendar__content__item__tips"
78
- :style="{ color: activeColor }"
79
- v-if="mode == 'range' && endDate == `${year}-${month}-${index + 1}`"
80
- >
81
- {{ endText }}
82
- </view>
83
- <view
84
- v-if="
85
- props.showLunar &&
86
- !(mode == 'range' && startDate == `${year}-${month}-${index + 1}` && startDate != endDate) &&
87
- !(mode == 'range' && endDate == `${year}-${month}-${index + 1}`)
88
- "
89
- class="u-calendar__content__item__tips"
90
- :style="{ color: getColor(index, 2) }"
91
- >
92
- {{ lunarArr[index]?.dayCn === '初一' ? lunarArr[index].monthCn : (lunarArr[index]?.dayCn ?? '') }}
93
- </view>
94
- </view>
95
- <view class="u-calendar__content__bg-month">{{ month }}</view>
96
- </view>
97
- <!-- <view class="u-calendar__bottom">
98
- <view class="u-calendar__bottom__choose">
99
- <text>{{ mode == 'date' ? activeDate : startDate }}</text>
100
- <text v-if="endDate">至{{ endDate }}</text>
101
- </view>
102
- <view class="u-calendar__bottom__btn">
103
- <u-button :type="btnType" shape="circle" size="default" @click="btnFix(false)">确定</u-button>
104
- </view>
105
- </view> -->
106
- </view>
107
2
  <u-popup
108
- v-else
109
- :maskCloseAble="maskCloseAble"
110
- mode="bottom"
111
- :popup="false"
112
3
  v-model="popupValue"
113
4
  length="auto"
5
+ :maskCloseAble="maskCloseAble"
6
+ :mode="props.isPage ? 'inline' : 'bottom'"
7
+ :popup="false"
114
8
  :safeAreaInsetBottom="safeAreaInsetBottom"
115
- @close="close"
116
9
  :z-index="uZIndex"
117
10
  :border-radius="borderRadius"
118
11
  :closeable="closeable"
12
+ @close="close"
119
13
  >
120
- <view class="u-calendar" :class="props.customClass" :style="$u.toStyle(customStyle)">
121
- <view class="u-calendar__header">
14
+ <view
15
+ class="u-calendar"
16
+ :class="[props.customClass, { 'u-calendar--page': props.isPage }]"
17
+ :style="$u.toStyle(customStyle)"
18
+ >
19
+ <view class="u-calendar__header" v-if="!props.isPage">
122
20
  <view class="u-calendar__header__text" v-if="!slots.tooltip">
123
21
  {{ toolTip }}
124
22
  </view>
@@ -174,53 +72,121 @@
174
72
  :class="{
175
73
  'u-hover-class': openDisAbled(year, month, index + 1),
176
74
  'u-calendar__content--start-date':
177
- (mode == 'range' && startDate == `${year}-${month}-${index + 1}`) || mode == 'date',
75
+ (mode == 'range' && startDate == `${year}-${formatNum(month)}-${formatNum(index + 1)}`) ||
76
+ mode == 'date',
178
77
  'u-calendar__content--end-date':
179
- (mode == 'range' && endDate == `${year}-${month}-${index + 1}`) || mode == 'date'
78
+ (mode == 'range' && endDate == `${year}-${formatNum(month)}-${formatNum(index + 1)}`) ||
79
+ mode == 'date',
80
+ 'u-calendar__content--checked': isCheckedDate(index + 1),
81
+ 'u-calendar__content--today-checked': isTodayChecked(index + 1),
82
+ 'u-calendar__content--checkin-mode': props.checkinMode
83
+ }"
84
+ :style="{
85
+ backgroundColor:
86
+ !props.checkinMode && props.checkedDates.length === 0 && !props.todayChecked
87
+ ? getColor(index, 1)
88
+ : ''
180
89
  }"
181
- :style="{ backgroundColor: getColor(index, 1) }"
182
90
  v-for="(item, index) in daysArr"
183
91
  :key="index"
184
92
  @tap="dateClick(index)"
185
93
  >
186
- <view class="u-calendar__content__item__inner" :style="{ color: getColor(index, 2) }">
187
- <view>{{ index + 1 }}</view>
188
- </view>
189
- <view
190
- class="u-calendar__content__item__tips"
191
- :style="{ color: activeColor }"
192
- v-if="mode == 'range' && startDate == `${year}-${month}-${index + 1}` && startDate != endDate"
193
- >
194
- {{ startText }}
195
- </view>
196
- <view
197
- class="u-calendar__content__item__tips"
198
- :style="{ color: activeColor }"
199
- v-if="mode == 'range' && endDate == `${year}-${month}-${index + 1}`"
200
- >
201
- {{ endText }}
202
- </view>
203
94
  <view
204
- v-if="
205
- props.showLunar &&
206
- !(
207
- mode == 'range' &&
208
- startDate == `${year}-${month}-${index + 1}` &&
209
- startDate != endDate
210
- ) &&
211
- !(mode == 'range' && endDate == `${year}-${month}-${index + 1}`)
212
- "
213
- class="u-calendar__content__item__tips"
214
- :style="{ color: getColor(index, 2) }"
95
+ class="u-calendar__content__item__inner"
96
+ :class="{ 'u-calendar__content__item__inner--today-checked': isTodayChecked(index + 1) }"
97
+ :style="{
98
+ color: getCheckinTextColor(index + 1) || getColor(index, 2),
99
+ backgroundColor: getCheckinColor(index + 1)
100
+ }"
215
101
  >
216
- {{
217
- lunarArr[index]?.dayCn === '初一' ? lunarArr[index].monthCn : (lunarArr[index]?.dayCn ?? '')
218
- }}
102
+ <!-- 今日已打卡时显示对勾,否则显示日期 -->
103
+ <view v-if="isTodayChecked(index + 1)" class="u-calendar__content__item__checkmark">
104
+ <u-icon name="checkmark" size="36" :color="props.checkedColor"></u-icon>
105
+ </view>
106
+ <template v-else>
107
+ <!-- 自定义日期内容插槽 - 优先级最高 -->
108
+ <template v-if="props.useDateSlot">
109
+ <view class="u-calendar__content__item__day">{{ index + 1 }}</view>
110
+ <view
111
+ class="u-calendar__content__item__lunar"
112
+ :style="{ color: getSlotColor(index + 1) }"
113
+ >
114
+ <slot name="date" :date="getDateInfo(index + 1)"></slot>
115
+ </view>
116
+ </template>
117
+ <template v-else>
118
+ <!-- 日期数字右上角标记:休/班 -->
119
+ <view
120
+ v-if="isHoliday(index + 1)"
121
+ class="u-calendar__content__item__mark u-calendar__content__item__mark--holiday"
122
+ :style="{ color: getHolidayWorkdayColor(index + 1, props.holidayColor) }"
123
+ >
124
+ {{ t('uCalendar.holiday') }}
125
+ </view>
126
+ <view
127
+ v-else-if="isWorkday(index + 1)"
128
+ class="u-calendar__content__item__mark u-calendar__content__item__mark--workday"
129
+ :style="{ color: getHolidayWorkdayColor(index + 1, props.workdayColor) }"
130
+ >
131
+ {{ t('uCalendar.workday') }}
132
+ </view>
133
+ <view class="u-calendar__content__item__day">{{ index + 1 }}</view>
134
+ <!-- 范围选择开始日期显示"开始" -->
135
+ <view
136
+ v-if="
137
+ mode == 'range' &&
138
+ startDate == `${year}-${formatNum(month)}-${formatNum(index + 1)}` &&
139
+ startDate != endDate
140
+ "
141
+ class="u-calendar__content__item__lunar"
142
+ :style="{ color: activeColor }"
143
+ >
144
+ {{ startText }}
145
+ </view>
146
+ <!-- 范围选择结束日期显示"结束" -->
147
+ <view
148
+ v-else-if="
149
+ mode == 'range' &&
150
+ endDate == `${year}-${formatNum(month)}-${formatNum(index + 1)}`
151
+ "
152
+ class="u-calendar__content__item__lunar"
153
+ :style="{ color: activeColor }"
154
+ >
155
+ {{ endText }}
156
+ </view>
157
+ <!-- 节日名称 -->
158
+ <view
159
+ v-else-if="getFestival(index + 1)"
160
+ class="u-calendar__content__item__lunar u-calendar__content__item__festival"
161
+ :style="{ color: getHolidayWorkdayColor(index + 1, props.festivalColor) }"
162
+ >
163
+ {{ getFestival(index + 1) }}
164
+ </view>
165
+ <!-- 农历 -->
166
+ <view
167
+ v-else-if="props.showLunar"
168
+ class="u-calendar__content__item__lunar"
169
+ :style="{ color: getCheckinLunarColor(index + 1) || getColor(index, 2) }"
170
+ >
171
+ {{
172
+ lunarArr[index]?.dayCn === '初一'
173
+ ? lunarArr[index].monthCn
174
+ : (lunarArr[index]?.dayCn ?? '')
175
+ }}
176
+ </view>
177
+ <!-- 占位元素:当有节日/农历数据时保持高度一致 -->
178
+ <view
179
+ v-else-if="props.showFestival"
180
+ class="u-calendar__content__item__lunar u-calendar__content__item__placeholder"
181
+ ></view>
182
+ </template>
183
+ </template>
219
184
  </view>
220
185
  </view>
221
186
  <view class="u-calendar__content__bg-month">{{ month }}</view>
222
187
  </view>
223
- <view class="u-calendar__bottom">
188
+ <!-- 页面模式下不显示确定按钮,选择完成自动触发change事件 -->
189
+ <view class="u-calendar__bottom" v-if="!props.isPage">
224
190
  <view class="u-calendar__bottom__choose">
225
191
  <text>{{ mode == 'date' ? activeDate : startDate }}</text>
226
192
  <text v-if="endDate">{{ t('uCalendar.to') }}{{ endDate }}</text>
@@ -288,6 +254,11 @@ import Calendar from '../../libs/util/calendar';
288
254
  * @property {String} btn-type 底部确定按钮的主题(默认 'primary')
289
255
  * @property {String} toolTip 顶部提示文字,如设置名为tooltip的slot,此参数将失效(默认 '选择日期')
290
256
  * @property {Boolean} closeable 是否显示右上角的关闭图标(默认true)
257
+ * @property {Boolean} is-page 是否在页面中直接显示,不使用弹窗(默认false)
258
+ * @property {String} default-date 默认选中的日期,mode=date时生效,格式:2024-01-01
259
+ * @property {String} start-date 默认选中的开始日期,mode=range时生效,格式:2024-01-01
260
+ * @property {String} end-date 默认选中的结束日期,mode=range时生效,格式:2024-01-01
261
+ * @property {Boolean} readonly 是否只读,只读模式下禁止点击选择日期(默认false)
291
262
  * @example <u-calendar v-model="show" :mode="mode"></u-calendar>
292
263
  */
293
264
 
@@ -332,8 +303,29 @@ const weekDayZh = ref([
332
303
  t('uCalendar.sat')
333
304
  ]);
334
305
 
306
+ // 内置中国传统节日(公历日期,格式:MM-DD)
307
+ const builtInFestivals: Record<string, string> = {
308
+ '01-01': '元旦',
309
+ '02-14': '情人节',
310
+ '03-08': '妇女节',
311
+ '03-12': '植树节',
312
+ '04-01': '愚人节',
313
+ '05-01': '劳动节',
314
+ '05-04': '青年节',
315
+ '06-01': '儿童节',
316
+ '07-01': '建党节',
317
+ '08-01': '建军节',
318
+ '09-10': '教师节',
319
+ '10-01': '国庆节',
320
+ '11-11': '光棍节',
321
+ '12-25': '圣诞节'
322
+ };
323
+
335
324
  const dataChange = computed(() => `${props.mode}-${props.minDate}-${props.maxDate}`);
336
325
  const lunarChange = computed(() => props.showLunar);
326
+ const defaultDateChange = computed(
327
+ () => `${props.defaultDate}-${props.startDate}-${props.endDate}-${props.defaultSelectToday}`
328
+ );
337
329
  // 如果用户有传递z-index值,优先使用
338
330
  const uZIndex = computed(() => (props.zIndex ? props.zIndex : $u.zIndex.popup));
339
331
  const popupValue = computed({
@@ -359,6 +351,10 @@ watch([dataChange, lunarChange], () => {
359
351
  init();
360
352
  });
361
353
 
354
+ watch(defaultDateChange, () => {
355
+ init();
356
+ });
357
+
362
358
  onMounted(() => {
363
359
  init();
364
360
  });
@@ -371,7 +367,7 @@ onMounted(() => {
371
367
  function getColor(index: number, type: number) {
372
368
  let color = type == 1 ? '' : props.color;
373
369
  let dayNum = index + 1;
374
- let date = `${year.value}-${month.value}-${dayNum}`;
370
+ let date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
375
371
  let timestamp = new Date(date.replace(/\-/g, '/')).getTime();
376
372
  let start = startDate.value.replace(/\-/g, '/');
377
373
  let end = endDate.value.replace(/\-/g, '/');
@@ -383,6 +379,218 @@ function getColor(index: number, type: number) {
383
379
  return color;
384
380
  }
385
381
 
382
+ /**
383
+ * 判断日期是否已打卡
384
+ */
385
+ function isCheckedDate(dayNum: number) {
386
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
387
+ return props.checkedDates.includes(date);
388
+ }
389
+
390
+ /**
391
+ * 判断是否是今日且已打卡
392
+ * 优先级:1. todayChecked 属性 2. 自动判断 checkedDates 中是否包含今天
393
+ */
394
+ function isTodayChecked(dayNum: number) {
395
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
396
+ // 首先检查是否是今天
397
+ if (date !== today.value) {
398
+ return false;
399
+ }
400
+ // 优先级1:手动设置的 todayChecked
401
+ if (props.todayChecked) {
402
+ return true;
403
+ }
404
+ // 优先级2:自动判断 checkedDates 中是否包含今天
405
+ if (props.checkedDates.includes(date)) {
406
+ return true;
407
+ }
408
+ return false;
409
+ }
410
+
411
+ /**
412
+ * 获取打卡日期背景色
413
+ */
414
+ function getCheckinColor(dayNum: number) {
415
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
416
+ const isToday = date === today.value;
417
+ const isInCheckedDates = props.checkedDates.includes(date);
418
+
419
+ // 只有在打卡签到模式下或设置了打卡数据时才返回颜色
420
+ if (!props.checkinMode && props.checkedDates.length === 0 && !props.todayChecked) {
421
+ return '';
422
+ }
423
+
424
+ // 今日已打卡显示绿色(优先级:todayChecked > 自动判断)
425
+ if (isToday && (props.todayChecked || isInCheckedDates)) {
426
+ return props.todayCheckedBgColor;
427
+ }
428
+
429
+ // 其他已打卡日期显示橙色
430
+ if (isInCheckedDates) {
431
+ return props.checkedBgColor;
432
+ }
433
+
434
+ // 打卡签到模式下,未打卡日期显示灰色
435
+ if (props.checkinMode) {
436
+ return props.uncheckedBgColor;
437
+ }
438
+ return '';
439
+ }
440
+
441
+ /**
442
+ * 获取打卡日期文字颜色
443
+ */
444
+ function getCheckinTextColor(dayNum: number) {
445
+ // 只有在打卡签到模式下或设置了打卡数据时才返回颜色
446
+ if (!props.checkinMode && props.checkedDates.length === 0 && !props.todayChecked) {
447
+ return '';
448
+ }
449
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
450
+ // 已打卡日期显示白色文字
451
+ if (props.checkedDates.includes(date) || (date === today.value && props.todayChecked)) {
452
+ return props.checkedColor;
453
+ }
454
+ // 打卡签到模式下,未打卡日期显示白色文字
455
+ if (props.checkinMode) {
456
+ return props.uncheckedColor;
457
+ }
458
+ return '';
459
+ }
460
+
461
+ /**
462
+ * 获取打卡日期农历文字颜色
463
+ */
464
+ function getCheckinLunarColor(dayNum: number) {
465
+ // 只有在打卡签到模式下或设置了打卡数据时才返回颜色
466
+ if (!props.checkinMode && props.checkedDates.length === 0 && !props.todayChecked) {
467
+ return '';
468
+ }
469
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
470
+ // 已打卡日期的农历显示白色文字
471
+ if (props.checkedDates.includes(date) || (date === today.value && props.todayChecked)) {
472
+ return props.checkedColor;
473
+ }
474
+ // 打卡签到模式下,未打卡日期的农历显示白色文字
475
+ if (props.checkinMode) {
476
+ return props.uncheckedColor;
477
+ }
478
+ return '';
479
+ }
480
+
481
+ /**
482
+ * 获取自定义插槽的颜色
483
+ * 当选中日期时显示白色,否则显示默认颜色
484
+ */
485
+ function getSlotColor(dayNum: number) {
486
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
487
+ // 选中日期的自定义内容显示白色(仅在 isActiveCurrent 为 true 时)
488
+ if (props.isActiveCurrent && (activeDate.value === date || startDate.value === date || endDate.value === date)) {
489
+ return props.activeColor;
490
+ }
491
+ // 打卡签到模式下使用对应的颜色
492
+ if (props.checkinMode || props.checkedDates.length > 0 || props.todayChecked) {
493
+ return getCheckinLunarColor(dayNum) || props.color;
494
+ }
495
+ return props.color;
496
+ }
497
+
498
+ /**
499
+ * 获取日期信息,用于自定义插槽
500
+ */
501
+ function getDateInfo(dayNum: number) {
502
+ const dateStr = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
503
+ const dateObj = new Date(dateStr.replace(/\-/g, '/'));
504
+ const dayOfWeek = dateObj.getDay();
505
+ const weekNames = [
506
+ t('uCalendar.sun'),
507
+ t('uCalendar.mon'),
508
+ t('uCalendar.tue'),
509
+ t('uCalendar.wed'),
510
+ t('uCalendar.thu'),
511
+ t('uCalendar.fri'),
512
+ t('uCalendar.sat')
513
+ ];
514
+
515
+ const isSelected = activeDate.value === dateStr || startDate.value === dateStr || endDate.value === dateStr;
516
+
517
+ return {
518
+ year: year.value,
519
+ month: month.value,
520
+ day: dayNum,
521
+ date: dateStr,
522
+ week: weekNames[dayOfWeek],
523
+ weekNum: dayOfWeek,
524
+ isToday: dateStr === today.value,
525
+ isHoliday: props.holidays.includes(dateStr),
526
+ isWorkday: props.workdays.includes(dateStr),
527
+ isChecked: props.checkedDates.includes(dateStr),
528
+ isSelected,
529
+ isTodayChecked: dateStr === today.value && props.todayChecked,
530
+ lunar: lunarArr.value[dayNum - 1] || null
531
+ };
532
+ }
533
+
534
+ /**
535
+ * 判断是否是节假日
536
+ */
537
+ function isHoliday(dayNum: number) {
538
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
539
+ return props.holidays.includes(date);
540
+ }
541
+
542
+ /**
543
+ * 判断是否是加班日
544
+ */
545
+ function isWorkday(dayNum: number) {
546
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
547
+ return props.workdays.includes(date);
548
+ }
549
+
550
+ /**
551
+ * 获取节日名称(合并内置节日和用户自定义节日)
552
+ * 用户传入空字符串可覆盖内置节日,表示不显示该节日
553
+ * 支持两种格式:
554
+ * 1. 年-月-日:特定年份的节日,如 '2024-04-04': '清明节'
555
+ * 2. 月-日:每年的固定节日,如 '04-04': '清明节'
556
+ */
557
+ function getFestival(dayNum: number) {
558
+ if (!props.showFestival && Object.keys(props.festivals).length === 0) {
559
+ return '';
560
+ }
561
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
562
+ const monthDay = `${formatNum(month.value)}-${formatNum(dayNum)}`;
563
+
564
+ // 优先检查用户自定义节日(特定年份格式)
565
+ if (date in props.festivals) {
566
+ return props.festivals[date];
567
+ }
568
+
569
+ // 然后检查用户自定义节日(每年固定格式,月-日)
570
+ if (monthDay in props.festivals) {
571
+ return props.festivals[monthDay];
572
+ }
573
+
574
+ // 最后检查内置节日(如果启用了 showFestival)
575
+ if (props.showFestival && builtInFestivals[monthDay]) {
576
+ return builtInFestivals[monthDay];
577
+ }
578
+ return '';
579
+ }
580
+
581
+ /**
582
+ * 获取节假日/加班日文字颜色
583
+ * 当选中日期时显示白色,否则显示对应的颜色
584
+ */
585
+ function getHolidayWorkdayColor(dayNum: number, defaultColor: string) {
586
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
587
+ // 选中日期的节假日/加班日显示白色
588
+ if (activeDate.value === date || startDate.value === date || endDate.value === date) {
589
+ return props.activeColor;
590
+ }
591
+ return defaultColor;
592
+ }
593
+
386
594
  /**
387
595
  * 初始化日历数据
388
596
  */
@@ -396,10 +604,72 @@ function init() {
396
604
  year.value = now.getFullYear();
397
605
  month.value = now.getMonth() + 1;
398
606
  day.value = now.getDate();
399
- today.value = `${now.getFullYear()}-${month.value}-${day.value}`;
400
- activeDate.value = today.value;
607
+ today.value = `${now.getFullYear()}-${formatNum(month.value)}-${formatNum(day.value)}`;
401
608
  min.value = initDate(String(props.minDate));
402
609
  max.value = initDate(String(props.maxDate) || today.value);
610
+
611
+ // 处理默认选中日期
612
+ // 优先级1: defaultDate / startDate / endDate(显式指定日期)
613
+ // 优先级2: defaultSelectToday(默认选中今天)
614
+ // 优先级3: 不选中任何日期
615
+ if (props.mode === 'date' && props.defaultDate) {
616
+ // 单选模式:使用 defaultDate(优先级1)
617
+ const defaultDateObj = new Date(props.defaultDate.replace(/\-/g, '/'));
618
+ if (!isNaN(defaultDateObj.getTime())) {
619
+ year.value = defaultDateObj.getFullYear();
620
+ month.value = defaultDateObj.getMonth() + 1;
621
+ day.value = defaultDateObj.getDate();
622
+ // 统一格式为 YYYY-MM-DD,与 getColor 中的格式一致
623
+ activeDate.value = `${year.value}-${formatNum(month.value)}-${formatNum(day.value)}`;
624
+ } else if (props.defaultSelectToday) {
625
+ activeDate.value = today.value;
626
+ } else {
627
+ activeDate.value = '';
628
+ }
629
+ } else if (props.mode === 'range' && (props.startDate || props.endDate)) {
630
+ // 范围模式:使用 startDate 和 endDate(优先级1)
631
+ const startDateObj = props.startDate ? new Date(props.startDate.replace(/\-/g, '/')) : null;
632
+ const endDateObj = props.endDate ? new Date(props.endDate.replace(/\-/g, '/')) : null;
633
+
634
+ if (startDateObj && !isNaN(startDateObj.getTime())) {
635
+ // 设置当前显示月份为开始日期所在月份
636
+ year.value = startDateObj.getFullYear();
637
+ month.value = startDateObj.getMonth() + 1;
638
+
639
+ // 设置开始日期 - 统一格式为 YYYY-MM-DD
640
+ startYear.value = startDateObj.getFullYear();
641
+ startMonth.value = startDateObj.getMonth() + 1;
642
+ startDay.value = startDateObj.getDate();
643
+ startDate.value = `${startYear.value}-${formatNum(startMonth.value)}-${formatNum(startDay.value)}`;
644
+ }
645
+
646
+ if (endDateObj && !isNaN(endDateObj.getTime())) {
647
+ // 设置结束日期 - 统一格式为 YYYY-MM-DD
648
+ endYear.value = endDateObj.getFullYear();
649
+ endMonth.value = endDateObj.getMonth() + 1;
650
+ endDay.value = endDateObj.getDate();
651
+ endDate.value = `${endYear.value}-${formatNum(endMonth.value)}-${formatNum(endDay.value)}`;
652
+ }
653
+
654
+ isStart.value = true;
655
+ activeDate.value = '';
656
+ } else if (props.defaultSelectToday) {
657
+ // 优先级2:默认选中今天
658
+ activeDate.value = today.value;
659
+ resetRangeState();
660
+ } else {
661
+ // 优先级3:不选中任何日期
662
+ activeDate.value = '';
663
+ resetRangeState();
664
+ }
665
+
666
+ changeData();
667
+ }
668
+
669
+ /**
670
+ * 重置范围选择状态
671
+ */
672
+ function resetRangeState() {
403
673
  startDate.value = '';
404
674
  startYear.value = 0;
405
675
  startMonth.value = 0;
@@ -409,7 +679,6 @@ function init() {
409
679
  endDay.value = 0;
410
680
  endDate.value = '';
411
681
  isStart.value = true;
412
- changeData();
413
682
  }
414
683
 
415
684
  /**
@@ -429,10 +698,10 @@ function initDate(date: string) {
429
698
  */
430
699
  function openDisAbled(yearNum: number, monthNum: number, dayNum: number) {
431
700
  let bool = true;
432
- let date = `${yearNum}/${monthNum}/${dayNum}`;
701
+ let date = `${yearNum}/${formatNum(monthNum)}/${formatNum(dayNum)}`;
433
702
  // let today = this.today.replace(/\-/g, '/');
434
- let minStr = min.value ? `${min.value.year}/${min.value.month}/${min.value.day}` : '';
435
- let maxStr = max.value ? `${max.value.year}/${max.value.month}/${max.value.day}` : '';
703
+ let minStr = min.value ? `${min.value.year}/${formatNum(min.value.month)}/${formatNum(min.value.day)}` : '';
704
+ let maxStr = max.value ? `${max.value.year}/${formatNum(max.value.month)}/${formatNum(max.value.day)}` : '';
436
705
  let timestamp = new Date(date).getTime();
437
706
  if (min.value && max.value && timestamp >= new Date(minStr).getTime() && timestamp <= new Date(maxStr).getTime()) {
438
707
  bool = false;
@@ -575,15 +844,20 @@ function getLunar(year: any, month: any, day: any) {
575
844
  * 日期点击事件
576
845
  */
577
846
  function dateClick(dayIdx: number) {
578
- if (props.isPage) {
579
- return;
580
- }
847
+ // 只读模式下禁止点击
848
+ if (props.readonly) return;
849
+
581
850
  const d = dayIdx + 1;
582
851
  if (!openDisAbled(year.value, month.value, d)) {
583
852
  day.value = d;
584
- let date = `${year.value}-${month.value}-${d}`;
853
+ let date = `${year.value}-${formatNum(month.value)}-${formatNum(d)}`;
585
854
  if (props.mode == 'date') {
586
855
  activeDate.value = date;
856
+ // 页面模式下,单选日期选择完成自动触发change事件
857
+ // 打卡签到模式下,弹窗模式也立即触发change事件
858
+ if (props.isPage || props.checkinMode) {
859
+ btnFix(true);
860
+ }
587
861
  } else {
588
862
  let compare =
589
863
  new Date(date.replace(/\-/g, '/')).getTime() < new Date(startDate.value.replace(/\-/g, '/')).getTime();
@@ -604,6 +878,10 @@ function dateClick(dayIdx: number) {
604
878
  endMonth.value = month.value;
605
879
  endDay.value = day.value;
606
880
  isStart.value = true;
881
+ // 页面模式下,范围选择完成(选了结束日期)自动触发change事件
882
+ if (props.isPage) {
883
+ btnFix(true);
884
+ }
607
885
  }
608
886
  }
609
887
  }
@@ -630,7 +908,8 @@ function getWeekText(date: string) {
630
908
  * 确定按钮事件
631
909
  */
632
910
  function btnFix(show: boolean) {
633
- if (!show) {
911
+ // 页面模式下不关闭,弹窗模式下关闭
912
+ if (!show && !props.isPage) {
634
913
  close();
635
914
  }
636
915
  if (props.mode == 'date') {
@@ -642,7 +921,7 @@ function btnFix(show: boolean) {
642
921
  let result = `${y}-${formatNum(m)}-${formatNum(d)}`;
643
922
  let weekText = getWeekText(result);
644
923
  let isToday = false;
645
- if (`${y}-${m}-${d}` == today.value) {
924
+ if (result == today.value) {
646
925
  // 今天
647
926
  isToday = true;
648
927
  }
@@ -698,6 +977,11 @@ function btnFix(show: boolean) {
698
977
  .u-calendar {
699
978
  color: $u-content-color;
700
979
 
980
+ &--page {
981
+ background-color: var(--u-bg-white);
982
+ border-radius: 16rpx;
983
+ }
984
+
701
985
  &__header {
702
986
  width: 100%;
703
987
  box-sizing: border-box;
@@ -762,6 +1046,39 @@ function btnFix(show: boolean) {
762
1046
  border-bottom-left-radius: 8rpx;
763
1047
  }
764
1048
 
1049
+ &--checked {
1050
+ .u-calendar__content__item__inner {
1051
+ width: 80rpx;
1052
+ height: 80rpx;
1053
+ border-radius: 50%;
1054
+ display: flex;
1055
+ align-items: center;
1056
+ justify-content: center;
1057
+ }
1058
+ }
1059
+
1060
+ &--today-checked {
1061
+ .u-calendar__content__item__inner {
1062
+ width: 80rpx;
1063
+ height: 80rpx;
1064
+ border-radius: 50%;
1065
+ display: flex;
1066
+ align-items: center;
1067
+ justify-content: center;
1068
+ }
1069
+ }
1070
+
1071
+ &--checkin-mode {
1072
+ .u-calendar__content__item__inner {
1073
+ width: 80rpx;
1074
+ height: 80rpx;
1075
+ border-radius: 50%;
1076
+ display: flex;
1077
+ align-items: center;
1078
+ justify-content: center;
1079
+ }
1080
+ }
1081
+
765
1082
  &__item {
766
1083
  width: 14.2857%;
767
1084
  @include vue-flex;
@@ -779,8 +1096,6 @@ function btnFix(show: boolean) {
779
1096
  justify-content: center;
780
1097
  flex-direction: column;
781
1098
  font-size: 32rpx;
782
- position: relative;
783
- border-radius: 50%;
784
1099
 
785
1100
  &__desc {
786
1101
  width: 100%;
@@ -795,6 +1110,61 @@ function btnFix(show: boolean) {
795
1110
  }
796
1111
  }
797
1112
 
1113
+ &__day {
1114
+ font-size: 32rpx;
1115
+ line-height: 1;
1116
+ }
1117
+
1118
+ &__lunar {
1119
+ font-size: 22rpx;
1120
+ line-height: 1;
1121
+ margin-top: 2rpx;
1122
+ transform: scale(0.85);
1123
+ }
1124
+
1125
+ // 节假日/加班日标签样式
1126
+ &__holiday,
1127
+ &__workday {
1128
+ font-size: 22rpx;
1129
+ line-height: 1;
1130
+ margin-top: 2rpx;
1131
+ transform: scale(0.85);
1132
+ }
1133
+
1134
+ // 右上角标记样式(休/班)
1135
+ &__mark {
1136
+ position: absolute;
1137
+ top: 8rpx;
1138
+ right: 8rpx;
1139
+ font-size: 24rpx;
1140
+ line-height: 1;
1141
+ transform: scale(0.75);
1142
+ z-index: 1;
1143
+
1144
+ &--holiday {
1145
+ color: var(--u-type-error);
1146
+ }
1147
+
1148
+ &--workday {
1149
+ color: var(--u-type-primary);
1150
+ }
1151
+ }
1152
+
1153
+ // 节日名称样式(与农历保持一致)
1154
+ &__festival {
1155
+ font-size: 22rpx;
1156
+ line-height: 1;
1157
+ transform: scale(0.85);
1158
+ color: var(--u-type-primary);
1159
+ }
1160
+
1161
+ // 占位元素样式
1162
+ &__placeholder {
1163
+ min-height: 22rpx;
1164
+ margin-top: 2rpx;
1165
+ opacity: 0;
1166
+ }
1167
+
798
1168
  &__tips {
799
1169
  width: 100%;
800
1170
  font-size: 24rpx;
@@ -807,6 +1177,27 @@ function btnFix(show: boolean) {
807
1177
  bottom: 8rpx;
808
1178
  z-index: 2;
809
1179
  }
1180
+
1181
+ &__check-icon {
1182
+ position: absolute;
1183
+ right: 4rpx;
1184
+ top: 4rpx;
1185
+ z-index: 3;
1186
+ }
1187
+
1188
+ &__checkmark {
1189
+ @include vue-flex;
1190
+ align-items: center;
1191
+ justify-content: center;
1192
+ width: 100%;
1193
+ height: 100%;
1194
+ }
1195
+
1196
+ &__inner--today-checked {
1197
+ @include vue-flex;
1198
+ align-items: center;
1199
+ justify-content: center;
1200
+ }
810
1201
  }
811
1202
 
812
1203
  &__bg-month {