@puresnake/snake-ui 0.0.0-stage → 0.0.1

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 (279) hide show
  1. package/LICENSE +21 -0
  2. package/changelog.md +1 -0
  3. package/components/snake-activity-list/snake-activity-list.vue +365 -0
  4. package/components/snake-activity-list/types.ts +29 -0
  5. package/components/snake-auxiliary-segmentation/snake-auxiliary-segmentation.vue +44 -0
  6. package/components/snake-auxiliary-segmentation/types.ts +5 -0
  7. package/components/snake-button/snake-button.vue +357 -0
  8. package/components/snake-button/types.ts +103 -0
  9. package/components/snake-button-sort/snake-button-sort.vue +72 -0
  10. package/components/snake-button-sort/types.ts +22 -0
  11. package/components/snake-calendar/snake-calendar.vue +998 -0
  12. package/components/snake-calendar/types.ts +189 -0
  13. package/components/snake-cell-group/snake-cell-group.vue +55 -0
  14. package/components/snake-cell-group/types.ts +19 -0
  15. package/components/snake-cell-item/snake-cell-item.vue +159 -0
  16. package/components/snake-cell-item/types.ts +66 -0
  17. package/components/snake-checkbox/snake-checkbox.vue +264 -0
  18. package/components/snake-checkbox/types.ts +70 -0
  19. package/components/snake-checkbox-group/snake-checkbox-group.vue +154 -0
  20. package/components/snake-checkbox-group/types.ts +52 -0
  21. package/components/snake-cms/blocks/v2/cms-v2-carousel.vue +87 -0
  22. package/components/snake-cms/blocks/v2/cms-v2-coupon.vue +25 -0
  23. package/components/snake-cms/blocks/v2/cms-v2-custom.vue +12 -0
  24. package/components/snake-cms/blocks/v2/cms-v2-divider.vue +33 -0
  25. package/components/snake-cms/blocks/v2/cms-v2-floatbutton.vue +28 -0
  26. package/components/snake-cms/blocks/v2/cms-v2-goods-list.vue +150 -0
  27. package/components/snake-cms/blocks/v2/cms-v2-hotzone.vue +41 -0
  28. package/components/snake-cms/blocks/v2/cms-v2-images.vue +41 -0
  29. package/components/snake-cms/blocks/v2/cms-v2-magiccube.vue +93 -0
  30. package/components/snake-cms/blocks/v2/cms-v2-notice.vue +80 -0
  31. package/components/snake-cms/blocks/v2/cms-v2-popup.vue +46 -0
  32. package/components/snake-cms/blocks/v2/cms-v2-richtext.vue +18 -0
  33. package/components/snake-cms/blocks/v2/cms-v2-search.vue +115 -0
  34. package/components/snake-cms/blocks/v2/cms-v2-video.vue +25 -0
  35. package/components/snake-cms/blocks/v2/shared.ts +84 -0
  36. package/components/snake-cms/snake-cms.vue +409 -0
  37. package/components/snake-cms/types.ts +50 -0
  38. package/components/snake-commodity-search/snake-commodity-search.vue +234 -0
  39. package/components/snake-commodity-search/types.ts +11 -0
  40. package/components/snake-config-provider/snake-config-provider.vue +61 -0
  41. package/components/snake-config-provider/types.ts +22 -0
  42. package/components/snake-count-down/snake-count-down.vue +231 -0
  43. package/components/snake-count-down/types.ts +103 -0
  44. package/components/snake-coupon/snake-coupon.vue +209 -0
  45. package/components/snake-coupon/types.ts +21 -0
  46. package/components/snake-custom-module/snake-custom-module.vue +25 -0
  47. package/components/snake-custom-module/types.ts +11 -0
  48. package/components/snake-drop-menu/snake-drop-menu.vue +123 -0
  49. package/components/snake-drop-menu/types.ts +95 -0
  50. package/components/snake-drop-menu-item/snake-drop-menu-item.vue +181 -0
  51. package/components/snake-drop-menu-item/types.ts +48 -0
  52. package/components/snake-empty/snake-empty.vue +100 -0
  53. package/components/snake-empty/types.ts +48 -0
  54. package/components/snake-filter-sorts/filter-page.vue +695 -0
  55. package/components/snake-filter-sorts/snake-filter-sorts.vue +185 -0
  56. package/components/snake-filter-sorts/types.ts +57 -0
  57. package/components/snake-floating-bubble/snake-floating-bubble.vue +157 -0
  58. package/components/snake-floating-bubble/types.ts +43 -0
  59. package/components/snake-follow/snake-follow.vue +18 -0
  60. package/components/snake-follow/types.ts +2 -0
  61. package/components/snake-form/snake-form.vue +124 -0
  62. package/components/snake-form/types.ts +96 -0
  63. package/components/snake-form-item/snake-form-item.vue +331 -0
  64. package/components/snake-form-item/types.ts +70 -0
  65. package/components/snake-goods-list/snake-goods-list.vue +792 -0
  66. package/components/snake-goods-list/types.ts +55 -0
  67. package/components/snake-graphic-navigation/snake-graphic-navigation.vue +198 -0
  68. package/components/snake-graphic-navigation/types.ts +43 -0
  69. package/components/snake-icon/icon-names.ts +222 -0
  70. package/components/snake-icon/snake-icon.vue +220 -0
  71. package/components/snake-icon/types.ts +45 -0
  72. package/components/snake-image/snake-image.vue +230 -0
  73. package/components/snake-image/types.ts +70 -0
  74. package/components/snake-images/snake-images.vue +80 -0
  75. package/components/snake-images/types.ts +5 -0
  76. package/components/snake-index-anchor/snake-index-anchor.vue +83 -0
  77. package/components/snake-index-anchor/types.ts +14 -0
  78. package/components/snake-index-list/anchor-registry.ts +80 -0
  79. package/components/snake-index-list/snake-index-list.vue +259 -0
  80. package/components/snake-index-list/types.ts +115 -0
  81. package/components/snake-index-list-panel/snake-index-list-panel.vue +277 -0
  82. package/components/snake-index-list-panel/types.ts +10 -0
  83. package/components/snake-input/snake-input.vue +225 -0
  84. package/components/snake-input/types.ts +99 -0
  85. package/components/snake-jump-applet/snake-jump-applet.vue +99 -0
  86. package/components/snake-jump-applet/types.ts +17 -0
  87. package/components/snake-line/snake-line.vue +42 -0
  88. package/components/snake-line/types.ts +30 -0
  89. package/components/snake-line-progress/snake-line-progress.vue +105 -0
  90. package/components/snake-line-progress/types.ts +26 -0
  91. package/components/snake-link/snake-link.vue +74 -0
  92. package/components/snake-link/types.ts +30 -0
  93. package/components/snake-list-switching/components/tree-select/index.vue +156 -0
  94. package/components/snake-list-switching/snake-list-switching.vue +1285 -0
  95. package/components/snake-list-switching/types.ts +11 -0
  96. package/components/snake-loading/snake-loading.vue +241 -0
  97. package/components/snake-loading/types.ts +34 -0
  98. package/components/snake-loadmore/snake-loadmore.vue +103 -0
  99. package/components/snake-loadmore/types.ts +56 -0
  100. package/components/snake-magic-cube/snake-magic-cube.vue +622 -0
  101. package/components/snake-magic-cube/types.ts +5 -0
  102. package/components/snake-modal/snake-modal.vue +434 -0
  103. package/components/snake-modal/types.ts +71 -0
  104. package/components/snake-navbar/snake-navbar.vue +194 -0
  105. package/components/snake-navbar/types.ts +54 -0
  106. package/components/snake-no-network/snake-no-network.vue +125 -0
  107. package/components/snake-no-network/types.ts +27 -0
  108. package/components/snake-notice/snake-notice.vue +44 -0
  109. package/components/snake-notice/types.ts +5 -0
  110. package/components/snake-notice-bar/snake-notice-bar.vue +336 -0
  111. package/components/snake-notice-bar/types.ts +82 -0
  112. package/components/snake-overlay/snake-overlay.vue +73 -0
  113. package/components/snake-overlay/types.ts +35 -0
  114. package/components/snake-page-popup/snake-page-popup.vue +146 -0
  115. package/components/snake-page-popup/types.ts +28 -0
  116. package/components/snake-parse/audio/audio.vue +286 -0
  117. package/components/snake-parse/audio/context.js +11 -0
  118. package/components/snake-parse/audio/index.js +34 -0
  119. package/components/snake-parse/node/node.vue +830 -0
  120. package/components/snake-parse/parser.js +1480 -0
  121. package/components/snake-parse/snake-parse.vue +548 -0
  122. package/components/snake-parse/static/app-plus/snake-parse/js/handler.js +109 -0
  123. package/components/snake-parse/static/app-plus/snake-parse/js/uni.webview.js +0 -0
  124. package/components/snake-parse/static/app-plus/snake-parse/js/uni.webview.min.js +1 -0
  125. package/components/snake-parse/static/app-plus/snake-parse/local.html +3 -0
  126. package/components/snake-parse/style/index.js +132 -0
  127. package/components/snake-parse/style/parser.js +175 -0
  128. package/components/snake-parse/types.ts +49 -0
  129. package/components/snake-picture-ads/snake-picture-ads.vue +292 -0
  130. package/components/snake-picture-ads/types.ts +5 -0
  131. package/components/snake-popup/snake-popup.vue +348 -0
  132. package/components/snake-popup/types.ts +88 -0
  133. package/components/snake-qr-code/qrcode.ts +1214 -0
  134. package/components/snake-qr-code/snake-qr-code.vue +408 -0
  135. package/components/snake-qr-code/types.ts +78 -0
  136. package/components/snake-radio/snake-radio.vue +271 -0
  137. package/components/snake-radio/types.ts +64 -0
  138. package/components/snake-radio-group/snake-radio-group.vue +98 -0
  139. package/components/snake-radio-group/types.ts +48 -0
  140. package/components/snake-read-more/snake-read-more.vue +140 -0
  141. package/components/snake-read-more/types.ts +53 -0
  142. package/components/snake-rich-text/snake-rich-text.vue +24 -0
  143. package/components/snake-rich-text/types.ts +5 -0
  144. package/components/snake-root-portal/snake-root-portal.vue +92 -0
  145. package/components/snake-root-portal/types.ts +9 -0
  146. package/components/snake-search/snake-search.vue +202 -0
  147. package/components/snake-search/types.ts +84 -0
  148. package/components/snake-select/snake-select.vue +371 -0
  149. package/components/snake-select/types.ts +75 -0
  150. package/components/snake-skeleton/snake-skeleton.vue +104 -0
  151. package/components/snake-skeleton/types.ts +32 -0
  152. package/components/snake-skeleton-item/snake-skeleton-item.vue +90 -0
  153. package/components/snake-skeleton-item/types.ts +30 -0
  154. package/components/snake-slider/snake-slider.vue +249 -0
  155. package/components/snake-slider/types.ts +59 -0
  156. package/components/snake-sticky/snake-sticky.vue +178 -0
  157. package/components/snake-sticky/types.ts +48 -0
  158. package/components/snake-subsection/snake-subsection.vue +188 -0
  159. package/components/snake-subsection/types.ts +54 -0
  160. package/components/snake-swiper/snake-swiper.vue +229 -0
  161. package/components/snake-swiper/types.ts +79 -0
  162. package/components/snake-switch/snake-switch.vue +100 -0
  163. package/components/snake-switch/types.ts +41 -0
  164. package/components/snake-tabbar/snake-tabbar.vue +306 -0
  165. package/components/snake-tabbar/types.ts +93 -0
  166. package/components/snake-tabs/snake-tabs.vue +305 -0
  167. package/components/snake-tabs/types.ts +79 -0
  168. package/components/snake-text/snake-text.vue +292 -0
  169. package/components/snake-text/types.ts +100 -0
  170. package/components/snake-textarea/snake-textarea.vue +171 -0
  171. package/components/snake-textarea/types.ts +79 -0
  172. package/components/snake-toast/snake-toast.vue +330 -0
  173. package/components/snake-toast/types.ts +55 -0
  174. package/components/snake-tooltip/position.ts +33 -0
  175. package/components/snake-tooltip/snake-tooltip.vue +339 -0
  176. package/components/snake-tooltip/types.ts +80 -0
  177. package/components/snake-transition/snake-transition.vue +315 -0
  178. package/components/snake-transition/types.ts +65 -0
  179. package/components/snake-upload/snake-upload.vue +1801 -0
  180. package/components/snake-upload/types.ts +256 -0
  181. package/components/snake-videos/snake-videos.vue +122 -0
  182. package/components/snake-videos/types.ts +7 -0
  183. package/iconfont.css +881 -0
  184. package/index.d.ts +7 -0
  185. package/index.scss +26 -0
  186. package/index.ts +94 -0
  187. package/libs/cms/blocks.ts +103 -0
  188. package/libs/cms/config.ts +25 -0
  189. package/libs/cms/index.ts +7 -0
  190. package/libs/cms/normalize.ts +135 -0
  191. package/libs/cms/resolve.ts +12 -0
  192. package/libs/cms/types.ts +164 -0
  193. package/libs/cms/use-commerce.ts +385 -0
  194. package/libs/cms/v2.ts +193 -0
  195. package/libs/config/index.ts +4 -0
  196. package/libs/config/modal.ts +93 -0
  197. package/libs/config/state.ts +33 -0
  198. package/libs/config/theme.ts +351 -0
  199. package/libs/config/toast.ts +71 -0
  200. package/libs/config/types.ts +135 -0
  201. package/libs/config/zIndex.ts +44 -0
  202. package/libs/css/color.scss +105 -0
  203. package/libs/css/common.scss +171 -0
  204. package/libs/css/style.h5.scss +7 -0
  205. package/libs/css/style.mp.scss +7 -0
  206. package/libs/css/style.theme.scss +70 -0
  207. package/libs/css/style.vue.scss +140 -0
  208. package/libs/form/async-validator.min.d.ts +75 -0
  209. package/libs/form/async-validator.min.js +3 -0
  210. package/libs/form/index.ts +3 -0
  211. package/libs/form/path.ts +40 -0
  212. package/libs/form/types.ts +51 -0
  213. package/libs/form/validate.ts +149 -0
  214. package/libs/foundation.ts +96 -0
  215. package/libs/function/$parent.ts +33 -0
  216. package/libs/function/clipboard.ts +13 -0
  217. package/libs/function/color.ts +55 -0
  218. package/libs/function/debounce.ts +34 -0
  219. package/libs/function/deepClone.ts +30 -0
  220. package/libs/function/deepMerge.ts +25 -0
  221. package/libs/function/emitter.ts +30 -0
  222. package/libs/function/format.ts +27 -0
  223. package/libs/function/getParent.ts +53 -0
  224. package/libs/function/getRect.ts +29 -0
  225. package/libs/function/guid.ts +11 -0
  226. package/libs/function/index.ts +31 -0
  227. package/libs/function/queryParams.ts +73 -0
  228. package/libs/function/random.ts +6 -0
  229. package/libs/function/randomArray.ts +14 -0
  230. package/libs/function/route.ts +155 -0
  231. package/libs/function/sleep.ts +7 -0
  232. package/libs/function/style.ts +58 -0
  233. package/libs/function/styleUtils.ts +83 -0
  234. package/libs/function/system.ts +14 -0
  235. package/libs/function/test.ts +202 -0
  236. package/libs/function/throttle.ts +36 -0
  237. package/libs/function/timeFormat.ts +27 -0
  238. package/libs/function/timeFrom.ts +27 -0
  239. package/libs/function/toast.ts +10 -0
  240. package/libs/function/trim.ts +11 -0
  241. package/libs/function/type2icon.ts +11 -0
  242. package/libs/function/unit.ts +45 -0
  243. package/libs/hooks/index.ts +11 -0
  244. package/libs/hooks/useChildren.ts +125 -0
  245. package/libs/hooks/useColor.ts +5 -0
  246. package/libs/hooks/useDebounce.ts +5 -0
  247. package/libs/hooks/useLocale.ts +13 -0
  248. package/libs/hooks/useModal.ts +131 -0
  249. package/libs/hooks/useParent.ts +62 -0
  250. package/libs/hooks/useRect.ts +44 -0
  251. package/libs/hooks/useRouter.ts +5 -0
  252. package/libs/hooks/useTheme.ts +31 -0
  253. package/libs/hooks/useThrottle.ts +5 -0
  254. package/libs/hooks/useToast.ts +116 -0
  255. package/libs/index.ts +18 -0
  256. package/libs/request/auto-http.ts +1 -0
  257. package/libs/request/index.ts +343 -0
  258. package/libs/runtime/accessors.ts +55 -0
  259. package/libs/runtime/context.ts +40 -0
  260. package/libs/runtime/index.ts +18 -0
  261. package/libs/runtime/types.ts +49 -0
  262. package/libs/util/calendar.ts +6 -0
  263. package/locale/index.ts +130 -0
  264. package/locale/lang/en-US.ts +67 -0
  265. package/locale/lang/zh-CN.ts +67 -0
  266. package/package.json +130 -5
  267. package/plugins/index.ts +1 -0
  268. package/plugins/root/README.md +86 -0
  269. package/plugins/root/index.ts +123 -0
  270. package/plugins/root/page.ts +77 -0
  271. package/plugins/root/root.ts +53 -0
  272. package/plugins/root/utils.ts +137 -0
  273. package/readme.md +79 -0
  274. package/theme.scss +118 -0
  275. package/types/components.d.ts +236 -0
  276. package/types/global.d.ts +21 -0
  277. package/types/index.d.ts +6 -0
  278. package/types/uni-app.d.ts +16 -0
  279. package/README.md +0 -3
