index.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <template>
  2. <view class="page adffc">
  3. <view class="week adf">
  4. <view class="week-pre adfacjc" v-for="(w, i) in weeks" :class="{ active: i === 0 || i === 6 }" :key="i">{{ w }}</view>
  5. </view>
  6. <view class="list">
  7. <view class="list-item" v-for="(c,i) in calendarsCopy" :key="i">
  8. <view class="title">{{c.monthTitle}}</view>
  9. <view class="days">
  10. <view class="days-pre"
  11. :class="{'active':d.selected&&d.isCurrentMonth,'in':d.inrange&&d.isCurrentMonth}"
  12. v-for="(d,di) in c.days" :key="di">
  13. <view class="today" v-if="d.isToday">今天</view>
  14. <view class="day adfacjc" :class="{'hui':d.isPast}">{{d.isCurrentMonth?d.day:''}}</view>
  15. <view class="se" v-if="d.start&&d.isCurrentMonth">开始</view>
  16. <view class="se" v-if="d.end&&d.isCurrentMonth">结束</view>
  17. </view>
  18. </view>
  19. </view>
  20. </view>
  21. </view>
  22. </template>
  23. <script setup name="ActivityCalendar">
  24. import { ref, onMounted } from 'vue';
  25. const weeks = ['日', '一', '二', '三', '四', '五', '六'];
  26. const calendars = ref([]);
  27. const calendarsCopy = ref([]);
  28. const startDay = ref('')
  29. const endDay = ref('')
  30. const createCalendar = (months) => {
  31. return new Promise((resolve,reject)=>{
  32. const calendarList = [];
  33. const today = new Date();
  34. today.setHours(0, 0, 0, 0); // 标准化日期
  35. for (let i = 0; i < months; i++) {
  36. // --- 1. 计算目标月份 ---
  37. const targetDate = new Date();
  38. targetDate.setHours(0, 0, 0, 0);
  39. targetDate.setMonth(targetDate.getMonth() + i, 1);
  40. const year = targetDate.getFullYear();
  41. const month = targetDate.getMonth(); // 0-11
  42. const monthData = {
  43. monthTitle: `${year}/${String(month + 1).padStart(2, '0')}`,
  44. days: []
  45. };
  46. // --- 2. 计算日历网格的起始和结束日期 ---
  47. let startDate;
  48. const firstDayOfMonth = new Date(year, month, 1);
  49. const lastDayOfMonth = new Date(year, month + 1, 0);
  50. // **核心逻辑:根据是否为第一个月,决定网格的起始日期**
  51. if (i === 0) {
  52. // **第一个月(当前月):** 网格从今天所在周的周日开始
  53. startDate = new Date(today);
  54. startDate.setDate(today.getDate() - today.getDay());
  55. } else {
  56. // **后续月份:** 网格从该月第一天所在周的周日开始,保证月份的完整性
  57. startDate = new Date(firstDayOfMonth);
  58. startDate.setDate(firstDayOfMonth.getDate() - firstDayOfMonth.getDay());
  59. }
  60. // **网格的结束日期总是该月最后一天所在周的周六**
  61. const endDate = new Date(lastDayOfMonth);
  62. endDate.setDate(lastDayOfMonth.getDate() + (6 - lastDayOfMonth.getDay()));
  63. // --- 3. 循环填充日历网格中的每一天 ---
  64. for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
  65. const currentYear = d.getFullYear();
  66. const currentMonth = d.getMonth() + 1;
  67. const currentDay = d.getDate();
  68. monthData.days.push({
  69. day: currentDay,
  70. dateStr: `${currentYear}-${String(currentMonth).padStart(2, '0')}-${String(currentDay).padStart(2, '0')}`,
  71. isToday: d.getTime() === today.getTime(),
  72. // 标识是否属于当前正在生成的月份 (e.g., 在7月数据中,8月1日这项为 false)
  73. isCurrentMonth: d.getMonth() === month,
  74. // 标识是否为过去日期 (仅在第一个月生效)
  75. isPast: i === 0 && d.getTime() < today.getTime()
  76. });
  77. }
  78. calendarList.push(monthData);
  79. }
  80. resolve(calendarList)
  81. })
  82. };
  83. const setStartEndDay = type => {//type 0:全部时间 1:一周内 2:一月内 3:本周末
  84. let d = new Date();
  85. let s = new Date().Format('yyyy-MM-dd');
  86. startDay.value = s;
  87. let e = null;
  88. switch(type){
  89. case 0:
  90. s = e = '';
  91. startDay.value = '';
  92. endDay.value = '';
  93. break;
  94. case 1:
  95. e = new Date(d.setDate(d.getDate(s)+6)).Format('yyyy-MM-dd');
  96. endDay.value = e;
  97. break;
  98. case 2:
  99. e = new Date(d.setDate(d.getDate(s)+30)).Format('yyyy-MM-dd');
  100. endDay.value = e;
  101. break;
  102. case 3:
  103. let sw = new Date(s).getDay();
  104. if(sw===0){//当天是周日
  105. s = new Date(d.setDate(d.getDate(s)-1)).Format('yyyy-MM-dd');
  106. e = new Date(s).Format('yyyy-MM-dd');
  107. }else{
  108. s = new Date(d.setDate(d.getDate(s)+(6-sw))).Format('yyyy-MM-dd');
  109. e = new Date(d.setDate(d.getDate(s)+1)).Format('yyyy-MM-dd');
  110. }
  111. startDay.value = s;
  112. endDay.value = e;
  113. break;
  114. }
  115. calendarsCopy.value = JSON.parse(JSON.stringify(calendars.value));
  116. if(!s||!e) return
  117. calendarsCopy.value.forEach(c=>{
  118. c?.days.forEach(d=>{
  119. if(d.dateStr===s||d.dateStr===e){
  120. d.selected = true;
  121. d.start = d.dateStr===s;
  122. d.end = d.dateStr===e;
  123. }
  124. if(Date.parse(new Date(d.dateStr))>Date.parse(new Date(s))&&Date.parse(new Date(d.dateStr))<Date.parse(new Date(e))){
  125. d.inrange = true;
  126. }
  127. })
  128. })
  129. }
  130. onMounted(async () => {
  131. calendars.value = await createCalendar(2);
  132. calendarsCopy.value = JSON.parse(JSON.stringify(calendars.value));
  133. });
  134. defineExpose({
  135. setStartEndDay,
  136. startDay,
  137. endDay
  138. })
  139. </script>
  140. <style scoped lang="scss">
  141. .page {
  142. width: 100%;
  143. height: 100%;
  144. background: #f7f7f7;
  145. .week {
  146. width: 100%;
  147. height: 81rpx;
  148. background: #ffffff;
  149. &-pre {
  150. width: calc(100% / 7);
  151. height: 100%;
  152. font-family: PingFangSC, PingFang SC;
  153. font-weight: 400;
  154. font-size: 24rpx;
  155. color: #6b7280;
  156. line-height: 33rpx;
  157. &.active {
  158. font-weight: bold;
  159. color: #70cf52;
  160. }
  161. }
  162. }
  163. .list {
  164. flex: 1;
  165. overflow-y: auto;
  166. &-item{
  167. .title{
  168. font-family: PingFang-SC, PingFang-SC;
  169. font-weight: bold;
  170. font-size: 30rpx;
  171. color: #252525;
  172. line-height: 30rpx;
  173. text-align: center;
  174. margin-top: 30rpx;
  175. }
  176. .days{
  177. display: flex;
  178. flex-wrap: wrap;
  179. margin-top: 40rpx;
  180. &-pre{
  181. width: calc(100% / 7);
  182. height: 120rpx;
  183. position: relative;
  184. &.active{
  185. background: #B7F358;
  186. }
  187. &.in{
  188. background: rgba(183, 243,88, .4);
  189. }
  190. .day{
  191. width: 100%;
  192. height: 100%;
  193. font-family: PingFang-SC, PingFang-SC;
  194. font-weight: bold;
  195. font-size: 30rpx;
  196. color: #252525;
  197. line-height: 42rpx;
  198. &.hui{
  199. color: #989998;
  200. }
  201. }
  202. .today{
  203. width: 100%;
  204. font-family: PingFangSC, PingFang SC;
  205. font-weight: 400;
  206. font-size: 20rpx;
  207. color: #989998;
  208. line-height: 28rpx;
  209. text-align: center;
  210. position: absolute;
  211. top: 10rpx;
  212. }
  213. .se{
  214. width: 100%;
  215. font-family: PingFangSC, PingFang SC;
  216. font-weight: bold;
  217. font-size: 20rpx;
  218. color: #989998;
  219. line-height: 28rpx;
  220. text-align: center;
  221. position: absolute;
  222. bottom: 15rpx;
  223. }
  224. }
  225. }
  226. }
  227. }
  228. }
  229. </style>