图书馆小程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

335 lines
8.2 KiB

  1. <template>
  2. <view class="booking-detail">
  3. <view class="section">
  4. <view class="section-title">日期选择</view>
  5. <view class="calendar">
  6. <view class="calendar-header">
  7. <text class="month">{{ currentYear }}{{ currentMonth }}</text>
  8. </view>
  9. <view class="calendar-weekdays">
  10. <text v-for="day in weekdays" :key="day" class="weekday">{{ day }}</text>
  11. </view>
  12. <view class="calendar-days">
  13. <view
  14. v-for="(day, index) in calendarDays"
  15. :key="index"
  16. class="day-item"
  17. :class="{
  18. 'other-month': !day.currentMonth,
  19. 'selected': day.date === selectedDate,
  20. 'disabled': !day.available,
  21. 'today': day.isToday
  22. }"
  23. @click="selectDate(day)"
  24. >
  25. <text class="day-num">{{ day.day }}</text>
  26. <text v-if="day.available && !day.isToday" class="available-tag">开放日</text>
  27. <text v-if="day.isToday" class="today-tag">今天</text>
  28. </view>
  29. </view>
  30. </view>
  31. </view>
  32. <view class="section">
  33. <view class="section-title">时间选择</view>
  34. <view class="time-slots">
  35. <view
  36. v-for="slot in timeSlots"
  37. :key="slot.id"
  38. class="time-slot"
  39. :class="{ 'selected': selectedTime === slot.time }"
  40. @click="selectTime(slot)"
  41. >
  42. <text class="time-text">{{ slot.time }}</text>
  43. <text v-if="slot.available" class="available-badge">可选</text>
  44. <text v-else class="unavailable-badge">已满</text>
  45. </view>
  46. </view>
  47. </view>
  48. <view class="bottom-bar">
  49. <button class="btn-primary" @click="confirmBooking">确认预约</button>
  50. </view>
  51. </view>
  52. </template>
  53. <script>
  54. export default {
  55. data() {
  56. return {
  57. currentYear: new Date().getFullYear(),
  58. currentMonth: new Date().getMonth() + 1,
  59. selectedDate: '',
  60. selectedTime: '',
  61. weekdays: ['日', '一', '二', '三', '四', '五', '六'],
  62. calendarDays: [],
  63. timeSlots: [
  64. { id: 1, time: '09:15-12:45', available: true },
  65. { id: 2, time: '13:00-16:30', available: false },
  66. { id: 3, time: '16:45-20:15', available: true }
  67. ]
  68. };
  69. },
  70. onLoad(options) {
  71. this.generateCalendar();
  72. },
  73. methods: {
  74. generateCalendar() {
  75. const year = this.currentYear;
  76. const month = this.currentMonth;
  77. const firstDay = new Date(year, month - 1, 1);
  78. const lastDay = new Date(year, month, 0);
  79. const daysInMonth = lastDay.getDate();
  80. const startWeekday = firstDay.getDay();
  81. const today = new Date();
  82. const todayStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
  83. const days = [];
  84. const prevMonthLastDay = new Date(year, month - 1, 0).getDate();
  85. for (let i = startWeekday - 1; i >= 0; i--) {
  86. days.push({
  87. day: prevMonthLastDay - i,
  88. date: '',
  89. currentMonth: false,
  90. available: false,
  91. isToday: false
  92. });
  93. }
  94. const todayDate = new Date();
  95. const availableDates = [];
  96. for (let i = 0; i < 7; i++) {
  97. const date = new Date(todayDate);
  98. date.setDate(todayDate.getDate() + i);
  99. const dateStr = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
  100. availableDates.push(dateStr);
  101. }
  102. for (let i = 1; i <= daysInMonth; i++) {
  103. const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(i).padStart(2, '0')}`;
  104. const isAvailable = availableDates.includes(dateStr);
  105. const isToday = dateStr === todayStr;
  106. days.push({
  107. day: i,
  108. date: dateStr,
  109. currentMonth: true,
  110. available: isAvailable,
  111. isToday: isToday
  112. });
  113. }
  114. const remainingDays = 42 - days.length;
  115. for (let i = 1; i <= remainingDays; i++) {
  116. days.push({
  117. day: i,
  118. date: '',
  119. currentMonth: false,
  120. available: false,
  121. isToday: false
  122. });
  123. }
  124. this.calendarDays = days;
  125. if (availableDates.length > 0) {
  126. this.selectedDate = availableDates[0];
  127. }
  128. },
  129. selectDate(day) {
  130. if (!day.available || !day.currentMonth) return;
  131. this.selectedDate = day.date;
  132. },
  133. selectTime(slot) {
  134. if (!slot.available) return;
  135. this.selectedTime = slot.time;
  136. },
  137. confirmBooking() {
  138. if (!this.selectedDate) {
  139. uni.showToast({ title: '请选择日期', icon: 'none' });
  140. return;
  141. }
  142. if (!this.selectedTime) {
  143. uni.showToast({ title: '请选择时间段', icon: 'none' });
  144. return;
  145. }
  146. uni.showLoading({ title: '预约中...' });
  147. setTimeout(() => {
  148. uni.hideLoading();
  149. uni.showModal({
  150. title: '预约成功',
  151. content: `您已成功预约\n日期:${this.selectedDate}\n时间:${this.selectedTime}`,
  152. showCancel: false,
  153. confirmText: '确定',
  154. success: () => {
  155. uni.navigateBack({ delta: 2 });
  156. }
  157. });
  158. }, 1500);
  159. }
  160. }
  161. };
  162. </script>
  163. <style lang="scss" scoped>
  164. .booking-detail {
  165. min-height: 100vh;
  166. background-color: #f5f5f5;
  167. padding-bottom: 70px;
  168. }
  169. .section {
  170. background-color: #fff;
  171. margin: 10px;
  172. border-radius: 12px;
  173. padding: 16px;
  174. }
  175. .section-title {
  176. font-size: 16px;
  177. font-weight: bold;
  178. color: #333;
  179. margin-bottom: 16px;
  180. }
  181. /* 日历 */
  182. .calendar-header {
  183. text-align: center;
  184. margin-bottom: 12px;
  185. }
  186. .month {
  187. font-size: 16px;
  188. color: #333;
  189. font-weight: bold;
  190. }
  191. .calendar-weekdays {
  192. display: flex;
  193. justify-content: space-between;
  194. margin-bottom: 10px;
  195. }
  196. .weekday {
  197. width: 14.28%;
  198. text-align: center;
  199. font-size: 13px;
  200. color: #999;
  201. }
  202. .calendar-days {
  203. display: flex;
  204. flex-wrap: wrap;
  205. }
  206. .day-item {
  207. width: 14.28%;
  208. aspect-ratio: 1;
  209. display: flex;
  210. flex-direction: column;
  211. align-items: center;
  212. justify-content: center;
  213. margin-bottom: 6px;
  214. border-radius: 6px;
  215. position: relative;
  216. }
  217. .day-item.other-month .day-num {
  218. color: #ccc;
  219. }
  220. .day-item.disabled .day-num {
  221. color: #ccc;
  222. }
  223. .day-item.selected {
  224. background-color: #01a4fe;
  225. }
  226. .day-item.selected .day-num {
  227. color: #fff;
  228. font-size: 15px;
  229. font-weight: bold;
  230. }
  231. .day-item.selected .available-tag,
  232. .day-item.selected .today-tag {
  233. color: rgba(255, 255, 255, 0.9);
  234. font-size: 10px;
  235. }
  236. .day-item.today:not(.selected) {
  237. background-color: #fff3cd;
  238. }
  239. .day-item.today:not(.selected) .day-num {
  240. color: #856404;
  241. }
  242. .day-num {
  243. font-size: 14px;
  244. color: #333;
  245. }
  246. .available-tag {
  247. font-size: 10px;
  248. color: #01a4fe;
  249. margin-top: 2px;
  250. }
  251. .today-tag {
  252. font-size: 10px;
  253. color: #856404;
  254. margin-top: 2px;
  255. }
  256. /* 时间选择 */
  257. .time-slots {
  258. display: flex;
  259. flex-wrap: wrap;
  260. gap: 10px;
  261. }
  262. .time-slot {
  263. width: calc(50% - 5px);
  264. display: flex;
  265. align-items: center;
  266. justify-content: space-between;
  267. padding: 12px;
  268. background-color: #f8f8f8;
  269. border-radius: 8px;
  270. border: 1px solid transparent;
  271. }
  272. .time-slot.selected {
  273. background-color: #e8f4fd;
  274. border-color: #01a4fe;
  275. }
  276. .time-text {
  277. font-size: 14px;
  278. color: #333;
  279. }
  280. .available-badge {
  281. font-size: 12px;
  282. color: #01a4fe;
  283. background-color: #e8f4fd;
  284. padding: 2px 8px;
  285. border-radius: 10px;
  286. }
  287. .unavailable-badge {
  288. font-size: 12px;
  289. color: #999;
  290. background-color: #f0f0f0;
  291. padding: 2px 8px;
  292. border-radius: 10px;
  293. }
  294. /* 底部按钮 */
  295. .bottom-bar {
  296. position: fixed;
  297. bottom: 0;
  298. left: 0;
  299. right: 0;
  300. padding: 10px;
  301. background-color: #fff;
  302. box-shadow: 0 -1px 5px rgba(0, 0, 0, 0.1);
  303. }
  304. .btn-primary {
  305. width: 100%;
  306. height: 44px;
  307. line-height: 44px;
  308. background-color: #01a4fe;
  309. color: #fff;
  310. border-radius: 22px;
  311. font-size: 15px;
  312. border: none;
  313. }
  314. .btn-primary::after {
  315. border: none;
  316. }
  317. </style>