time-select.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. Component({
  2. properties: {
  3. // 可以接收外部传入的属性
  4. },
  5. data: {
  6. weekdays: ['一', '二', '三', '四', '五', '六', '日'],
  7. currentYear: new Date().getFullYear(),
  8. currentMonth: new Date().getMonth() + 1,
  9. selectedDate: null,
  10. daysInMonth: [],
  11. apiData: null // 存储API返回的数据
  12. },
  13. lifetimes: {
  14. attached() {
  15. this.loadAppointmentSlots();
  16. }
  17. },
  18. computed: {
  19. currentYearMonth() {
  20. return `${this.data.currentYear}-${this.data.currentMonth.toString().padStart(2, '0')}`;
  21. }
  22. },
  23. methods: {
  24. // 加载预约时段数据
  25. loadAppointmentSlots() {
  26. // 这里应该调用实际的API接口
  27. // 暂时使用模拟数据
  28. const mockApiData = {
  29. time: {
  30. monday: 385,
  31. tuesday: 250,
  32. wednesday: 250,
  33. thursday: 250,
  34. friday: 250,
  35. saturday: 250,
  36. sunday: 250
  37. },
  38. stopDate: {
  39. rtf: "2025-06-08,2025-06-14"
  40. }
  41. };
  42. this.setData({
  43. apiData: mockApiData
  44. }, () => {
  45. // 确保apiData设置完成后再初始化日历和默认日期
  46. this.initCalendar();
  47. this.initDefaultDate();
  48. });
  49. },
  50. // 初始化日历
  51. initCalendar() {
  52. const year = this.data.currentYear;
  53. const month = this.data.currentMonth - 1;
  54. // 获取当月第一天是星期几(调整为中文星期顺序:周一=0, 周二=1...周日=6)
  55. const firstDayWeek = new Date(year, month, 1).getDay();
  56. const firstDay = firstDayWeek === 0 ? 6 : firstDayWeek - 1; // 将周日(0)转为6,其他-1
  57. // 获取当月的总天数
  58. const totalDays = new Date(year, month + 1, 0).getDate();
  59. const days = [];
  60. const today = new Date();
  61. today.setHours(0, 0, 0, 0);
  62. // 计算开放日期范围(从今天开始的七天)
  63. const openStart = new Date(today);
  64. const openEnd = new Date(today);
  65. openEnd.setDate(today.getDate() + 6);
  66. openEnd.setHours(23, 59, 59, 999);
  67. // 解析停止开放的日期
  68. const stopDates = this.parseStopDates();
  69. // 添加空白占位符,不显示上个月日期
  70. for (let i = 0; i < firstDay; i++) {
  71. days.push({
  72. date: '',
  73. fullDate: null,
  74. isCurrentMonth: false,
  75. isEmpty: true, // 标记为空白
  76. isPast: true,
  77. isToday: false,
  78. isInOpenRange: false,
  79. status: ''
  80. });
  81. }
  82. // 添加当月的日期
  83. for (let i = 1; i <= totalDays; i++) {
  84. const date = new Date(year, month, i);
  85. const isToday = date.getDate() === today.getDate() &&
  86. date.getMonth() === today.getMonth() &&
  87. date.getFullYear() === today.getFullYear();
  88. const isPast = date < today;
  89. const isInOpenRange = this.isDateInOpenRange(date, openStart, openEnd);
  90. // 获取日期状态
  91. const status = this.getDateStatus(date, isPast, isToday, isInOpenRange, stopDates);
  92. // 添加到数组
  93. const dayData = {
  94. date: i,
  95. fullDate: date,
  96. isCurrentMonth: true,
  97. isEmpty: false,
  98. isPast,
  99. isToday,
  100. isInOpenRange,
  101. status
  102. };
  103. // 预计算CSS类名
  104. dayData.cssClass = this.calculateDayClass(dayData);
  105. days.push(dayData);
  106. }
  107. // 添加空白占位符填充剩余位置
  108. const totalCells = Math.ceil(days.length / 7) * 7; // 确保是7的倍数
  109. const remainingCells = totalCells - days.length;
  110. for (let i = 0; i < remainingCells; i++) {
  111. // 添加空白占位符
  112. const emptyDay = {
  113. date: '',
  114. fullDate: null,
  115. isCurrentMonth: false,
  116. isEmpty: true,
  117. isPast: false,
  118. isToday: false,
  119. isInOpenRange: false,
  120. status: ''
  121. };
  122. emptyDay.cssClass = 'empty-cell';
  123. days.push(emptyDay);
  124. }
  125. this.setData({
  126. daysInMonth: days,
  127. currentYearMonth: this.data.currentYear + '-' + this.data.currentMonth.toString().padStart(2, '0')
  128. });
  129. },
  130. // 判断日期是否在开放日期范围内
  131. isDateInOpenRange(date, start, end) {
  132. return date >= start && date <= end;
  133. },
  134. // 解析停止开放的日期
  135. parseStopDates() {
  136. if (!this.data.apiData || !this.data.apiData.stopDate || !this.data.apiData.stopDate.rtf) {
  137. return [];
  138. }
  139. const stopDateStr = this.data.apiData.stopDate.rtf;
  140. const dateStrings = stopDateStr.split(',');
  141. return dateStrings.map(dateStr => {
  142. const date = new Date(dateStr.trim());
  143. date.setHours(0, 0, 0, 0);
  144. return date;
  145. });
  146. },
  147. // 获取日期状态
  148. getDateStatus(date, isPast, isToday, isInOpenRange, stopDates) {
  149. // 已结束的日期
  150. if (isPast) {
  151. return '已结束';
  152. }
  153. // 检查是否在停止开放日期列表中
  154. const isStopDate = stopDates.some(stopDate =>
  155. date.getTime() === stopDate.getTime()
  156. );
  157. if (isStopDate) {
  158. return '未开放';
  159. }
  160. // 超过七天的日期显示未开放
  161. if (!isInOpenRange) {
  162. return '未开放';
  163. }
  164. // 根据API数据判断当天是否开放
  165. const dayOfWeek = date.getDay();
  166. const weekdayMap = {
  167. 0: 'sunday',
  168. 1: 'monday',
  169. 2: 'tuesday',
  170. 3: 'wednesday',
  171. 4: 'thursday',
  172. 5: 'friday',
  173. 6: 'saturday'
  174. };
  175. const weekdayKey = weekdayMap[dayOfWeek];
  176. const availableSlots = this.data.apiData && this.data.apiData.time ? this.data.apiData.time[weekdayKey] : 0;
  177. if (isToday) {
  178. return availableSlots > 0 ? '今天' : '已约满';
  179. } else {
  180. return availableSlots > 0 ? '已开放' : '已约满';
  181. }
  182. },
  183. // 计算日期单元格的CSS类(用于数据预处理)
  184. calculateDayClass(day) {
  185. // 如果是空白单元格,直接返回空白样式
  186. if (day.isEmpty) {
  187. return 'empty-cell';
  188. }
  189. const selectedDate = this.data.selectedDate ? new Date(this.data.selectedDate) : null;
  190. const isSelected = selectedDate && day.fullDate &&
  191. day.fullDate.getDate() === selectedDate.getDate() &&
  192. day.fullDate.getMonth() === selectedDate.getMonth() &&
  193. day.fullDate.getFullYear() === selectedDate.getFullYear();
  194. let classes = [];
  195. // 基础状态类
  196. if (!day.isCurrentMonth && !day.isEmpty) classes.push('other-month');
  197. if (day.isToday) classes.push('today'); // 今天总是添加today类,不管是否开放
  198. if (day.isPast) classes.push('past');
  199. // 可选择状态
  200. if (day.isInOpenRange && !day.isPast && day.status !== '已约满' && day.status !== '未开放') {
  201. classes.push('selectable');
  202. }
  203. // 选中状态 - 优先级最高
  204. if (isSelected && day.status !== '未开放') {
  205. classes.push('selected');
  206. }
  207. // 状态相关的样式类
  208. if (day.status === '已开放' || day.status === '今天') {
  209. classes.push('available');
  210. }
  211. if (day.status === '未开放') {
  212. classes.push('unavailable');
  213. }
  214. if (day.status === '已约满') {
  215. classes.push('full');
  216. }
  217. return classes.join(' ');
  218. },
  219. // 更新所有日期的CSS类(当选中状态改变时调用)
  220. updateDayClasses() {
  221. const updatedDays = this.data.daysInMonth.map(day => {
  222. return {
  223. ...day,
  224. cssClass: this.calculateDayClass(day)
  225. };
  226. });
  227. this.setData({
  228. daysInMonth: updatedDays
  229. });
  230. },
  231. // 选择日期
  232. selectDay(e) {
  233. const dayIndex = e.currentTarget.dataset.index;
  234. const day = this.data.daysInMonth[dayIndex];
  235. // 空白单元格不可点击
  236. if (day.isEmpty || !day.fullDate) {
  237. return;
  238. }
  239. // 只有开放日期范围内且未过期且未约满且不是未开放的日期才可选择
  240. if (day.isInOpenRange && !day.isPast && day.status !== '已约满' && day.status !== '未开放') {
  241. this.setData({
  242. selectedDate: day.fullDate
  243. });
  244. // 更新所有日期的CSS类以反映新的选中状态
  245. this.updateDayClasses();
  246. // 触发自定义事件,向父组件传递选择的日期
  247. this.triggerEvent('datechange', {
  248. date: day.fullDate,
  249. dateString: this.formatDate(day.fullDate)
  250. });
  251. console.log('选择的日期:', day.fullDate);
  252. } else {
  253. console.log('日期不可选择,状态:', day.status);
  254. }
  255. },
  256. // 上一个月
  257. prevMonth() {
  258. if (this.data.currentMonth === 1) {
  259. this.setData({
  260. currentYear: this.data.currentYear - 1,
  261. currentMonth: 12
  262. });
  263. } else {
  264. this.setData({
  265. currentMonth: this.data.currentMonth - 1
  266. });
  267. }
  268. this.initCalendar();
  269. },
  270. // 下一个月
  271. nextMonth() {
  272. if (this.data.currentMonth === 12) {
  273. this.setData({
  274. currentYear: this.data.currentYear + 1,
  275. currentMonth: 1
  276. });
  277. } else {
  278. this.setData({
  279. currentMonth: this.data.currentMonth + 1
  280. });
  281. }
  282. this.initCalendar();
  283. },
  284. // 初始化默认日期
  285. initDefaultDate() {
  286. const today = new Date();
  287. today.setHours(0, 0, 0, 0);
  288. // 计算开放日期范围(从今天开始的七天)
  289. const openStart = new Date(today);
  290. const openEnd = new Date(today);
  291. openEnd.setDate(today.getDate() + 6);
  292. openEnd.setHours(23, 59, 59, 999);
  293. const isInOpenRange = this.isDateInOpenRange(today, openStart, openEnd);
  294. const stopDates = this.parseStopDates();
  295. const todayStatus = this.getDateStatus(today, false, true, isInOpenRange, stopDates);
  296. console.log('今天状态检查:', {
  297. isInOpenRange,
  298. todayStatus,
  299. apiData: this.data.apiData
  300. });
  301. // 只有今天可以选择时才默认选中(今天状态为'今天'表示可约)
  302. if (isInOpenRange && (todayStatus === '今天' || todayStatus === '已开放')) {
  303. this.setData({
  304. selectedDate: today
  305. });
  306. // 更新日历显示以反映选中状态
  307. this.updateDayClasses();
  308. // 触发自定义事件,向父组件传递默认选择的日期
  309. this.triggerEvent('datechange', {
  310. date: today,
  311. dateString: this.formatDate(today)
  312. });
  313. console.log('默认选中今天:', today, '状态:', todayStatus);
  314. } else {
  315. // 今天不可选择时,确保selectedDate为null,不触发datechange事件
  316. this.setData({
  317. selectedDate: null
  318. });
  319. console.log('今天不可选择,不默认选中。状态:', todayStatus);
  320. }
  321. },
  322. // 格式化日期
  323. formatDate(date) {
  324. const year = date.getFullYear();
  325. const month = (date.getMonth() + 1).toString().padStart(2, '0');
  326. const day = date.getDate().toString().padStart(2, '0');
  327. return `${year}-${month}-${day}`;
  328. }
  329. }
  330. });