图书馆小程序
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.

420 lines
9.9 KiB

1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
1 month ago
  1. <template>
  2. <view class="booking-detail">
  3. <view class="section">
  4. <view class="calendar-wrap">
  5. <view class="calendar-top">
  6. <text class="calendar-title">选择入馆日期</text>
  7. <text class="calendar-month">{{ currentYear }}.{{ String(currentMonth).padStart(2,'0') }}</text>
  8. </view>
  9. <view class="calendar-days-grid">
  10. <view
  11. v-for="(day, index) in calendarDays"
  12. :key="index"
  13. class="day-card"
  14. :class="{
  15. 'card-selected': day.date === selectedDate,
  16. 'card-available': day.available,
  17. 'card-disabled': !day.available,
  18. 'card-today': day.isToday
  19. }"
  20. @click="selectDate(day)"
  21. >
  22. <!-- 今天左上角纯色块三角无文字 -->
  23. <view v-if="day.isToday" class="corner-block-today"></view>
  24. <!-- 如果是今天就显示今天否则显示星期 -->
  25. <text class="card-week">{{ day.isToday ? '今天' : day.weekText }}</text>
  26. <text class="card-num">{{ day.day }}</text>
  27. <text class="card-status">
  28. {{ day.available ? '可预约' : '' }}
  29. </text>
  30. </view>
  31. </view>
  32. </view>
  33. </view>
  34. <view class="section">
  35. <view class="section-title">时间选择</view>
  36. <view class="time-slots">
  37. <view
  38. v-for="slot in timeSlots"
  39. :key="slot.id"
  40. class="time-slot"
  41. :class="{ 'selected': selectedTime === slot.time }"
  42. @click="selectTime(slot)"
  43. >
  44. <text class="time-text">{{ slot.time }}</text>
  45. <text v-if="slot.available" class="available-badge">可选</text>
  46. <text v-else class="unavailable-badge">已满</text>
  47. </view>
  48. </view>
  49. </view>
  50. <view class="bottom-bar">
  51. <button class="btn-primary" @click="confirmBooking">确认预约</button>
  52. </view>
  53. </view>
  54. </template>
  55. <script>
  56. export default {
  57. data() {
  58. return {
  59. currentYear: new Date().getFullYear(),
  60. currentMonth: new Date().getMonth() + 1,
  61. selectedDate: '',
  62. selectedTime: '',
  63. calendarDays: [],
  64. timeSlots: [],
  65. reserveConfig: null
  66. };
  67. },
  68. onLoad(options) {
  69. this.reserveConfig = {
  70. "frequency": "15",
  71. "weekDays": [
  72. {
  73. "id": 1,
  74. "name": "周一",
  75. "checked": true,
  76. "times": [
  77. "00:00 - 23:59"
  78. ],
  79. "showPicker": false,
  80. "newTime": ""
  81. },
  82. {
  83. "id": 2,
  84. "name": "周二",
  85. "checked": false,
  86. "times": [],
  87. "showPicker": false,
  88. "newTime": ""
  89. },
  90. {
  91. "id": 3,
  92. "name": "周三",
  93. "checked": false,
  94. "times": [],
  95. "showPicker": false,
  96. "newTime": ""
  97. },
  98. {
  99. "id": 4,
  100. "name": "周四",
  101. "checked": false,
  102. "times": [],
  103. "showPicker": false,
  104. "newTime": ""
  105. },
  106. {
  107. "id": 5,
  108. "name": "周五",
  109. "checked": false,
  110. "times": [],
  111. "showPicker": false,
  112. "newTime": ""
  113. },
  114. {
  115. "id": 6,
  116. "name": "周六",
  117. "checked": false,
  118. "times": [],
  119. "showPicker": false,
  120. "newTime": ""
  121. },
  122. {
  123. "id": 7,
  124. "name": "周日",
  125. "checked": false,
  126. "showPicker": false,
  127. "newTime": ""
  128. }
  129. ]
  130. }
  131. this.generateCalendar();
  132. },
  133. methods: {
  134. getWeekNum(dateStr) {
  135. const d = new Date(dateStr);
  136. const day = d.getDay();
  137. return day === 0 ? 7 : day
  138. },
  139. getWeekText(weekNo){
  140. const map = {1:'周一',2:'周二',3:'周三',4:'周四',5:'周五',6:'周六',7:'周日'}
  141. return map[weekNo]
  142. },
  143. generateCalendar() {
  144. const today = new Date();
  145. const todayStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
  146. const freq = Number(this.reserveConfig.frequency);
  147. const allowDateList = [];
  148. for(let i = 0; i < freq; i++){
  149. const temp = new Date(today);
  150. temp.setDate(today.getDate() + i);
  151. const ds = `${temp.getFullYear()}-${String(temp.getMonth()+1).padStart(2,'0')}-${String(temp.getDate()).padStart(2,'0')}`
  152. allowDateList.push(ds)
  153. }
  154. const list = []
  155. allowDateList.forEach(dateStr=>{
  156. const weekNo = this.getWeekNum(dateStr)
  157. const weekCfg = this.reserveConfig.weekDays.find(w => w.id === weekNo)
  158. const available = !!weekCfg?.checked
  159. const d = new Date(dateStr)
  160. list.push({
  161. day: d.getDate(),
  162. date: dateStr,
  163. weekText: this.getWeekText(weekNo),
  164. available: available,
  165. isToday: dateStr === todayStr
  166. })
  167. })
  168. this.calendarDays = list
  169. const firstAvail = this.calendarDays.find(d => d.available);
  170. if(firstAvail){
  171. this.selectedDate = firstAvail.date
  172. this.refreshTimeSlots(this.selectedDate)
  173. }else{
  174. this.selectedDate = ''
  175. this.timeSlots = []
  176. }
  177. },
  178. refreshTimeSlots(dateStr){
  179. this.selectedTime = ''
  180. if(!dateStr) {
  181. this.timeSlots = []
  182. return
  183. }
  184. const weekNo = this.getWeekNum(dateStr)
  185. const weekCfg = this.reserveConfig.weekDays.find(w => w.id === weekNo)
  186. if(!weekCfg || !weekCfg.checked){
  187. this.timeSlots = []
  188. return
  189. }
  190. this.timeSlots = weekCfg.times.map((t, idx)=>{
  191. return {
  192. id: idx + 1,
  193. time: t,
  194. available: true
  195. }
  196. })
  197. },
  198. selectDate(day) {
  199. if (!day.available) return;
  200. this.selectedDate = day.date;
  201. this.refreshTimeSlots(day.date)
  202. },
  203. selectTime(slot) {
  204. if (!slot.available) return;
  205. this.selectedTime = slot.time;
  206. },
  207. confirmBooking() {
  208. if (!this.selectedDate) {
  209. uni.showToast({ title: '请选择日期', icon: 'none' });
  210. return;
  211. }
  212. if (!this.selectedTime) {
  213. uni.showToast({ title: '请选择时间段', icon: 'none' });
  214. return;
  215. }
  216. uni.showLoading({ title: '预约中...' });
  217. setTimeout(() => {
  218. uni.hideLoading();
  219. uni.showModal({
  220. title: '预约成功',
  221. content: `您已成功预约\n日期:${this.selectedDate}\n时间:${this.selectedTime}`,
  222. showCancel: false,
  223. confirmText: '确定',
  224. success: () => {
  225. uni.navigateBack({ delta: 2 });
  226. }
  227. });
  228. }, 1500);
  229. }
  230. }
  231. };
  232. </script>
  233. <style lang="scss" scoped>
  234. .booking-detail {
  235. min-height: 100vh;
  236. background-color: #f5f5f5;
  237. padding-bottom: 70px;
  238. }
  239. .section {
  240. background-color: #fff;
  241. margin: 10px;
  242. border-radius: 12px;
  243. padding: 16px;
  244. }
  245. .calendar-wrap{
  246. background:#ffffff;
  247. border-radius:16px;
  248. // padding:10px;
  249. }
  250. .calendar-top{
  251. display:flex;
  252. justify-content:space-between;
  253. align-items:center;
  254. margin-bottom:12px;
  255. }
  256. .calendar-title{
  257. font-size:17px;
  258. font-weight:bold;
  259. color:#222;
  260. }
  261. .calendar-month{
  262. font-size:16px;
  263. color:#333;
  264. font-weight:bold;
  265. }
  266. .calendar-days-grid{
  267. display:flex;
  268. flex-wrap:wrap;
  269. gap:8px;
  270. }
  271. .day-card{
  272. width: calc((100% - 48px) / 7);
  273. // aspect-ratio: 0.85;
  274. background:#f7f8fa;
  275. border-radius: 4px;
  276. padding: 4px 0;
  277. display:flex;
  278. flex-direction:column;
  279. align-items:center;
  280. justify-content:center;
  281. position:relative;
  282. overflow: hidden;
  283. }
  284. /* 左上角纯三角色块,无文字,模仿截图角标 */
  285. .corner-block-today{
  286. position:absolute;
  287. top:0;
  288. left:0;
  289. width: 0;
  290. height: 0;
  291. border-top: 12px solid #01a4fe;
  292. border-right: 12px solid transparent;
  293. border-bottom-left-radius: 0;
  294. }
  295. .card-week{
  296. font-size:12px;
  297. color:#666;
  298. margin-bottom:4px;
  299. }
  300. .card-num{
  301. font-size:18px;
  302. font-weight:bold;
  303. color:#333;
  304. }
  305. .card-status{
  306. font-size:11px;
  307. margin-top:4px;
  308. }
  309. /* 可预约 */
  310. .day-card.card-available{
  311. background:#e8f4fd;
  312. }
  313. .day-card.card-available .card-status{
  314. color:#01a4fe;
  315. }
  316. /* 闭馆(不可选) */
  317. .day-card.card-disabled{
  318. background:#f7f8fa;
  319. }
  320. .day-card.card-disabled .card-week,
  321. .day-card.card-disabled .card-num,
  322. .day-card.card-disabled .card-status{
  323. color:#999;
  324. }
  325. /* 选中状态 */
  326. .day-card.card-selected{
  327. background:#01a4fe;
  328. }
  329. .day-card.card-selected .card-week,
  330. .day-card.card-selected .card-num,
  331. .day-card.card-selected .card-status{
  332. color:#ffffff;
  333. }
  334. /* 时间选择 */
  335. .section-title {
  336. font-size: 16px;
  337. font-weight: bold;
  338. color: #333;
  339. margin-bottom: 16px;
  340. }
  341. .time-slots {
  342. display: flex;
  343. flex-wrap: wrap;
  344. gap: 10px;
  345. }
  346. .time-slot {
  347. width: calc(50% - 5px);
  348. display: flex;
  349. align-items: center;
  350. justify-content: space-between;
  351. padding: 12px;
  352. background-color: #f8f8f8;
  353. border-radius: 8px;
  354. border: 1px solid transparent;
  355. }
  356. .time-slot.selected {
  357. background-color: #e8f4fd;
  358. border-color: #01a4fe;
  359. }
  360. .time-text {
  361. font-size: 14px;
  362. color: #333;
  363. }
  364. .available-badge {
  365. font-size: 12px;
  366. color: #01a4fe;
  367. background-color: #e8f4fd;
  368. padding: 2px 8px;
  369. border-radius: 10px;
  370. }
  371. .unavailable-badge {
  372. font-size: 12px;
  373. color: #999;
  374. background-color: #f0f0f0;
  375. padding: 2px 8px;
  376. border-radius: 10px;
  377. }
  378. /* 底部按钮 */
  379. .bottom-bar {
  380. position: fixed;
  381. bottom: 0;
  382. left: 0;
  383. right: 0;
  384. padding: 10px;
  385. background-color: #fff;
  386. box-shadow: 0 -1px 5px rgba(0, 0, 0, 0.1);
  387. }
  388. .btn-primary {
  389. width: 100%;
  390. height: 44px;
  391. line-height: 44px;
  392. background-color: #01a4fe;
  393. color: #fff;
  394. border-radius: 22px;
  395. font-size: 15px;
  396. border: none;
  397. }
  398. .btn-primary::after {
  399. border: none;
  400. }
  401. </style>