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

387 lines
8.8 KiB

4 weeks ago
4 weeks ago
1 month ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
1 month ago
4 weeks ago
1 month ago
4 weeks ago
1 month ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
  1. <template>
  2. <view class="activity-list">
  3. <view class="tab-box">
  4. <view
  5. class="tab-item"
  6. :class="{ active: currentTab === 0 }"
  7. @click="switchTab(0)"
  8. >
  9. 未开始
  10. </view>
  11. <view
  12. class="tab-item"
  13. :class="{ active: currentTab === 1 }"
  14. @click="switchTab(1)"
  15. >
  16. 进行中
  17. </view>
  18. <view
  19. class="tab-item"
  20. :class="{ active: currentTab === 2 }"
  21. @click="switchTab(2)"
  22. >
  23. 已结束
  24. </view>
  25. </view>
  26. <scroll-view
  27. scroll-y
  28. @scrolltolower="onScrollLower"
  29. lower-threshold="150"
  30. class="scroll-view"
  31. >
  32. <view v-if="loading" class="loading-box">
  33. <text class="loading-text">加载中...</text>
  34. </view>
  35. <view
  36. class="activity-item"
  37. v-for="item in activityList"
  38. :key="item.id"
  39. >
  40. <view class="activity-info">
  41. <view class="title-row">
  42. <text class="title">{{ item.fondsName }}</text>
  43. </view>
  44. <view class="item-info">
  45. <text class="label">入馆日期</text>
  46. <text class="value">{{ item.visitDate }}</text>
  47. </view>
  48. <view class="item-info">
  49. <text class="label">入馆时间</text>
  50. <text class="value">
  51. {{ (item.visitTime + '-' + item.endTimeHm) === '00:00-23:59' ? '全天' : (item.visitTime + '-' + item.endTimeHm) }}
  52. </text>
  53. </view>
  54. <!-- <view class="item-info">
  55. <text class="label">所在馆</text>
  56. <text class="value">{{ item.fondsName }}</text>
  57. </view> -->
  58. <view class="btn-box" v-if="item.status === 0">
  59. <button class="activity-btn" type="primary" @click.stop="cancelAppoint(item)">
  60. 取消预约
  61. </button>
  62. </view>
  63. </view>
  64. </view>
  65. <view v-if="!loading && noMore && activityList.length > 0" class="nomore-box">
  66. <text class="nomore-text">没有更多数据了</text>
  67. </view>
  68. <view class="empty-box" v-if="!loading && activityList.length === 0">
  69. <uni-icons type="empty" size="80" color="#ccc"></uni-icons>
  70. <text class="empty-text">{{ getEmptyText() }}</text>
  71. </view>
  72. </scroll-view>
  73. </view>
  74. </template>
  75. <script>
  76. import { FetchMyReservation, FetchCancelReservation } from '@/api/reservation';
  77. import { getOpenId } from '@/utils/storage';
  78. import config from '@/utils/config';
  79. export default {
  80. data() {
  81. return {
  82. currentTab: 0,
  83. activityList: [],
  84. refreshing: false,
  85. loading: false,
  86. noMore: false,
  87. page: 0,
  88. pageSize: 10
  89. };
  90. },
  91. onLoad() {
  92. this.getActivityList(true);
  93. },
  94. onPullDownRefresh() {
  95. if (this.loading) {
  96. uni.stopPullDownRefresh()
  97. return
  98. }
  99. this.getActivityList(true).finally(() => {
  100. uni.stopPullDownRefresh()
  101. })
  102. },
  103. methods: {
  104. formatTsToDate(ts) {
  105. const d = new Date(ts);
  106. const y = d.getFullYear();
  107. const m = String(d.getMonth() + 1).padStart(2, '0');
  108. const day = String(d.getDate()).padStart(2, '0');
  109. return `${y}-${m}-${day}`;
  110. },
  111. formatTimeHm(timeStr) {
  112. if (!timeStr) return ''
  113. return timeStr.substring(0, 5)
  114. },
  115. switchTab(index) {
  116. this.currentTab = index;
  117. this.page = 0;
  118. this.noMore = false;
  119. this.activityList = [];
  120. this.getActivityList(true);
  121. },
  122. async fetchMyReservation() {
  123. const openId = await getOpenId();
  124. const res = await FetchMyReservation({
  125. libcode: config.LIB_CODE,
  126. openId: openId,
  127. page: this.page,
  128. size: this.pageSize,
  129. status: this.currentTab
  130. })
  131. console.log('我的预约', res)
  132. if (res.code === 200 && Array.isArray(res.data.content)) {
  133. const list = res.data.content.map(raw => {
  134. return {
  135. id: raw.id,
  136. fondsName: raw.fondsName || '',
  137. visitDate: this.formatTsToDate(raw.specificDate),
  138. visitTime: this.formatTimeHm(raw.startTime),
  139. endTimeHm: this.formatTimeHm(raw.endTime),
  140. status: Number(this.currentTab),
  141. startTimeRaw: raw.startTime,
  142. endTimeRaw: raw.endTime,
  143. specificDate: raw.specificDate
  144. }
  145. })
  146. // 分页判断(和活动预约逻辑保持一致)
  147. const currentPage = res.data.number ?? 0;
  148. const totalPage = res.data.totalPages ?? 0;
  149. return {
  150. list,
  151. noMore: currentPage >= totalPage - 1
  152. }
  153. }
  154. return { list: [], noMore: true }
  155. },
  156. async getActivityList(isRefresh = false) {
  157. if (this.loading) return;
  158. if (isRefresh) {
  159. this.page = 0;
  160. this.noMore = false;
  161. this.refreshing = true;
  162. } else {
  163. if (this.noMore) return;
  164. }
  165. this.loading = true;
  166. try {
  167. const { list, noMore } = await this.fetchMyReservation();
  168. this.noMore = noMore;
  169. if (isRefresh) {
  170. this.activityList = list;
  171. } else {
  172. this.activityList = [...this.activityList, ...list];
  173. }
  174. } catch (e) {
  175. console.error('获取我的预约异常', e)
  176. uni.showToast({ title: '获取预约列表失败', icon: 'none' })
  177. if (isRefresh) this.activityList = [];
  178. } finally {
  179. this.refreshing = false;
  180. this.loading = false;
  181. }
  182. },
  183. onScrollLower() {
  184. if (this.loading || this.noMore) return;
  185. this.page += 1;
  186. this.getActivityList(false);
  187. },
  188. async cancelAppoint(item) {
  189. uni.showModal({
  190. title: '提示',
  191. content: `确定要取消当前时间段的预约吗?`,
  192. success: async (res) => {
  193. if (res.confirm) {
  194. const res = await FetchCancelReservation({
  195. id: item.id
  196. })
  197. if (res.code === 200) {
  198. uni.showToast({ title: '取消成功', icon: 'success' });
  199. this.getActivityList(true);
  200. } else {
  201. uni.showToast({ title: res.msg || '取消预约失败', icon: 'none' })
  202. }
  203. }
  204. }
  205. })
  206. },
  207. getEmptyText() {
  208. const textMap = {
  209. 0: '未开始',
  210. 1: '进行中',
  211. 2: '已结束'
  212. }
  213. return `暂无${textMap[this.currentTab]}的预约~`
  214. }
  215. },
  216. };
  217. </script>
  218. <style lang="scss" scoped>
  219. .activity-list {
  220. padding: 0;
  221. height: 100vh;
  222. box-sizing: border-box;
  223. background-color: #f5f5f5;
  224. display: flex;
  225. flex-direction: column;
  226. }
  227. .tab-box {
  228. display: flex;
  229. background-color: #fff;
  230. margin-bottom: 10px;
  231. position: sticky;
  232. top: 0;
  233. z-index: 99;
  234. }
  235. .tab-item {
  236. flex: 1;
  237. text-align: center;
  238. padding: 12px 0;
  239. font-size: 15px;
  240. color: #666;
  241. position: relative;
  242. }
  243. .tab-item.active {
  244. color: #01a4fe;
  245. font-weight: bold;
  246. }
  247. .tab-item.active::after {
  248. content: '';
  249. position: absolute;
  250. width: 30px;
  251. height: 3px;
  252. background-color: #01a4fe;
  253. bottom: 0;
  254. left: 50%;
  255. transform: translateX(-50%);
  256. border-radius: 2px;
  257. }
  258. .scroll-view {
  259. flex: 1;
  260. height: 0;
  261. padding: 0 12px;
  262. box-sizing: border-box;
  263. }
  264. .loading-box {
  265. padding: 20px 0;
  266. text-align: center;
  267. }
  268. .loading-text {
  269. font-size: 14px;
  270. color: #999;
  271. }
  272. .nomore-box {
  273. padding: 15px 0;
  274. text-align: center;
  275. }
  276. .nomore-text {
  277. font-size: 13px;
  278. color: #999;
  279. }
  280. .empty-box {
  281. height: calc(100vh - 180px);
  282. display: flex;
  283. flex-direction: column;
  284. align-items: center;
  285. justify-content: center;
  286. }
  287. .empty-text {
  288. margin-top: 16px;
  289. font-size: 14px;
  290. color: #999;
  291. }
  292. .activity-item {
  293. background: #fff;
  294. border-radius: 12px;
  295. margin-bottom: 12px;
  296. overflow: hidden;
  297. box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
  298. cursor: pointer;
  299. transition: transform 0.2s;
  300. &:active {
  301. transform: scale(0.98);
  302. }
  303. }
  304. .activity-info {
  305. display: flex;
  306. flex-direction: column;
  307. align-items: flex-start;
  308. padding: 16px;
  309. .title {
  310. font-size: 16px;
  311. font-weight: bold;
  312. color: #222;
  313. margin-bottom: 12px;
  314. display: block;
  315. }
  316. .item-info {
  317. width: 100%;
  318. display: flex;
  319. justify-content: space-between;
  320. padding: 4px 0;
  321. font-size: 14px;
  322. .label {
  323. color: #666;
  324. width: 72px;
  325. }
  326. .value {
  327. color: #333;
  328. flex: 1;
  329. text-align: right;
  330. }
  331. }
  332. }
  333. .status-tag {
  334. padding: 2px 8px;
  335. border-radius: 4px;
  336. font-size: 12px;
  337. }
  338. .status-tag.status-0 {
  339. color: #01a4fe;
  340. background: #e6f7ff;
  341. }
  342. .status-tag.status-1 {
  343. color: #00b42a;
  344. background: #e6ffed;
  345. }
  346. .status-tag.status-2 {
  347. color: #999;
  348. background: #f5f5f5;
  349. }
  350. .btn-box {
  351. width: 100%;
  352. display: flex;
  353. justify-content: flex-end;
  354. margin-top: 10px;
  355. padding-top: 10px;
  356. border-top: 1px solid #e5e5e5;
  357. }
  358. .activity-btn {
  359. background-color: #01a4fe;
  360. font-size: 13px;
  361. border-radius: 6px;
  362. padding: 0 14px;
  363. height: 28px;
  364. line-height: 28px;
  365. }
  366. </style>