@@ -0,0 +1,998 @@
1
+ <template>
2
+ <snake-popup
3
+ v-model="popupValue"
4
+ mode="bottom"
5
+ :overlay="!isPage"
6
+ :close-on-click-overlay="maskCloseAble"
7
+ :safe-area-inset-bottom="safeAreaInsetBottom"
8
+ :z-index="layerZIndex"
9
+ :round="borderRadius"
10
+ :closeable="closeable"
11
+ :custom-style="popupCustomStyle"
12
+ @close="onPopupClose"
13
+ >
14
+ <view class="snake-calendar" :class="[customClass, { 'snake-calendar--page': isPage }]" :style="[customStyle]">
15
+ <view v-if="!isPage" class="snake-calendar__header">
16
+ <view v-if="!slots.tooltip" class="snake-calendar__header__text">{{ toolTipValue }}</view>
17
+ <slot v-else name="tooltip" />
18
+ </view>
19
+ <view class="snake-calendar__action">
20
+ <view class="snake-calendar__action__icon">
21
+ <snake-icon
22
+ v-if="changeYear"
23
+ name="arrow-left-double"
24
+ :color="yearArrowColor"
25
+ @click="changeYearHandler(0)"
26
+ />
27
+ </view>
28
+ <view class="snake-calendar__action__icon">
29
+ <snake-icon
30
+ v-if="changeMonth"
31
+ name="arrow-left"
32
+ :color="monthArrowColor"
33
+ @click="changeMonthHandler(0)"
34
+ />
35
+ </view>
36
+ <view class="snake-calendar__action__text">{{ showTitle }}</view>
37
+ <view class="snake-calendar__action__icon">
38
+ <snake-icon
39
+ v-if="changeMonth"
40
+ name="arrow-right"
41
+ :color="monthArrowColor"
42
+ @click="changeMonthHandler(1)"
43
+ />
44
+ </view>
45
+ <view class="snake-calendar__action__icon">
46
+ <snake-icon
47
+ v-if="changeYear"
48
+ name="arrow-right-double"
49
+ :color="yearArrowColor"
50
+ @click="changeYearHandler(1)"
51
+ />
52
+ </view>
53
+ </view>
54
+ <view class="snake-calendar__week-day">
55
+ <view v-for="(item, index) in weekDayZh" :key="index" class="snake-calendar__week-day__text">
56
+ {{ item }}
57
+ </view>
58
+ </view>
59
+ <view class="snake-calendar__content">
60
+ <!-- 前置空白部分 -->
61
+ <block v-for="(item, index) in weekdayArr" :key="index">
62
+ <view class="snake-calendar__content__item"></view>
63
+ </block>
64
+ <view
65
+ v-for="(item, index) in daysArr"
66
+ :key="index"
67
+ class="snake-calendar__content__item"
68
+ :class="{
69
+ 'snake-calendar__content__item--hover': openDisAbled(year, month, index + 1),
70
+ 'snake-calendar__content--start-date':
71
+ (mode === 'range' && startDate === `${year}-${formatNum(month)}-${formatNum(index + 1)}`) ||
72
+ mode === 'date',
73
+ 'snake-calendar__content--end-date':
74
+ (mode === 'range' && endDate === `${year}-${formatNum(month)}-${formatNum(index + 1)}`) ||
75
+ mode === 'date',
76
+ 'snake-calendar__content--checked': isCheckedDate(index + 1),
77
+ 'snake-calendar__content--today-checked': isTodayChecked(index + 1),
78
+ 'snake-calendar__content--checkin-mode': checkinMode
79
+ }"
80
+ :style="{
81
+ backgroundColor:
82
+ !checkinMode && checkedDates.length === 0 && !todayChecked ? getColor(index, 1) : ''
83
+ }"
84
+ @click="dateClick(index)"
85
+ >
86
+ <view
87
+ class="snake-calendar__content__item__inner"
88
+ :class="{ 'snake-calendar__content__item__inner--today-checked': isTodayChecked(index + 1) }"
89
+ :style="{
90
+ color: getCheckinTextColor(index + 1) || getColor(index, 2),
91
+ backgroundColor: getCheckinColor(index + 1)
92
+ }"
93
+ >
94
+ <!-- 今日已打卡时显示对勾,否则显示日期 -->
95
+ <view v-if="isTodayChecked(index + 1)" class="snake-calendar__content__item__checkmark">
96
+ <snake-icon name="checkmark" size="36" :color="checkedColor" />
97
+ </view>
98
+ <template v-else>
99
+ <!-- 自定义日期内容插槽 - 优先级最高 -->
100
+ <template v-if="useDateSlot">
101
+ <view class="snake-calendar__content__item__day">{{ index + 1 }}</view>
102
+ <view
103
+ class="snake-calendar__content__item__lunar"
104
+ :style="{ color: getSlotColor(index + 1) }"
105
+ >
106
+ <slot name="date" :date="getDateInfo(index + 1)" />
107
+ </view>
108
+ </template>
109
+ <template v-else>
110
+ <!-- 日期数字右上角标记:休/班 -->
111
+ <view
112
+ v-if="isHoliday(index + 1)"
113
+ class="snake-calendar__content__item__mark snake-calendar__content__item__mark--holiday"
114
+ :style="{ color: getHolidayWorkdayColor(index + 1, holidayColor) }"
115
+ >
116
+ {{ t('holiday') }}
117
+ </view>
118
+ <view
119
+ v-else-if="isWorkday(index + 1)"
120
+ class="snake-calendar__content__item__mark snake-calendar__content__item__mark--workday"
121
+ :style="{ color: getHolidayWorkdayColor(index + 1, workdayColor) }"
122
+ >
123
+ {{ t('workday') }}
124
+ </view>
125
+ <view class="snake-calendar__content__item__day">{{ index + 1 }}</view>
126
+ <!-- 范围选择开始日期显示"开始" -->
127
+ <view
128
+ v-if="
129
+ mode === 'range' &&
130
+ startDate === `${year}-${formatNum(month)}-${formatNum(index + 1)}` &&
131
+ startDate !== endDate
132
+ "
133
+ class="snake-calendar__content__item__lunar"
134
+ :style="{ color: activeColor }"
135
+ >
136
+ {{ startTextValue }}
137
+ </view>
138
+ <!-- 范围选择结束日期显示"结束" -->
139
+ <view
140
+ v-else-if="
141
+ mode === 'range' &&
142
+ endDate === `${year}-${formatNum(month)}-${formatNum(index + 1)}`
143
+ "
144
+ class="snake-calendar__content__item__lunar"
145
+ :style="{ color: activeColor }"
146
+ >
147
+ {{ endTextValue }}
148
+ </view>
149
+ <!-- 节日名称 -->
150
+ <view
151
+ v-else-if="getFestival(index + 1)"
152
+ class="snake-calendar__content__item__lunar snake-calendar__content__item__festival"
153
+ :style="{ color: getHolidayWorkdayColor(index + 1, festivalColor) }"
154
+ >
155
+ {{ getFestival(index + 1) }}
156
+ </view>
157
+ <!-- 农历 -->
158
+ <view
159
+ v-else-if="showLunar"
160
+ class="snake-calendar__content__item__lunar"
161
+ :style="{ color: getCheckinLunarColor(index + 1) || getColor(index, 2) }"
162
+ >
163
+ {{
164
+ lunarArr[index]?.dayCn === '初一'
165
+ ? lunarArr[index].monthCn
166
+ : (lunarArr[index]?.dayCn ?? '')
167
+ }}
168
+ </view>
169
+ <!-- 占位元素:当有节日/农历数据时保持高度一致 -->
170
+ <view
171
+ v-else-if="showFestival"
172
+ class="snake-calendar__content__item__lunar snake-calendar__content__item__placeholder"
173
+ ></view>
174
+ </template>
175
+ </template>
176
+ </view>
177
+ </view>
178
+ <view class="snake-calendar__content__bg-month">{{ month }}</view>
179
+ </view>
180
+ <!-- 页面模式下不显示确定按钮,选择完成自动触发 change 事件 -->
181
+ <view v-if="!isPage" class="snake-calendar__bottom">
182
+ <view class="snake-calendar__bottom__choose">
183
+ <text>{{ mode === 'date' ? activeDate : startDate }}</text>
184
+ <text v-if="endDate">{{ t('to') }}{{ endDate }}</text>
185
+ </view>
186
+ <view class="snake-calendar__bottom__btn">
187
+ <snake-button
188
+ :type="btnType"
189
+ :disabled="btnDisable"
190
+ shape="circle"
191
+ size="default"
192
+ @click="btnFix(false)"
193
+ >
194
+ {{ t('confirmText') }}
195
+ </snake-button>
196
+ </view>
197
+ </view>
198
+ </view>
199
+ </snake-popup>
200
+ </template>
201
+
202
+ <script setup lang="ts">
203
+ /**
204
+ * @component Calendar
205
+ * @description 日历,用于单个日期或日期范围的选择,包裹在底部弹层中,支持农历、节日、打卡签到与自定义插槽。
206
+ * @see /components/calendar
207
+ */
208
+
209
+ import { computed, onMounted, ref, useSlots, watch } from 'vue';
210
+ import zIndex from '../../libs/config/zIndex';
211
+ import { useLocale } from '../../libs/hooks/useLocale';
212
+ import Calendar from '../../libs/util/calendar';
213
+ import Button from '../snake-button/snake-button.vue';
214
+ import Icon from '../snake-icon/snake-icon.vue';
215
+ import Popup from '../snake-popup/snake-popup.vue';
216
+ import {
217
+ calendarProps,
218
+ type CalendarChangeDate,
219
+ type CalendarChangeRange,
220
+ type CalendarEmits,
221
+ type CalendarLunar
222
+ } from './types';
223
+
224
+ defineOptions({ name: 'Calendar', options: { virtualHost: true, styleIsolation: 'shared' } });
225
+ const props = defineProps(calendarProps);
226
+ const emit = defineEmits<CalendarEmits>();
227
+ const slots = useSlots();
228
+ const { t } = useLocale('calendar');
229
+
230
+ // 组件内部状态
231
+ // 星期几,值为0-6
232
+ const weekday = ref(1);
233
+ const weekdayArr = ref<number[]>([]);
234
+ const days = ref(0);
235
+ const daysArr = ref<number[]>([]);
236
+ const lunarArr = ref<CalendarLunar[]>([]);
237
+ const year = ref(2020);
238
+ const month = ref(0);
239
+ // 当前月有多少天
240
+ const day = ref(0);
241
+ const startYear = ref(0);
242
+ const startMonth = ref(0);
243
+ const startDay = ref(0);
244
+ const endYear = ref(0);
245
+ const endMonth = ref(0);
246
+ const endDay = ref(0);
247
+ const today = ref('');
248
+ const activeDate = ref('');
249
+ const startDate = ref('');
250
+ const endDate = ref('');
251
+ const isStart = ref(true);
252
+ const min = ref<{ year: number; month: number; day: number } | null>(null);
253
+ const max = ref<{ year: number; month: number; day: number } | null>(null);
254
+ const weekDayZh = ref([t('sun'), t('mon'), t('tue'), t('wed'), t('thu'), t('fri'), t('sat')]);
255
+
256
+ // 内置中国传统节日(公历日期,格式:MM-DD)
257
+ const builtInFestivals: Record<string, string> = {
258
+ '01-01': '元旦',
259
+ '02-14': '情人节',
260
+ '03-08': '妇女节',
261
+ '03-12': '植树节',
262
+ '04-01': '愚人节',
263
+ '05-01': '劳动节',
264
+ '05-04': '青年节',
265
+ '06-01': '儿童节',
266
+ '07-01': '建党节',
267
+ '08-01': '建军节',
268
+ '09-10': '教师节',
269
+ '10-01': '国庆节',
270
+ '11-11': '光棍节',
271
+ '12-25': '圣诞节'
272
+ };
273
+
274
+ const dataChange = computed(() => `${props.mode}-${props.minDate}-${props.maxDate}`);
275
+ const lunarChange = computed(() => props.showLunar);
276
+ const defaultDateChange = computed(
277
+ () => `${props.defaultDate}-${props.startDate}-${props.endDate}-${props.defaultSelectToday}`
278
+ );
279
+ // 用户有传递层级值时优先使用
280
+ const layerZIndex = computed(() => Number(props.zIndex) || zIndex.popup);
281
+ const popupValue = computed({
282
+ get: () => props.modelValue,
283
+ set: (val: boolean) => emit('update:modelValue', val)
284
+ });
285
+ // 页面模式下通过去掉遮罩与固定定位实现内联展示
286
+ const popupCustomStyle = computed(() => {
287
+ if (!props.isPage) return props.customStyle;
288
+ const staticPosition = { position: 'static' } as const;
289
+ return Array.isArray(props.customStyle)
290
+ ? [staticPosition, ...props.customStyle]
291
+ : [staticPosition, props.customStyle];
292
+ });
293
+
294
+ const btnDisable = computed(() => {
295
+ let disable = false;
296
+ if (props.mode === 'range') {
297
+ if (!startDate.value || !endDate.value) {
298
+ disable = true;
299
+ }
300
+ } else {
301
+ if (!activeDate.value) {
302
+ disable = true;
303
+ }
304
+ }
305
+ return disable;
306
+ });
307
+
308
+ // 国际化计算属性
309
+ const startTextValue = computed(() => props.startText || t('startText'));
310
+ const endTextValue = computed(() => props.endText || t('endText'));
311
+ const toolTipValue = computed(() => props.toolTip || t('toolTip'));
312
+ const showTitle = computed(() => `${year.value}${t('year')}${month.value}${t('month')}`);
313
+
314
+ watch([dataChange, lunarChange], () => {
315
+ init();
316
+ });
317
+
318
+ watch(defaultDateChange, () => {
319
+ init();
320
+ });
321
+
322
+ onMounted(() => {
323
+ init();
324
+ });
325
+
326
+ /** 获取日期颜色。 */
327
+ function getColor(index: number, type: number) {
328
+ let color = type === 1 ? '' : props.color;
329
+ const dayNum = index + 1;
330
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
331
+ const timestamp = new Date(date.replace(/-/g, '/')).getTime();
332
+ const start = startDate.value.replace(/-/g, '/');
333
+ const end = endDate.value.replace(/-/g, '/');
334
+ if ((props.isActiveCurrent && activeDate.value === date) || startDate.value === date || endDate.value === date) {
335
+ color = type === 1 ? props.activeBgColor : props.activeColor;
336
+ } else if (endDate.value && timestamp > new Date(start).getTime() && timestamp < new Date(end).getTime()) {
337
+ color = type === 1 ? props.rangeBgColor : props.rangeColor;
338
+ }
339
+ return color;
340
+ }
341
+
342
+ /** 判断日期是否已打卡。 */
343
+ function isCheckedDate(dayNum: number) {
344
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
345
+ return props.checkedDates.includes(date);
346
+ }
347
+
348
+ /** 判断是否是今日且已打卡。 */
349
+ function isTodayChecked(dayNum: number) {
350
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
351
+ if (date !== today.value) {
352
+ return false;
353
+ }
354
+ if (props.todayChecked) {
355
+ return true;
356
+ }
357
+ if (props.checkedDates.includes(date)) {
358
+ return true;
359
+ }
360
+ return false;
361
+ }
362
+
363
+ /** 获取打卡日期背景色。 */
364
+ function getCheckinColor(dayNum: number) {
365
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
366
+ const isToday = date === today.value;
367
+ const isInCheckedDates = props.checkedDates.includes(date);
368
+
369
+ if (!props.checkinMode && props.checkedDates.length === 0 && !props.todayChecked) {
370
+ return '';
371
+ }
372
+ if (isToday && (props.todayChecked || isInCheckedDates)) {
373
+ return props.todayCheckedBgColor;
374
+ }
375
+ if (isInCheckedDates) {
376
+ return props.checkedBgColor;
377
+ }
378
+ if (props.checkinMode) {
379
+ return props.uncheckedBgColor;
380
+ }
381
+ return '';
382
+ }
383
+
384
+ /** 获取打卡日期文字颜色。 */
385
+ function getCheckinTextColor(dayNum: number) {
386
+ if (!props.checkinMode && props.checkedDates.length === 0 && !props.todayChecked) {
387
+ return '';
388
+ }
389
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
390
+ if (props.checkedDates.includes(date) || (date === today.value && props.todayChecked)) {
391
+ return props.checkedColor;
392
+ }
393
+ if (props.checkinMode) {
394
+ return props.uncheckedColor;
395
+ }
396
+ return '';
397
+ }
398
+
399
+ /** 获取打卡日期农历文字颜色。 */
400
+ function getCheckinLunarColor(dayNum: number) {
401
+ if (!props.checkinMode && props.checkedDates.length === 0 && !props.todayChecked) {
402
+ return '';
403
+ }
404
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
405
+ if (props.checkedDates.includes(date) || (date === today.value && props.todayChecked)) {
406
+ return props.checkedColor;
407
+ }
408
+ if (props.checkinMode) {
409
+ return props.uncheckedColor;
410
+ }
411
+ return '';
412
+ }
413
+
414
+ /** 获取自定义插槽的颜色。 */
415
+ function getSlotColor(dayNum: number) {
416
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
417
+ if (props.isActiveCurrent && (activeDate.value === date || startDate.value === date || endDate.value === date)) {
418
+ return props.activeColor;
419
+ }
420
+ if (props.checkinMode || props.checkedDates.length > 0 || props.todayChecked) {
421
+ return getCheckinLunarColor(dayNum) || props.color;
422
+ }
423
+ return props.color;
424
+ }
425
+
426
+ /** 获取日期信息,用于自定义插槽。 */
427
+ function getDateInfo(dayNum: number) {
428
+ const dateStr = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
429
+ const dateObj = new Date(dateStr.replace(/-/g, '/'));
430
+ const dayOfWeek = dateObj.getDay();
431
+ const weekNames = [t('sun'), t('mon'), t('tue'), t('wed'), t('thu'), t('fri'), t('sat')];
432
+ const isSelected = activeDate.value === dateStr || startDate.value === dateStr || endDate.value === dateStr;
433
+
434
+ return {
435
+ year: year.value,
436
+ month: month.value,
437
+ day: dayNum,
438
+ date: dateStr,
439
+ week: weekNames[dayOfWeek],
440
+ weekNum: dayOfWeek,
441
+ isToday: dateStr === today.value,
442
+ isHoliday: props.holidays.includes(dateStr),
443
+ isWorkday: props.workdays.includes(dateStr),
444
+ isChecked: props.checkedDates.includes(dateStr),
445
+ isSelected,
446
+ isTodayChecked: dateStr === today.value && props.todayChecked,
447
+ lunar: lunarArr.value[dayNum - 1] || null
448
+ };
449
+ }
450
+
451
+ /** 判断是否是节假日。 */
452
+ function isHoliday(dayNum: number) {
453
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
454
+ return props.holidays.includes(date);
455
+ }
456
+
457
+ /** 判断是否是加班日。 */
458
+ function isWorkday(dayNum: number) {
459
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
460
+ return props.workdays.includes(date);
461
+ }
462
+
463
+ /** 获取节日名称(合并内置节日和用户自定义节日)。 */
464
+ function getFestival(dayNum: number) {
465
+ if (!props.showFestival && Object.keys(props.festivals).length === 0) {
466
+ return '';
467
+ }
468
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
469
+ const monthDay = `${formatNum(month.value)}-${formatNum(dayNum)}`;
470
+ if (date in props.festivals) {
471
+ return props.festivals[date];
472
+ }
473
+ if (monthDay in props.festivals) {
474
+ return props.festivals[monthDay];
475
+ }
476
+ if (props.showFestival && builtInFestivals[monthDay]) {
477
+ return builtInFestivals[monthDay];
478
+ }
479
+ return '';
480
+ }
481
+
482
+ /** 获取节假日/加班日文字颜色。 */
483
+ function getHolidayWorkdayColor(dayNum: number, defaultColor: string) {
484
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(dayNum)}`;
485
+ if (activeDate.value === date || startDate.value === date || endDate.value === date) {
486
+ return props.activeColor;
487
+ }
488
+ return defaultColor;
489
+ }
490
+
491
+ /** 初始化日历数据。 */
492
+ function init() {
493
+ let now = new Date();
494
+ const minDateObj = new Date(String(props.minDate));
495
+ let maxDateObj = new Date(String(props.maxDate || ''));
496
+ if (Number.isNaN(maxDateObj.getTime())) maxDateObj = new Date();
497
+ if (now < minDateObj) now = minDateObj;
498
+ if (now > maxDateObj) now = maxDateObj;
499
+ year.value = now.getFullYear();
500
+ month.value = now.getMonth() + 1;
501
+ day.value = now.getDate();
502
+ today.value = `${now.getFullYear()}-${formatNum(month.value)}-${formatNum(day.value)}`;
503
+ min.value = initDate(String(props.minDate));
504
+ max.value = initDate(String(props.maxDate) || today.value);
505
+
506
+ if (props.mode === 'date' && props.defaultDate) {
507
+ const defaultDateObj = new Date(props.defaultDate.replace(/-/g, '/'));
508
+ if (!Number.isNaN(defaultDateObj.getTime())) {
509
+ year.value = defaultDateObj.getFullYear();
510
+ month.value = defaultDateObj.getMonth() + 1;
511
+ day.value = defaultDateObj.getDate();
512
+ activeDate.value = `${year.value}-${formatNum(month.value)}-${formatNum(day.value)}`;
513
+ } else if (props.defaultSelectToday) {
514
+ activeDate.value = today.value;
515
+ } else {
516
+ activeDate.value = '';
517
+ }
518
+ } else if (props.mode === 'range' && (props.startDate || props.endDate)) {
519
+ const startDateObj = props.startDate ? new Date(props.startDate.replace(/-/g, '/')) : null;
520
+ const endDateObj = props.endDate ? new Date(props.endDate.replace(/-/g, '/')) : null;
521
+
522
+ if (startDateObj && !Number.isNaN(startDateObj.getTime())) {
523
+ year.value = startDateObj.getFullYear();
524
+ month.value = startDateObj.getMonth() + 1;
525
+ startYear.value = startDateObj.getFullYear();
526
+ startMonth.value = startDateObj.getMonth() + 1;
527
+ startDay.value = startDateObj.getDate();
528
+ startDate.value = `${startYear.value}-${formatNum(startMonth.value)}-${formatNum(startDay.value)}`;
529
+ }
530
+ if (endDateObj && !Number.isNaN(endDateObj.getTime())) {
531
+ endYear.value = endDateObj.getFullYear();
532
+ endMonth.value = endDateObj.getMonth() + 1;
533
+ endDay.value = endDateObj.getDate();
534
+ endDate.value = `${endYear.value}-${formatNum(endMonth.value)}-${formatNum(endDay.value)}`;
535
+ }
536
+ isStart.value = true;
537
+ activeDate.value = '';
538
+ } else if (props.defaultSelectToday) {
539
+ activeDate.value = today.value;
540
+ resetRangeState();
541
+ } else {
542
+ activeDate.value = '';
543
+ resetRangeState();
544
+ }
545
+
546
+ changeData();
547
+ }
548
+
549
+ /** 重置范围选择状态。 */
550
+ function resetRangeState() {
551
+ startDate.value = '';
552
+ startYear.value = 0;
553
+ startMonth.value = 0;
554
+ startDay.value = 0;
555
+ endYear.value = 0;
556
+ endMonth.value = 0;
557
+ endDay.value = 0;
558
+ endDate.value = '';
559
+ isStart.value = true;
560
+ }
561
+
562
+ /** 日期字符串转对象。 */
563
+ function initDate(date: string) {
564
+ const parts = date.split('-');
565
+ return {
566
+ year: Number(parts[0] || 1920),
567
+ month: Number(parts[1] || 1),
568
+ day: Number(parts[2] || 1)
569
+ };
570
+ }
571
+
572
+ /** 判断日期是否可选。 */
573
+ function openDisAbled(yearNum: number, monthNum: number, dayNum: number) {
574
+ let bool = true;
575
+ const date = `${yearNum}/${formatNum(monthNum)}/${formatNum(dayNum)}`;
576
+ const minStr = min.value ? `${min.value.year}/${formatNum(min.value.month)}/${formatNum(min.value.day)}` : '';
577
+ const maxStr = max.value ? `${max.value.year}/${formatNum(max.value.month)}/${formatNum(max.value.day)}` : '';
578
+ const timestamp = new Date(date).getTime();
579
+ if (min.value && max.value && timestamp >= new Date(minStr).getTime() && timestamp <= new Date(maxStr).getTime()) {
580
+ bool = false;
581
+ }
582
+ return bool;
583
+ }
584
+
585
+ /** 生成数组。 */
586
+ function generateArray(start: number, end: number) {
587
+ return Array.from(new Array(end + 1).keys()).slice(start);
588
+ }
589
+
590
+ /** 格式化数字。 */
591
+ function formatNum(num: number) {
592
+ return num < 10 ? `0${num}` : `${num}`;
593
+ }
594
+
595
+ /** 获取某月天数。 */
596
+ function getMonthDay(yearNum: number, monthNum: number) {
597
+ return new Date(yearNum, monthNum, 0).getDate();
598
+ }
599
+
600
+ /** 获取某月第一天星期几。 */
601
+ function getWeekday(yearNum: number, monthNum: number) {
602
+ const date = new Date(`${yearNum}/${monthNum}/01 00:00:00`);
603
+ return date.getDay();
604
+ }
605
+
606
+ /** 检查年月是否超出范围。 */
607
+ function checkRange(yearNum: number, monthNum: number) {
608
+ if (yearNum < Number(props.minYear) || yearNum > Number(props.maxYear)) {
609
+ uni.showToast({ title: t('outOfRange'), icon: 'none' });
610
+ return true;
611
+ }
612
+ const beforeMin =
613
+ min.value && (yearNum < min.value.year || (yearNum === min.value.year && monthNum < min.value.month));
614
+ const afterMax =
615
+ max.value && (yearNum > max.value.year || (yearNum === max.value.year && monthNum > max.value.month));
616
+ if (beforeMin || afterMax) {
617
+ uni.showToast({ title: t('outOfRange'), icon: 'none' });
618
+ return true;
619
+ }
620
+ return false;
621
+ }
622
+
623
+ /** 切换月份。 */
624
+ function changeMonthHandler(isAdd: number) {
625
+ if (isAdd) {
626
+ let m = month.value + 1;
627
+ let y = m > 12 ? year.value + 1 : year.value;
628
+ m = m > 12 ? 1 : m;
629
+ if (!checkRange(y, m)) {
630
+ month.value = m;
631
+ year.value = y;
632
+ changeData();
633
+ }
634
+ } else {
635
+ let m = month.value - 1;
636
+ let y = m < 1 ? year.value - 1 : year.value;
637
+ m = m < 1 ? 12 : m;
638
+ if (!checkRange(y, m)) {
639
+ month.value = m;
640
+ year.value = y;
641
+ changeData();
642
+ }
643
+ }
644
+ }
645
+
646
+ /** 切换年份:若 minDate/maxDate 的月份在当前年超出,则切换到该年的最小/最大有效月。 */
647
+ function changeYearHandler(isAdd: number) {
648
+ let y = isAdd ? year.value + 1 : year.value - 1;
649
+ let m = month.value;
650
+ if (min.value && y === min.value.year && m < min.value.month) {
651
+ m = min.value.month;
652
+ }
653
+ if (max.value && y === max.value.year && m > max.value.month) {
654
+ m = max.value.month;
655
+ }
656
+ if (!checkRange(y, m)) {
657
+ year.value = y;
658
+ month.value = m;
659
+ changeData();
660
+ }
661
+ }
662
+
663
+ /** 更新日历数据。 */
664
+ function changeData() {
665
+ days.value = getMonthDay(year.value, month.value);
666
+ daysArr.value = generateArray(1, days.value);
667
+ weekday.value = getWeekday(year.value, month.value);
668
+ weekdayArr.value = generateArray(1, weekday.value);
669
+ if (props.showLunar) {
670
+ lunarArr.value = [];
671
+ daysArr.value.forEach(d => {
672
+ lunarArr.value.push(getLunar(year.value, month.value, d));
673
+ });
674
+ }
675
+ if (props.isChange && props.mode === 'date') {
676
+ btnFix(true);
677
+ }
678
+ }
679
+
680
+ /** 获取农历。 */
681
+ function getLunar(yearNum: number, monthNum: number, dayNum: number): CalendarLunar {
682
+ const val = Calendar.solar2lunar(yearNum, monthNum, dayNum) as {
683
+ IDayCn: string;
684
+ ncWeek: string;
685
+ IMonthCn: string;
686
+ lDay: number;
687
+ nWeek: number;
688
+ lMonth: number;
689
+ lYear: number;
690
+ };
691
+ return {
692
+ dayCn: val.IDayCn,
693
+ weekCn: val.ncWeek,
694
+ monthCn: val.IMonthCn,
695
+ day: val.lDay,
696
+ week: val.nWeek,
697
+ month: val.lMonth,
698
+ year: val.lYear
699
+ };
700
+ }
701
+
702
+ /** 日期点击事件。 */
703
+ function dateClick(dayIdx: number) {
704
+ // 只读模式下禁止点击
705
+ if (props.readonly) return;
706
+
707
+ const d = dayIdx + 1;
708
+ if (!openDisAbled(year.value, month.value, d)) {
709
+ day.value = d;
710
+ const date = `${year.value}-${formatNum(month.value)}-${formatNum(d)}`;
711
+ if (props.mode === 'date') {
712
+ activeDate.value = date;
713
+ // 页面模式下单选完成自动触发 change;打卡签到模式下弹窗模式也立即触发
714
+ if (props.isPage || props.checkinMode) {
715
+ btnFix(true);
716
+ }
717
+ } else {
718
+ const compare =
719
+ new Date(date.replace(/-/g, '/')).getTime() < new Date(startDate.value.replace(/-/g, '/')).getTime();
720
+ if (isStart.value || compare) {
721
+ startDate.value = date;
722
+ startYear.value = year.value;
723
+ startMonth.value = month.value;
724
+ startDay.value = day.value;
725
+ endYear.value = 0;
726
+ endMonth.value = 0;
727
+ endDay.value = 0;
728
+ endDate.value = '';
729
+ activeDate.value = '';
730
+ isStart.value = false;
731
+ } else {
732
+ endDate.value = date;
733
+ endYear.value = year.value;
734
+ endMonth.value = month.value;
735
+ endDay.value = day.value;
736
+ isStart.value = true;
737
+ // 页面模式下范围选择完成自动触发 change
738
+ if (props.isPage) {
739
+ btnFix(true);
740
+ }
741
+ }
742
+ }
743
+ }
744
+ }
745
+
746
+ /** 关闭弹窗。 */
747
+ function closeCalendar() {
748
+ emit('input', false);
749
+ emit('update:modelValue', false);
750
+ }
751
+
752
+ /** 遮罩或关闭按钮收起时触发。 */
753
+ function onPopupClose() {
754
+ emit('close');
755
+ }
756
+
757
+ /** 获取星期文本。 */
758
+ function getWeekText(date: string) {
759
+ const d = new Date(`${date.replace(/-/g, '/')} 00:00:00`);
760
+ const week = d.getDay();
761
+ return `星期${['日', '一', '二', '三', '四', '五', '六'][week]}`;
762
+ }
763
+
764
+ /** 确定按钮事件。 */
765
+ function btnFix(show: boolean) {
766
+ // 页面模式下不关闭,弹窗模式下关闭
767
+ if (!show && !props.isPage) {
768
+ closeCalendar();
769
+ }
770
+ if (props.mode === 'date') {
771
+ const arr = activeDate.value.split('-');
772
+ const y = props.isChange ? year.value : Number(arr[0]);
773
+ const m = props.isChange ? month.value : Number(arr[1]);
774
+ const d = props.isChange ? day.value : Number(arr[2]);
775
+ const daysNum = getMonthDay(y, m);
776
+ const result = `${y}-${formatNum(m)}-${formatNum(d)}`;
777
+ const weekText = getWeekText(result);
778
+ let isToday = false;
779
+ if (result === today.value) {
780
+ isToday = true;
781
+ }
782
+ const lunar = props.showLunar ? getLunar(y, m, d) : null;
783
+ const payload: CalendarChangeDate = {
784
+ year: y,
785
+ month: m,
786
+ day: d,
787
+ days: daysNum,
788
+ result,
789
+ week: weekText,
790
+ isToday,
791
+ lunar
792
+ };
793
+ emit('change', payload);
794
+ } else {
795
+ if (!startDate.value || !endDate.value) return;
796
+ const startDateStr = `${startYear.value}-${formatNum(startMonth.value)}-${formatNum(startDay.value)}`;
797
+ const startWeek = getWeekText(startDateStr);
798
+ const endDateStr = `${endYear.value}-${formatNum(endMonth.value)}-${formatNum(endDay.value)}`;
799
+ const endWeek = getWeekText(endDateStr);
800
+ let startLunar: CalendarLunar | null = null;
801
+ let endLunar: CalendarLunar | null = null;
802
+ if (props.showLunar) {
803
+ startLunar = getLunar(startYear.value, startMonth.value, startDay.value);
804
+ endLunar = getLunar(endYear.value, endMonth.value, endDay.value);
805
+ }
806
+ const payload: CalendarChangeRange = {
807
+ startYear: startYear.value,
808
+ startMonth: startMonth.value,
809
+ startDay: startDay.value,
810
+ startDate: startDateStr,
811
+ startWeek,
812
+ endYear: endYear.value,
813
+ endMonth: endMonth.value,
814
+ endDay: endDay.value,
815
+ endDate: endDateStr,
816
+ endWeek,
817
+ startLunar,
818
+ endLunar
819
+ };
820
+ emit('change', payload);
821
+ }
822
+ }
823
+ </script>
824
+
825
+ <style scoped>
826
+ .snake-calendar {
827
+ color: var(--snake-color-text-secondary, #606266);
828
+ }
829
+ .snake-calendar--page {
830
+ background-color: var(--snake-color-surface, #fff);
831
+ border-radius: 16rpx;
832
+ }
833
+ .snake-calendar__header {
834
+ box-sizing: border-box;
835
+ width: 100%;
836
+ font-size: 30rpx;
837
+ background-color: var(--snake-color-surface, #fff);
838
+ color: var(--snake-color-text, #303133);
839
+ }
840
+ .snake-calendar__header__text {
841
+ display: flex;
842
+ align-items: center;
843
+ justify-content: center;
844
+ margin-top: 30rpx;
845
+ padding: 0 60rpx;
846
+ }
847
+ .snake-calendar__action {
848
+ display: flex;
849
+ align-items: center;
850
+ justify-content: center;
851
+ padding: 40rpx 0;
852
+ }
853
+ .snake-calendar__action__icon {
854
+ margin: 0 16rpx;
855
+ }
856
+ .snake-calendar__action__text {
857
+ padding: 0 16rpx;
858
+ color: var(--snake-color-text, #303133);
859
+ font-size: 32rpx;
860
+ font-weight: bold;
861
+ line-height: 32rpx;
862
+ }
863
+ .snake-calendar__week-day {
864
+ display: flex;
865
+ align-items: center;
866
+ justify-content: center;
867
+ padding: 6px 0;
868
+ overflow: hidden;
869
+ }
870
+ .snake-calendar__week-day__text {
871
+ flex: 1;
872
+ text-align: center;
873
+ }
874
+ .snake-calendar__content {
875
+ position: relative;
876
+ box-sizing: border-box;
877
+ display: flex;
878
+ flex-wrap: wrap;
879
+ width: 100%;
880
+ padding: 6px 0;
881
+ background-color: var(--snake-color-surface, #fff);
882
+ }
883
+ .snake-calendar__content--end-date {
884
+ border-top-right-radius: 8rpx;
885
+ border-bottom-right-radius: 8rpx;
886
+ }
887
+ .snake-calendar__content--start-date {
888
+ border-top-left-radius: 8rpx;
889
+ border-bottom-left-radius: 8rpx;
890
+ }
891
+ .snake-calendar__content--checked .snake-calendar__content__item__inner,
892
+ .snake-calendar__content--today-checked .snake-calendar__content__item__inner,
893
+ .snake-calendar__content--checkin-mode .snake-calendar__content__item__inner {
894
+ display: flex;
895
+ align-items: center;
896
+ justify-content: center;
897
+ width: 80rpx;
898
+ height: 80rpx;
899
+ border-radius: 50%;
900
+ }
901
+ .snake-calendar__content__item {
902
+ position: relative;
903
+ z-index: 2;
904
+ display: flex;
905
+ align-items: center;
906
+ justify-content: center;
907
+ width: 14.2857%;
908
+ padding: 6px 0;
909
+ overflow: hidden;
910
+ }
911
+ .snake-calendar__content__item--hover {
912
+ opacity: 0.6;
913
+ }
914
+ .snake-calendar__content__item__inner {
915
+ display: flex;
916
+ flex-direction: column;
917
+ align-items: center;
918
+ justify-content: center;
919
+ height: 84rpx;
920
+ font-size: 32rpx;
921
+ }
922
+ .snake-calendar__content__item__inner--today-checked {
923
+ display: flex;
924
+ align-items: center;
925
+ justify-content: center;
926
+ }
927
+ .snake-calendar__content__item__day {
928
+ font-size: 32rpx;
929
+ line-height: 1;
930
+ }
931
+ .snake-calendar__content__item__lunar {
932
+ margin-top: 2rpx;
933
+ font-size: 22rpx;
934
+ line-height: 1;
935
+ transform: scale(0.85);
936
+ }
937
+ .snake-calendar__content__item__mark {
938
+ position: absolute;
939
+ top: 8rpx;
940
+ right: 8rpx;
941
+ z-index: 1;
942
+ font-size: 24rpx;
943
+ line-height: 1;
944
+ transform: scale(0.75);
945
+ }
946
+ .snake-calendar__content__item__mark--holiday {
947
+ color: var(--snake-color-error, #fa3534);
948
+ }
949
+ .snake-calendar__content__item__mark--workday {
950
+ color: var(--snake-color-primary, #06d290);
951
+ }
952
+ .snake-calendar__content__item__festival {
953
+ font-size: 22rpx;
954
+ line-height: 1;
955
+ color: var(--snake-color-primary, #06d290);
956
+ transform: scale(0.85);
957
+ }
958
+ .snake-calendar__content__item__placeholder {
959
+ min-height: 22rpx;
960
+ margin-top: 2rpx;
961
+ opacity: 0;
962
+ }
963
+ .snake-calendar__content__item__checkmark {
964
+ display: flex;
965
+ align-items: center;
966
+ justify-content: center;
967
+ width: 100%;
968
+ height: 100%;
969
+ }
970
+ .snake-calendar__content__bg-month {
971
+ position: absolute;
972
+ z-index: 1;
973
+ top: 50%;
974
+ left: 50%;
975
+ font-size: 130px;
976
+ line-height: 130px;
977
+ color: var(--snake-color-border, #e4e7ed);
978
+ transform: translate(-50%, -50%);
979
+ }
980
+ .snake-calendar__bottom {
981
+ box-sizing: border-box;
982
+ display: flex;
983
+ flex-direction: column;
984
+ align-items: center;
985
+ justify-content: center;
986
+ width: 100%;
987
+ padding: 0 40rpx 30rpx;
988
+ font-size: 24rpx;
989
+ color: var(--snake-color-text-muted, #909399);
990
+ background-color: var(--snake-color-surface, #fff);
991
+ }
992
+ .snake-calendar__bottom__choose {
993
+ height: 50rpx;
994
+ }
995
+ .snake-calendar__bottom__btn {
996
+ width: 100%;
997
+ }
998
+ </style>