uview-pro 0.6.18 → 0.6.19

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.
Files changed (278) hide show
  1. package/changelog.md +1778 -1768
  2. package/components/u-action-sheet/types.ts +52 -52
  3. package/components/u-action-sheet/u-action-sheet.vue +196 -196
  4. package/components/u-action-sheet-item/types.ts +35 -35
  5. package/components/u-action-sheet-item/u-action-sheet-item.vue +85 -85
  6. package/components/u-alert-tips/types.ts +49 -49
  7. package/components/u-alert-tips/u-alert-tips.vue +239 -239
  8. package/components/u-avatar/types.ts +44 -44
  9. package/components/u-avatar/u-avatar.vue +218 -218
  10. package/components/u-avatar-cropper/types.ts +23 -23
  11. package/components/u-avatar-cropper/u-avatar-cropper.vue +299 -299
  12. package/components/u-avatar-cropper/weCropper.d.ts +62 -62
  13. package/components/u-avatar-cropper/weCropper.js +1281 -1281
  14. package/components/u-avatar-cropper/weCropper.ts +1276 -1276
  15. package/components/u-back-top/types.ts +44 -44
  16. package/components/u-back-top/u-back-top.vue +141 -141
  17. package/components/u-badge/types.ts +46 -46
  18. package/components/u-badge/u-badge.vue +153 -153
  19. package/components/u-button/types.ts +76 -76
  20. package/components/u-button/u-button.vue +608 -608
  21. package/components/u-calendar/types.ts +126 -126
  22. package/components/u-calendar/u-calendar.vue +1243 -1243
  23. package/components/u-car-keyboard/types.ts +22 -22
  24. package/components/u-car-keyboard/u-car-keyboard.vue +263 -263
  25. package/components/u-card/types.ts +69 -69
  26. package/components/u-card/u-card.vue +210 -210
  27. package/components/u-cell-group/types.ts +27 -27
  28. package/components/u-cell-group/u-cell-group.vue +60 -60
  29. package/components/u-cell-item/types.ts +64 -64
  30. package/components/u-cell-item/u-cell-item.vue +221 -221
  31. package/components/u-checkbox/types.ts +45 -45
  32. package/components/u-checkbox/u-checkbox.vue +363 -363
  33. package/components/u-checkbox-group/types.ts +45 -45
  34. package/components/u-checkbox-group/u-checkbox-group.vue +172 -172
  35. package/components/u-circle-progress/types.ts +63 -63
  36. package/components/u-circle-progress/u-circle-progress.vue +299 -299
  37. package/components/u-city-select/types.ts +30 -30
  38. package/components/u-city-select/u-city-select.vue +281 -281
  39. package/components/u-col/types.ts +40 -40
  40. package/components/u-col/u-col.vue +142 -142
  41. package/components/u-collapse/types.ts +41 -41
  42. package/components/u-collapse/u-collapse.vue +190 -190
  43. package/components/u-collapse-item/types.ts +35 -35
  44. package/components/u-collapse-item/u-collapse-item.vue +291 -291
  45. package/components/u-column-notice/types.ts +58 -58
  46. package/components/u-column-notice/u-column-notice.vue +223 -223
  47. package/components/u-config-provider/types.ts +53 -53
  48. package/components/u-config-provider/u-config-provider.vue +185 -185
  49. package/components/u-count-down/types.ts +52 -52
  50. package/components/u-count-down/u-count-down.vue +288 -288
  51. package/components/u-count-to/types.ts +42 -42
  52. package/components/u-count-to/u-count-to.vue +266 -266
  53. package/components/u-divider/types.ts +41 -41
  54. package/components/u-divider/u-divider.vue +146 -146
  55. package/components/u-dropdown/types.ts +67 -67
  56. package/components/u-dropdown/u-dropdown.vue +436 -436
  57. package/components/u-dropdown-item/types.ts +40 -40
  58. package/components/u-dropdown-item/u-dropdown-item.vue +124 -124
  59. package/components/u-empty/types.ts +46 -46
  60. package/components/u-empty/u-empty.vue +109 -109
  61. package/components/u-fab/types.ts +83 -83
  62. package/components/u-fab/u-fab.vue +330 -330
  63. package/components/u-field/types.ts +81 -81
  64. package/components/u-field/u-field.vue +398 -398
  65. package/components/u-form/types.ts +39 -39
  66. package/components/u-form/u-form.vue +175 -135
  67. package/components/u-form-item/types.ts +95 -95
  68. package/components/u-form-item/u-form-item.vue +563 -563
  69. package/components/u-full-screen/types.ts +26 -26
  70. package/components/u-full-screen/u-full-screen.vue +100 -100
  71. package/components/u-gap/types.ts +28 -28
  72. package/components/u-gap/u-gap.vue +50 -50
  73. package/components/u-grid/types.ts +29 -29
  74. package/components/u-grid/u-grid.vue +91 -91
  75. package/components/u-grid-item/types.ts +24 -24
  76. package/components/u-grid-item/u-grid-item.vue +130 -130
  77. package/components/u-icon/types.ts +69 -69
  78. package/components/u-icon/u-icon.vue +301 -301
  79. package/components/u-image/types.ts +61 -61
  80. package/components/u-image/u-image.vue +245 -245
  81. package/components/u-index-anchor/types.ts +24 -24
  82. package/components/u-index-anchor/u-index-anchor.vue +120 -120
  83. package/components/u-index-list/types.ts +44 -44
  84. package/components/u-index-list/u-index-list.vue +346 -346
  85. package/components/u-input/types.ts +165 -165
  86. package/components/u-input/u-input.vue +453 -453
  87. package/components/u-keyboard/types.ts +40 -40
  88. package/components/u-keyboard/u-keyboard.vue +201 -201
  89. package/components/u-lazy-load/types.ts +37 -37
  90. package/components/u-lazy-load/u-lazy-load.vue +246 -246
  91. package/components/u-line/types.ts +44 -44
  92. package/components/u-line/u-line.vue +68 -68
  93. package/components/u-line-progress/types.ts +58 -58
  94. package/components/u-line-progress/u-line-progress.vue +131 -131
  95. package/components/u-link/types.ts +54 -54
  96. package/components/u-link/u-link.vue +100 -100
  97. package/components/u-loading/types.ts +45 -45
  98. package/components/u-loading/u-loading.vue +106 -106
  99. package/components/u-loading-popup/types.ts +26 -26
  100. package/components/u-loading-popup/u-loading-popup.vue +254 -254
  101. package/components/u-loadmore/types.ts +88 -88
  102. package/components/u-loadmore/u-loadmore.vue +170 -170
  103. package/components/u-mask/types.ts +48 -48
  104. package/components/u-mask/u-mask.vue +113 -113
  105. package/components/u-message-input/types.ts +90 -90
  106. package/components/u-message-input/u-message-input.vue +283 -283
  107. package/components/u-modal/service.ts +48 -48
  108. package/components/u-modal/types.ts +139 -139
  109. package/components/u-modal/u-modal.vue +465 -465
  110. package/components/u-navbar/types.ts +104 -104
  111. package/components/u-navbar/u-navbar.vue +268 -268
  112. package/components/u-no-network/image.ts +2 -2
  113. package/components/u-no-network/types.ts +29 -29
  114. package/components/u-no-network/u-no-network.vue +313 -313
  115. package/components/u-notice-bar/types.ts +111 -111
  116. package/components/u-notice-bar/u-notice-bar.vue +191 -191
  117. package/components/u-number-box/types.ts +42 -42
  118. package/components/u-number-box/u-number-box.vue +322 -322
  119. package/components/u-number-keyboard/types.ts +26 -26
  120. package/components/u-number-keyboard/u-number-keyboard.vue +189 -189
  121. package/components/u-pagination/types.ts +38 -38
  122. package/components/u-pagination/u-pagination.vue +102 -102
  123. package/components/u-picker/types.ts +140 -140
  124. package/components/u-picker/u-picker.vue +854 -854
  125. package/components/u-popup/types.ts +64 -64
  126. package/components/u-popup/u-popup.vue +427 -427
  127. package/components/u-radio/types.ts +39 -39
  128. package/components/u-radio/u-radio.vue +349 -349
  129. package/components/u-radio-group/types.ts +40 -40
  130. package/components/u-radio-group/u-radio-group.vue +96 -96
  131. package/components/u-rate/types.ts +50 -50
  132. package/components/u-rate/u-rate.vue +250 -250
  133. package/components/u-read-more/types.ts +46 -46
  134. package/components/u-read-more/u-read-more.vue +182 -182
  135. package/components/u-root-portal/u-root-portal.vue +55 -55
  136. package/components/u-row/types.ts +30 -30
  137. package/components/u-row/u-row.vue +105 -105
  138. package/components/u-row-notice/types.ts +49 -49
  139. package/components/u-row-notice/u-row-notice.vue +257 -257
  140. package/components/u-safe-bottom/types.ts +19 -19
  141. package/components/u-safe-bottom/u-safe-bottom.vue +55 -55
  142. package/components/u-search/u-search.vue +297 -297
  143. package/components/u-section/types.ts +42 -42
  144. package/components/u-section/u-section.vue +156 -156
  145. package/components/u-select/types.ts +56 -56
  146. package/components/u-select/u-select.vue +447 -447
  147. package/components/u-skeleton/types.ts +89 -89
  148. package/components/u-skeleton/u-skeleton.vue +253 -253
  149. package/components/u-slider/types.ts +55 -55
  150. package/components/u-slider/u-slider.vue +422 -422
  151. package/components/u-status-bar/types.ts +21 -21
  152. package/components/u-status-bar/u-status-bar.vue +64 -64
  153. package/components/u-step/types.ts +37 -37
  154. package/components/u-step/u-step.vue +292 -292
  155. package/components/u-steps/types.ts +39 -39
  156. package/components/u-steps/u-steps.vue +105 -105
  157. package/components/u-sticky/types.ts +33 -33
  158. package/components/u-sticky/u-sticky.vue +177 -177
  159. package/components/u-subsection/types.ts +46 -46
  160. package/components/u-subsection/u-subsection.vue +358 -358
  161. package/components/u-swipe-action/types.ts +60 -60
  162. package/components/u-swipe-action/u-swipe-action.vue +276 -276
  163. package/components/u-swiper/types.ts +57 -57
  164. package/components/u-swiper/u-swiper.vue +308 -308
  165. package/components/u-switch/types.ts +39 -39
  166. package/components/u-switch/u-switch.vue +212 -212
  167. package/components/u-tabbar/types.ts +57 -57
  168. package/components/u-tabbar/u-tabbar.vue +480 -480
  169. package/components/u-table/types.ts +35 -35
  170. package/components/u-table/u-table.vue +67 -67
  171. package/components/u-tabs/types.ts +64 -64
  172. package/components/u-tabs/u-tabs.vue +328 -328
  173. package/components/u-tabs-swiper/types.ts +64 -64
  174. package/components/u-tabs-swiper/u-tabs-swiper.vue +407 -407
  175. package/components/u-tag/types.ts +47 -47
  176. package/components/u-tag/u-tag.vue +276 -276
  177. package/components/u-td/types.ts +22 -22
  178. package/components/u-td/u-td.vue +65 -65
  179. package/components/u-text/types.ts +80 -80
  180. package/components/u-text/u-text.vue +355 -355
  181. package/components/u-textarea/types.ts +105 -105
  182. package/components/u-textarea/u-textarea.vue +450 -450
  183. package/components/u-th/types.ts +22 -22
  184. package/components/u-th/u-th.vue +63 -63
  185. package/components/u-time-line/u-time-line.vue +64 -64
  186. package/components/u-time-line-item/types.ts +24 -24
  187. package/components/u-time-line-item/u-time-line-item.vue +89 -89
  188. package/components/u-toast/service.ts +40 -40
  189. package/components/u-toast/types.ts +54 -54
  190. package/components/u-toast/u-toast.vue +385 -385
  191. package/components/u-top-tips/types.ts +25 -25
  192. package/components/u-top-tips/u-top-tips.vue +130 -130
  193. package/components/u-tr/types.ts +19 -19
  194. package/components/u-tr/u-tr.vue +39 -39
  195. package/components/u-transition/types.ts +43 -43
  196. package/components/u-transition/u-transition.vue +437 -437
  197. package/components/u-upload/types.ts +171 -171
  198. package/components/u-upload/u-upload.vue +1605 -1605
  199. package/components/u-verification-code/types.ts +32 -32
  200. package/components/u-verification-code/u-verification-code.vue +183 -183
  201. package/components/u-waterfall/types.ts +26 -26
  202. package/components/u-waterfall/u-waterfall.vue +188 -188
  203. package/iconfont.css +913 -913
  204. package/index.scss +26 -26
  205. package/libs/config/color.ts +9 -9
  206. package/libs/config/config.ts +59 -59
  207. package/libs/config/theme-tokens.ts +102 -102
  208. package/libs/config/zIndex.ts +32 -32
  209. package/libs/css/color.scss +155 -155
  210. package/libs/css/common.scss +183 -183
  211. package/libs/css/style.components.scss +16 -16
  212. package/libs/css/style.h5.scss +8 -8
  213. package/libs/css/style.mp.scss +68 -68
  214. package/libs/css/style.nvue.scss +15 -15
  215. package/libs/css/style.theme.scss +56 -56
  216. package/libs/css/style.vue.scss +188 -188
  217. package/libs/function/$parent.ts +24 -24
  218. package/libs/function/addUnit.ts +50 -50
  219. package/libs/function/clipboard.ts +73 -73
  220. package/libs/function/color.ts +28 -28
  221. package/libs/function/colorGradient.ts +139 -139
  222. package/libs/function/debounce.ts +28 -28
  223. package/libs/function/deepClone.ts +39 -39
  224. package/libs/function/deepMerge.ts +35 -35
  225. package/libs/function/getParent.ts +63 -63
  226. package/libs/function/getRect.ts +26 -26
  227. package/libs/function/guid.ts +42 -42
  228. package/libs/function/md5.ts +403 -403
  229. package/libs/function/parent.ts +21 -21
  230. package/libs/function/queryParams.ts +64 -64
  231. package/libs/function/random.ts +16 -16
  232. package/libs/function/randomArray.ts +11 -11
  233. package/libs/function/route.ts +118 -118
  234. package/libs/function/styleUtils.ts +83 -83
  235. package/libs/function/sys.ts +15 -15
  236. package/libs/function/test.ts +289 -289
  237. package/libs/function/throttle.ts +31 -31
  238. package/libs/function/timeFormat.ts +55 -55
  239. package/libs/function/timeFrom.ts +48 -48
  240. package/libs/function/toast.ts +14 -14
  241. package/libs/function/trim.ts +21 -21
  242. package/libs/function/type2icon.ts +38 -38
  243. package/libs/hooks/index.ts +10 -10
  244. package/libs/hooks/useColor.ts +61 -61
  245. package/libs/hooks/useCompRelation.ts +424 -424
  246. package/libs/hooks/useDebounce.ts +15 -15
  247. package/libs/hooks/useEmitter.ts +79 -79
  248. package/libs/hooks/useLocale.ts +34 -34
  249. package/libs/hooks/useModal.ts +131 -131
  250. package/libs/hooks/useRect.ts +40 -40
  251. package/libs/hooks/useRouter.ts +173 -173
  252. package/libs/hooks/useTheme.ts +173 -173
  253. package/libs/hooks/useThrottle.ts +16 -16
  254. package/libs/hooks/useToast.ts +115 -115
  255. package/libs/index.ts +349 -349
  256. package/libs/request/auto-http.ts +76 -76
  257. package/libs/request/index.ts +301 -301
  258. package/libs/store/index.ts +88 -88
  259. package/libs/util/async-validator.d.ts +75 -75
  260. package/libs/util/async-validator.js +1 -1
  261. package/libs/util/calendar.d.ts +57 -57
  262. package/libs/util/canvas-2d.ts +182 -182
  263. package/libs/util/config-provider.ts +804 -804
  264. package/libs/util/emitter.ts +112 -112
  265. package/libs/util/mitt.ts +118 -118
  266. package/libs/util/system-theme.ts +25 -25
  267. package/locale/index.ts +2 -2
  268. package/locale/lang/en-US.ts +136 -136
  269. package/locale/lang/zh-CN.ts +136 -136
  270. package/package.json +1 -1
  271. package/plugins/root/page.ts +77 -77
  272. package/plugins/root/root.ts +53 -53
  273. package/theme.scss +149 -149
  274. package/types/components.d.ts +105 -105
  275. package/types/global.d.ts +470 -470
  276. package/types/ignore-errors.d.ts +30 -30
  277. package/types/index.d.ts +15 -15
  278. package/types/uni-app.d.ts +63 -63
