appointment.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. import PageContainerBehavior from "../../../../core/behavior/page-container.behavior";
  2. import DictionariesBehavior from "../../../../core/behavior/dictionaries.behavior";
  3. import tickleBehavior from "../../../../core/behavior/tickle.behavior";
  4. import { patientOfflineTreatmentAppointmentMethod ,updateAppointmentMethod} from "../../request";
  5. // module/order/pages/appointment/appointment.ts
  6. Page({
  7. behaviors: [PageContainerBehavior, DictionariesBehavior, tickleBehavior],
  8. data: {
  9. goodsInfo: { appointmentTime: '' },
  10. dateList: [] as Array<{
  11. label: string;
  12. dateStr: string;
  13. date: string;
  14. isSelected: boolean;
  15. }>,
  16. timeSlots: [] as Array<{
  17. time: string;
  18. isSelected: boolean;
  19. isDisabled: boolean;
  20. }>,
  21. selectedDate: "",
  22. selectedTime: "",
  23. show: false,
  24. date: '',
  25. offlineId: "",
  26. // 是否为修改预约模式,以及原始预约时间(用于高亮和对比是否有改动)
  27. isEditMode: false,
  28. originalAppointmentTime: "",
  29. },
  30. onLoad(options: any) {
  31. const goodsInfoFromStorage = wx.getStorageSync('goodsInfo');
  32. let parsedGoodsInfo: any = null;
  33. if ( goodsInfoFromStorage) {
  34. try {
  35. parsedGoodsInfo = JSON.parse(goodsInfoFromStorage || '{}');
  36. } catch (e) {
  37. console.error("解析 goodsInfo 失败", e);
  38. }
  39. }
  40. const originalAppointmentTime =
  41. (options.arrangeTime && decodeURIComponent(options.arrangeTime)) ||
  42. parsedGoodsInfo?.arrangeTime ||
  43. parsedGoodsInfo?.appointmentTime ||
  44. "";
  45. const isEditMode = options.mode === "edit" || !!originalAppointmentTime;
  46. // 如果有原始预约时间,则围绕该日期初始化列表;否则从今天开始 5 天
  47. if (originalAppointmentTime) {
  48. const [datePart] = originalAppointmentTime.split(" ");
  49. const baseDate = datePart ? new Date(datePart) : new Date();
  50. this.initDateList(baseDate);
  51. } else {
  52. this.initDateList();
  53. }
  54. if (parsedGoodsInfo) {
  55. this.setData({
  56. goodsInfo: parsedGoodsInfo,
  57. offlineId: parsedGoodsInfo.offlineId || "",
  58. isEditMode,
  59. originalAppointmentTime,
  60. });
  61. } else {
  62. this.setData({
  63. isEditMode,
  64. originalAppointmentTime,
  65. });
  66. }
  67. // 修改预约场景:根据原始预约时间高亮默认选中项
  68. if (originalAppointmentTime) {
  69. this.highlightOriginalAppointmentTime(originalAppointmentTime);
  70. }
  71. },
  72. // 初始化日期列表
  73. initDateList(startDate?: Date) {
  74. const dateList: Array<{
  75. label: string;
  76. dateStr: string;
  77. date: string;
  78. isSelected: boolean;
  79. }> = [];
  80. const baseDate = startDate || new Date();
  81. // 设置时间为0点,避免时区问题
  82. baseDate.setHours(0, 0, 0, 0);
  83. const today = new Date();
  84. today.setHours(0, 0, 0, 0);
  85. const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
  86. for (let i = 0; i < 5; i++) {
  87. const date = new Date(baseDate);
  88. date.setDate(baseDate.getDate() + i);
  89. const month = date.getMonth() + 1;
  90. const day = date.getDate();
  91. const weekDay = weekDays[date.getDay()];
  92. // 判断相对今天的位置
  93. const diffDays = Math.floor((date.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
  94. let label = '';
  95. if (diffDays === 0) {
  96. label = '今天';
  97. } else if (diffDays === 1) {
  98. label = '明天';
  99. } else {
  100. label = weekDay;
  101. }
  102. const dateStr = `${month}月${day}日`;
  103. const dateValue = `${date.getFullYear()}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
  104. dateList.push({
  105. label,
  106. dateStr,
  107. date: dateValue,
  108. isSelected: i === 0, // 默认选中第一个
  109. });
  110. }
  111. this.setData({
  112. dateList,
  113. selectedDate: dateList[0].date, // 默认选中第一个日期
  114. });
  115. // 初始化时间列表,传入默认选中的日期
  116. this.initTimeSlots(dateList[0].date);
  117. },
  118. // 初始化时间列表 - 固定8:00-20:00
  119. // 可选传入 preSelectedTime,在初始化时高亮该时间段
  120. initTimeSlots(selectedDate?: string, preSelectedTime?: string) {
  121. const timeSlots: Array<{
  122. time: string;
  123. isSelected: boolean;
  124. isDisabled: boolean;
  125. }> = [];
  126. const now = new Date();
  127. const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
  128. const selectedDateObj = selectedDate ? new Date(selectedDate) : today;
  129. const selectedDateOnly = new Date(selectedDateObj.getFullYear(), selectedDateObj.getMonth(), selectedDateObj.getDate());
  130. // 判断选中的日期是否是今天
  131. const isToday = selectedDateOnly.getTime() === today.getTime();
  132. const currentHour = now.getHours();
  133. const currentMinute = now.getMinutes();
  134. // 从08:00到20:00,每30分钟一个时间段
  135. for (let hour = 8; hour <= 20; hour++) {
  136. for (let minute = 0; minute < 60; minute += 30) {
  137. if (timeSlots.length >= 25) break; // 限制为25个时间段
  138. const timeStr = `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`;
  139. // 如果是今天,需要判断时间是否已过去
  140. let isDisabled = false;
  141. if (isToday) {
  142. // 如果时间已过去,禁用
  143. if (hour < currentHour || (hour === currentHour && minute <= currentMinute)) {
  144. isDisabled = true;
  145. }
  146. }
  147. timeSlots.push({
  148. time: timeStr,
  149. isSelected: preSelectedTime ? timeStr === preSelectedTime : false,
  150. isDisabled,
  151. });
  152. }
  153. }
  154. this.setData({ timeSlots });
  155. },
  156. // 根据原始预约时间高亮日期和时间
  157. highlightOriginalAppointmentTime(appointmentTime: string) {
  158. if (!appointmentTime) return;
  159. const [rawDatePart, rawTimePart] = appointmentTime.split(" ");
  160. if (!rawDatePart || !rawTimePart) return;
  161. // 兼容不同格式:2025-03-13 / 2025/03/13 以及带秒的时间 14:30:00
  162. const datePart = rawDatePart.replace(/\//g, "-").substring(0, 10);
  163. const timePart = rawTimePart.substring(0, 5); // 只取 HH:mm
  164. // 高亮日期
  165. const dateList = this.data.dateList.map((item) => ({
  166. ...item,
  167. isSelected: item.date === datePart,
  168. }));
  169. // 如果当前 5 天中没有该日期,则直接返回(保持已有逻辑);更复杂的滚动可后续再优化
  170. const hasTargetDate = dateList.some((item) => item.isSelected);
  171. if (!hasTargetDate) {
  172. return;
  173. }
  174. this.setData({
  175. dateList,
  176. selectedDate: datePart,
  177. selectedTime: timePart,
  178. });
  179. // 按该日期重新初始化时间段,并选中对应时间
  180. this.initTimeSlots(datePart, timePart);
  181. },
  182. // 选择日期
  183. onDateSelect(e: any) {
  184. const index = e.currentTarget.dataset.index;
  185. const dateList = this.data.dateList.map((item, i) => ({
  186. ...item,
  187. isSelected: i === index,
  188. }));
  189. const selectedDate = dateList[index].date;
  190. // 重新初始化时间列表,根据选中的日期禁用已过去的时间
  191. this.initTimeSlots(selectedDate);
  192. this.setData({
  193. dateList,
  194. selectedDate,
  195. // 切换日期时清空已选时间
  196. selectedTime: "",
  197. });
  198. },
  199. // 选择时间
  200. onTimeSelect(e: any) {
  201. const index = e.currentTarget.dataset.index;
  202. const time = e.currentTarget.dataset.time;
  203. const timeSlot = this.data.timeSlots[index];
  204. // 如果时间段被禁用,不处理
  205. if (timeSlot && timeSlot.isDisabled) {
  206. return;
  207. }
  208. // 每次只能选择一个时间段,点击新的会取消之前的选择
  209. const timeSlots = this.data.timeSlots.map((item, i) => ({
  210. ...item,
  211. isSelected: i === index, // 只有当前点击的为选中状态
  212. }));
  213. this.setData({
  214. timeSlots,
  215. selectedTime: time,
  216. });
  217. },
  218. // 提交预约
  219. async onSubmit() {
  220. if (!this.data.selectedDate) {
  221. wx.showToast({
  222. title: "请选择日期",
  223. icon: "none",
  224. });
  225. return;
  226. }
  227. if (!this.data.selectedTime) {
  228. wx.showToast({
  229. title: "请选择时间",
  230. icon: "none",
  231. });
  232. return;
  233. }
  234. const appointmentTime = `${this.data.selectedDate} ${this.data.selectedTime}`;
  235. // 归一化原始预约时间为 "YYYY-MM-DD HH:mm" 用于对比
  236. let normalizedOriginal = "";
  237. if (this.data.originalAppointmentTime) {
  238. const [rawDatePart, rawTimePart] = this.data.originalAppointmentTime.split(" ");
  239. if (rawDatePart && rawTimePart) {
  240. const datePart = rawDatePart.replace(/\//g, "-").substring(0, 10);
  241. const timePart = rawTimePart.substring(0, 5); // HH:mm
  242. normalizedOriginal = `${datePart} ${timePart}`;
  243. }
  244. }
  245. // 修改预约模式下,如果时间未变化,则提示修改失败,不调接口
  246. if (this.data.isEditMode && normalizedOriginal && appointmentTime === normalizedOriginal) {
  247. wx.showToast({
  248. title: "预约时间未修改",
  249. icon: "none",
  250. });
  251. return;
  252. }
  253. this.setData({
  254. goodsInfo: {
  255. ...this.data.goodsInfo,
  256. appointmentTime: `${this.data.selectedDate} ${this.data.selectedTime}`,
  257. },
  258. });
  259. try {
  260. if (this.data.isEditMode) {
  261. await updateAppointmentMethod(this.data.offlineId, appointmentTime);
  262. } else {
  263. await patientOfflineTreatmentAppointmentMethod(
  264. this.data.offlineId,
  265. appointmentTime
  266. );
  267. }
  268. wx.showToast({
  269. title: '预约成功',
  270. icon: 'success',
  271. });
  272. setTimeout(() => {
  273. wx.redirectTo({
  274. url: `/module/order/pages/appointment-success/appointment-success?goodsInfo=${encodeURIComponent(JSON.stringify(this.data.goodsInfo))}`,
  275. });
  276. }, 1000);
  277. } catch (error: any) {
  278. wx.showToast({
  279. title: error.errMsg || "预约失败",
  280. icon: "none",
  281. });
  282. }
  283. },
  284. // 打开日历弹窗
  285. onOpenCalendar() {
  286. this.setData({ show: true });
  287. },
  288. // 关闭日历弹窗
  289. onClose() {
  290. this.setData({ show: false });
  291. },
  292. // 格式化日期
  293. formatDate(date: any) {
  294. date = new Date(date);
  295. return `${date.getMonth() + 1}/${date.getDate()}`;
  296. },
  297. // 日历弹窗选择日期(同时监听 change 和 select,双保险)
  298. _calendarLock: false,
  299. onCalendarSelect(event: any) {
  300. // 防止 change 和 select 同时触发导致重复执行
  301. if ((this as any)._calendarLock) return;
  302. (this as any)._calendarLock = true;
  303. setTimeout(() => { (this as any)._calendarLock = false; }, 300);
  304. const timestamp = event.detail.value;
  305. if (!timestamp) return;
  306. const dateObj = new Date(timestamp);
  307. dateObj.setHours(0, 0, 0, 0);
  308. const dateValue = `${dateObj.getFullYear()}-${String(dateObj.getMonth() + 1).padStart(2, '0')}-${String(dateObj.getDate()).padStart(2, '0')}`;
  309. // 关闭弹窗
  310. this.setData({ show: false });
  311. // 检查选择的日期是否在当前展示的5天中
  312. const existingDateIndex = this.data.dateList.findIndex(item => item.date === dateValue);
  313. if (existingDateIndex !== -1) {
  314. // 日期在已展示的5天中,更新选中状态
  315. const dateList = this.data.dateList.map((item, index) => ({
  316. ...item,
  317. isSelected: index === existingDateIndex,
  318. }));
  319. this.initTimeSlots(dateValue);
  320. this.setData({
  321. dateList,
  322. selectedDate: dateValue,
  323. selectedTime: "",
  324. });
  325. } else {
  326. // 日期不在已展示的5天中,重新生成日期列表
  327. this.initDateList(new Date(dateObj.getTime())); // 传副本,避免 initDateList 修改原对象
  328. this.setData({
  329. selectedDate: dateValue,
  330. selectedTime: "",
  331. });
  332. }
  333. },
  334. });