time-select.js 12 KB

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