@@ -1,1243 +1,1243 @@
1
- <template>
2
- <u-popup
3
- v-model="popupValue"
4
- length="auto"
5
- :maskCloseAble="maskCloseAble"
6
- :mode="props.isPage ? 'inline' : 'bottom'"
7
- :popup="false"
8
- :safeAreaInsetBottom="safeAreaInsetBottom"
9
- :z-index="uZIndex"
10
- :border-radius="borderRadius"
11
- :closeable="closeable"
12
- @close="close"
13
- >
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">
20
- <view class="u-calendar__header__text" v-if="!slots.tooltip">
21
- {{ getToolTip }}
22
- </view>
23
- <slot v-else name="tooltip" />
24
- </view>
25
- <view class="u-calendar__action u-flex u-row-center">
26
- <view class="u-calendar__action__icon">
27
- <u-icon
28
- v-if="changeYear"
29
- name="arrow-left-double"
30
- :color="yearArrowColor"
31
- @click="changeYearHandler(0)"
32
- ></u-icon>
33
- </view>
34
- <view class="u-calendar__action__icon">
35
- <u-icon
36
- v-if="changeMonth"
37
- name="arrow-left"
38
- :color="monthArrowColor"
39
- @click="changeMonthHandler(0)"
40
- ></u-icon>
41
- </view>
42
- <view class="u-calendar__action__text">{{ showTitle }}</view>
43
- <view class="u-calendar__action__icon">
44
- <u-icon
45
- v-if="changeMonth"
46
- name="arrow-right"
47
- :color="monthArrowColor"
48
- @click="changeMonthHandler(1)"
49
- ></u-icon>
50
- </view>
51
- <view class="u-calendar__action__icon">
52
- <u-icon
53
- v-if="changeYear"
54
- name="arrow-right-double"
55
- :color="yearArrowColor"
56
- @click="changeYearHandler(1)"
57
- ></u-icon>
58
- </view>
59
- </view>
60
- <view class="u-calendar__week-day">
61
- <view class="u-calendar__week-day__text" v-for="(item, index) in weekDayZh" :key="index">
62
- {{ item }}
63
- </view>
64
- </view>
65
- <view class="u-calendar__content">
66
- <!-- 前置空白部分 -->
67
- <block v-for="(item, index) in weekdayArr" :key="index">
68
- <view class="u-calendar__content__item"></view>
69
- </block>
70
- <view
71
- class="u-calendar__content__item"
72
- :class="{
73
- 'u-hover-class': openDisAbled(year, month, index + 1),
74
- 'u-calendar__content--start-date':
75
- (mode == 'range' && startDate == `${year}-${formatNum(month)}-${formatNum(index + 1)}`) ||
76
- mode == 'date',
77
- 'u-calendar__content--end-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
- : ''
89
- }"
90
- v-for="(item, index) in daysArr"
91
- :key="index"
92
- @tap="dateClick(index)"
93
- >
94
- <view
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
- }"
101
- >
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
- {{ getStartText }}
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
- {{ getEndText }}
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>
184
- </view>
185
- </view>
186
- <view class="u-calendar__content__bg-month">{{ month }}</view>
187
- </view>
188
- <!-- 页面模式下不显示确定按钮,选择完成自动触发change事件 -->
189
- <view class="u-calendar__bottom" v-if="!props.isPage">
190
- <view class="u-calendar__bottom__choose">
191
- <text>{{ mode == 'date' ? activeDate : startDate }}</text>
192
- <text v-if="endDate">{{ t('uCalendar.to') }}{{ endDate }}</text>
193
- </view>
194
- <view class="u-calendar__bottom__btn">
195
- <u-button
196
- :type="btnType"
197
- :disabled="btnDisable"
198
- shape="circle"
199
- size="default"
200
- @click="btnFix(false)"
201
- >
202
- {{ t('uCalendar.confirmText') }}
203
- </u-button>
204
- </view>
205
- </view>
206
- </view>
207
- </u-popup>
208
- </template>
209
-
210
- <script lang="ts">
211
- export default {
212
- name: 'u-calendar',
213
- options: {
214
- addGlobalClass: true,
215
- // #ifndef MP-TOUTIAO
216
- virtualHost: true,
217
- // #endif
218
- styleIsolation: 'shared'
219
- }
220
- };
221
- </script>
222
-
223
- <script setup lang="ts">
224
- import { ref, computed, watch, onMounted, useSlots } from 'vue';
225
- import { $u, useLocale } from '../..';
226
- import { CalendarProps, type CalendarEmits } from './types';
227
- import Calendar from '../../libs/util/calendar';
228
- import uButton from '../u-button/u-button.vue';
229
- import uIcon from '../u-icon/u-icon.vue';
230
- import uPopup from '../u-popup/u-popup.vue';
231
-
232
- /**
233
- * calendar 日历
234
- * @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中。
235
- * @tutorial https://uviewpro.cn/zh/components/calendar.html
236
- * @property {String} mode 选择日期的模式,date-为单个日期,range-为选择日期范围
237
- * @property {Boolean} v-model 布尔值变量,用于控制日历的弹出与收起
238
- * @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
239
- * @property {Boolean} change-year 是否显示顶部的切换年份方向的按钮(默认true)
240
- * @property {Boolean} change-month 是否显示顶部的切换月份方向的按钮(默认true)
241
- * @property {String Number} max-year 可切换的最大年份(默认2050)
242
- * @property {String Number} min-year 可切换的最小年份(默认1950)
243
- * @property {String Number} min-date 最小可选日期(默认1950-01-01)
244
- * @property {String Number} max-date 最大可选日期(默认当前日期)
245
- * @property {String Number} 弹窗顶部左右两边的圆角值,单位rpx(默认20)
246
- * @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭日历(默认true)
247
- * @property {String} month-arrow-color 月份切换按钮箭头颜色(默认var(--u-content-color))
248
- * @property {String} year-arrow-color 年份切换按钮箭头颜色(默认var(--u-tips-color))
249
- * @property {String} color 日期字体的默认颜色(默认var(--u-main-color))
250
- * @property {String} active-bg-color 起始/结束日期按钮的背景色(默认主题色primary)
251
- * @property {String Number} z-index 弹出时的z-index值(默认10075)
252
- * @property {String} active-color 起始/结束日期按钮的字体颜色(默认var(--u-white-color))
253
- * @property {String} range-bg-color 起始/结束日期之间的区域的背景颜色(默认rgba(41,121,255,0.13))
254
- * @property {String} range-color 选择范围内字体颜色(默认主题色primary)
255
- * @property {String} start-text 起始日期底部的提示文字(默认 '开始')
256
- * @property {String} end-text 结束日期底部的提示文字(默认 '结束')
257
- * @property {String} btn-type 底部确定按钮的主题(默认 'primary')
258
- * @property {String} toolTip 顶部提示文字,如设置名为tooltip的slot,此参数将失效(默认 '选择日期')
259
- * @property {Boolean} closeable 是否显示右上角的关闭图标(默认true)
260
- * @property {Boolean} is-page 是否在页面中直接显示,不使用弹窗(默认false)
261
- * @property {String} default-date 默认选中的日期,mode=date时生效,格式:2024-01-01
262
- * @property {String} start-date 默认选中的开始日期,mode=range时生效,格式:2024-01-01
263
- * @property {String} end-date 默认选中的结束日期,mode=range时生效,格式:2024-01-01
264
- * @property {Boolean} readonly 是否只读,只读模式下禁止点击选择日期(默认false)
265
- * @example <u-calendar v-model="show" :mode="mode"></u-calendar>
266
- */
267
-
268
- const props = defineProps(CalendarProps);
269
- const emit = defineEmits<CalendarEmits>();
270
- const slots = useSlots();
271
-
272
- const { t } = useLocale();
273
-
274
- // 组件内部状态
275
- // 星期几,值为1-7
276
- const weekday = ref(1);
277
- const weekdayArr = ref<number[]>([]);
278
- const days = ref(0);
279
- const daysArr = ref<number[]>([]);
280
- const lunarArr = ref<any[]>([]);
281
- const year = ref(2020);
282
- const month = ref(0);
283
- // 当前月有多少天
284
- const day = ref(0);
285
- const startYear = ref(0);
286
- const startMonth = ref(0);
287
- const startDay = ref(0);
288
- const endYear = ref(0);
289
- const endMonth = ref(0);
290
- const endDay = ref(0);
291
- const today = ref('');
292
- const activeDate = ref('');
293
- const startDate = ref('');
294
- const endDate = ref('');
295
- const isStart = ref(true);
296
- const min = ref<{ year: number; month: number; day: number } | null>(null);
297
- const max = ref<{ year: number; month: number; day: number } | null>(null);
298
- const weekDayZh = ref([
299
- t('uCalendar.sun'),
300
- t('uCalendar.mon'),
301
- t('uCalendar.tue'),
302
- t('uCalendar.wed'),
303
- t('uCalendar.thu'),
304
- t('uCalendar.fri'),
305
- t('uCalendar.sat')
306
- ]);
307
-
308
- // 内置中国传统节日(公历日期,格式:MM-DD)
309
- const builtInFestivals: Record<string, string> = {
310
- '01-01': '元旦',
311
- '02-14': '情人节',
312
- '03-08': '妇女节',
313
- '03-12': '植树节',
314
- '04-01': '愚人节',
315
- '05-01': '劳动节',
316
- '05-04': '青年节',
317
- '06-01': '儿童节',
318
- '07-01': '建党节',
319
- '08-01': '建军节',
320
- '09-10': '教师节',
321
- '10-01': '国庆节',
322
- '11-11': '光棍节',
323
- '12-25': '圣诞节'
324
- };
325
-
326
- const dataChange = computed(() => `${props.mode}-${props.minDate}-${props.maxDate}`);
327
- const lunarChange = computed(() => props.showLunar);
328
- const defaultDateChange = computed(
329
- () => `${props.defaultDate}-${props.startDate}-${props.endDate}-${props.defaultSelectToday}`
330
- );
331
- // 如果用户有传递z-index值,优先使用
332
- const uZIndex = computed(() => (props.zIndex ? props.zIndex : $u.zIndex.popup));
333
- const popupValue = computed({
334
- get: () => props.modelValue,
335
- set: (val: boolean) => emit('update:modelValue', val)
336
- });
337
-
338
- const btnDisable = computed(() => {
339
- let disable = false;
340
- if (props.mode == 'range') {
341
- if (!startDate.value || !endDate.value) {
342
- disable = true;
343
- }
344
- } else {
345
- if (!activeDate.value) {
346
- disable = true;
347
- }
348
- }
349
- return disable;
350
- });
351
-
352
- // 国际化计算属性
353
- const getStartText = computed(() => props.startText || t('uCalendar.startText'));
354
- const getEndText = computed(() => props.endText || t('uCalendar.endText'));
355
- const getToolTip = computed(() => props.toolTip || t('uCalendar.toolTip'));
356
- const showTitle = computed(() => `${year.value}${t('uCalendar.year')}${month.value}${t('uCalendar.month')}`);
357
-
358
- watch([dataChange, lunarChange], () => {
359
- init();
360
- });
361
-
362
- watch(defaultDateChange, () => {
363
- init();
364
- });
365
-
366
- onMounted(() => {
367
- init();
368
- });
369
-
370
- /**
371
- * 获取日期颜色
372
- * @param index
373
- * @param type 1 背景色 2 字体色
374
- */
375
- function getColor(index: number, type: number) {
376
- let color = type == 1 ? '' : props.color;
377
- let dayNum = index + 1;
378
- let date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
379
- let timestamp = new Date(date.replace(/\-/g, '/')).getTime();
380
- let start = startDate.value.replace(/\-/g, '/');
381
- let end = endDate.value.replace(/\-/g, '/');
382
- if ((props.isActiveCurrent && activeDate.value == date) || startDate.value == date || endDate.value == date) {
383
- color = type == 1 ? props.activeBgColor : props.activeColor;
384
- } else if (endDate.value && timestamp > new Date(start).getTime() && timestamp < new Date(end).getTime()) {
385
- color = type == 1 ? props.rangeBgColor : props.rangeColor;
386
- }
387
- return color;
388
- }
389
-
390
- /**
391
- * 判断日期是否已打卡
392
- */
393
- function isCheckedDate(dayNum: number) {
394
- const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
395
- return props.checkedDates.includes(date);
396
- }
397
-
398
- /**
399
- * 判断是否是今日且已打卡
400
- * 优先级:1. todayChecked 属性 2. 自动判断 checkedDates 中是否包含今天
401
- */
402
- function isTodayChecked(dayNum: number) {
403
- const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
404
- // 首先检查是否是今天
405
- if (date !== today.value) {
406
- return false;
407
- }
408
- // 优先级1:手动设置的 todayChecked
409
- if (props.todayChecked) {
410
- return true;
411
- }
412
- // 优先级2:自动判断 checkedDates 中是否包含今天
413
- if (props.checkedDates.includes(date)) {
414
- return true;
415
- }
416
- return false;
417
- }
418
-
419
- /**
420
- * 获取打卡日期背景色
421
- */
422
- function getCheckinColor(dayNum: number) {
423
- const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
424
- const isToday = date === today.value;
425
- const isInCheckedDates = props.checkedDates.includes(date);
426
-
427
- // 只有在打卡签到模式下或设置了打卡数据时才返回颜色
428
- if (!props.checkinMode && props.checkedDates.length === 0 && !props.todayChecked) {
429
- return '';
430
- }
431
-
432
- // 今日已打卡显示绿色(优先级:todayChecked > 自动判断)
433
- if (isToday && (props.todayChecked || isInCheckedDates)) {
434
- return props.todayCheckedBgColor;
435
- }
436
-
437
- // 其他已打卡日期显示橙色
438
- if (isInCheckedDates) {
439
- return props.checkedBgColor;
440
- }
441
-
442
- // 打卡签到模式下,未打卡日期显示灰色
443
- if (props.checkinMode) {
444
- return props.uncheckedBgColor;
445
- }
446
- return '';
447
- }
448
-
449
- /**
450
- * 获取打卡日期文字颜色
451
- */
452
- function getCheckinTextColor(dayNum: number) {
453
- // 只有在打卡签到模式下或设置了打卡数据时才返回颜色
454
- if (!props.checkinMode && props.checkedDates.length === 0 && !props.todayChecked) {
455
- return '';
456
- }
457
- const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
458
- // 已打卡日期显示白色文字
459
- if (props.checkedDates.includes(date) || (date === today.value && props.todayChecked)) {
460
- return props.checkedColor;
461
- }
462
- // 打卡签到模式下,未打卡日期显示白色文字
463
- if (props.checkinMode) {
464
- return props.uncheckedColor;
465
- }
466
- return '';
467
- }
468
-
469
- /**
470
- * 获取打卡日期农历文字颜色
471
- */
472
- function getCheckinLunarColor(dayNum: number) {
473
- // 只有在打卡签到模式下或设置了打卡数据时才返回颜色
474
- if (!props.checkinMode && props.checkedDates.length === 0 && !props.todayChecked) {
475
- return '';
476
- }
477
- const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
478
- // 已打卡日期的农历显示白色文字
479
- if (props.checkedDates.includes(date) || (date === today.value && props.todayChecked)) {
480
- return props.checkedColor;
481
- }
482
- // 打卡签到模式下,未打卡日期的农历显示白色文字
483
- if (props.checkinMode) {
484
- return props.uncheckedColor;
485
- }
486
- return '';
487
- }
488
-
489
- /**
490
- * 获取自定义插槽的颜色
491
- * 当选中日期时显示白色,否则显示默认颜色
492
- */
493
- function getSlotColor(dayNum: number) {
494
- const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
495
- // 选中日期的自定义内容显示白色(仅在 isActiveCurrent 为 true 时)
496
- if (props.isActiveCurrent && (activeDate.value === date || startDate.value === date || endDate.value === date)) {
497
- return props.activeColor;
498
- }
499
- // 打卡签到模式下使用对应的颜色
500
- if (props.checkinMode || props.checkedDates.length > 0 || props.todayChecked) {
501
- return getCheckinLunarColor(dayNum) || props.color;
502
- }
503
- return props.color;
504
- }
505
-
506
- /**
507
- * 获取日期信息,用于自定义插槽
508
- */
509
- function getDateInfo(dayNum: number) {
510
- const dateStr = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
511
- const dateObj = new Date(dateStr.replace(/\-/g, '/'));
512
- const dayOfWeek = dateObj.getDay();
513
- const weekNames = [
514
- t('uCalendar.sun'),
515
- t('uCalendar.mon'),
516
- t('uCalendar.tue'),
517
- t('uCalendar.wed'),
518
- t('uCalendar.thu'),
519
- t('uCalendar.fri'),
520
- t('uCalendar.sat')
521
- ];
522
-
523
- const isSelected = activeDate.value === dateStr || startDate.value === dateStr || endDate.value === dateStr;
524
-
525
- return {
526
- year: year.value,
527
- month: month.value,
528
- day: dayNum,
529
- date: dateStr,
530
- week: weekNames[dayOfWeek],
531
- weekNum: dayOfWeek,
532
- isToday: dateStr === today.value,
533
- isHoliday: props.holidays.includes(dateStr),
534
- isWorkday: props.workdays.includes(dateStr),
535
- isChecked: props.checkedDates.includes(dateStr),
536
- isSelected,
537
- isTodayChecked: dateStr === today.value && props.todayChecked,
538
- lunar: lunarArr.value[dayNum - 1] || null
539
- };
540
- }
541
-
542
- /**
543
- * 判断是否是节假日
544
- */
545
- function isHoliday(dayNum: number) {
546
- const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
547
- return props.holidays.includes(date);
548
- }
549
-
550
- /**
551
- * 判断是否是加班日
552
- */
553
- function isWorkday(dayNum: number) {
554
- const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
555
- return props.workdays.includes(date);
556
- }
557
-
558
- /**
559
- * 获取节日名称(合并内置节日和用户自定义节日)
560
- * 用户传入空字符串可覆盖内置节日,表示不显示该节日
561
- * 支持两种格式:
562
- * 1. 年-月-日:特定年份的节日,如 '2024-04-04': '清明节'
563
- * 2. 月-日:每年的固定节日,如 '04-04': '清明节'
564
- */
565
- function getFestival(dayNum: number) {
566
- if (!props.showFestival && Object.keys(props.festivals).length === 0) {
567
- return '';
568
- }
569
- const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
570
- const monthDay = `${formatNum(month.value)}-${formatNum(dayNum)}`;
571
-
572
- // 优先检查用户自定义节日(特定年份格式)
573
- if (date in props.festivals) {
574
- return props.festivals[date];
575
- }
576
-
577
- // 然后检查用户自定义节日(每年固定格式,月-日)
578
- if (monthDay in props.festivals) {
579
- return props.festivals[monthDay];
580
- }
581
-
582
- // 最后检查内置节日(如果启用了 showFestival)
583
- if (props.showFestival && builtInFestivals[monthDay]) {
584
- return builtInFestivals[monthDay];
585
- }
586
- return '';
587
- }
588
-
589
- /**
590
- * 获取节假日/加班日文字颜色
591
- * 当选中日期时显示白色,否则显示对应的颜色
592
- */
593
- function getHolidayWorkdayColor(dayNum: number, defaultColor: string) {
594
- const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
595
- // 选中日期的节假日/加班日显示白色
596
- if (activeDate.value === date || startDate.value === date || endDate.value === date) {
597
- return props.activeColor;
598
- }
599
- return defaultColor;
600
- }
601
-
602
- /**
603
- * 初始化日历数据
604
- */
605
- function init() {
606
- let now = new Date();
607
- let minDateObj = new Date(String(props.minDate));
608
- let maxDateObj = new Date(String(props.maxDate || ''));
609
- if (isNaN(maxDateObj.getTime())) maxDateObj = new Date();
610
- if (now < minDateObj) now = minDateObj;
611
- if (now > maxDateObj) now = maxDateObj;
612
- year.value = now.getFullYear();
613
- month.value = now.getMonth() + 1;
614
- day.value = now.getDate();
615
- today.value = `${now.getFullYear()}-${formatNum(month.value)}-${formatNum(day.value)}`;
616
- min.value = initDate(String(props.minDate));
617
- max.value = initDate(String(props.maxDate) || today.value);
618
-
619
- // 处理默认选中日期
620
- // 优先级1: defaultDate / startDate / endDate(显式指定日期)
621
- // 优先级2: defaultSelectToday(默认选中今天)
622
- // 优先级3: 不选中任何日期
623
- if (props.mode === 'date' && props.defaultDate) {
624
- // 单选模式:使用 defaultDate(优先级1)
625
- const defaultDateObj = new Date(props.defaultDate.replace(/\-/g, '/'));
626
- if (!isNaN(defaultDateObj.getTime())) {
627
- year.value = defaultDateObj.getFullYear();
628
- month.value = defaultDateObj.getMonth() + 1;
629
- day.value = defaultDateObj.getDate();
630
- // 统一格式为 YYYY-MM-DD,与 getColor 中的格式一致
631
- activeDate.value = `${year.value}-${formatNum(month.value)}-${formatNum(day.value)}`;
632
- } else if (props.defaultSelectToday) {
633
- activeDate.value = today.value;
634
- } else {
635
- activeDate.value = '';
636
- }
637
- } else if (props.mode === 'range' && (props.startDate || props.endDate)) {
638
- // 范围模式:使用 startDate 和 endDate(优先级1)
639
- const startDateObj = props.startDate ? new Date(props.startDate.replace(/\-/g, '/')) : null;
640
- const endDateObj = props.endDate ? new Date(props.endDate.replace(/\-/g, '/')) : null;
641
-
642
- if (startDateObj && !isNaN(startDateObj.getTime())) {
643
- // 设置当前显示月份为开始日期所在月份
644
- year.value = startDateObj.getFullYear();
645
- month.value = startDateObj.getMonth() + 1;
646
-
647
- // 设置开始日期 - 统一格式为 YYYY-MM-DD
648
- startYear.value = startDateObj.getFullYear();
649
- startMonth.value = startDateObj.getMonth() + 1;
650
- startDay.value = startDateObj.getDate();
651
- startDate.value = `${startYear.value}-${formatNum(startMonth.value)}-${formatNum(startDay.value)}`;
652
- }
653
-
654
- if (endDateObj && !isNaN(endDateObj.getTime())) {
655
- // 设置结束日期 - 统一格式为 YYYY-MM-DD
656
- endYear.value = endDateObj.getFullYear();
657
- endMonth.value = endDateObj.getMonth() + 1;
658
- endDay.value = endDateObj.getDate();
659
- endDate.value = `${endYear.value}-${formatNum(endMonth.value)}-${formatNum(endDay.value)}`;
660
- }
661
-
662
- isStart.value = true;
663
- activeDate.value = '';
664
- } else if (props.defaultSelectToday) {
665
- // 优先级2:默认选中今天
666
- activeDate.value = today.value;
667
- resetRangeState();
668
- } else {
669
- // 优先级3:不选中任何日期
670
- activeDate.value = '';
671
- resetRangeState();
672
- }
673
-
674
- changeData();
675
- }
676
-
677
- /**
678
- * 重置范围选择状态
679
- */
680
- function resetRangeState() {
681
- startDate.value = '';
682
- startYear.value = 0;
683
- startMonth.value = 0;
684
- startDay.value = 0;
685
- endYear.value = 0;
686
- endMonth.value = 0;
687
- endDay.value = 0;
688
- endDate.value = '';
689
- isStart.value = true;
690
- }
691
-
692
- /**
693
- * 日期字符串转对象
694
- */
695
- function initDate(date: string) {
696
- let fdate = date.split('-');
697
- return {
698
- year: Number(fdate[0] || 1920),
699
- month: Number(fdate[1] || 1),
700
- day: Number(fdate[2] || 1)
701
- };
702
- }
703
-
704
- /**
705
- * 判断日期是否可选
706
- */
707
- function openDisAbled(yearNum: number, monthNum: number, dayNum: number) {
708
- let bool = true;
709
- let date = `${yearNum}/${formatNum(monthNum)}/${formatNum(dayNum)}`;
710
- // let today = this.today.replace(/\-/g, '/');
711
- let minStr = min.value ? `${min.value.year}/${formatNum(min.value.month)}/${formatNum(min.value.day)}` : '';
712
- let maxStr = max.value ? `${max.value.year}/${formatNum(max.value.month)}/${formatNum(max.value.day)}` : '';
713
- let timestamp = new Date(date).getTime();
714
- if (min.value && max.value && timestamp >= new Date(minStr).getTime() && timestamp <= new Date(maxStr).getTime()) {
715
- bool = false;
716
- }
717
- return bool;
718
- }
719
-
720
- /**
721
- * 生成数组
722
- */
723
- function generateArray(start: number, end: number) {
724
- return Array.from(new Array(end + 1).keys()).slice(start);
725
- }
726
-
727
- /**
728
- * 格式化数字
729
- */
730
- function formatNum(num: number) {
731
- return num < 10 ? '0' + num : num + '';
732
- }
733
-
734
- /**
735
- * 获取某月天数
736
- */
737
- function getMonthDay(yearNum: number, monthNum: number) {
738
- return new Date(yearNum, monthNum, 0).getDate();
739
- }
740
-
741
- /**
742
- * 获取某月第一天星期几
743
- */
744
- function getWeekday(yearNum: number, monthNum: number) {
745
- let date = new Date(`${yearNum}/${monthNum}/01 00:00:00`);
746
- return date.getDay();
747
- }
748
-
749
- /**
750
- * 检查年月是否超出范围
751
- */
752
- function checkRange(yearNum: number, monthNum: number) {
753
- if (yearNum < Number(props.minYear) || yearNum > Number(props.maxYear)) {
754
- uni.showToast({ title: t('uCalendar.outOfRange'), icon: 'none' });
755
- return true;
756
- }
757
-
758
- const beforeMin =
759
- min.value && (yearNum < min.value.year || (yearNum === min.value.year && monthNum < min.value.month));
760
- const afterMax =
761
- max.value && (yearNum > max.value.year || (yearNum === max.value.year && monthNum > max.value.month));
762
-
763
- if (beforeMin || afterMax) {
764
- uni.showToast({ title: t('uCalendar.outOfRange'), icon: 'none' });
765
- return true;
766
- }
767
-
768
- return false;
769
- }
770
-
771
- /**
772
- * 切换月份
773
- */
774
- function changeMonthHandler(isAdd: number) {
775
- if (isAdd) {
776
- let m = month.value + 1;
777
- let y = m > 12 ? year.value + 1 : year.value;
778
- m = m > 12 ? 1 : m;
779
- if (!checkRange(y, m)) {
780
- month.value = m;
781
- year.value = y;
782
- changeData();
783
- }
784
- } else {
785
- let m = month.value - 1;
786
- let y = m < 1 ? year.value - 1 : year.value;
787
- m = m < 1 ? 12 : m;
788
- if (!checkRange(y, m)) {
789
- month.value = m;
790
- year.value = y;
791
- changeData();
792
- }
793
- }
794
- }
795
-
796
- /**
797
- * 切换年份:若 minDate/maxDate 的月份在当前年超出,则切换到该年的最小/最大有效月
798
- */
799
- function changeYearHandler(isAdd: number) {
800
- let y = isAdd ? year.value + 1 : year.value - 1;
801
- let m = month.value;
802
- if (min.value && y === min.value.year && m < min.value.month) {
803
- m = min.value.month;
804
- }
805
- if (max.value && y === max.value.year && m > max.value.month) {
806
- m = max.value.month;
807
- }
808
- if (!checkRange(y, m)) {
809
- year.value = y;
810
- month.value = m;
811
- changeData();
812
- }
813
- }
814
-
815
- /**
816
- * 更新日历数据
817
- */
818
- function changeData() {
819
- days.value = getMonthDay(year.value, month.value);
820
- daysArr.value = generateArray(1, days.value);
821
- weekday.value = getWeekday(year.value, month.value);
822
- weekdayArr.value = generateArray(1, weekday.value);
823
- if (props.showLunar) {
824
- lunarArr.value = [];
825
- daysArr.value.forEach(d => {
826
- lunarArr.value.push(getLunar(year.value, month.value, d));
827
- });
828
- }
829
- if (props.isChange && props.mode == 'date') {
830
- btnFix(true);
831
- }
832
- }
833
-
834
- /**
835
- * 获取农历
836
- */
837
- function getLunar(year: any, month: any, day: any) {
838
- const val = Calendar.solar2lunar(year, month, day);
839
- return {
840
- dayCn: val.IDayCn,
841
- weekCn: val.ncWeek,
842
- monthCn: val.IMonthCn,
843
- day: val.lDay,
844
- week: val.nWeek,
845
- month: val.lMonth,
846
- year: val.lYear
847
- };
848
- }
849
-
850
- /**
851
- * 日期点击事件
852
- */
853
- function dateClick(dayIdx: number) {
854
- // 只读模式下禁止点击
855
- if (props.readonly) return;
856
-
857
- const d = dayIdx + 1;
858
- if (!openDisAbled(year.value, month.value, d)) {
859
- day.value = d;
860
- let date = `${year.value}-${formatNum(month.value)}-${formatNum(d)}`;
861
- if (props.mode == 'date') {
862
- activeDate.value = date;
863
- // 页面模式下,单选日期选择完成自动触发change事件
864
- // 打卡签到模式下,弹窗模式也立即触发change事件
865
- if (props.isPage || props.checkinMode) {
866
- btnFix(true);
867
- }
868
- } else {
869
- let compare =
870
- new Date(date.replace(/\-/g, '/')).getTime() < new Date(startDate.value.replace(/\-/g, '/')).getTime();
871
- if (isStart.value || compare) {
872
- startDate.value = date;
873
- startYear.value = year.value;
874
- startMonth.value = month.value;
875
- startDay.value = day.value;
876
- endYear.value = 0;
877
- endMonth.value = 0;
878
- endDay.value = 0;
879
- endDate.value = '';
880
- activeDate.value = '';
881
- isStart.value = false;
882
- } else {
883
- endDate.value = date;
884
- endYear.value = year.value;
885
- endMonth.value = month.value;
886
- endDay.value = day.value;
887
- isStart.value = true;
888
- // 页面模式下,范围选择完成(选了结束日期)自动触发change事件
889
- if (props.isPage) {
890
- btnFix(true);
891
- }
892
- }
893
- }
894
- }
895
- }
896
-
897
- /**
898
- * 关闭弹窗
899
- */
900
- function close() {
901
- emit('input', false);
902
- emit('update:modelValue', false);
903
- }
904
-
905
- /**
906
- * 获取星期文本
907
- */
908
- function getWeekText(date: string) {
909
- const d = new Date(`${date.replace(/\-/g, '/')} 00:00:00`);
910
- let week = d.getDay();
911
- return '星期' + ['日', '一', '二', '三', '四', '五', '六'][week];
912
- }
913
-
914
- /**
915
- * 确定按钮事件
916
- */
917
- function btnFix(show: boolean) {
918
- // 页面模式下不关闭,弹窗模式下关闭
919
- if (!show && !props.isPage) {
920
- close();
921
- }
922
- if (props.mode == 'date') {
923
- let arr = activeDate.value.split('-');
924
- let y = props.isChange ? year.value : Number(arr[0]);
925
- let m = props.isChange ? month.value : Number(arr[1]);
926
- let d = props.isChange ? day.value : Number(arr[2]);
927
- let daysNum = getMonthDay(y, m);
928
- let result = `${y}-${formatNum(m)}-${formatNum(d)}`;
929
- let weekText = getWeekText(result);
930
- let isToday = false;
931
- if (result == today.value) {
932
- // 今天
933
- isToday = true;
934
- }
935
- const lunar = props.showLunar ? getLunar(y, m, d) : null;
936
- emit('change', {
937
- year: y,
938
- month: m,
939
- day: d,
940
- days: daysNum,
941
- result: result,
942
- week: weekText,
943
- isToday: isToday,
944
- lunar: lunar
945
- // switch: show //是否是切换年月操作
946
- });
947
- } else {
948
- if (!startDate.value || !endDate.value) return;
949
- let startMonthStr = formatNum(startMonth.value);
950
- let startDayStr = formatNum(startDay.value);
951
- let startDateStr = `${startYear.value}-${startMonthStr}-${startDayStr}`;
952
- let startWeek = getWeekText(startDateStr);
953
- let endMonthStr = formatNum(endMonth.value);
954
- let endDayStr = formatNum(endDay.value);
955
- let endDateStr = `${endYear.value}-${endMonthStr}-${endDayStr}`;
956
- let endWeek = getWeekText(endDateStr);
957
- let startLunar = null;
958
- let endLunar = null;
959
- if (props.showLunar) {
960
- startLunar = getLunar(startYear.value, startMonth.value, startDay.value);
961
- endLunar = getLunar(endYear.value, endMonth.value, endDay.value);
962
- }
963
- emit('change', {
964
- startYear: startYear.value,
965
- startMonth: startMonth.value,
966
- startDay: startDay.value,
967
- startDate: startDateStr,
968
- startWeek: startWeek,
969
- endYear: endYear.value,
970
- endMonth: endMonth.value,
971
- endDay: endDay.value,
972
- endDate: endDateStr,
973
- endWeek: endWeek,
974
- startLunar: startLunar,
975
- endLunar: endLunar
976
- });
977
- }
978
- }
979
- </script>
980
-
981
- <style scoped lang="scss">
982
- @import '../../libs/css/style.components.scss';
983
-
984
- .u-calendar {
985
- color: $u-content-color;
986
-
987
- &--page {
988
- background-color: var(--u-bg-white);
989
- border-radius: 16rpx;
990
- }
991
-
992
- &__header {
993
- width: 100%;
994
- box-sizing: border-box;
995
- font-size: 30rpx;
996
- background-color: var(--u-bg-white);
997
- color: $u-main-color;
998
-
999
- &__text {
1000
- margin-top: 30rpx;
1001
- padding: 0 60rpx;
1002
- @include vue-flex;
1003
- justify-content: center;
1004
- align-items: center;
1005
- }
1006
- }
1007
-
1008
- &__action {
1009
- padding: 40rpx 0 40rpx 0;
1010
-
1011
- &__icon {
1012
- margin: 0 16rpx;
1013
- }
1014
-
1015
- &__text {
1016
- padding: 0 16rpx;
1017
- color: $u-main-color;
1018
- font-size: 32rpx;
1019
- line-height: 32rpx;
1020
- font-weight: bold;
1021
- }
1022
- }
1023
-
1024
- &__week-day {
1025
- @include vue-flex;
1026
- align-items: center;
1027
- justify-content: center;
1028
- padding: 6px 0;
1029
- overflow: hidden;
1030
-
1031
- &__text {
1032
- flex: 1;
1033
- text-align: center;
1034
- }
1035
- }
1036
-
1037
- &__content {
1038
- width: 100%;
1039
- @include vue-flex;
1040
- flex-wrap: wrap;
1041
- padding: 6px 0;
1042
- box-sizing: border-box;
1043
- background-color: var(--u-bg-white);
1044
- position: relative;
1045
-
1046
- &--end-date {
1047
- border-top-right-radius: 8rpx;
1048
- border-bottom-right-radius: 8rpx;
1049
- }
1050
-
1051
- &--start-date {
1052
- border-top-left-radius: 8rpx;
1053
- border-bottom-left-radius: 8rpx;
1054
- }
1055
-
1056
- &--checked {
1057
- .u-calendar__content__item__inner {
1058
- width: 80rpx;
1059
- height: 80rpx;
1060
- border-radius: 50%;
1061
- display: flex;
1062
- align-items: center;
1063
- justify-content: center;
1064
- }
1065
- }
1066
-
1067
- &--today-checked {
1068
- .u-calendar__content__item__inner {
1069
- width: 80rpx;
1070
- height: 80rpx;
1071
- border-radius: 50%;
1072
- display: flex;
1073
- align-items: center;
1074
- justify-content: center;
1075
- }
1076
- }
1077
-
1078
- &--checkin-mode {
1079
- .u-calendar__content__item__inner {
1080
- width: 80rpx;
1081
- height: 80rpx;
1082
- border-radius: 50%;
1083
- display: flex;
1084
- align-items: center;
1085
- justify-content: center;
1086
- }
1087
- }
1088
-
1089
- &__item {
1090
- width: 14.2857%;
1091
- @include vue-flex;
1092
- align-items: center;
1093
- justify-content: center;
1094
- padding: 6px 0;
1095
- overflow: hidden;
1096
- position: relative;
1097
- z-index: 2;
1098
-
1099
- &__inner {
1100
- height: 84rpx;
1101
- @include vue-flex;
1102
- align-items: center;
1103
- justify-content: center;
1104
- flex-direction: column;
1105
- font-size: 32rpx;
1106
-
1107
- &__desc {
1108
- width: 100%;
1109
- font-size: 24rpx;
1110
- line-height: 24rpx;
1111
- transform: scale(0.75);
1112
- transform-origin: center center;
1113
- position: absolute;
1114
- left: 0;
1115
- text-align: center;
1116
- bottom: 2rpx;
1117
- }
1118
- }
1119
-
1120
- &__day {
1121
- font-size: 32rpx;
1122
- line-height: 1;
1123
- }
1124
-
1125
- &__lunar {
1126
- font-size: 22rpx;
1127
- line-height: 1;
1128
- margin-top: 2rpx;
1129
- transform: scale(0.85);
1130
- }
1131
-
1132
- // 节假日/加班日标签样式
1133
- &__holiday,
1134
- &__workday {
1135
- font-size: 22rpx;
1136
- line-height: 1;
1137
- margin-top: 2rpx;
1138
- transform: scale(0.85);
1139
- }
1140
-
1141
- // 右上角标记样式(休/班)
1142
- &__mark {
1143
- position: absolute;
1144
- top: 8rpx;
1145
- right: 8rpx;
1146
- font-size: 24rpx;
1147
- line-height: 1;
1148
- transform: scale(0.75);
1149
- z-index: 1;
1150
-
1151
- &--holiday {
1152
- color: var(--u-type-error);
1153
- }
1154
-
1155
- &--workday {
1156
- color: var(--u-type-primary);
1157
- }
1158
- }
1159
-
1160
- // 节日名称样式(与农历保持一致)
1161
- &__festival {
1162
- font-size: 22rpx;
1163
- line-height: 1;
1164
- transform: scale(0.85);
1165
- color: var(--u-type-primary);
1166
- }
1167
-
1168
- // 占位元素样式
1169
- &__placeholder {
1170
- min-height: 22rpx;
1171
- margin-top: 2rpx;
1172
- opacity: 0;
1173
- }
1174
-
1175
- &__tips {
1176
- width: 100%;
1177
- font-size: 24rpx;
1178
- line-height: 24rpx;
1179
- position: absolute;
1180
- left: 0;
1181
- transform: scale(0.8);
1182
- transform-origin: center center;
1183
- text-align: center;
1184
- bottom: 8rpx;
1185
- z-index: 2;
1186
- }
1187
-
1188
- &__check-icon {
1189
- position: absolute;
1190
- right: 4rpx;
1191
- top: 4rpx;
1192
- z-index: 3;
1193
- }
1194
-
1195
- &__checkmark {
1196
- @include vue-flex;
1197
- align-items: center;
1198
- justify-content: center;
1199
- width: 100%;
1200
- height: 100%;
1201
- }
1202
-
1203
- &__inner--today-checked {
1204
- @include vue-flex;
1205
- align-items: center;
1206
- justify-content: center;
1207
- }
1208
- }
1209
-
1210
- &__bg-month {
1211
- position: absolute;
1212
- font-size: 130px;
1213
- line-height: 130px;
1214
- left: 50%;
1215
- top: 50%;
1216
- transform: translate(-50%, -50%);
1217
- color: var(--u-border-color);
1218
- z-index: 1;
1219
- }
1220
- }
1221
-
1222
- &__bottom {
1223
- width: 100%;
1224
- @include vue-flex;
1225
- align-items: center;
1226
- justify-content: center;
1227
- flex-direction: column;
1228
- background-color: var(--u-bg-white);
1229
- padding: 0 40rpx 30rpx;
1230
- box-sizing: border-box;
1231
- font-size: 24rpx;
1232
- color: $u-tips-color;
1233
-
1234
- &__choose {
1235
- height: 50rpx;
1236
- }
1237
-
1238
- &__btn {
1239
- width: 100%;
1240
- }
1241
- }
1242
- }
1243
- </style>
1
+ <template>
2
+ <u-popup
3
+ v-model="popupValue"
4
+ length="auto"
5
+ :maskCloseAble="maskCloseAble"
6
+ :mode="props.isPage ? 'inline' : 'bottom'"
7
+ :popup="false"
8
+ :safeAreaInsetBottom="safeAreaInsetBottom"
9
+ :z-index="uZIndex"
10
+ :border-radius="borderRadius"
11
+ :closeable="closeable"
12
+ @close="close"
13
+ >
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">
20
+ <view class="u-calendar__header__text" v-if="!slots.tooltip">
21
+ {{ getToolTip }}
22
+ </view>
23
+ <slot v-else name="tooltip" />
24
+ </view>
25
+ <view class="u-calendar__action u-flex u-row-center">
26
+ <view class="u-calendar__action__icon">
27
+ <u-icon
28
+ v-if="changeYear"
29
+ name="arrow-left-double"
30
+ :color="yearArrowColor"
31
+ @click="changeYearHandler(0)"
32
+ ></u-icon>
33
+ </view>
34
+ <view class="u-calendar__action__icon">
35
+ <u-icon
36
+ v-if="changeMonth"
37
+ name="arrow-left"
38
+ :color="monthArrowColor"
39
+ @click="changeMonthHandler(0)"
40
+ ></u-icon>
41
+ </view>
42
+ <view class="u-calendar__action__text">{{ showTitle }}</view>
43
+ <view class="u-calendar__action__icon">
44
+ <u-icon
45
+ v-if="changeMonth"
46
+ name="arrow-right"
47
+ :color="monthArrowColor"
48
+ @click="changeMonthHandler(1)"
49
+ ></u-icon>
50
+ </view>
51
+ <view class="u-calendar__action__icon">
52
+ <u-icon
53
+ v-if="changeYear"
54
+ name="arrow-right-double"
55
+ :color="yearArrowColor"
56
+ @click="changeYearHandler(1)"
57
+ ></u-icon>
58
+ </view>
59
+ </view>
60
+ <view class="u-calendar__week-day">
61
+ <view class="u-calendar__week-day__text" v-for="(item, index) in weekDayZh" :key="index">
62
+ {{ item }}
63
+ </view>
64
+ </view>
65
+ <view class="u-calendar__content">
66
+ <!-- 前置空白部分 -->
67
+ <block v-for="(item, index) in weekdayArr" :key="index">
68
+ <view class="u-calendar__content__item"></view>
69
+ </block>
70
+ <view
71
+ class="u-calendar__content__item"
72
+ :class="{
73
+ 'u-hover-class': openDisAbled(year, month, index + 1),
74
+ 'u-calendar__content--start-date':
75
+ (mode == 'range' && startDate == `${year}-${formatNum(month)}-${formatNum(index + 1)}`) ||
76
+ mode == 'date',
77
+ 'u-calendar__content--end-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
+ : ''
89
+ }"
90
+ v-for="(item, index) in daysArr"
91
+ :key="index"
92
+ @tap="dateClick(index)"
93
+ >
94
+ <view
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
+ }"
101
+ >
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
+ {{ getStartText }}
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
+ {{ getEndText }}
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>
184
+ </view>
185
+ </view>
186
+ <view class="u-calendar__content__bg-month">{{ month }}</view>
187
+ </view>
188
+ <!-- 页面模式下不显示确定按钮,选择完成自动触发change事件 -->
189
+ <view class="u-calendar__bottom" v-if="!props.isPage">
190
+ <view class="u-calendar__bottom__choose">
191
+ <text>{{ mode == 'date' ? activeDate : startDate }}</text>
192
+ <text v-if="endDate">{{ t('uCalendar.to') }}{{ endDate }}</text>
193
+ </view>
194
+ <view class="u-calendar__bottom__btn">
195
+ <u-button
196
+ :type="btnType"
197
+ :disabled="btnDisable"
198
+ shape="circle"
199
+ size="default"
200
+ @click="btnFix(false)"
201
+ >
202
+ {{ t('uCalendar.confirmText') }}
203
+ </u-button>
204
+ </view>
205
+ </view>
206
+ </view>
207
+ </u-popup>
208
+ </template>
209
+
210
+ <script lang="ts">
211
+ export default {
212
+ name: 'u-calendar',
213
+ options: {
214
+ addGlobalClass: true,
215
+ // #ifndef MP-TOUTIAO
216
+ virtualHost: true,
217
+ // #endif
218
+ styleIsolation: 'shared'
219
+ }
220
+ };
221
+ </script>
222
+
223
+ <script setup lang="ts">
224
+ import { ref, computed, watch, onMounted, useSlots } from 'vue';
225
+ import { $u, useLocale } from '../..';
226
+ import { CalendarProps, type CalendarEmits } from './types';
227
+ import Calendar from '../../libs/util/calendar';
228
+ import uButton from '../u-button/u-button.vue';
229
+ import uIcon from '../u-icon/u-icon.vue';
230
+ import uPopup from '../u-popup/u-popup.vue';
231
+
232
+ /**
233
+ * calendar 日历
234
+ * @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中。
235
+ * @tutorial https://uviewpro.cn/zh/components/calendar.html
236
+ * @property {String} mode 选择日期的模式,date-为单个日期,range-为选择日期范围
237
+ * @property {Boolean} v-model 布尔值变量,用于控制日历的弹出与收起
238
+ * @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
239
+ * @property {Boolean} change-year 是否显示顶部的切换年份方向的按钮(默认true)
240
+ * @property {Boolean} change-month 是否显示顶部的切换月份方向的按钮(默认true)
241
+ * @property {String Number} max-year 可切换的最大年份(默认2050)
242
+ * @property {String Number} min-year 可切换的最小年份(默认1950)
243
+ * @property {String Number} min-date 最小可选日期(默认1950-01-01)
244
+ * @property {String Number} max-date 最大可选日期(默认当前日期)
245
+ * @property {String Number} 弹窗顶部左右两边的圆角值,单位rpx(默认20)
246
+ * @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭日历(默认true)
247
+ * @property {String} month-arrow-color 月份切换按钮箭头颜色(默认var(--u-content-color))
248
+ * @property {String} year-arrow-color 年份切换按钮箭头颜色(默认var(--u-tips-color))
249
+ * @property {String} color 日期字体的默认颜色(默认var(--u-main-color))
250
+ * @property {String} active-bg-color 起始/结束日期按钮的背景色(默认主题色primary)
251
+ * @property {String Number} z-index 弹出时的z-index值(默认10075)
252
+ * @property {String} active-color 起始/结束日期按钮的字体颜色(默认var(--u-white-color))
253
+ * @property {String} range-bg-color 起始/结束日期之间的区域的背景颜色(默认rgba(41,121,255,0.13))
254
+ * @property {String} range-color 选择范围内字体颜色(默认主题色primary)
255
+ * @property {String} start-text 起始日期底部的提示文字(默认 '开始')
256
+ * @property {String} end-text 结束日期底部的提示文字(默认 '结束')
257
+ * @property {String} btn-type 底部确定按钮的主题(默认 'primary')
258
+ * @property {String} toolTip 顶部提示文字,如设置名为tooltip的slot,此参数将失效(默认 '选择日期')
259
+ * @property {Boolean} closeable 是否显示右上角的关闭图标(默认true)
260
+ * @property {Boolean} is-page 是否在页面中直接显示,不使用弹窗(默认false)
261
+ * @property {String} default-date 默认选中的日期,mode=date时生效,格式:2024-01-01
262
+ * @property {String} start-date 默认选中的开始日期,mode=range时生效,格式:2024-01-01
263
+ * @property {String} end-date 默认选中的结束日期,mode=range时生效,格式:2024-01-01
264
+ * @property {Boolean} readonly 是否只读,只读模式下禁止点击选择日期(默认false)
265
+ * @example <u-calendar v-model="show" :mode="mode"></u-calendar>
266
+ */
267
+
268
+ const props = defineProps(CalendarProps);
269
+ const emit = defineEmits<CalendarEmits>();
270
+ const slots = useSlots();
271
+
272
+ const { t } = useLocale();
273
+
274
+ // 组件内部状态
275
+ // 星期几,值为1-7
276
+ const weekday = ref(1);
277
+ const weekdayArr = ref<number[]>([]);
278
+ const days = ref(0);
279
+ const daysArr = ref<number[]>([]);
280
+ const lunarArr = ref<any[]>([]);
281
+ const year = ref(2020);
282
+ const month = ref(0);
283
+ // 当前月有多少天
284
+ const day = ref(0);
285
+ const startYear = ref(0);
286
+ const startMonth = ref(0);
287
+ const startDay = ref(0);
288
+ const endYear = ref(0);
289
+ const endMonth = ref(0);
290
+ const endDay = ref(0);
291
+ const today = ref('');
292
+ const activeDate = ref('');
293
+ const startDate = ref('');
294
+ const endDate = ref('');
295
+ const isStart = ref(true);
296
+ const min = ref<{ year: number; month: number; day: number } | null>(null);
297
+ const max = ref<{ year: number; month: number; day: number } | null>(null);
298
+ const weekDayZh = ref([
299
+ t('uCalendar.sun'),
300
+ t('uCalendar.mon'),
301
+ t('uCalendar.tue'),
302
+ t('uCalendar.wed'),
303
+ t('uCalendar.thu'),
304
+ t('uCalendar.fri'),
305
+ t('uCalendar.sat')
306
+ ]);
307
+
308
+ // 内置中国传统节日(公历日期,格式:MM-DD)
309
+ const builtInFestivals: Record<string, string> = {
310
+ '01-01': '元旦',
311
+ '02-14': '情人节',
312
+ '03-08': '妇女节',
313
+ '03-12': '植树节',
314
+ '04-01': '愚人节',
315
+ '05-01': '劳动节',
316
+ '05-04': '青年节',
317
+ '06-01': '儿童节',
318
+ '07-01': '建党节',
319
+ '08-01': '建军节',
320
+ '09-10': '教师节',
321
+ '10-01': '国庆节',
322
+ '11-11': '光棍节',
323
+ '12-25': '圣诞节'
324
+ };
325
+
326
+ const dataChange = computed(() => `${props.mode}-${props.minDate}-${props.maxDate}`);
327
+ const lunarChange = computed(() => props.showLunar);
328
+ const defaultDateChange = computed(
329
+ () => `${props.defaultDate}-${props.startDate}-${props.endDate}-${props.defaultSelectToday}`
330
+ );
331
+ // 如果用户有传递z-index值,优先使用
332
+ const uZIndex = computed(() => (props.zIndex ? props.zIndex : $u.zIndex.popup));
333
+ const popupValue = computed({
334
+ get: () => props.modelValue,
335
+ set: (val: boolean) => emit('update:modelValue', val)
336
+ });
337
+
338
+ const btnDisable = computed(() => {
339
+ let disable = false;
340
+ if (props.mode == 'range') {
341
+ if (!startDate.value || !endDate.value) {
342
+ disable = true;
343
+ }
344
+ } else {
345
+ if (!activeDate.value) {
346
+ disable = true;
347
+ }
348
+ }
349
+ return disable;
350
+ });
351
+
352
+ // 国际化计算属性
353
+ const getStartText = computed(() => props.startText || t('uCalendar.startText'));
354
+ const getEndText = computed(() => props.endText || t('uCalendar.endText'));
355
+ const getToolTip = computed(() => props.toolTip || t('uCalendar.toolTip'));
356
+ const showTitle = computed(() => `${year.value}${t('uCalendar.year')}${month.value}${t('uCalendar.month')}`);
357
+
358
+ watch([dataChange, lunarChange], () => {
359
+ init();
360
+ });
361
+
362
+ watch(defaultDateChange, () => {
363
+ init();
364
+ });
365
+
366
+ onMounted(() => {
367
+ init();
368
+ });
369
+
370
+ /**
371
+ * 获取日期颜色
372
+ * @param index
373
+ * @param type 1 背景色 2 字体色
374
+ */
375
+ function getColor(index: number, type: number) {
376
+ let color = type == 1 ? '' : props.color;
377
+ let dayNum = index + 1;
378
+ let date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
379
+ let timestamp = new Date(date.replace(/\-/g, '/')).getTime();
380
+ let start = startDate.value.replace(/\-/g, '/');
381
+ let end = endDate.value.replace(/\-/g, '/');
382
+ if ((props.isActiveCurrent && activeDate.value == date) || startDate.value == date || endDate.value == date) {
383
+ color = type == 1 ? props.activeBgColor : props.activeColor;
384
+ } else if (endDate.value && timestamp > new Date(start).getTime() && timestamp < new Date(end).getTime()) {
385
+ color = type == 1 ? props.rangeBgColor : props.rangeColor;
386
+ }
387
+ return color;
388
+ }
389
+
390
+ /**
391
+ * 判断日期是否已打卡
392
+ */
393
+ function isCheckedDate(dayNum: number) {
394
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
395
+ return props.checkedDates.includes(date);
396
+ }
397
+
398
+ /**
399
+ * 判断是否是今日且已打卡
400
+ * 优先级:1. todayChecked 属性 2. 自动判断 checkedDates 中是否包含今天
401
+ */
402
+ function isTodayChecked(dayNum: number) {
403
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
404
+ // 首先检查是否是今天
405
+ if (date !== today.value) {
406
+ return false;
407
+ }
408
+ // 优先级1:手动设置的 todayChecked
409
+ if (props.todayChecked) {
410
+ return true;
411
+ }
412
+ // 优先级2:自动判断 checkedDates 中是否包含今天
413
+ if (props.checkedDates.includes(date)) {
414
+ return true;
415
+ }
416
+ return false;
417
+ }
418
+
419
+ /**
420
+ * 获取打卡日期背景色
421
+ */
422
+ function getCheckinColor(dayNum: number) {
423
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
424
+ const isToday = date === today.value;
425
+ const isInCheckedDates = props.checkedDates.includes(date);
426
+
427
+ // 只有在打卡签到模式下或设置了打卡数据时才返回颜色
428
+ if (!props.checkinMode && props.checkedDates.length === 0 && !props.todayChecked) {
429
+ return '';
430
+ }
431
+
432
+ // 今日已打卡显示绿色(优先级:todayChecked > 自动判断)
433
+ if (isToday && (props.todayChecked || isInCheckedDates)) {
434
+ return props.todayCheckedBgColor;
435
+ }
436
+
437
+ // 其他已打卡日期显示橙色
438
+ if (isInCheckedDates) {
439
+ return props.checkedBgColor;
440
+ }
441
+
442
+ // 打卡签到模式下,未打卡日期显示灰色
443
+ if (props.checkinMode) {
444
+ return props.uncheckedBgColor;
445
+ }
446
+ return '';
447
+ }
448
+
449
+ /**
450
+ * 获取打卡日期文字颜色
451
+ */
452
+ function getCheckinTextColor(dayNum: number) {
453
+ // 只有在打卡签到模式下或设置了打卡数据时才返回颜色
454
+ if (!props.checkinMode && props.checkedDates.length === 0 && !props.todayChecked) {
455
+ return '';
456
+ }
457
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
458
+ // 已打卡日期显示白色文字
459
+ if (props.checkedDates.includes(date) || (date === today.value && props.todayChecked)) {
460
+ return props.checkedColor;
461
+ }
462
+ // 打卡签到模式下,未打卡日期显示白色文字
463
+ if (props.checkinMode) {
464
+ return props.uncheckedColor;
465
+ }
466
+ return '';
467
+ }
468
+
469
+ /**
470
+ * 获取打卡日期农历文字颜色
471
+ */
472
+ function getCheckinLunarColor(dayNum: number) {
473
+ // 只有在打卡签到模式下或设置了打卡数据时才返回颜色
474
+ if (!props.checkinMode && props.checkedDates.length === 0 && !props.todayChecked) {
475
+ return '';
476
+ }
477
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
478
+ // 已打卡日期的农历显示白色文字
479
+ if (props.checkedDates.includes(date) || (date === today.value && props.todayChecked)) {
480
+ return props.checkedColor;
481
+ }
482
+ // 打卡签到模式下,未打卡日期的农历显示白色文字
483
+ if (props.checkinMode) {
484
+ return props.uncheckedColor;
485
+ }
486
+ return '';
487
+ }
488
+
489
+ /**
490
+ * 获取自定义插槽的颜色
491
+ * 当选中日期时显示白色,否则显示默认颜色
492
+ */
493
+ function getSlotColor(dayNum: number) {
494
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
495
+ // 选中日期的自定义内容显示白色(仅在 isActiveCurrent 为 true 时)
496
+ if (props.isActiveCurrent && (activeDate.value === date || startDate.value === date || endDate.value === date)) {
497
+ return props.activeColor;
498
+ }
499
+ // 打卡签到模式下使用对应的颜色
500
+ if (props.checkinMode || props.checkedDates.length > 0 || props.todayChecked) {
501
+ return getCheckinLunarColor(dayNum) || props.color;
502
+ }
503
+ return props.color;
504
+ }
505
+
506
+ /**
507
+ * 获取日期信息,用于自定义插槽
508
+ */
509
+ function getDateInfo(dayNum: number) {
510
+ const dateStr = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
511
+ const dateObj = new Date(dateStr.replace(/\-/g, '/'));
512
+ const dayOfWeek = dateObj.getDay();
513
+ const weekNames = [
514
+ t('uCalendar.sun'),
515
+ t('uCalendar.mon'),
516
+ t('uCalendar.tue'),
517
+ t('uCalendar.wed'),
518
+ t('uCalendar.thu'),
519
+ t('uCalendar.fri'),
520
+ t('uCalendar.sat')
521
+ ];
522
+
523
+ const isSelected = activeDate.value === dateStr || startDate.value === dateStr || endDate.value === dateStr;
524
+
525
+ return {
526
+ year: year.value,
527
+ month: month.value,
528
+ day: dayNum,
529
+ date: dateStr,
530
+ week: weekNames[dayOfWeek],
531
+ weekNum: dayOfWeek,
532
+ isToday: dateStr === today.value,
533
+ isHoliday: props.holidays.includes(dateStr),
534
+ isWorkday: props.workdays.includes(dateStr),
535
+ isChecked: props.checkedDates.includes(dateStr),
536
+ isSelected,
537
+ isTodayChecked: dateStr === today.value && props.todayChecked,
538
+ lunar: lunarArr.value[dayNum - 1] || null
539
+ };
540
+ }
541
+
542
+ /**
543
+ * 判断是否是节假日
544
+ */
545
+ function isHoliday(dayNum: number) {
546
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
547
+ return props.holidays.includes(date);
548
+ }
549
+
550
+ /**
551
+ * 判断是否是加班日
552
+ */
553
+ function isWorkday(dayNum: number) {
554
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
555
+ return props.workdays.includes(date);
556
+ }
557
+
558
+ /**
559
+ * 获取节日名称(合并内置节日和用户自定义节日)
560
+ * 用户传入空字符串可覆盖内置节日,表示不显示该节日
561
+ * 支持两种格式:
562
+ * 1. 年-月-日:特定年份的节日,如 '2024-04-04': '清明节'
563
+ * 2. 月-日:每年的固定节日,如 '04-04': '清明节'
564
+ */
565
+ function getFestival(dayNum: number) {
566
+ if (!props.showFestival && Object.keys(props.festivals).length === 0) {
567
+ return '';
568
+ }
569
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
570
+ const monthDay = `${formatNum(month.value)}-${formatNum(dayNum)}`;
571
+
572
+ // 优先检查用户自定义节日(特定年份格式)
573
+ if (date in props.festivals) {
574
+ return props.festivals[date];
575
+ }
576
+
577
+ // 然后检查用户自定义节日(每年固定格式,月-日)
578
+ if (monthDay in props.festivals) {
579
+ return props.festivals[monthDay];
580
+ }
581
+
582
+ // 最后检查内置节日(如果启用了 showFestival)
583
+ if (props.showFestival && builtInFestivals[monthDay]) {
584
+ return builtInFestivals[monthDay];
585
+ }
586
+ return '';
587
+ }
588
+
589
+ /**
590
+ * 获取节假日/加班日文字颜色
591
+ * 当选中日期时显示白色,否则显示对应的颜色
592
+ */
593
+ function getHolidayWorkdayColor(dayNum: number, defaultColor: string) {
594
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
595
+ // 选中日期的节假日/加班日显示白色
596
+ if (activeDate.value === date || startDate.value === date || endDate.value === date) {
597
+ return props.activeColor;
598
+ }
599
+ return defaultColor;
600
+ }
601
+
602
+ /**
603
+ * 初始化日历数据
604
+ */
605
+ function init() {
606
+ let now = new Date();
607
+ let minDateObj = new Date(String(props.minDate));
608
+ let maxDateObj = new Date(String(props.maxDate || ''));
609
+ if (isNaN(maxDateObj.getTime())) maxDateObj = new Date();
610
+ if (now < minDateObj) now = minDateObj;
611
+ if (now > maxDateObj) now = maxDateObj;
612
+ year.value = now.getFullYear();
613
+ month.value = now.getMonth() + 1;
614
+ day.value = now.getDate();
615
+ today.value = `${now.getFullYear()}-${formatNum(month.value)}-${formatNum(day.value)}`;
616
+ min.value = initDate(String(props.minDate));
617
+ max.value = initDate(String(props.maxDate) || today.value);
618
+
619
+ // 处理默认选中日期
620
+ // 优先级1: defaultDate / startDate / endDate(显式指定日期)
621
+ // 优先级2: defaultSelectToday(默认选中今天)
622
+ // 优先级3: 不选中任何日期
623
+ if (props.mode === 'date' && props.defaultDate) {
624
+ // 单选模式:使用 defaultDate(优先级1)
625
+ const defaultDateObj = new Date(props.defaultDate.replace(/\-/g, '/'));
626
+ if (!isNaN(defaultDateObj.getTime())) {
627
+ year.value = defaultDateObj.getFullYear();
628
+ month.value = defaultDateObj.getMonth() + 1;
629
+ day.value = defaultDateObj.getDate();
630
+ // 统一格式为 YYYY-MM-DD,与 getColor 中的格式一致
631
+ activeDate.value = `${year.value}-${formatNum(month.value)}-${formatNum(day.value)}`;
632
+ } else if (props.defaultSelectToday) {
633
+ activeDate.value = today.value;
634
+ } else {
635
+ activeDate.value = '';
636
+ }
637
+ } else if (props.mode === 'range' && (props.startDate || props.endDate)) {
638
+ // 范围模式:使用 startDate 和 endDate(优先级1)
639
+ const startDateObj = props.startDate ? new Date(props.startDate.replace(/\-/g, '/')) : null;
640
+ const endDateObj = props.endDate ? new Date(props.endDate.replace(/\-/g, '/')) : null;
641
+
642
+ if (startDateObj && !isNaN(startDateObj.getTime())) {
643
+ // 设置当前显示月份为开始日期所在月份
644
+ year.value = startDateObj.getFullYear();
645
+ month.value = startDateObj.getMonth() + 1;
646
+
647
+ // 设置开始日期 - 统一格式为 YYYY-MM-DD
648
+ startYear.value = startDateObj.getFullYear();
649
+ startMonth.value = startDateObj.getMonth() + 1;
650
+ startDay.value = startDateObj.getDate();
651
+ startDate.value = `${startYear.value}-${formatNum(startMonth.value)}-${formatNum(startDay.value)}`;
652
+ }
653
+
654
+ if (endDateObj && !isNaN(endDateObj.getTime())) {
655
+ // 设置结束日期 - 统一格式为 YYYY-MM-DD
656
+ endYear.value = endDateObj.getFullYear();
657
+ endMonth.value = endDateObj.getMonth() + 1;
658
+ endDay.value = endDateObj.getDate();
659
+ endDate.value = `${endYear.value}-${formatNum(endMonth.value)}-${formatNum(endDay.value)}`;
660
+ }
661
+
662
+ isStart.value = true;
663
+ activeDate.value = '';
664
+ } else if (props.defaultSelectToday) {
665
+ // 优先级2:默认选中今天
666
+ activeDate.value = today.value;
667
+ resetRangeState();
668
+ } else {
669
+ // 优先级3:不选中任何日期
670
+ activeDate.value = '';
671
+ resetRangeState();
672
+ }
673
+
674
+ changeData();
675
+ }
676
+
677
+ /**
678
+ * 重置范围选择状态
679
+ */
680
+ function resetRangeState() {
681
+ startDate.value = '';
682
+ startYear.value = 0;
683
+ startMonth.value = 0;
684
+ startDay.value = 0;
685
+ endYear.value = 0;
686
+ endMonth.value = 0;
687
+ endDay.value = 0;
688
+ endDate.value = '';
689
+ isStart.value = true;
690
+ }
691
+
692
+ /**
693
+ * 日期字符串转对象
694
+ */
695
+ function initDate(date: string) {
696
+ let fdate = date.split('-');
697
+ return {
698
+ year: Number(fdate[0] || 1920),
699
+ month: Number(fdate[1] || 1),
700
+ day: Number(fdate[2] || 1)
701
+ };
702
+ }
703
+
704
+ /**
705
+ * 判断日期是否可选
706
+ */
707
+ function openDisAbled(yearNum: number, monthNum: number, dayNum: number) {
708
+ let bool = true;
709
+ let date = `${yearNum}/${formatNum(monthNum)}/${formatNum(dayNum)}`;
710
+ // let today = this.today.replace(/\-/g, '/');
711
+ let minStr = min.value ? `${min.value.year}/${formatNum(min.value.month)}/${formatNum(min.value.day)}` : '';
712
+ let maxStr = max.value ? `${max.value.year}/${formatNum(max.value.month)}/${formatNum(max.value.day)}` : '';
713
+ let timestamp = new Date(date).getTime();
714
+ if (min.value && max.value && timestamp >= new Date(minStr).getTime() && timestamp <= new Date(maxStr).getTime()) {
715
+ bool = false;
716
+ }
717
+ return bool;
718
+ }
719
+
720
+ /**
721
+ * 生成数组
722
+ */
723
+ function generateArray(start: number, end: number) {
724
+ return Array.from(new Array(end + 1).keys()).slice(start);
725
+ }
726
+
727
+ /**
728
+ * 格式化数字
729
+ */
730
+ function formatNum(num: number) {
731
+ return num < 10 ? '0' + num : num + '';
732
+ }
733
+
734
+ /**
735
+ * 获取某月天数
736
+ */
737
+ function getMonthDay(yearNum: number, monthNum: number) {
738
+ return new Date(yearNum, monthNum, 0).getDate();
739
+ }
740
+
741
+ /**
742
+ * 获取某月第一天星期几
743
+ */
744
+ function getWeekday(yearNum: number, monthNum: number) {
745
+ let date = new Date(`${yearNum}/${monthNum}/01 00:00:00`);
746
+ return date.getDay();
747
+ }
748
+
749
+ /**
750
+ * 检查年月是否超出范围
751
+ */
752
+ function checkRange(yearNum: number, monthNum: number) {
753
+ if (yearNum < Number(props.minYear) || yearNum > Number(props.maxYear)) {
754
+ uni.showToast({ title: t('uCalendar.outOfRange'), icon: 'none' });
755
+ return true;
756
+ }
757
+
758
+ const beforeMin =
759
+ min.value && (yearNum < min.value.year || (yearNum === min.value.year && monthNum < min.value.month));
760
+ const afterMax =
761
+ max.value && (yearNum > max.value.year || (yearNum === max.value.year && monthNum > max.value.month));
762
+
763
+ if (beforeMin || afterMax) {
764
+ uni.showToast({ title: t('uCalendar.outOfRange'), icon: 'none' });
765
+ return true;
766
+ }
767
+
768
+ return false;
769
+ }
770
+
771
+ /**
772
+ * 切换月份
773
+ */
774
+ function changeMonthHandler(isAdd: number) {
775
+ if (isAdd) {
776
+ let m = month.value + 1;
777
+ let y = m > 12 ? year.value + 1 : year.value;
778
+ m = m > 12 ? 1 : m;
779
+ if (!checkRange(y, m)) {
780
+ month.value = m;
781
+ year.value = y;
782
+ changeData();
783
+ }
784
+ } else {
785
+ let m = month.value - 1;
786
+ let y = m < 1 ? year.value - 1 : year.value;
787
+ m = m < 1 ? 12 : m;
788
+ if (!checkRange(y, m)) {
789
+ month.value = m;
790
+ year.value = y;
791
+ changeData();
792
+ }
793
+ }
794
+ }
795
+
796
+ /**
797
+ * 切换年份:若 minDate/maxDate 的月份在当前年超出,则切换到该年的最小/最大有效月
798
+ */
799
+ function changeYearHandler(isAdd: number) {
800
+ let y = isAdd ? year.value + 1 : year.value - 1;
801
+ let m = month.value;
802
+ if (min.value && y === min.value.year && m < min.value.month) {
803
+ m = min.value.month;
804
+ }
805
+ if (max.value && y === max.value.year && m > max.value.month) {
806
+ m = max.value.month;
807
+ }
808
+ if (!checkRange(y, m)) {
809
+ year.value = y;
810
+ month.value = m;
811
+ changeData();
812
+ }
813
+ }
814
+
815
+ /**
816
+ * 更新日历数据
817
+ */
818
+ function changeData() {
819
+ days.value = getMonthDay(year.value, month.value);
820
+ daysArr.value = generateArray(1, days.value);
821
+ weekday.value = getWeekday(year.value, month.value);
822
+ weekdayArr.value = generateArray(1, weekday.value);
823
+ if (props.showLunar) {
824
+ lunarArr.value = [];
825
+ daysArr.value.forEach(d => {
826
+ lunarArr.value.push(getLunar(year.value, month.value, d));
827
+ });
828
+ }
829
+ if (props.isChange && props.mode == 'date') {
830
+ btnFix(true);
831
+ }
832
+ }
833
+
834
+ /**
835
+ * 获取农历
836
+ */
837
+ function getLunar(year: any, month: any, day: any) {
838
+ const val = Calendar.solar2lunar(year, month, day);
839
+ return {
840
+ dayCn: val.IDayCn,
841
+ weekCn: val.ncWeek,
842
+ monthCn: val.IMonthCn,
843
+ day: val.lDay,
844
+ week: val.nWeek,
845
+ month: val.lMonth,
846
+ year: val.lYear
847
+ };
848
+ }
849
+
850
+ /**
851
+ * 日期点击事件
852
+ */
853
+ function dateClick(dayIdx: number) {
854
+ // 只读模式下禁止点击
855
+ if (props.readonly) return;
856
+
857
+ const d = dayIdx + 1;
858
+ if (!openDisAbled(year.value, month.value, d)) {
859
+ day.value = d;
860
+ let date = `${year.value}-${formatNum(month.value)}-${formatNum(d)}`;
861
+ if (props.mode == 'date') {
862
+ activeDate.value = date;
863
+ // 页面模式下,单选日期选择完成自动触发change事件
864
+ // 打卡签到模式下,弹窗模式也立即触发change事件
865
+ if (props.isPage || props.checkinMode) {
866
+ btnFix(true);
867
+ }
868
+ } else {
869
+ let compare =
870
+ new Date(date.replace(/\-/g, '/')).getTime() < new Date(startDate.value.replace(/\-/g, '/')).getTime();
871
+ if (isStart.value || compare) {
872
+ startDate.value = date;
873
+ startYear.value = year.value;
874
+ startMonth.value = month.value;
875
+ startDay.value = day.value;
876
+ endYear.value = 0;
877
+ endMonth.value = 0;
878
+ endDay.value = 0;
879
+ endDate.value = '';
880
+ activeDate.value = '';
881
+ isStart.value = false;
882
+ } else {
883
+ endDate.value = date;
884
+ endYear.value = year.value;
885
+ endMonth.value = month.value;
886
+ endDay.value = day.value;
887
+ isStart.value = true;
888
+ // 页面模式下,范围选择完成(选了结束日期)自动触发change事件
889
+ if (props.isPage) {
890
+ btnFix(true);
891
+ }
892
+ }
893
+ }
894
+ }
895
+ }
896
+
897
+ /**
898
+ * 关闭弹窗
899
+ */
900
+ function close() {
901
+ emit('input', false);
902
+ emit('update:modelValue', false);
903
+ }
904
+
905
+ /**
906
+ * 获取星期文本
907
+ */
908
+ function getWeekText(date: string) {
909
+ const d = new Date(`${date.replace(/\-/g, '/')} 00:00:00`);
910
+ let week = d.getDay();
911
+ return '星期' + ['日', '一', '二', '三', '四', '五', '六'][week];
912
+ }
913
+
914
+ /**
915
+ * 确定按钮事件
916
+ */
917
+ function btnFix(show: boolean) {
918
+ // 页面模式下不关闭,弹窗模式下关闭
919
+ if (!show && !props.isPage) {
920
+ close();
921
+ }
922
+ if (props.mode == 'date') {
923
+ let arr = activeDate.value.split('-');
924
+ let y = props.isChange ? year.value : Number(arr[0]);
925
+ let m = props.isChange ? month.value : Number(arr[1]);
926
+ let d = props.isChange ? day.value : Number(arr[2]);
927
+ let daysNum = getMonthDay(y, m);
928
+ let result = `${y}-${formatNum(m)}-${formatNum(d)}`;
929
+ let weekText = getWeekText(result);
930
+ let isToday = false;
931
+ if (result == today.value) {
932
+ // 今天
933
+ isToday = true;
934
+ }
935
+ const lunar = props.showLunar ? getLunar(y, m, d) : null;
936
+ emit('change', {
937
+ year: y,
938
+ month: m,
939
+ day: d,
940
+ days: daysNum,
941
+ result: result,
942
+ week: weekText,
943
+ isToday: isToday,
944
+ lunar: lunar
945
+ // switch: show //是否是切换年月操作
946
+ });
947
+ } else {
948
+ if (!startDate.value || !endDate.value) return;
949
+ let startMonthStr = formatNum(startMonth.value);
950
+ let startDayStr = formatNum(startDay.value);
951
+ let startDateStr = `${startYear.value}-${startMonthStr}-${startDayStr}`;
952
+ let startWeek = getWeekText(startDateStr);
953
+ let endMonthStr = formatNum(endMonth.value);
954
+ let endDayStr = formatNum(endDay.value);
955
+ let endDateStr = `${endYear.value}-${endMonthStr}-${endDayStr}`;
956
+ let endWeek = getWeekText(endDateStr);
957
+ let startLunar = null;
958
+ let endLunar = null;
959
+ if (props.showLunar) {
960
+ startLunar = getLunar(startYear.value, startMonth.value, startDay.value);
961
+ endLunar = getLunar(endYear.value, endMonth.value, endDay.value);
962
+ }
963
+ emit('change', {
964
+ startYear: startYear.value,
965
+ startMonth: startMonth.value,
966
+ startDay: startDay.value,
967
+ startDate: startDateStr,
968
+ startWeek: startWeek,
969
+ endYear: endYear.value,
970
+ endMonth: endMonth.value,
971
+ endDay: endDay.value,
972
+ endDate: endDateStr,
973
+ endWeek: endWeek,
974
+ startLunar: startLunar,
975
+ endLunar: endLunar
976
+ });
977
+ }
978
+ }
979
+ </script>
980
+
981
+ <style scoped lang="scss">
982
+ @import '../../libs/css/style.components.scss';
983
+
984
+ .u-calendar {
985
+ color: $u-content-color;
986
+
987
+ &--page {
988
+ background-color: var(--u-bg-white);
989
+ border-radius: 16rpx;
990
+ }
991
+
992
+ &__header {
993
+ width: 100%;
994
+ box-sizing: border-box;
995
+ font-size: 30rpx;
996
+ background-color: var(--u-bg-white);
997
+ color: $u-main-color;
998
+
999
+ &__text {
1000
+ margin-top: 30rpx;
1001
+ padding: 0 60rpx;
1002
+ @include vue-flex;
1003
+ justify-content: center;
1004
+ align-items: center;
1005
+ }
1006
+ }
1007
+
1008
+ &__action {
1009
+ padding: 40rpx 0 40rpx 0;
1010
+
1011
+ &__icon {
1012
+ margin: 0 16rpx;
1013
+ }
1014
+
1015
+ &__text {
1016
+ padding: 0 16rpx;
1017
+ color: $u-main-color;
1018
+ font-size: 32rpx;
1019
+ line-height: 32rpx;
1020
+ font-weight: bold;
1021
+ }
1022
+ }
1023
+
1024
+ &__week-day {
1025
+ @include vue-flex;
1026
+ align-items: center;
1027
+ justify-content: center;
1028
+ padding: 6px 0;
1029
+ overflow: hidden;
1030
+
1031
+ &__text {
1032
+ flex: 1;
1033
+ text-align: center;
1034
+ }
1035
+ }
1036
+
1037
+ &__content {
1038
+ width: 100%;
1039
+ @include vue-flex;
1040
+ flex-wrap: wrap;
1041
+ padding: 6px 0;
1042
+ box-sizing: border-box;
1043
+ background-color: var(--u-bg-white);
1044
+ position: relative;
1045
+
1046
+ &--end-date {
1047
+ border-top-right-radius: 8rpx;
1048
+ border-bottom-right-radius: 8rpx;
1049
+ }
1050
+
1051
+ &--start-date {
1052
+ border-top-left-radius: 8rpx;
1053
+ border-bottom-left-radius: 8rpx;
1054
+ }
1055
+
1056
+ &--checked {
1057
+ .u-calendar__content__item__inner {
1058
+ width: 80rpx;
1059
+ height: 80rpx;
1060
+ border-radius: 50%;
1061
+ display: flex;
1062
+ align-items: center;
1063
+ justify-content: center;
1064
+ }
1065
+ }
1066
+
1067
+ &--today-checked {
1068
+ .u-calendar__content__item__inner {
1069
+ width: 80rpx;
1070
+ height: 80rpx;
1071
+ border-radius: 50%;
1072
+ display: flex;
1073
+ align-items: center;
1074
+ justify-content: center;
1075
+ }
1076
+ }
1077
+
1078
+ &--checkin-mode {
1079
+ .u-calendar__content__item__inner {
1080
+ width: 80rpx;
1081
+ height: 80rpx;
1082
+ border-radius: 50%;
1083
+ display: flex;
1084
+ align-items: center;
1085
+ justify-content: center;
1086
+ }
1087
+ }
1088
+
1089
+ &__item {
1090
+ width: 14.2857%;
1091
+ @include vue-flex;
1092
+ align-items: center;
1093
+ justify-content: center;
1094
+ padding: 6px 0;
1095
+ overflow: hidden;
1096
+ position: relative;
1097
+ z-index: 2;
1098
+
1099
+ &__inner {
1100
+ height: 84rpx;
1101
+ @include vue-flex;
1102
+ align-items: center;
1103
+ justify-content: center;
1104
+ flex-direction: column;
1105
+ font-size: 32rpx;
1106
+
1107
+ &__desc {
1108
+ width: 100%;
1109
+ font-size: 24rpx;
1110
+ line-height: 24rpx;
1111
+ transform: scale(0.75);
1112
+ transform-origin: center center;
1113
+ position: absolute;
1114
+ left: 0;
1115
+ text-align: center;
1116
+ bottom: 2rpx;
1117
+ }
1118
+ }
1119
+
1120
+ &__day {
1121
+ font-size: 32rpx;
1122
+ line-height: 1;
1123
+ }
1124
+
1125
+ &__lunar {
1126
+ font-size: 22rpx;
1127
+ line-height: 1;
1128
+ margin-top: 2rpx;
1129
+ transform: scale(0.85);
1130
+ }
1131
+
1132
+ // 节假日/加班日标签样式
1133
+ &__holiday,
1134
+ &__workday {
1135
+ font-size: 22rpx;
1136
+ line-height: 1;
1137
+ margin-top: 2rpx;
1138
+ transform: scale(0.85);
1139
+ }
1140
+
1141
+ // 右上角标记样式(休/班)
1142
+ &__mark {
1143
+ position: absolute;
1144
+ top: 8rpx;
1145
+ right: 8rpx;
1146
+ font-size: 24rpx;
1147
+ line-height: 1;
1148
+ transform: scale(0.75);
1149
+ z-index: 1;
1150
+
1151
+ &--holiday {
1152
+ color: var(--u-type-error);
1153
+ }
1154
+
1155
+ &--workday {
1156
+ color: var(--u-type-primary);
1157
+ }
1158
+ }
1159
+
1160
+ // 节日名称样式(与农历保持一致)
1161
+ &__festival {
1162
+ font-size: 22rpx;
1163
+ line-height: 1;
1164
+ transform: scale(0.85);
1165
+ color: var(--u-type-primary);
1166
+ }
1167
+
1168
+ // 占位元素样式
1169
+ &__placeholder {
1170
+ min-height: 22rpx;
1171
+ margin-top: 2rpx;
1172
+ opacity: 0;
1173
+ }
1174
+
1175
+ &__tips {
1176
+ width: 100%;
1177
+ font-size: 24rpx;
1178
+ line-height: 24rpx;
1179
+ position: absolute;
1180
+ left: 0;
1181
+ transform: scale(0.8);
1182
+ transform-origin: center center;
1183
+ text-align: center;
1184
+ bottom: 8rpx;
1185
+ z-index: 2;
1186
+ }
1187
+
1188
+ &__check-icon {
1189
+ position: absolute;
1190
+ right: 4rpx;
1191
+ top: 4rpx;
1192
+ z-index: 3;
1193
+ }
1194
+
1195
+ &__checkmark {
1196
+ @include vue-flex;
1197
+ align-items: center;
1198
+ justify-content: center;
1199
+ width: 100%;
1200
+ height: 100%;
1201
+ }
1202
+
1203
+ &__inner--today-checked {
1204
+ @include vue-flex;
1205
+ align-items: center;
1206
+ justify-content: center;
1207
+ }
1208
+ }
1209
+
1210
+ &__bg-month {
1211
+ position: absolute;
1212
+ font-size: 130px;
1213
+ line-height: 130px;
1214
+ left: 50%;
1215
+ top: 50%;
1216
+ transform: translate(-50%, -50%);
1217
+ color: var(--u-border-color);
1218
+ z-index: 1;
1219
+ }
1220
+ }
1221
+
1222
+ &__bottom {
1223
+ width: 100%;
1224
+ @include vue-flex;
1225
+ align-items: center;
1226
+ justify-content: center;
1227
+ flex-direction: column;
1228
+ background-color: var(--u-bg-white);
1229
+ padding: 0 40rpx 30rpx;
1230
+ box-sizing: border-box;
1231
+ font-size: 24rpx;
1232
+ color: $u-tips-color;
1233
+
1234
+ &__choose {
1235
+ height: 50rpx;
1236
+ }
1237
+
1238
+ &__btn {
1239
+ width: 100%;
1240
+ }
1241
+ }
1242
+ }
1243
+ </style>