浏览代码

Initial commit

wangfumin 2 月之前
当前提交
2da80c3e21
共有 98 个文件被更改,包括 9037 次插入0 次删除
  1. 105 0
      app.js
  2. 62 0
      app.json
  3. 10 0
      app.wxss
  4. 429 0
      components/active-time-select/index.js
  5. 4 0
      components/active-time-select/index.json
  6. 35 0
      components/active-time-select/index.wxml
  7. 198 0
      components/active-time-select/index.wxss
  8. 87 0
      components/loading/index.js
  9. 4 0
      components/loading/index.json
  10. 27 0
      components/loading/index.wxml
  11. 170 0
      components/loading/index.wxss
  12. 388 0
      components/time-select/time-select.js
  13. 4 0
      components/time-select/time-select.json
  14. 35 0
      components/time-select/time-select.wxml
  15. 198 0
      components/time-select/time-select.wxss
  16. 二进制
      imgs/icon_culture_active.png
  17. 二进制
      imgs/icon_culture_normal.png
  18. 二进制
      imgs/icon_exhibition_active.png
  19. 二进制
      imgs/icon_exhibition_normal.png
  20. 二进制
      imgs/icon_home_active.png
  21. 二进制
      imgs/icon_home_normal.png
  22. 二进制
      imgs/icon_user_active.png
  23. 二进制
      imgs/icon_user_normal.png
  24. 156 0
      pages/collection/index.js
  25. 6 0
      pages/collection/index.json
  26. 65 0
      pages/collection/index.wxml
  27. 212 0
      pages/collection/index.wxss
  28. 148 0
      pages/exhibition/index.js
  29. 8 0
      pages/exhibition/index.json
  30. 53 0
      pages/exhibition/index.wxml
  31. 173 0
      pages/exhibition/index.wxss
  32. 132 0
      pages/index/active-page/active-page.js
  33. 7 0
      pages/index/active-page/active-page.json
  34. 9 0
      pages/index/active-page/active-page.wxml
  35. 112 0
      pages/index/active-page/active-page.wxss
  36. 374 0
      pages/index/active-people/active-people.js
  37. 4 0
      pages/index/active-people/active-people.json
  38. 135 0
      pages/index/active-people/active-people.wxml
  39. 310 0
      pages/index/active-people/active-people.wxss
  40. 66 0
      pages/index/active-preview/active-preview.js
  41. 8 0
      pages/index/active-preview/active-preview.json
  42. 48 0
      pages/index/active-preview/active-preview.wxml
  43. 189 0
      pages/index/active-preview/active-preview.wxss
  44. 61 0
      pages/index/activity/activity.js
  45. 8 0
      pages/index/activity/activity.json
  46. 35 0
      pages/index/activity/activity.wxml
  47. 146 0
      pages/index/activity/activity.wxss
  48. 311 0
      pages/index/index.js
  49. 8 0
      pages/index/index.json
  50. 91 0
      pages/index/index.wxml
  51. 228 0
      pages/index/index.wxss
  52. 61 0
      pages/index/news/news.js
  53. 8 0
      pages/index/news/news.json
  54. 36 0
      pages/index/news/news.wxml
  55. 160 0
      pages/index/news/news.wxss
  56. 161 0
      pages/index/start-preview/start-preview.js
  57. 11 0
      pages/index/start-preview/start-preview.json
  58. 28 0
      pages/index/start-preview/start-preview.wxml
  59. 112 0
      pages/index/start-preview/start-preview.wxss
  60. 365 0
      pages/index/visit-people/visit-people.js
  61. 4 0
      pages/index/visit-people/visit-people.json
  62. 135 0
      pages/index/visit-people/visit-people.wxml
  63. 310 0
      pages/index/visit-people/visit-people.wxss
  64. 42 0
      pages/index/visit-preview/visit-preview.js
  65. 8 0
      pages/index/visit-preview/visit-preview.json
  66. 43 0
      pages/index/visit-preview/visit-preview.wxml
  67. 167 0
      pages/index/visit-preview/visit-preview.wxss
  68. 18 0
      pages/logs/logs.js
  69. 4 0
      pages/logs/logs.json
  70. 6 0
      pages/logs/logs.wxml
  71. 16 0
      pages/logs/logs.wxss
  72. 156 0
      pages/user/feedback/index.js
  73. 5 0
      pages/user/feedback/index.json
  74. 43 0
      pages/user/feedback/index.wxml
  75. 133 0
      pages/user/feedback/index.wxss
  76. 178 0
      pages/user/index.js
  77. 4 0
      pages/user/index.json
  78. 43 0
      pages/user/index.wxml
  79. 156 0
      pages/user/index.wxss
  80. 220 0
      pages/user/my-preview/index.js
  81. 7 0
      pages/user/my-preview/index.json
  82. 82 0
      pages/user/my-preview/index.wxml
  83. 217 0
      pages/user/my-preview/index.wxss
  84. 237 0
      pages/user/userList/index.js
  85. 9 0
      pages/user/userList/index.json
  86. 116 0
      pages/user/userList/index.wxml
  87. 218 0
      pages/user/userList/index.wxss
  88. 12 0
      pages/webview/index.js
  89. 3 0
      pages/webview/index.json
  90. 1 0
      pages/webview/index.wxml
  91. 0 0
      pages/webview/index.wxss
  92. 41 0
      project.config.json
  93. 24 0
      project.private.config.json
  94. 7 0
      sitemap.json
  95. 321 0
      utils/api.js
  96. 74 0
      utils/htmlProcessor.js
  97. 118 0
      utils/request.js
  98. 58 0
      utils/util.js

+ 105 - 0
app.js

@@ -0,0 +1,105 @@
+// app.js
+const { utils } = require('./utils/api.js');
+App({
+  onLaunch() {
+    // 展示本地存储能力
+    const logs = wx.getStorageSync('logs') || []
+    // logs.unshift(Date.now())
+    // wx.setStorageSync('logs', logs)
+
+    // 微信登录获取token
+    this.wxLogin()
+  },
+
+  // 微信登录方法
+  wxLogin() {
+    wx.login({
+      success: (res) => {
+        if (res.code) {
+          console.log('微信登录获取code成功:', res.code)
+          // 调用登录接口
+          this.callLoginApi(res.code)
+        } else {
+          console.error('微信登录失败:', res.errMsg)
+        }
+      },
+      fail: (error) => {
+        console.error('微信登录调用失败:', error)
+      }
+    })
+  },
+
+  // 调用登录接口
+  callLoginApi(code) {
+    wx.request({
+      url: 'https://sit-kelamayi.4dage.com/api/museum/wxMini/login',
+      method: 'GET',
+      data: {
+        code: code
+      },
+      header: {
+        'content-type': 'application/x-www-form-urlencoded'
+      },
+      success: (response) => {
+        console.log('登录接口调用成功:', response.data)
+        if (response.data && response.data.code === 0) {
+          // 存储基本用户信息(微信用户)
+          const basicUserInfo = {
+            nickName: '微信用户',
+            avatarUrl: 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0',
+            isWxUser: true
+          }
+          const token = response.data.data.token
+          this.globalData.userInfo = basicUserInfo
+          wx.setStorageSync('userInfo', basicUserInfo)
+          // 存储token到全局数据
+          this.globalData.token = token
+          // 也存储到本地存储
+          wx.setStorageSync('token', token)
+          console.log('token获取成功:', token)
+          // 登录成功后获取token
+          this.getAccessToken()
+        } else {
+          console.error('登录失败:', response.data.message || '未知错误')
+        }
+      },
+      fail: (error) => {
+        console.error('登录接口调用失败:', error)
+      }
+    })
+  },
+
+  // 获取访问token
+  getAccessToken() {
+    wx.request({
+      url: 'https://sit-kelamayi.4dage.com/api/museum/wxMini/getAccessToken',
+      method: 'GET',
+      header: {
+        'content-type': 'application/x-www-form-urlencoded'
+      },
+      success: (response) => {
+        console.log('获取accessToken接口调用成功:', response.data)
+        if (response.data && response.data.code === 0) {
+          const accessToken = response.data.data
+          // 存储token到全局数据
+          this.globalData.accessToken = accessToken
+          
+          // 也存储到本地存储
+          wx.setStorageSync('accessToken', accessToken)
+          
+          console.log('accessToken获取成功:', accessToken)
+        } else {
+          console.error('获取token失败:', response.data.message || '未知错误')
+        }
+      },
+      fail: (error) => {
+        console.error('获取token接口调用失败:', error)
+      }
+    })
+  },
+
+  globalData: {
+    userInfo: null,
+    token: null
+  }
+})

+ 62 - 0
app.json

@@ -0,0 +1,62 @@
+{
+  "pages": [
+    "pages/index/index",
+    "pages/collection/index",
+    "pages/exhibition/index",
+    "pages/user/index",
+    "pages/webview/index",
+    "pages/index/visit-preview/visit-preview",
+    "pages/index/start-preview/start-preview",
+    "pages/index/active-preview/active-preview",
+    "pages/index/active-page/active-page",
+    "pages/index/active-people/active-people",
+    "pages/index/visit-people/visit-people",
+    "pages/user/userList/index",
+    "pages/user/my-preview/index",
+    "pages/index/activity/activity",
+    "pages/index/news/news",
+    "pages/user/feedback/index"
+  ],
+  "window": {
+    "navigationBarTextStyle": "black",
+    "navigationBarTitleText": "克拉玛依市博物馆",
+    "navigationBarBackgroundColor": "#ffffff",
+    "enablePullDownRefresh": false
+  },
+  "tabBar": {
+    "color": "#412A12",
+    "selectedColor": "#B1967B",
+    "borderStyle": "black",
+    "backgroundColor": "#ffffff",
+    "list": [
+      {
+        "pagePath": "pages/index/index",
+        "iconPath": "imgs/icon_home_normal.png",
+        "selectedIconPath": "imgs/icon_home_active.png",
+        "text": "首页"
+      },
+      {
+        "pagePath": "pages/exhibition/index",
+        "iconPath": "imgs/icon_exhibition_normal.png",
+        "selectedIconPath": "imgs/icon_exhibition_active.png",
+        "text": "展览"
+      },
+      {
+        "pagePath": "pages/collection/index",
+        "iconPath": "imgs/icon_culture_normal.png",
+        "selectedIconPath": "imgs/icon_culture_active.png",
+        "text": "典藏"
+      },
+      {
+        "pagePath": "pages/user/index",
+        "iconPath": "imgs/icon_user_normal.png",
+        "selectedIconPath": "imgs/icon_user_active.png",
+        "text": "我的"
+      }
+    ]
+  },
+  "style": "v2",
+  "componentFramework": "glass-easel",
+  "sitemapLocation": "sitemap.json",
+  "lazyCodeLoading": "requiredComponents"
+}

+ 10 - 0
app.wxss

@@ -0,0 +1,10 @@
+/**app.wxss**/
+.container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: space-between;
+  padding: 200rpx 0;
+  box-sizing: border-box;
+} 

+ 429 - 0
components/active-time-select/index.js

@@ -0,0 +1,429 @@
+Component({
+  properties: {
+    // 活动数据
+    activityData: {
+      type: Object,
+      value: {},
+    }
+  },
+  observers: {
+    'activityData': function (newactivityData) {
+        if (newactivityData && Object.keys(newactivityData).length > 0) {
+            console.log('activityData有变化', newactivityData);
+            // 当监听到 activityData有变化时执行 
+            this.data.activityData = newactivityData;
+            this.adjustCalendarForActivity();
+          }
+      }
+  },
+  data: {
+    weekdays: ['一', '二', '三', '四', '五', '六', '日'],
+    currentYear: new Date().getFullYear(),
+    currentMonth: new Date().getMonth() + 1,
+    selectedDate: null,
+    daysInMonth: [],
+    activityData: {}
+  },
+
+  lifetimes: {
+    attached() {
+      // 不在这里初始化日历,等待activityData数据后再初始化
+    }
+  },
+
+  computed: {
+    currentYearMonth() {
+      return `${this.data.currentYear}-${this.data.currentMonth.toString().padStart(2, '0')}`;
+    }
+  },
+
+  methods: {
+
+
+    // 初始化日历
+    initCalendar() {
+      const year = this.data.currentYear;
+      const month = this.data.currentMonth - 1;
+      
+      // 获取当月第一天是星期几(调整为中文星期顺序:周一=0, 周二=1...周日=6)
+      const firstDayWeek = new Date(year, month, 1).getDay();
+      const firstDay = firstDayWeek === 0 ? 6 : firstDayWeek - 1; // 将周日(0)转为6,其他-1
+      
+      // 获取当月的总天数
+      const totalDays = new Date(year, month + 1, 0).getDate();
+      
+      const days = [];
+      const today = new Date();
+      today.setHours(0, 0, 0, 0);
+      
+      // 计算开放日期范围
+      let openStart, openEnd;
+      if (this.data.activityData && this.data.activityData.startTime && this.data.activityData.endTime) {
+        // 开放范围从活动开始时间和今天中的较大值开始,到活动结束时间结束
+        const activityStart = new Date(this.data.activityData.startTime);
+        activityStart.setHours(0, 0, 0, 0);
+        
+        openStart = activityStart >= today ? activityStart : today;
+        openEnd = new Date(this.data.activityData.endTime);
+        openEnd.setHours(23, 59, 59, 999);
+      } else {
+        // 没有活动数据时不显示任何可选日期
+        openStart = new Date(0);
+        openEnd = new Date(0);
+      }
+      
+      // 添加空白占位符,不显示上个月日期
+      for (let i = 0; i < firstDay; i++) {
+        days.push({
+          date: '',
+          fullDate: null,
+          isCurrentMonth: false,
+          isEmpty: true, // 标记为空白
+          isPast: true,
+          isToday: false,
+          isInOpenRange: false,
+          status: ''
+        });
+      }
+      
+      // 添加当月的日期
+      for (let i = 1; i <= totalDays; i++) {
+        const date = new Date(year, month, i);
+        const isToday = date.getDate() === today.getDate() && 
+                        date.getMonth() === today.getMonth() && 
+                        date.getFullYear() === today.getFullYear();
+        
+        const isPast = date < today;
+        const isInOpenRange = this.isDateInOpenRange(date, openStart, openEnd);
+        
+        // 获取日期状态
+        const status = this.getDateStatus(date, isPast, isToday, isInOpenRange);
+        
+        // 添加到数组
+        const dayData = {
+          date: i,
+          fullDate: date,
+          isCurrentMonth: true,
+          isEmpty: false,
+          isPast,
+          isToday,
+          isInOpenRange,
+          status
+        };
+        
+        // 预计算CSS类名
+        dayData.cssClass = this.calculateDayClass(dayData);
+        
+        days.push(dayData);
+      }
+      
+      // 添加空白占位符填充剩余位置
+      const totalCells = Math.ceil(days.length / 7) * 7; // 确保是7的倍数
+      const remainingCells = totalCells - days.length;
+      for (let i = 0; i < remainingCells; i++) {
+        // 添加空白占位符
+        const emptyDay = {
+          date: '',
+          fullDate: null,
+          isCurrentMonth: false,
+          isEmpty: true,
+          isPast: false,
+          isToday: false,
+          isInOpenRange: false,
+          status: ''
+        };
+        emptyDay.cssClass = 'empty-cell';
+        days.push(emptyDay);
+      }
+      
+      this.setData({
+        daysInMonth: days,
+        currentYearMonth: this.data.currentYear + '-' + this.data.currentMonth.toString().padStart(2, '0')
+      });
+    },
+
+    // 判断日期是否在开放日期范围内
+    isDateInOpenRange(date, start, end) {
+      return date >= start && date <= end;
+    },
+
+
+
+    // 获取日期状态
+    getDateStatus(date, isPast, isToday, isInOpenRange) {
+      // 已结束的日期
+      if (isPast) {
+        return '已结束';
+      }
+      
+      // 不在开放日期范围内显示未开放
+      if (!isInOpenRange) {
+        return '未开放';
+      }
+      
+      // 如果有活动数据,检查是否在活动开始时间之前
+      if (this.data.activityData && this.data.activityData.startTime) {
+        const activityStartDate = new Date(this.data.activityData.startTime);
+        activityStartDate.setHours(0, 0, 0, 0);
+        
+        // 在活动开始时间之前的日期显示为未开放
+        if (date < activityStartDate) {
+          return '未开放';
+        }
+        
+        // 根据活动数据的personCount判断
+        const personCount = this.data.activityData.personCount || 0;
+        if (isToday) {
+          return personCount > 0 ? '今天' : '已约满';
+        } else {
+          return personCount > 0 ? '已开放' : '已约满';
+        }
+      }
+      
+      // 没有活动数据时默认为未开放
+      return '未开放';
+    },
+
+    // 计算日期单元格的CSS类(用于数据预处理)
+    calculateDayClass(day) {
+      // 如果是空白单元格,直接返回空白样式
+      if (day.isEmpty) {
+        return 'empty-cell';
+      }
+      
+      const selectedDate = this.data.selectedDate ? new Date(this.data.selectedDate) : null;
+      const isSelected = selectedDate && day.fullDate &&
+                        day.fullDate.getDate() === selectedDate.getDate() && 
+                        day.fullDate.getMonth() === selectedDate.getMonth() && 
+                        day.fullDate.getFullYear() === selectedDate.getFullYear();
+      
+      let classes = [];
+      
+      // 基础状态类
+      if (!day.isCurrentMonth && !day.isEmpty) classes.push('other-month');
+      if (day.isToday) classes.push('today'); // 今天总是添加today类,不管是否开放
+      if (day.isPast) classes.push('past');
+      
+      // 可选择状态
+      if (day.isInOpenRange && !day.isPast && day.status !== '已约满' && day.status !== '未开放') {
+        classes.push('selectable');
+      }
+      
+      // 选中状态 - 优先级最高
+      if (isSelected && day.status !== '未开放') {
+        classes.push('selected');
+      }
+      
+      // 状态相关的样式类
+      if (day.status === '已开放' || day.status === '今天') {
+        classes.push('available');
+      }
+      if (day.status === '未开放') {
+        classes.push('unavailable');
+      }
+      if (day.status === '已约满') {
+        classes.push('full');
+      }
+      
+      return classes.join(' ');
+    },
+
+    // 更新所有日期的CSS类(当选中状态改变时调用)
+    updateDayClasses() {
+      const updatedDays = this.data.daysInMonth.map(day => {
+        return {
+          ...day,
+          cssClass: this.calculateDayClass(day)
+        };
+      });
+      
+      this.setData({
+        daysInMonth: updatedDays
+      });
+    },
+
+    // 选择日期
+    selectDay(e) {
+      const dayIndex = e.currentTarget.dataset.index;
+      const day = this.data.daysInMonth[dayIndex];
+      
+      // 空白单元格不可点击
+      if (day.isEmpty || !day.fullDate) {
+        return;
+      }
+      
+      // 只有开放日期范围内且未过期且未约满且不是未开放的日期才可选择
+      if (day.isInOpenRange && !day.isPast && day.status !== '已约满' && day.status !== '未开放') {
+        this.setData({
+          selectedDate: day.fullDate
+        });
+        
+        // 更新所有日期的CSS类以反映新的选中状态
+        this.updateDayClasses();
+        
+        // 触发自定义事件,向父组件传递选择的日期
+        this.triggerEvent('datechange', {
+          date: day.fullDate,
+          dateString: this.formatDate(day.fullDate)
+        });
+        
+        console.log('选择的日期:', day.fullDate);
+      } else {
+        console.log('日期不可选择,状态:', day.status);
+      }
+    },
+
+    // 上一个月
+    prevMonth() {
+      if (this.data.currentMonth === 1) {
+        this.setData({
+          currentYear: this.data.currentYear - 1,
+          currentMonth: 12
+        });
+      } else {
+        this.setData({
+          currentMonth: this.data.currentMonth - 1
+        });
+      }
+      this.initCalendar();
+    },
+
+    // 下一个月
+    nextMonth() {
+      if (this.data.currentMonth === 12) {
+        this.setData({
+          currentYear: this.data.currentYear + 1,
+          currentMonth: 1
+        });
+      } else {
+        this.setData({
+          currentMonth: this.data.currentMonth + 1
+        });
+      }
+      this.initCalendar();
+    },
+
+    // 初始化默认日期
+    initDefaultDate() {
+      const today = new Date();
+      today.setHours(0, 0, 0, 0);
+      
+      // 计算开放日期范围
+      let openStart, openEnd;
+      if (this.data.activityData && this.data.activityData.startTime && this.data.activityData.endTime) {
+        // 如果有活动数据,使用活动的时间范围
+        openStart = new Date(this.data.activityData.startTime);
+        openEnd = new Date(this.data.activityData.endTime);
+        openStart.setHours(0, 0, 0, 0);
+        openEnd.setHours(23, 59, 59, 999);
+      } else {
+        // 默认逻辑:从今天开始的七天
+        openStart = new Date(today);
+        openEnd = new Date(today);
+        openEnd.setDate(today.getDate() + 6);
+        openEnd.setHours(23, 59, 59, 999);
+      }
+      
+      const isInOpenRange = this.isDateInOpenRange(today, openStart, openEnd);
+      const todayStatus = this.getDateStatus(today, false, true, isInOpenRange);
+      
+      console.log('今天状态检查:', {
+        isInOpenRange,
+        todayStatus,
+        activityData: this.data.activityData
+      });
+      
+      // 只有今天可以选择时才默认选中(今天状态为'今天'表示可约)
+      if (isInOpenRange && (todayStatus === '今天' || todayStatus === '已开放')) {
+        this.setData({
+          selectedDate: today
+        });
+        
+        // 更新日历显示以反映选中状态
+        this.updateDayClasses();
+        
+        // 触发自定义事件,向父组件传递默认选择的日期
+        this.triggerEvent('datechange', {
+          date: today,
+          dateString: this.formatDate(today)
+        });
+        
+        console.log('默认选中今天:', today, '状态:', todayStatus);
+      } else {
+        // 今天不可选择时,如果有活动数据且活动开始时间在未来,选择活动开始时间
+        if (this.data.activityData && this.data.activityData.startTime) {
+          const activityStartDate = new Date(this.data.activityData.startTime);
+          activityStartDate.setHours(0, 0, 0, 0);
+          
+          // 检查活动开始时间是否在未来且在活动时间范围内
+          if (activityStartDate >= today && this.isDateInOpenRange(activityStartDate, openStart, openEnd)) {
+            const startDateStatus = this.getDateStatus(activityStartDate, false, false, true);
+            if (startDateStatus === '已开放') {
+              this.setData({
+                selectedDate: activityStartDate
+              });
+              
+              // 更新日历显示以反映选中状态
+              this.updateDayClasses();
+              
+              // 触发自定义事件,向父组件传递默认选择的日期
+              this.triggerEvent('datechange', {
+                date: activityStartDate,
+                dateString: this.formatDate(activityStartDate)
+              });
+              
+              console.log('默认选中活动开始时间:', activityStartDate);
+              return;
+            }
+          }
+        }
+        
+        // 今天不可选择且没有合适的活动开始时间时,确保selectedDate为null
+        this.setData({
+          selectedDate: null
+        });
+        console.log('今天不可选择,不默认选中。状态:', todayStatus);
+      }
+    },
+
+    // 格式化日期
+    formatDate(date) {
+      const year = date.getFullYear();
+      const month = (date.getMonth() + 1).toString().padStart(2, '0');
+      const day = date.getDate().toString().padStart(2, '0');
+      return `${year}-${month}-${day}`;
+    },
+
+
+
+    // 根据活动时间调整日历
+    adjustCalendarForActivity() {
+      if (!this.data.activityData) return;
+      
+      const { startTime, endTime } = this.data.activityData;
+      if (!startTime || !endTime) return;
+      
+      const startDate = new Date(startTime);
+      const endDate = new Date(endTime);
+      
+      // 如果开始时间不在当前月份,跳转到开始时间所在月份
+      const currentYear = this.data.currentYear;
+      const currentMonth = this.data.currentMonth;
+      const startYear = startDate.getFullYear();
+      const startMonth = startDate.getMonth() + 1;
+      
+      if (startYear !== currentYear || startMonth !== currentMonth) {
+        this.setData({
+          currentYear: startYear,
+          currentMonth: startMonth
+        }, () => {
+          this.initCalendar();
+          this.initDefaultDate();
+        });
+      } else {
+        this.initCalendar();
+        this.initDefaultDate();
+      }
+    }
+  }
+});

+ 4 - 0
components/active-time-select/index.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 35 - 0
components/active-time-select/index.wxml

@@ -0,0 +1,35 @@
+<view class="time-select-container">
+  <view class="time-select-header">
+    <view class="time-select-title">请选择参观日期</view>
+    <view class="month-selector">
+      <view class="arrow prev" bindtap="prevMonth">
+        <image src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/icon_left.png" class="arrow-icon"></image>
+      </view>
+      <view class="current-month">{{currentYearMonth}}</view>
+      <view class="arrow next" bindtap="nextMonth">
+        <image src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/icon_right.png" class="arrow-icon"></image>
+      </view>
+    </view>
+  </view>
+  
+  <view class="calendar">
+    <!-- 星期标题 -->
+    <view class="weekdays">
+      <view class="weekday" wx:for="{{weekdays}}" wx:key="index">{{item}}</view>
+    </view>
+    
+    <!-- 日期网格 -->
+    <view class="days-grid">
+      <view 
+        wx:for="{{daysInMonth}}" 
+        wx:key="index"
+        class="day-cell {{item.cssClass}}"
+        bindtap="selectDay"
+        data-index="{{index}}"
+      >
+        <view class="day-number" wx:if="{{!item.isEmpty}}">{{item.date}}</view>
+        <view class="day-status" wx:if="{{!item.isEmpty}}">{{item.status}}</view>
+      </view>
+    </view>
+  </view>
+</view>

+ 198 - 0
components/active-time-select/index.wxss

@@ -0,0 +1,198 @@
+.time-select-container {
+  width: 100%;
+  max-width: 100%;
+  border-radius: 16rpx;
+  font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
+  overflow-x: hidden;
+}
+
+.time-select-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 30rpx;
+}
+
+.time-select-title {
+  font-size: 32rpx;
+  color: #584735;
+  font-weight: bold;
+}
+
+.month-selector {
+  display: flex;
+  align-items: center;
+  color: #94765A;
+}
+
+.current-month {
+  margin: 0 20rpx;
+  font-size: 32rpx;
+  font-weight: 500;
+}
+
+.arrow {
+  font-size: 36rpx;
+  width: 48rpx;
+  height: 48rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #8B5D3B;
+}
+
+.arrow-icon {
+  width: 48rpx;
+  height: 48rpx;
+}
+
+.weekdays {
+  display: grid;
+  grid-template-columns: repeat(7, 1fr);
+  text-align: center;
+  margin-bottom: 20rpx;
+  width: 100%;
+  max-width: 100%;
+  box-sizing: border-box;
+}
+
+.weekday {
+  font-size: 28rpx;
+  color: #584735;
+  font-weight: bold;
+  padding: 10rpx 4rpx;
+  text-align: center;
+  min-width: 0;
+  box-sizing: border-box;
+}
+
+.days-grid {
+  display: grid;
+  grid-template-columns: repeat(7, 1fr);
+  gap: 4rpx;
+  width: 100%;
+  max-width: 100%;
+  box-sizing: border-box;
+}
+
+.day-cell {
+  aspect-ratio: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  border-radius: 8rpx;
+  position: relative;
+  padding: 6rpx 2rpx;
+  min-width: 0;
+  box-sizing: border-box;
+  overflow: hidden;
+  transition: all 0.2s ease;
+}
+
+.day-number {
+  font-size: 28rpx;
+  font-weight: 500;
+  margin-bottom: 2rpx;
+  color: #584735;
+  text-align: center;
+  line-height: 1.2;
+}
+
+.day-status {
+  font-size: 24rpx;
+  text-align: center;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 100%;
+  line-height: 1.2;
+  padding: 0 2rpx;
+}
+
+.day-cell.other-month {
+  color: #ccc;
+  display: none;
+}
+
+.day-cell.past {
+  color: #999;
+}
+
+/* 可用日期样式 */
+.day-cell.available .day-number,
+.day-cell.available .day-status {
+  color: #8B5D3B;
+}
+
+/* 今天的特殊样式 */
+.day-cell.today .day-number {
+  color: #8B5D3B;
+  font-weight: bold;
+}
+
+/* 不可用日期样式 */
+.day-cell.unavailable .day-status {
+  color: rgba(88, 71, 53, 0.5);
+}
+
+/* 已约满日期样式 */
+.day-cell.full .day-status {
+  color: #B1967B;
+}
+
+/* 选中状态样式 - 最高优先级 */
+.day-cell.selected {
+  background: #B1967B !important;
+  transform: scale(1.08);
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  z-index: 10;
+  position: relative;
+}
+
+.day-cell.selected .day-number {
+  color: #fff !important;
+  font-weight: bold;
+}
+
+.day-cell.selected .day-status {
+  color: #fff !important;
+  font-weight: 500;
+}
+
+/* 可选择日期的悬停效果 */
+.day-cell.selectable:not(.selected) {
+  transition: all 0.2s ease;
+  cursor: pointer;
+}
+
+.day-cell.selectable:not(.selected):hover {
+  background-color: rgba(139, 93, 59, 0.2);
+  transform: scale(1.02);
+}
+
+/* 选中动画效果 */
+@keyframes selectAnimation {
+  0% {
+    transform: scale(1);
+  }
+  50% {
+    transform: scale(1.12);
+  }
+  100% {
+    transform: scale(1.08);
+  }
+}
+
+.day-cell.selected {
+  animation: selectAnimation 0.3s ease-out;
+}
+
+
+
+/* 空白单元格样式 */
+.day-cell.empty-cell {
+  background: transparent;
+  cursor: default;
+  pointer-events: none;
+}

+ 87 - 0
components/loading/index.js

@@ -0,0 +1,87 @@
+// components/loading/index.js
+Component({
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    progress: 0,
+    progressText: 0,
+    showProgressBar: true,
+    showLogo: false,
+    showExploreButton: false
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    // 点击"开始探索"按钮
+    goToIndexPage() {
+      console.log(111111)
+      // 触发自定义事件,通知父组件
+      this.triggerEvent('startexplore');
+      
+      // 设置已访问标记
+      wx.setStorageSync('hasVisited', true);
+    },
+
+    // 开始进度条动画
+    startProgress() {
+      const duration = 3000; // 3秒
+      const interval = 30; // 每30ms更新一次
+      const steps = duration / interval;
+      const increment = 100 / steps;
+
+      const timer = setInterval(() => {
+        let currentProgress = this.data.progress + increment;
+        
+        if (currentProgress >= 100) {
+          clearInterval(timer);
+          currentProgress = 100;
+          
+          this.setData({
+            progress: currentProgress,
+            progressText: Math.floor(currentProgress)
+          });
+
+          // 进度条完成后,延迟显示logo
+          setTimeout(() => {
+            this.setData({
+              showProgressBar: false,
+              showLogo: true
+            });
+
+            // logo显示2秒后,显示"开始探索"按钮
+            setTimeout(() => {
+              this.setData({
+                showExploreButton: true
+              });
+            }, 2000);
+          }, 500);
+        } else {
+          this.setData({
+            progress: currentProgress,
+            progressText: Math.floor(currentProgress)
+          });
+        }
+      }, interval);
+    }
+  },
+
+  /**
+   * 组件生命周期
+   */
+  lifetimes: {
+    attached() {
+      // 组件实例进入页面节点树时执行
+      this.startProgress();
+    }
+  }
+});

+ 4 - 0
components/loading/index.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 27 - 0
components/loading/index.wxml

@@ -0,0 +1,27 @@
+<!--components/loading/index.wxml-->
+<view class="loading-container" bindtap="{{showExploreButton ? 'goToIndexPage' : ''}}">
+  <!-- 进度条加载页面 -->
+  <view class="car-loading" wx:if="{{showProgressBar}}">
+    <view class="car-container">
+      <view class="car"></view>
+    </view>
+
+    <view class="progress-container">
+      <image src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/loading/process.png" class="progress-bg" mode="widthFix" />
+      <view class="custom-progress-bar">
+        <view class="progress-fill" style="width: {{progress}}%"></view>
+      </view>
+      <view class="progress-text">{{progress}}%</view>
+    </view>
+  </view>
+  
+  <!-- Logo加载页面 -->
+  <view wx:if="{{showLogo}}" class="logo-container">
+    <view class="logo"></view>
+    <view wx:if="{{showExploreButton}}" class="explore-button" bindtap="goToIndexPage">
+      <view class="explore-line-left"></view>
+      开始探索
+      <view class="explore-line-right"></view>
+    </view>
+  </view>
+</view>

+ 170 - 0
components/loading/index.wxss

@@ -0,0 +1,170 @@
+/* components/loading/index.wxss */
+.loading-container {
+  width: 100%;
+  height: 100vh;
+  overflow: hidden;
+  background: url('https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/karamay/loading-bg.png') no-repeat center center;
+  background-size: cover;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  position: relative;
+}
+
+.car-loading {
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  position: relative;
+}
+
+.car-container {
+  width: 100%;
+  height: 100px;
+  position: relative;
+  display: flex;
+  justify-content: center;
+}
+
+.car {
+  width: 150px;
+  height: 132px;
+  background: url('https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/karamay/carLoading.png') 0 0;
+  position: relative;
+  bottom: 0;
+  background-size: 12500% 100%;
+  animation: car 3s steps(10) infinite;
+}
+
+@keyframes car {
+  from {
+    background-position: 0 0;
+  }
+  to {
+    background-position: -12500% 0;
+  }
+}
+
+.progress-container {
+  width: 80%;
+  max-width: 500px;
+  position: relative;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.progress-bg {
+  width: 100%;
+  height: auto;
+}
+
+.custom-progress-bar {
+  position: absolute;
+  top: 22%;
+  left: 22%;
+  transform: translateY(-50%);
+  width: 78%;
+  height: 16px;
+  padding: 0 10px;
+  box-sizing: border-box;
+  background-color: transparent;
+}
+
+.progress-fill {
+  height: 100%;
+  border-radius: 8px;
+  background: linear-gradient(to right,
+      rgba(230, 162, 60, 0) 0%,
+      rgba(230, 162, 60, 0) 20%,
+      rgba(230, 162, 60, 0.5) 60%,
+      rgba(230, 162, 60, 1) 100%);
+  transition: width 0.3s ease;
+}
+
+.progress-text {
+  margin-top: 10px;
+  color: #fff;
+  font-size: 18px;
+  font-weight: bold;
+}
+
+/* Logo页面样式 */
+.logo-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+}
+
+.logo {
+  width: 300px;
+  height: 300px;
+  background: url('https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/karamay/logo.png') 0 0;
+  position: relative;
+  bottom: 0;
+  background-size: 2400% 100%;
+  animation: sprite 3s steps(23) 1 forwards;
+}
+
+@keyframes sprite {
+  from {
+    background-position: 0 0;
+  }
+  to {
+    background-position: -2300% 0;
+  }
+}
+
+.explore-button {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: auto;
+  height: 40px;
+  line-height: 40px;
+  text-align: center;
+  color: #ECD6A4;
+  font-size: 16px;
+  background-color: transparent;
+  margin-top: 100px;
+  animation: buttonFadeIn 1s ease-in-out;
+  transition: all 0.3s ease;
+  position: relative;
+  border: none;
+}
+
+.explore-line-left {
+  width: 125px;
+  height: 1px;
+  margin-right: 10px;
+  background: linear-gradient(90deg, rgba(236, 214, 164, 0) 0%, #ECD6A4 100%);
+  border-radius: 0px 0px 0px 0px;
+  opacity: 0.5;
+}
+
+.explore-line-right {
+  width: 125px;
+  height: 1px;
+  background: linear-gradient(90deg, #ECD6A4 0%, rgba(236, 214, 164, 0) 100%);
+  border-radius: 0px 0px 0px 0px;
+  opacity: 0.5;
+  margin-left: 10px;
+}
+
+@keyframes buttonFadeIn {
+  from {
+    opacity: 0;
+    transform: translateY(20px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}

+ 388 - 0
components/time-select/time-select.js

@@ -0,0 +1,388 @@
+Component({
+  properties: {
+    // 可以接收外部传入的属性
+  },
+
+  data: {
+    weekdays: ['一', '二', '三', '四', '五', '六', '日'],
+    currentYear: new Date().getFullYear(),
+    currentMonth: new Date().getMonth() + 1,
+    selectedDate: null,
+    daysInMonth: [],
+    apiData: null // 存储API返回的数据
+  },
+
+  lifetimes: {
+    attached() {
+      this.loadAppointmentSlots();
+    }
+  },
+
+  computed: {
+    currentYearMonth() {
+      return `${this.data.currentYear}-${this.data.currentMonth.toString().padStart(2, '0')}`;
+    }
+  },
+
+  methods: {
+    // 加载预约时段数据
+    loadAppointmentSlots() {
+      // 这里应该调用实际的API接口
+      // 暂时使用模拟数据
+      const mockApiData = {
+        time: {
+          monday: 385,
+          tuesday: 250,
+          wednesday: 250,
+          thursday: 250,
+          friday: 250,
+          saturday: 250,
+          sunday: 250
+        },
+        stopDate: {
+          rtf: "2025-06-08,2025-06-14"
+        }
+      };
+      
+      this.setData({
+        apiData: mockApiData
+      }, () => {
+        // 确保apiData设置完成后再初始化日历和默认日期
+        this.initCalendar();
+        this.initDefaultDate();
+      });
+    },
+
+    // 初始化日历
+    initCalendar() {
+      const year = this.data.currentYear;
+      const month = this.data.currentMonth - 1;
+      
+      // 获取当月第一天是星期几(调整为中文星期顺序:周一=0, 周二=1...周日=6)
+      const firstDayWeek = new Date(year, month, 1).getDay();
+      const firstDay = firstDayWeek === 0 ? 6 : firstDayWeek - 1; // 将周日(0)转为6,其他-1
+      
+      // 获取当月的总天数
+      const totalDays = new Date(year, month + 1, 0).getDate();
+      
+      const days = [];
+      const today = new Date();
+      today.setHours(0, 0, 0, 0);
+      
+      // 计算开放日期范围(从今天开始的七天)
+      const openStart = new Date(today);
+      const openEnd = new Date(today);
+      openEnd.setDate(today.getDate() + 6);
+      openEnd.setHours(23, 59, 59, 999);
+      
+      // 解析停止开放的日期
+      const stopDates = this.parseStopDates();
+      
+      // 添加空白占位符,不显示上个月日期
+      for (let i = 0; i < firstDay; i++) {
+        days.push({
+          date: '',
+          fullDate: null,
+          isCurrentMonth: false,
+          isEmpty: true, // 标记为空白
+          isPast: true,
+          isToday: false,
+          isInOpenRange: false,
+          status: ''
+        });
+      }
+      
+      // 添加当月的日期
+      for (let i = 1; i <= totalDays; i++) {
+        const date = new Date(year, month, i);
+        const isToday = date.getDate() === today.getDate() && 
+                        date.getMonth() === today.getMonth() && 
+                        date.getFullYear() === today.getFullYear();
+        
+        const isPast = date < today;
+        const isInOpenRange = this.isDateInOpenRange(date, openStart, openEnd);
+        
+        // 获取日期状态
+        const status = this.getDateStatus(date, isPast, isToday, isInOpenRange, stopDates);
+        
+        // 添加到数组
+        const dayData = {
+          date: i,
+          fullDate: date,
+          isCurrentMonth: true,
+          isEmpty: false,
+          isPast,
+          isToday,
+          isInOpenRange,
+          status
+        };
+        
+        // 预计算CSS类名
+        dayData.cssClass = this.calculateDayClass(dayData);
+        
+        days.push(dayData);
+      }
+      
+      // 添加空白占位符填充剩余位置
+      const totalCells = Math.ceil(days.length / 7) * 7; // 确保是7的倍数
+      const remainingCells = totalCells - days.length;
+      for (let i = 0; i < remainingCells; i++) {
+        // 添加空白占位符
+        const emptyDay = {
+          date: '',
+          fullDate: null,
+          isCurrentMonth: false,
+          isEmpty: true,
+          isPast: false,
+          isToday: false,
+          isInOpenRange: false,
+          status: ''
+        };
+        emptyDay.cssClass = 'empty-cell';
+        days.push(emptyDay);
+      }
+      
+      this.setData({
+        daysInMonth: days,
+        currentYearMonth: this.data.currentYear + '-' + this.data.currentMonth.toString().padStart(2, '0')
+      });
+    },
+
+    // 判断日期是否在开放日期范围内
+    isDateInOpenRange(date, start, end) {
+      return date >= start && date <= end;
+    },
+
+    // 解析停止开放的日期
+    parseStopDates() {
+      if (!this.data.apiData || !this.data.apiData.stopDate || !this.data.apiData.stopDate.rtf) {
+        return [];
+      }
+      
+      const stopDateStr = this.data.apiData.stopDate.rtf;
+      const dateStrings = stopDateStr.split(',');
+      
+      return dateStrings.map(dateStr => {
+        const date = new Date(dateStr.trim());
+        date.setHours(0, 0, 0, 0);
+        return date;
+      });
+    },
+
+    // 获取日期状态
+    getDateStatus(date, isPast, isToday, isInOpenRange, stopDates) {
+      // 已结束的日期
+      if (isPast) {
+        return '已结束';
+      }
+      
+      // 检查是否在停止开放日期列表中
+      const isStopDate = stopDates.some(stopDate => 
+        date.getTime() === stopDate.getTime()
+      );
+      
+      if (isStopDate) {
+        return '未开放';
+      }
+      
+      // 超过七天的日期显示未开放
+      if (!isInOpenRange) {
+        return '未开放';
+      }
+      
+      // 根据API数据判断当天是否开放
+      const dayOfWeek = date.getDay();
+      const weekdayMap = {
+        0: 'sunday',
+        1: 'monday', 
+        2: 'tuesday',
+        3: 'wednesday',
+        4: 'thursday',
+        5: 'friday',
+        6: 'saturday'
+      };
+      
+      const weekdayKey = weekdayMap[dayOfWeek];
+      const availableSlots = this.data.apiData && this.data.apiData.time ? this.data.apiData.time[weekdayKey] : 0;
+      
+      if (isToday) {
+        return availableSlots > 0 ? '今天' : '已约满';
+      } else {
+        return availableSlots > 0 ? '已开放' : '已约满';
+      }
+    },
+
+    // 计算日期单元格的CSS类(用于数据预处理)
+    calculateDayClass(day) {
+      // 如果是空白单元格,直接返回空白样式
+      if (day.isEmpty) {
+        return 'empty-cell';
+      }
+      
+      const selectedDate = this.data.selectedDate ? new Date(this.data.selectedDate) : null;
+      const isSelected = selectedDate && day.fullDate &&
+                        day.fullDate.getDate() === selectedDate.getDate() && 
+                        day.fullDate.getMonth() === selectedDate.getMonth() && 
+                        day.fullDate.getFullYear() === selectedDate.getFullYear();
+      
+      let classes = [];
+      
+      // 基础状态类
+      if (!day.isCurrentMonth && !day.isEmpty) classes.push('other-month');
+      if (day.isToday) classes.push('today'); // 今天总是添加today类,不管是否开放
+      if (day.isPast) classes.push('past');
+      
+      // 可选择状态
+      if (day.isInOpenRange && !day.isPast && day.status !== '已约满' && day.status !== '未开放') {
+        classes.push('selectable');
+      }
+      
+      // 选中状态 - 优先级最高
+      if (isSelected && day.status !== '未开放') {
+        classes.push('selected');
+      }
+      
+      // 状态相关的样式类
+      if (day.status === '已开放' || day.status === '今天') {
+        classes.push('available');
+      }
+      if (day.status === '未开放') {
+        classes.push('unavailable');
+      }
+      if (day.status === '已约满') {
+        classes.push('full');
+      }
+      
+      return classes.join(' ');
+    },
+
+    // 更新所有日期的CSS类(当选中状态改变时调用)
+    updateDayClasses() {
+      const updatedDays = this.data.daysInMonth.map(day => {
+        return {
+          ...day,
+          cssClass: this.calculateDayClass(day)
+        };
+      });
+      
+      this.setData({
+        daysInMonth: updatedDays
+      });
+    },
+
+    // 选择日期
+    selectDay(e) {
+      const dayIndex = e.currentTarget.dataset.index;
+      const day = this.data.daysInMonth[dayIndex];
+      
+      // 空白单元格不可点击
+      if (day.isEmpty || !day.fullDate) {
+        return;
+      }
+      
+      // 只有开放日期范围内且未过期且未约满且不是未开放的日期才可选择
+      if (day.isInOpenRange && !day.isPast && day.status !== '已约满' && day.status !== '未开放') {
+        this.setData({
+          selectedDate: day.fullDate
+        });
+        
+        // 更新所有日期的CSS类以反映新的选中状态
+        this.updateDayClasses();
+        
+        // 触发自定义事件,向父组件传递选择的日期
+        this.triggerEvent('datechange', {
+          date: day.fullDate,
+          dateString: this.formatDate(day.fullDate)
+        });
+        
+        console.log('选择的日期:', day.fullDate);
+      } else {
+        console.log('日期不可选择,状态:', day.status);
+      }
+    },
+
+    // 上一个月
+    prevMonth() {
+      if (this.data.currentMonth === 1) {
+        this.setData({
+          currentYear: this.data.currentYear - 1,
+          currentMonth: 12
+        });
+      } else {
+        this.setData({
+          currentMonth: this.data.currentMonth - 1
+        });
+      }
+      this.initCalendar();
+    },
+
+    // 下一个月
+    nextMonth() {
+      if (this.data.currentMonth === 12) {
+        this.setData({
+          currentYear: this.data.currentYear + 1,
+          currentMonth: 1
+        });
+      } else {
+        this.setData({
+          currentMonth: this.data.currentMonth + 1
+        });
+      }
+      this.initCalendar();
+    },
+
+    // 初始化默认日期
+    initDefaultDate() {
+      const today = new Date();
+      today.setHours(0, 0, 0, 0);
+      
+      // 计算开放日期范围(从今天开始的七天)
+      const openStart = new Date(today);
+      const openEnd = new Date(today);
+      openEnd.setDate(today.getDate() + 6);
+      openEnd.setHours(23, 59, 59, 999);
+      
+      const isInOpenRange = this.isDateInOpenRange(today, openStart, openEnd);
+      const stopDates = this.parseStopDates();
+      const todayStatus = this.getDateStatus(today, false, true, isInOpenRange, stopDates);
+      
+      console.log('今天状态检查:', {
+        isInOpenRange,
+        todayStatus,
+        apiData: this.data.apiData
+      });
+      
+      // 只有今天可以选择时才默认选中(今天状态为'今天'表示可约)
+      if (isInOpenRange && (todayStatus === '今天' || todayStatus === '已开放')) {
+        this.setData({
+          selectedDate: today
+        });
+        
+        // 更新日历显示以反映选中状态
+        this.updateDayClasses();
+        
+        // 触发自定义事件,向父组件传递默认选择的日期
+        this.triggerEvent('datechange', {
+          date: today,
+          dateString: this.formatDate(today)
+        });
+        
+        console.log('默认选中今天:', today, '状态:', todayStatus);
+      } else {
+        // 今天不可选择时,确保selectedDate为null,不触发datechange事件
+        this.setData({
+          selectedDate: null
+        });
+        console.log('今天不可选择,不默认选中。状态:', todayStatus);
+      }
+    },
+
+    // 格式化日期
+    formatDate(date) {
+      const year = date.getFullYear();
+      const month = (date.getMonth() + 1).toString().padStart(2, '0');
+      const day = date.getDate().toString().padStart(2, '0');
+      return `${year}-${month}-${day}`;
+    }
+  }
+});

+ 4 - 0
components/time-select/time-select.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 35 - 0
components/time-select/time-select.wxml

@@ -0,0 +1,35 @@
+<view class="time-select-container">
+  <view class="time-select-header">
+    <view class="time-select-title">请选择参观日期</view>
+    <view class="month-selector">
+      <view class="arrow prev" bindtap="prevMonth">
+        <image src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/icon_left.png" class="arrow-icon"></image>
+      </view>
+      <view class="current-month">{{currentYearMonth}}</view>
+      <view class="arrow next" bindtap="nextMonth">
+        <image src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/icon_right.png" class="arrow-icon"></image>
+      </view>
+    </view>
+  </view>
+  
+  <view class="calendar">
+    <!-- 星期标题 -->
+    <view class="weekdays">
+      <view class="weekday" wx:for="{{weekdays}}" wx:key="index">{{item}}</view>
+    </view>
+    
+    <!-- 日期网格 -->
+    <view class="days-grid">
+      <view 
+        wx:for="{{daysInMonth}}" 
+        wx:key="index"
+        class="day-cell {{item.cssClass}}"
+        bindtap="selectDay"
+        data-index="{{index}}"
+      >
+        <view class="day-number" wx:if="{{!item.isEmpty}}">{{item.date}}</view>
+        <view class="day-status" wx:if="{{!item.isEmpty}}">{{item.status}}</view>
+      </view>
+    </view>
+  </view>
+</view>

+ 198 - 0
components/time-select/time-select.wxss

@@ -0,0 +1,198 @@
+.time-select-container {
+  width: 100%;
+  max-width: 100%;
+  border-radius: 16rpx;
+  font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
+  overflow-x: hidden;
+}
+
+.time-select-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 30rpx;
+}
+
+.time-select-title {
+  font-size: 32rpx;
+  color: #584735;
+  font-weight: bold;
+}
+
+.month-selector {
+  display: flex;
+  align-items: center;
+  color: #94765A;
+}
+
+.current-month {
+  margin: 0 20rpx;
+  font-size: 32rpx;
+  font-weight: 500;
+}
+
+.arrow {
+  font-size: 36rpx;
+  width: 48rpx;
+  height: 48rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #8B5D3B;
+}
+
+.arrow-icon {
+  width: 48rpx;
+  height: 48rpx;
+}
+
+.weekdays {
+  display: grid;
+  grid-template-columns: repeat(7, 1fr);
+  text-align: center;
+  margin-bottom: 20rpx;
+  width: 100%;
+  max-width: 100%;
+  box-sizing: border-box;
+}
+
+.weekday {
+  font-size: 28rpx;
+  color: #584735;
+  font-weight: bold;
+  padding: 10rpx 4rpx;
+  text-align: center;
+  min-width: 0;
+  box-sizing: border-box;
+}
+
+.days-grid {
+  display: grid;
+  grid-template-columns: repeat(7, 1fr);
+  gap: 4rpx;
+  width: 100%;
+  max-width: 100%;
+  box-sizing: border-box;
+}
+
+.day-cell {
+  aspect-ratio: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  border-radius: 8rpx;
+  position: relative;
+  padding: 6rpx 2rpx;
+  min-width: 0;
+  box-sizing: border-box;
+  overflow: hidden;
+  transition: all 0.2s ease;
+}
+
+.day-number {
+  font-size: 28rpx;
+  font-weight: 500;
+  margin-bottom: 2rpx;
+  color: #584735;
+  text-align: center;
+  line-height: 1.2;
+}
+
+.day-status {
+  font-size: 24rpx;
+  text-align: center;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 100%;
+  line-height: 1.2;
+  padding: 0 2rpx;
+}
+
+.day-cell.other-month {
+  color: #ccc;
+  display: none;
+}
+
+.day-cell.past {
+  color: #999;
+}
+
+/* 可用日期样式 */
+.day-cell.available .day-number,
+.day-cell.available .day-status {
+  color: #8B5D3B;
+}
+
+/* 今天的特殊样式 */
+.day-cell.today .day-number {
+  color: #8B5D3B;
+  font-weight: bold;
+}
+
+/* 不可用日期样式 */
+.day-cell.unavailable .day-status {
+  color: rgba(88, 71, 53, 0.5);
+}
+
+/* 已约满日期样式 */
+.day-cell.full .day-status {
+  color: #B1967B;
+}
+
+/* 选中状态样式 - 最高优先级 */
+.day-cell.selected {
+  background: #B1967B !important;
+  transform: scale(1.08);
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  z-index: 10;
+  position: relative;
+}
+
+.day-cell.selected .day-number {
+  color: #fff !important;
+  font-weight: bold;
+}
+
+.day-cell.selected .day-status {
+  color: #fff !important;
+  font-weight: 500;
+}
+
+/* 可选择日期的悬停效果 */
+.day-cell.selectable:not(.selected) {
+  transition: all 0.2s ease;
+  cursor: pointer;
+}
+
+.day-cell.selectable:not(.selected):hover {
+  background-color: rgba(139, 93, 59, 0.2);
+  transform: scale(1.02);
+}
+
+/* 选中动画效果 */
+@keyframes selectAnimation {
+  0% {
+    transform: scale(1);
+  }
+  50% {
+    transform: scale(1.12);
+  }
+  100% {
+    transform: scale(1.08);
+  }
+}
+
+.day-cell.selected {
+  animation: selectAnimation 0.3s ease-out;
+}
+
+
+
+/* 空白单元格样式 */
+.day-cell.empty-cell {
+  background: transparent;
+  cursor: default;
+  pointer-events: none;
+}

二进制
imgs/icon_culture_active.png


二进制
imgs/icon_culture_normal.png


二进制
imgs/icon_exhibition_active.png


二进制
imgs/icon_exhibition_normal.png


二进制
imgs/icon_home_active.png


二进制
imgs/icon_home_normal.png


二进制
imgs/icon_user_active.png


二进制
imgs/icon_user_normal.png


+ 156 - 0
pages/collection/index.js

@@ -0,0 +1,156 @@
+// pages/collection/index.js
+const { museumApi } = require('../../utils/api.js');
+const { navigateToWebview } = require('../../utils/util.js');
+
+Page({
+  /**
+   * 页面的初始数据
+   */
+  data: {
+    artifactList: [], // 收藏列表
+    loading: false, // 加载状态
+    hasMore: true, // 是否还有更多数据
+    searchText: '', // 搜索文本
+    activeCategory: 'all', // 当前激活的分类
+    selectedType: 0, // 当前选中的类型:0-全部,1-平面纸质类,2-棉麻丝绸类,3-专业器物类
+    pageNum: 1, // 当前页码
+    pageSize: 10, // 每页数量
+    categories: [
+      { id: '1', name: '平面纸质类' },
+      { id: '2', name: '棉麻丝绸类' },
+      { id: '3', name: '专业器物类' }
+    ]
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad(options) {
+    this.initData();
+  },
+
+  /**
+   * 初始化数据
+   */
+  async initData() {
+    await this.getCollectionList(true);
+  },
+
+  /**
+   * 获取收藏列表
+   */
+  async getCollectionList(reset = false) {
+    if (this.data.loading) return;
+    
+    this.setData({ loading: true });
+    
+    try {
+      const params = {
+        pageNum: reset ? 1 : this.data.pageNum,
+        pageSize: this.data.pageSize,
+        type: this.data.selectedType || undefined,
+        title: this.data.searchText || undefined
+      };
+      
+      const res = await museumApi.getCollectionList(params);
+      console.log('获取收藏列表参数:', res);
+      if (res.records) {
+        const newList = res.records || [];
+        const artifactList = reset ? newList : [...this.data.artifactList, ...newList];
+        const hasMore = newList.length === this.data.pageSize;
+        console.log('获取收藏列表成功:', artifactList);
+        this.setData({
+          artifactList,
+          hasMore,
+          pageNum: reset ? 2 : this.data.pageNum + 1
+        });
+      }
+    } catch (error) {
+      console.error('获取收藏列表失败:', error);
+      wx.showToast({
+        title: '获取数据失败',
+        icon: 'none'
+      });
+    } finally {
+      this.setData({ loading: false });
+      wx.stopPullDownRefresh();
+    }
+  },
+
+  /**
+   * 处理分类选择
+   */
+  async selectCategory(e) {
+    const { type } = e.currentTarget.dataset;
+    const typeId = type === 'all' ? 0 : parseInt(type);
+    
+    this.setData({
+      activeCategory: type,
+      selectedType: typeId,
+      pageNum: 1
+    });
+    
+    await this.getCollectionList(true);
+  },
+
+  /**
+   * 处理搜索输入
+   */
+  onSearchInput(e) {
+    this.setData({ searchText: e.detail.value });
+  },
+
+  /**
+   * 处理搜索
+   */
+  async onSearch() {
+    this.setData({ pageNum: 1 });
+    await this.getCollectionList(true);
+  },
+
+  /**
+   * 清空搜索
+   */
+  async onSearchClear() {
+    this.setData({ 
+      searchText: '',
+      pageNum: 1 
+    });
+    await this.getCollectionList(true);
+  },
+
+  /**
+   * 跳转到收藏详情页
+   */
+  goCollectDetail(e) {
+    const { item } = e.currentTarget.dataset;
+    const url = `/collectDetail?id=${item.artifactId}&isFrom=weixin`;
+    navigateToWebview(url);
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh() {
+    this.getCollectionList(true);
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom() {
+    if (this.data.hasMore && !this.data.loading) {
+      this.getCollectionList();
+    }
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage() {
+    return {
+      title: '克拉玛依博物馆 - 典藏',
+      path: '/pages/collection/index'
+    };
+  }
+})

+ 6 - 0
pages/collection/index.json

@@ -0,0 +1,6 @@
+{
+  "navigationBarTitleText": "典藏",
+  "enablePullDownRefresh": false,
+  "onReachBottomDistance": 50,
+  "usingComponents": {}
+}

+ 65 - 0
pages/collection/index.wxml

@@ -0,0 +1,65 @@
+<!--pages/collection/index.wxml-->
+<view class="collection-container">
+  <!-- 顶部导航和搜索区域 -->
+  <view class="top-section">
+    <!-- 顶部导航栏 -->
+    <view class="nav-scroll-container">
+      <scroll-view class="category-menu" scroll-x="true">
+        <view class="category-item {{activeCategory === 'all' ? 'active' : ''}}" bindtap="selectCategory" data-type="all">
+          <text class="category-text">全部</text>
+        </view>
+        <view class="category-item {{activeCategory === item.id ? 'active' : ''}}" wx:for="{{categories}}" wx:key="id" bindtap="selectCategory" data-type="{{item.id}}">
+          <text class="category-text">{{item.name}}</text>
+        </view>
+      </scroll-view>
+    </view>
+
+    <!-- 搜索框 -->
+    <view class="search-container">
+      <view class="search-input">
+        <input type="text" placeholder="请输入要搜索的内容..." value="{{searchText}}" bindinput="onSearchInput" bindconfirm="onSearch" />
+        <view class="search-icon" wx:if="{{!searchText}}" bindtap="onSearch">
+          <icon class="icon-small" type="search" size="24"></icon>
+        </view>
+        <view class="clear-icon" wx:if="{{searchText}}" bindtap="onSearchClear">
+          <text class="iconfont">✕</text>
+        </view>
+      </view>
+    </view>
+  </view>
+
+  <!-- 内容区域 -->
+  <scroll-view class="content-section" scroll-y="true" bindscrolltolower="onReachBottom">
+    <view class="collection-list">
+      <view class="collection-item" wx:for="{{artifactList}}" wx:key="artifactId" bindtap="goCollectDetail" data-item="{{item}}">
+        <view class="item-image-container">
+          <image class="item-image" src="{{item.img}}" mode="aspectFill"></image>
+          <view class="view-button">
+            <text>查看</text>
+            <text class="arrow">→</text>
+          </view>
+          <view class="item-info">
+            <view class="item-category">{{item.category || '三维文物'}}</view>
+            <view class="item-title">{{item.title || '未知标题'}}</view>
+            <view class="item-description">{{item.description || '暂无描述'}}</view>
+          </view>
+        </view>
+      </view>
+
+      <!-- 加载状态 -->
+      <view wx:if="{{loading}}" class="loading-container">
+        <view class="loading-text">加载中...</view>
+      </view>
+
+      <!-- 没有更多数据 -->
+      <view wx:if="{{!hasMore && artifactList.length > 0}}" class="no-more-container">
+        <view class="no-more-text">没有更多数据了</view>
+      </view>
+
+      <!-- 暂无数据 -->
+      <view wx:if="{{!loading && artifactList.length === 0}}" class="empty-container">
+        <view class="empty-text">暂无数据</view>
+      </view>
+    </view>
+  </scroll-view>
+</view>

+ 212 - 0
pages/collection/index.wxss

@@ -0,0 +1,212 @@
+/* pages/collection/index.wxss */
+.collection-container {
+  height: 100vh;
+  background-image: url('https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/karamay/bg.png');
+  background-size: cover;
+  background-repeat: no-repeat;
+  box-sizing: border-box;
+  padding: 0 30rpx;
+}
+
+.top-section {
+  position: sticky;
+  top: 0;
+  z-index: 10;
+  background-color: rgba(245, 242, 235, 0.95);
+}
+
+.nav-scroll-container {
+  overflow-x: auto;
+  white-space: nowrap;
+}
+
+.category-menu {
+  display: flex;
+  margin-top: 20rpx;
+  white-space: nowrap;
+}
+
+.category-item {
+  display: inline-flex;
+  align-items: center;
+  height: 80rpx;
+  line-height: 80rpx;
+  font-size: 32rpx;
+  color: #584735;
+  padding: 0 30rpx;
+  white-space: nowrap;
+  flex-shrink: 0;
+}
+
+.category-item.active {
+  color: #584735;
+  font-weight: bold;
+  font-size: 40rpx;
+}
+
+.category-item.active .category-text {
+  border-bottom: 2rpx solid #F3B200;
+}
+
+.search-container {
+  padding: 20rpx 30rpx;
+}
+
+.search-input {
+  position: relative;
+  height: 90rpx;
+  border-radius: 40rpx;
+  border: 2rpx solid #5B472E;
+  background-color: transparent;
+  display: flex;
+  align-items: center;
+  padding: 0 30rpx;
+}
+
+.search-input input {
+  flex: 1;
+  height: 100%;
+  font-size: 32rpx;
+  color: #584735;
+}
+
+.search-input input::placeholder {
+  color: rgba(88, 71, 53, 0.5);
+}
+
+.search-icon {
+  margin-left: 20rpx;
+  font-size: 38rpx;
+  color: #584735;
+}
+.icon-small{
+  margin-top: 12rpx;
+}
+.clear-icon {
+  margin-left: 20rpx;
+  font-size: 32rpx;
+  color: #999;
+}
+
+.content-section {
+  height: calc(100vh - 300rpx);
+}
+
+.collection-list {
+  display: flex;
+  flex-direction: column;
+  gap: 30rpx;
+}
+
+.collection-item {
+  background-color: #fff;
+  border-radius: 16rpx;
+  overflow: hidden;
+  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
+}
+
+.item-image-container {
+  position: relative;
+  height: 560rpx;
+  overflow: hidden;
+}
+
+.item-image {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+
+.view-button {
+  position: absolute;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 160rpx;
+  height: 72rpx;
+  top: 24rpx;
+  right: 24rpx;
+  background-color: rgba(0, 0, 0, 0.2);
+  color: #fff;
+  border-radius: 100rpx;
+  font-size: 24rpx;
+  border: 2rpx solid #fff;
+}
+
+.view-button text {
+  font-size: 28rpx;
+}
+
+.arrow {
+  margin-left: 12rpx;
+}
+
+.item-info {
+  position: absolute;
+  width: 90%;
+  bottom: 32rpx;
+  left: 48rpx;
+}
+
+.item-category {
+  font-size: 28rpx;
+  color: #fff;
+  margin-bottom: 8rpx;
+}
+
+.item-title {
+  font-size: 72rpx;
+  font-weight: bold;
+  color: #fff;
+  margin-bottom: 8rpx;
+  line-height: 1.2;
+}
+
+.item-description {
+  font-size: 28rpx;
+  color: #fff;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+/* 加载和状态样式 */
+.loading-container,
+.no-more-container,
+.empty-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 40rpx;
+  margin: 40rpx 0;
+}
+
+.loading-text,
+.no-more-text,
+.empty-text {
+  color: #584735;
+  font-size: 28rpx;
+  opacity: 0.7;
+}
+
+.loading-text {
+  position: relative;
+}
+
+.loading-text::after {
+  content: '';
+  display: inline-block;
+  width: 24rpx;
+  height: 24rpx;
+  margin-left: 16rpx;
+  border: 4rpx solid #584735;
+  border-top-color: transparent;
+  border-radius: 50%;
+  animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+  to {
+    transform: rotate(360deg);
+  }
+}

+ 148 - 0
pages/exhibition/index.js

@@ -0,0 +1,148 @@
+// pages/exhibition/index.js
+const { museumApi } = require('../../utils/api.js');
+const { navigateToWebview } = require('../../utils/util.js');
+
+Page({
+  /**
+   * 页面的初始数据
+   */
+  data: {
+    selectedType: 0, // 当前选中的类型:0-全部,1-室内,2-室外
+    carouselList: [], // 轮播图数据
+    exhibitionList: [], // 展览列表数据
+    loading: false, // 加载状态
+    hasMore: true, // 是否还有更多数据
+    currentPage: 1, // 当前页码
+    pageSize: 10 // 每页数量
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad(options) {
+    this.initData();
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh() {
+    this.initData();
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom() {
+    this.loadMore();
+  },
+
+  // 初始化数据
+  async initData() {
+    await this.getCarouselData();
+    await this.getExhibitionList(false);
+    wx.stopPullDownRefresh();
+  },
+
+  // 获取轮播图数据(type为0的前5条)
+  async getCarouselData() {
+    try {
+      const response = await museumApi.getExhibitionList({
+        pageNum: 1,
+        pageSize: 5,
+        type: 0
+      });
+      if (response && response.records) {
+        this.setData({
+          carouselList: response.records
+        });
+      }
+    } catch (error) {
+      console.error('获取轮播图数据失败:', error);
+    }
+  },
+
+  // 获取展览列表数据
+  async getExhibitionList(isLoadMore = false) {
+    if (this.data.loading) return;
+
+    this.setData({ loading: true });
+
+    try {
+      const response = await museumApi.getExhibitionList({
+        pageNum: isLoadMore ? this.data.currentPage : 1,
+        pageSize: this.data.pageSize,
+        type: this.data.selectedType
+      });
+
+      if (response && response.records) {
+        let exhibitionList;
+        if (isLoadMore) {
+          // 加载更多,追加数据
+          exhibitionList = [...this.data.exhibitionList, ...response.records];
+        } else {
+          // 重新加载,替换数据
+          exhibitionList = response.records;
+          this.setData({ currentPage: 1 });
+        }
+
+        this.setData({
+          exhibitionList: exhibitionList,
+          hasMore: response.records.length === this.data.pageSize
+        });
+
+        if (isLoadMore) {
+          this.setData({
+            currentPage: this.data.currentPage + 1
+          });
+        }
+      }
+    } catch (error) {
+      console.error('获取展览列表失败:', error);
+      wx.showToast({
+        title: '加载失败',
+        icon: 'none'
+      });
+    } finally {
+      this.setData({ loading: false });
+    }
+  },
+
+  // 选择分类
+  async selectCategory(e) {
+    const type = e.currentTarget.dataset.type;
+    if (this.data.selectedType === type) return;
+
+    this.setData({
+      selectedType: type,
+      currentPage: 1,
+      hasMore: true
+    });
+
+    // 重新获取列表数据
+    await this.getExhibitionList(false);
+  },
+
+  // 加载更多
+  async loadMore() {
+    if (!this.data.hasMore || this.data.loading) return;
+    await this.getExhibitionList(true);
+  },
+
+  // 轮播图点击事件
+  onCarouselTap(e) {
+    const item = e.currentTarget.dataset.item;
+    this.goToDetail(item.exhibitId);
+  },
+
+  // 展览项点击事件
+  onExhibitionTap(e) {
+    const item = e.currentTarget.dataset.item;
+    this.goToDetail(item.exhibitId);
+  },
+
+  // 跳转到详情页面
+  goToDetail(id) {
+    navigateToWebview(`/allDetailsShow?isFrom=weixin&id=${id}&type=exhibition`);
+  }
+});

+ 8 - 0
pages/exhibition/index.json

@@ -0,0 +1,8 @@
+{
+  "navigationBarTitleText": "展览",
+  "navigationBarBackgroundColor": "#B1967B",
+  "navigationBarTextStyle": "white",
+  "backgroundColor": "#f8f8f8",
+  "enablePullDownRefresh": false,
+  "usingComponents": {}
+}

+ 53 - 0
pages/exhibition/index.wxml

@@ -0,0 +1,53 @@
+<!--pages/exhibition/index.wxml-->
+<view class="exhibition-container">
+  <!-- 轮播图 -->
+  <view class="carousel-section">
+    <swiper class="carousel" indicator-dots="{{true}}" autoplay="{{true}}" interval="3000" duration="500">
+      <swiper-item wx:for="{{carouselList}}" wx:key="exhibitId" bindtap="onCarouselTap" data-item="{{item}}">
+        <image class="carousel-image" src="{{item.img}}" mode="aspectFill"></image>
+        <!-- <view class="carousel-overlay">
+          <text class="carousel-title">{{item.exhibitName}}</text>
+        </view> -->
+      </swiper-item>
+    </swiper>
+  </view>
+
+  <!-- 分类选择 -->
+  <view class="category-section">
+    <view class="category-tabs">
+      <view class="category-tab {{selectedType === 1 ? 'active' : ''}}" bindtap="selectCategory" data-type="1">
+        <image class="category-icon" src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/exhibition/icon_in.png" mode="aspectFit"></image>
+        <text>室内展览</text>
+      </view>
+      <view class="category-tab {{selectedType === 2 ? 'active' : ''}}" bindtap="selectCategory" data-type="2">
+        <image class="category-icon" src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/exhibition/icon_out.png" mode="aspectFit"></image>
+        <text>室外展览</text>
+      </view>
+    </view>
+  </view>
+
+  <!-- 展览列表 -->
+  <view class="exhibition-list">
+    <view class="exhibition-item" wx:for="{{exhibitionList}}" wx:key="exhibitId" bindtap="onExhibitionTap" data-item="{{item}}">
+      <image class="exhibition-image" src="{{item.img}}" mode="aspectFill"></image>
+      <view class="exhibition-content">
+        <text class="exhibition-title">{{item.exhibitName || '记忆与传承的艺术史'}}</text>
+      </view>
+    </view>
+  </view>
+
+  <!-- 加载状态 -->
+  <view class="loading-section" wx:if="{{loading}}">
+    <text>加载中...</text>
+  </view>
+
+  <!-- 没有更多数据 -->
+  <view class="no-more-section" wx:if="{{!hasMore && exhibitionList.length > 0}}">
+    <text>没有更多数据了</text>
+  </view>
+
+  <!-- 空状态 -->
+  <view class="empty-section" wx:if="{{exhibitionList.length === 0 && !loading}}">
+    <text>暂无展览信息</text>
+  </view>
+</view>

+ 173 - 0
pages/exhibition/index.wxss

@@ -0,0 +1,173 @@
+/* pages/exhibition/index.wxss */
+.exhibition-container {
+  background-image: url('https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/karamay/bg.png');
+  background-size: cover;
+  background-repeat: no-repeat;
+  min-height: 100vh;
+  box-sizing: border-box;
+}
+
+/* 轮播图样式 */
+.carousel-section {
+  margin-bottom: 20rpx;
+}
+
+.carousel {
+  height: 400rpx;
+  width: 100%;
+}
+
+.carousel-image {
+  width: 100%;
+  height: 100%;
+}
+
+.carousel-overlay {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background: linear-gradient(transparent, rgba(0, 0, 0, 0.6));
+  padding: 40rpx 30rpx 30rpx;
+}
+
+.carousel-title {
+  color: white;
+  font-size: 32rpx;
+  font-weight: bold;
+  text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
+}
+
+/* 分类选择样式 */
+.category-section {
+  padding: 0 40rpx;
+  margin-bottom: 40rpx;
+}
+
+.category-tabs {
+  display: flex;
+  justify-content: space-between;
+  gap: 20rpx;
+}
+
+.category-tab {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  background-color: rgba(229, 200, 142, 0.2);
+  padding: 30rpx;
+  border-radius: 10rpx;
+  border: 2rpx solid rgba(148, 118, 90, 0.5);
+  transition: all 0.3s ease;
+}
+
+.category-tab.active {
+  background-color: rgba(229, 200, 142, 0.6);
+  border-color: rgba(148, 118, 90, 0.8);
+  transform: scale(1.02);
+}
+
+.category-icon {
+  width: 90rpx;
+  height: 78rpx;
+  margin-bottom: 12rpx;
+}
+
+.category-tab text {
+  font-size: 32rpx;
+  color: #B1967B;
+  font-weight: 500;
+}
+
+.category-tab.active text {
+  color: #B1967B;
+}
+
+/* 展览列表样式 */
+.exhibition-list {
+  padding: 0 40rpx;
+}
+
+.exhibition-item {
+  position: relative;
+  width: 100%;
+  height: 412rpx;
+  border-radius: 16rpx;
+  overflow: hidden;
+  margin-bottom: 40rpx;
+  transition: all 0.3s ease;
+  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
+}
+
+.exhibition-item:active {
+  transform: translateY(-4rpx);
+  box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15);
+}
+
+.exhibition-image {
+  width: 100%;
+  height: 412rpx;
+  object-fit: cover;
+}
+
+.exhibition-content {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  height: 60rpx;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background-color: #B1967B;
+  opacity: 0.8;
+}
+
+.exhibition-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #fff;
+  margin: 0;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  padding: 0 20rpx;
+}
+
+.exhibition-desc {
+  display: none;
+}
+
+.exhibition-meta {
+  display: none;
+}
+
+.exhibition-time,
+.exhibition-location {
+  display: none;
+}
+
+/* 加载状态样式 */
+.loading-section {
+  text-align: center;
+  padding: 40rpx;
+  color: #999;
+  font-size: 28rpx;
+}
+
+/* 没有更多数据样式 */
+.no-more-section {
+  text-align: center;
+  padding: 40rpx;
+  color: #999;
+  font-size: 26rpx;
+}
+
+/* 空状态样式 */
+.empty-section {
+  text-align: center;
+  padding: 100rpx 40rpx;
+  color: #999;
+  font-size: 28rpx;
+}

+ 132 - 0
pages/index/active-page/active-page.js

@@ -0,0 +1,132 @@
+// active-page.js
+const { museumApi } = require('../../../utils/api.js');
+
+Page({
+  data: {
+    selectedTime: '',
+    selectedDate: null,
+    step: 1,
+    activityId: 0, // 活动ID
+    type: 1, // 预约类型:1-普通预约,2-活动预约
+    activityData: {}
+  },
+
+  onLoad(options) {
+    // 页面加载时的逻辑
+    console.log('active-page接收到的参数:', options);
+    if (options.activityId) {
+      this.setData({
+        activityId: parseInt(options.activityId)
+      });
+    }
+    if (options.type) {
+      this.setData({
+        type: parseInt(options.type)
+      });
+    }
+  },
+
+  onReady() {
+    // 页面初次渲染完成后获取活动详情
+    if (this.data.activityId) {
+      this.getActivityInfo(this.data.activityId);
+    }
+  },
+
+  onShow() {
+    // 页面显示时的逻辑
+  },
+
+  // 日期选择回调
+  onDateChange(e) {
+    console.log('选择的日期:', e.detail);
+    const selectedDate = e.detail.dateString;
+    
+    // 只有当日期可选时才设置selectedDate
+    if (selectedDate) {
+      this.setData({
+        selectedDate: selectedDate,
+        selectedTime: '' // 清空时间选择状态
+      });
+    } else {
+      // 如果日期不可选,清空选择状态
+      this.setData({
+        selectedDate: null,
+        selectedTime: ''
+      });
+    }
+  },
+
+  // 格式化日期
+  formatDate(date) {
+    const year = date.getFullYear();
+    const month = (date.getMonth() + 1).toString().padStart(2, '0');
+    const day = date.getDate().toString().padStart(2, '0');
+    return `${year}-${month}-${day}`;
+  },
+
+
+
+  // 选择时间段
+  selectTime(e) {
+    if (this.data.selectedDate && this.data.activityData.time) {
+      this.setData({
+        selectedTime: 'selected'
+      });
+      console.log('选择的时间段:', this.data.activityData.time);
+    } else {
+      wx.showToast({
+        title: '请先选择日期',
+        icon: 'none'
+      });
+    }
+  },
+
+  // 下一步
+  goNext() {
+    if (this.data.selectedDate && this.data.activityData.title) {
+      console.log('选择的日期:', this.data.selectedDate);
+      console.log('活动标题:', this.data.activityData.title);
+      
+      // 传递活动标题而不是appointmentSlotsId
+      const title = this.data.activityData.title;
+      
+      wx.navigateTo({
+        url: `/pages/index/active-people/active-people?date=${this.data.selectedDate}&title=${encodeURIComponent(title)}&type=2&activityId=${this.data.activityId}`
+      });
+    } else {
+      wx.showToast({
+        title: '请选择日期',
+        icon: 'none'
+      });
+    }
+  },
+
+  onShareAppMessage() {
+    return {
+      title: '克拉玛依博物馆 - 开始预约',
+      path: '/pages/index/start-preview/start-preview'
+    };
+  },
+
+  // 获取活动详情
+  getActivityInfo(activityId) {
+    museumApi.getActivityInfo(activityId)
+      .then(response => {
+        console.log('获取活动详情成功:', response);
+        if (response) {
+          const activityData = response;
+          console.log('活动数据:', activityData);
+          // 将活动数据设置到页面data中,通过属性传递给子组件
+          this.setData({
+            activityData: activityData
+          });
+        } else {
+          console.log('API响应数据为空:', response);
+        }
+      })
+      .catch(error => {
+        console.error('获取活动详情失败:', error);
+      });
+  }
+});

+ 7 - 0
pages/index/active-page/active-page.json

@@ -0,0 +1,7 @@
+{
+  "navigationBarTitleText": "活动预约",
+  "usingComponents": {
+    "active-time-select": "/components/active-time-select/index",
+    "active-people": "/pages/index/active-people/active-people"
+  }
+}

+ 9 - 0
pages/index/active-page/active-page.wxml

@@ -0,0 +1,9 @@
+<view class="preview-contain">
+  <view class="time-select">
+    <active-time-select activity-data="{{activityData}}" bind:datechange="onDateChange"></active-time-select>
+    <view class="divider"></view>
+    <view class="select-moon" wx:if="{{selectedDate}}">
+      <view class="next-btn {{!selectedDate ? 'disabled' : ''}}" bindtap="goNext" disabled="{{!selectedDate}}">下一步</view>
+    </view>
+  </view>
+</view>

+ 112 - 0
pages/index/active-page/active-page.wxss

@@ -0,0 +1,112 @@
+.preview-contain {
+  height: 100vh;
+  background: url('https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/karamay/bg.png') no-repeat center center;
+  background-size: cover;
+  overflow: auto;
+}
+
+.time-select {
+  padding: 32rpx;
+}
+
+.divider {
+  width: 698rpx;
+  height: 4rpx;
+  background: linear-gradient(90deg, #5B472E 0%, rgba(91, 71, 46, 0) 100%);
+  opacity: 0.5;
+  margin-top: 32rpx;
+}
+
+.select-moon {
+  margin-top: 32rpx;
+}
+
+.moon-time {
+  color: #584735;
+  font-size: 32rpx;
+  font-weight: bold;
+  margin-bottom: 32rpx;
+  display: block;
+}
+
+.morning-moon {
+  display: flex;
+  flex-direction: column;
+  gap: 24rpx;
+}
+
+.time-card {
+  position: relative;
+  background: transparent;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  border: 2rpx solid #B1967B;
+  border-radius: 16rpx;
+  padding: 32rpx 40rpx;
+  transition: all 0.3s ease;
+}
+
+.time-card.active {
+  background: rgba(177, 150, 123, 0.4);
+  border-color: #B1967B;
+}
+
+.time-period {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #584735;
+  margin-bottom: 8rpx;
+}
+
+.time-range {
+  font-size: 32rpx;
+  color: #5B472E;
+  margin-bottom: 16rpx;
+}
+
+.availability {
+  font-size: 24rpx;
+  color: #5B472E;
+}
+
+.check-icon {
+  position: absolute;
+  top: 50%;
+  right: 40rpx;
+  transform: translateY(-50%);
+  width: 48rpx;
+  height: 48rpx;
+  background: #B1967B;
+  color: white;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 28rpx;
+  font-weight: bold;
+}
+
+.next-btn {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background: #B1967B;
+  color: white;
+  border: none;
+  border-radius: 16rpx;
+  padding: 32rpx;
+  font-size: 32rpx;
+  font-weight: bold;
+  margin-top: 40rpx;
+  transition: all 0.3s ease;
+}
+
+.next-btn.disabled {
+  background: #ccc;
+}
+
+.next-btn:not(.disabled):active {
+  background: #9A8169;
+}

+ 374 - 0
pages/index/active-people/active-people.js

@@ -0,0 +1,374 @@
+// 调用预约API
+const { museumApi } = require('../../../utils/api.js');
+Page({
+  data: {
+    selectedDate: '',
+    title: '', // 活动标题
+    activityId: 0, // 活动ID,type=2时需要
+    type: 1, // 预约类型:1-普通预约,2-活动预约
+    visitors: [], // 参观人列表
+    showExistingPage: false,
+    showSuccessModal: false,
+    isFormValid: false,
+    idTypes: [{value: 1, name: '身份证'}],
+    existingVisitors: [] // 我的联系人列表
+  },
+
+  onLoad(options) {
+    this.addNewVisitor();
+    // 获取页面参数
+    if (options.date) {
+      this.setData({
+        selectedDate: options.date
+      });
+    }
+    
+    // 获取活动标题
+    if (options.title) {
+      this.setData({
+        title: decodeURIComponent(options.title)
+      });
+    }
+    
+    // 保存activityId和type用于后续提交
+    if (options.activityId) {
+      this.setData({
+        activityId: parseInt(options.activityId)
+      });
+    }
+    if (options.type) {
+      this.setData({
+        type: parseInt(options.type)
+      });
+    }
+    
+    // 加载已有参观人数据
+    this.loadExistingVisitors();
+  },
+
+  // 新增参观人
+  addNewVisitor() {
+      const visitors = this.data.visitors;
+      visitors.unshift({
+        id: 0, // 新增参观人ID为0
+        name: '',
+        phone: '',
+        cardType: this.data.idTypes[0].value, // 证件类型
+        idCard: '', // 证件号码
+        idType: this.data.idTypes[0].name, // 用于显示
+        idTypeIndex: 0,
+        idCard: '', // 用于显示
+        nameError: '',
+        phoneError: '',
+        idNumberError: '',
+        isFromContacts: false // 标识是否来自联系人
+      });
+      this.setData({
+        visitors: visitors
+      });
+      this.checkFormValid();
+    },
+
+    // 删除参观人
+    removeVisitor(e) {
+      const index = e.currentTarget.dataset.index;
+      const visitors = this.data.visitors;
+      visitors.splice(index, 1);
+      this.setData({
+        visitors: visitors
+      });
+      this.checkFormValid();
+    },
+
+    // 姓名输入
+    onNameInput(e) {
+      const index = e.currentTarget.dataset.index;
+      const value = e.detail.value;
+      const visitors = this.data.visitors;
+      visitors[index].name = value;
+      this.setData({
+        visitors: visitors
+      });
+    },
+
+    // 电话输入
+    onPhoneInput(e) {
+      const index = e.currentTarget.dataset.index;
+      const value = e.detail.value;
+      const visitors = this.data.visitors;
+      visitors[index].phone = value;
+      this.setData({
+        visitors: visitors
+      });
+    },
+
+    // 证件号码输入
+    onIdNumberInput(e) {
+      const index = e.currentTarget.dataset.index;
+      const value = e.detail.value;
+      const visitors = this.data.visitors;
+      visitors[index].idCard = value;
+      visitors[index].idCard = value; // 同步更新API字段
+      this.setData({
+        visitors: visitors
+      });
+    },
+
+    // 证件类型选择
+    onIdTypeChange(e) {
+      const index = e.currentTarget.dataset.index;
+      const value = e.detail.value;
+      const visitors = this.data.visitors;
+      const selectedIdType = this.data.idTypes[value];
+      visitors[index].idTypeIndex = value;
+      visitors[index].idType = selectedIdType.name;
+      visitors[index].cardType = selectedIdType.value; // 同步更新API字段
+      this.setData({
+        visitors: visitors
+      });
+    },
+
+    // 验证姓名
+    validateName(e) {
+      const index = e.currentTarget.dataset.index;
+      const visitors = this.data.visitors;
+      const visitor = visitors[index];
+      
+      if (!visitor.name.trim()) {
+        visitor.nameError = '请输入姓名';
+      } else if (visitor.name.length < 2) {
+        visitor.nameError = '姓名至少2个字符';
+      } else {
+        visitor.nameError = '';
+      }
+      
+      this.setData({
+        visitors: visitors
+      });
+      this.checkFormValid();
+    },
+
+    // 验证电话
+    validatePhone(e) {
+      const index = e.currentTarget.dataset.index;
+      const visitors = this.data.visitors;
+      const visitor = visitors[index];
+      const phoneRegex = /^1[3-9]\d{9}$/;
+      
+      if (!visitor.phone) {
+        visitor.phoneError = '请输入电话号码';
+      } else if (!phoneRegex.test(visitor.phone)) {
+        visitor.phoneError = '请输入正确的11位手机号码';
+      } else {
+        visitor.phoneError = '';
+      }
+      
+      this.setData({
+        visitors: visitors
+      });
+      this.checkFormValid();
+    },
+
+    // 验证证件号码
+    validateIdNumber(e) {
+      const index = e.currentTarget.dataset.index;
+      const visitors = this.data.visitors;
+      const visitor = visitors[index];
+      const idRegex = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
+      
+      if (!visitor.idCard) {
+        visitor.idNumberError = '请输入证件号码';
+      } else if (!idRegex.test(visitor.idCard)) {
+        visitor.idNumberError = '请输入正确的18位身份证号码';
+      } else {
+        visitor.idNumberError = '';
+      }
+      
+      this.setData({
+        visitors: visitors
+      });
+      this.checkFormValid();
+    },
+
+    // 检查表单是否有效
+    checkFormValid() {
+      const visitors = this.data.visitors;
+      if (visitors.length === 0) {
+        this.setData({ isFormValid: false });
+        return;
+      }
+      
+      const isValid = visitors.every(visitor => 
+        visitor.name && 
+        visitor.phone && 
+        visitor.idCard && 
+        !visitor.nameError && 
+        !visitor.phoneError && 
+        !visitor.idNumberError
+      );
+      
+      this.setData({ isFormValid: isValid });
+    },
+
+    // 加载已有参观人数据
+    loadExistingVisitors() {
+      museumApi.getMyVisitors()
+        .then(response => {
+          console.log('获取参观人列表成功:', response);
+          if (response && response.length > 0) {
+            const existingVisitors = response.map(visitor => ({
+              id: visitor.id,
+              name: visitor.name,
+              idCard: visitor.idCard,
+              phone: visitor.phone,
+              selected: false
+            }));
+            this.setData({ existingVisitors });
+          }
+        })
+        .catch(error => {
+          console.error('获取参观人列表失败:', error);
+          // 如果接口失败,可以使用模拟数据
+          this.setData({
+            existingVisitors: [
+              {
+                name: '周明明',
+                idCard: '210112196705041430',
+                phone: '18416573665',
+                selected: false
+              },
+              {
+                name: '李明',
+                idCard: '621124199504251508',
+                phone: '13902376115',
+                selected: false
+              }
+            ]
+          });
+        });
+    },
+
+    // 显示已有参观人
+    showExistingVisitors() {
+      // 每次显示前重新加载数据
+      this.loadExistingVisitors();
+      this.setData({
+        showExistingPage: true
+      });
+    },
+
+    // 切换已有参观人选择状态
+    toggleExistingVisitor(e) {
+      const index = e.currentTarget.dataset.index;
+      const existingVisitors = this.data.existingVisitors;
+      existingVisitors[index].selected = !existingVisitors[index].selected;
+      this.setData({
+        existingVisitors: existingVisitors
+      });
+    },
+
+    // 确认选择已有参观人
+    confirmExistingVisitors() {
+      const selectedVisitors = this.data.existingVisitors.filter(visitor => visitor.selected);
+      const visitors = this.data.visitors;
+      
+      selectedVisitors.forEach(existingVisitor => {
+        // if (visitors.length < 5) {
+          visitors.unshift({
+            id: existingVisitor.id, // 使用联系人的真实ID
+            name: existingVisitor.name,
+            phone: existingVisitor.phone,
+            cardType: this.data.idTypes[0].value, // 证件类型
+            idCard: existingVisitor.idCard, // 证件号码
+            idType: this.data.idTypes[0].name, // 用于显示
+            idTypeIndex: 0,
+            idCard: existingVisitor.idCard, // 用于显示
+            nameError: '',
+            phoneError: '',
+            idNumberError: '',
+            isFromContacts: true // 标识来自联系人
+          });
+        // }
+      });
+      
+      // 重置选择状态
+      const existingVisitors = this.data.existingVisitors;
+      existingVisitors.forEach(visitor => {
+        visitor.selected = false;
+      });
+      
+      this.setData({
+        visitors: visitors,
+        existingVisitors: existingVisitors,
+        showExistingPage: false
+      });
+      
+      this.checkFormValid();
+    },
+
+    // 提交预约
+    submitReservation() {
+      if (!this.data.isFormValid) {
+        wx.showToast({
+          title: '请完善所有参观人信息',
+          icon: 'none'
+        });
+        return;
+      }
+      
+      wx.showLoading({
+        title: '提交中...',
+        mask: true
+      });
+      
+      // 构建API参数
+      const requestData = {
+        activityId: this.data.activityId || 0,
+        appointmentSlotsId: this.data.appointmentSlotsId || 0,
+        appointmentTime: this.data.selectedDate,
+        type: this.data.type,
+        visitors: this.data.visitors.map(visitor => ({
+          cardType: this.data.idTypes[visitor.idTypeIndex].value,
+          id: visitor.id, // 新增参观人为0,联系人为真实ID
+          idCard: visitor.idCard,
+          name: visitor.name,
+          phone: visitor.phone
+        }))
+      };
+      
+      console.log('提交预约参数:', requestData);
+      museumApi.submitReservation(requestData)
+        .then(response => {
+          console.log('预约提交成功:', response);
+          
+          this.setData({
+            showSuccessModal: true
+          });
+          wx.hideLoading();
+        })
+        .catch(error => {
+          wx.hideLoading();
+          console.error('预约提交失败:', error);
+          wx.showToast({
+            title: error.message || '预约失败,请重试',
+            icon: 'none'
+          });
+        });
+    },
+
+    // 关闭成功弹窗
+    closeSuccessModal() {
+      this.setData({
+        showSuccessModal: false
+      });
+      // 跳转回首页
+      wx.switchTab({
+        url: '/pages/index/index'
+      });
+    },
+
+  // 阻止事件冒泡
+  stopPropagation() {
+    // 空函数,用于阻止事件冒泡
+  }
+});

+ 4 - 0
pages/index/active-people/active-people.json

@@ -0,0 +1,4 @@
+{
+  "navigationBarTitleText": "参观人信息",
+  "usingComponents": {}
+}

+ 135 - 0
pages/index/active-people/active-people.wxml

@@ -0,0 +1,135 @@
+<view class="visit-people-container">
+  <!-- 预约日期信息 -->
+  <view class="date-info" wx:if="{{!showExistingPage}}">
+    <image class="bg-img" src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/indexPage/img.png" mode="aspectFill"></image>
+    <view class="date-content">
+      <view class="date-label"><text class="title">预约日期:</text>{{selectedDate}}</view>
+      <view class="time-label"><text class="title">活动标题:</text>{{title}}</view>
+    </view>
+  </view>
+
+  <!-- 新增参观人页面 -->
+  <view wx:if="{{!showExistingPage}}" class="main-content">
+    <!-- 新增参观人和选择已有参观人按钮 -->
+    <view class="action-buttons">
+      <button class="add-new-btn" bindtap="addNewVisitor">新增参观人</button>
+      <button class="select-existing-btn" bindtap="showExistingVisitors">选择已有参观人</button>
+    </view>
+
+    <!-- 参观人信息列表 -->
+    <scroll-view class="visitor-list" scroll-y="true">
+      <view wx:for="{{visitors}}" wx:key="index" class="visitor-card">
+        <view class="visitor-header">
+          <text class="visitor-title">参观人信息{{index + 1}}</text>
+          <view class="delete-btn" bindtap="removeVisitor" data-index="{{index}}">
+            <image class="delete-img" src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/indexPage/icon_delete.png" />
+            删除
+          </view>
+        </view>
+        
+        <view class="visitor-form">
+          <view class="form-item">
+            <view class="form-label">姓名</view>
+            <view class="form-input">
+              <input 
+                class="bottom-line-input {{item.nameError ? 'error-line' : ''}}"
+                placeholder="请输入您的姓名"
+                value="{{item.name}}"
+                bindinput="onNameInput"
+                bindblur="validateName"
+                data-index="{{index}}"
+              />
+              <view wx:if="{{item.nameError}}" class="error-msg">{{item.nameError}}</view>
+            </view>
+          </view>
+
+          <view class="form-item">
+            <view class="form-label">电话号码</view>
+            <view class="form-input">
+              <input 
+                class="bottom-line-input {{item.phoneError ? 'error-line' : ''}}"
+                placeholder="请输入11位数字"
+                value="{{item.phone}}"
+                bindinput="onPhoneInput"
+                bindblur="validatePhone"
+                data-index="{{index}}"
+                type="number"
+              />
+              <view wx:if="{{item.phoneError}}" class="error-msg">{{item.phoneError}}</view>
+            </view>
+          </view>
+
+          <view class="form-item">
+            <view class="form-label">证件类型</view>
+            <view class="form-input">
+              <picker 
+                class="bottom-line-select"
+                range="{{idTypes}}"
+                range-key="name"
+                value="{{item.idTypeIndex}}"
+                bindchange="onIdTypeChange"
+                data-index="{{index}}"
+              >
+                <view class="picker-text">{{item.idType || '请选择证件类型'}}</view>
+              </picker>
+            </view>
+          </view>
+
+          <view class="form-item">
+            <view class="form-label">证件号码</view>
+            <view class="form-input">
+              <input 
+                class="bottom-line-input {{item.idNumberError ? 'error-line' : ''}}"
+                placeholder="请输入18位证件编码"
+                value="{{item.idCard}}"
+                bindinput="onIdNumberInput"
+                bindblur="validateIdNumber"
+                data-index="{{index}}"
+              />
+              <view wx:if="{{item.idNumberError}}" class="error-msg">{{item.idNumberError}}</view>
+            </view>
+          </view>
+        </view>
+      </view>
+    </scroll-view>
+
+    <!-- 下一步按钮 -->
+    <view class="next-button-container">
+      <view class="next-btn {{!isFormValid ? 'disabled' : ''}}" bindtap="submitReservation" disabled="{{!isFormValid}}">下一步</view>
+    </view>
+  </view>
+
+  <!-- 选择已有参观人页面 -->
+  <view wx:if="{{showExistingPage}}" class="existing-page">
+    <view class="existing-visitors-list">
+      <view 
+        wx:for="{{existingVisitors}}" 
+        wx:key="index" 
+        class="existing-visitor-item"
+        bindtap="toggleExistingVisitor"
+        data-index="{{index}}"
+      >
+        <view class="visitor-info">
+          <view class="visitor-name">姓名:{{item.name}}</view>
+          <view class="visitor-id">证件号:{{item.idCard}}</view>
+          <view class="visitor-phone">电话号码:{{item.phone}}</view>
+        </view>
+        <view class="checkbox {{item.selected ? 'checked' : ''}}">
+          <text wx:if="{{item.selected}}">✓</text>
+        </view>
+      </view>
+    </view>
+    <view class="existing-actions">
+      <button class="confirm-btn" bindtap="confirmExistingVisitors">确定</button>
+    </view>
+  </view>
+
+  <!-- 成功弹窗 -->
+  <view wx:if="{{showSuccessModal}}" class="modal-overlay" bindtap="closeSuccessModal">
+    <view class="success-modal" catchtap="stopPropagation">
+      <view class="success-icon">✓</view>
+      <view class="success-text">预约成功</view>
+      <button class="success-btn" bindtap="closeSuccessModal">确认</button>
+    </view>
+  </view>
+</view>

+ 310 - 0
pages/index/active-people/active-people.wxss

@@ -0,0 +1,310 @@
+.visit-people-container {
+  height: 97vh;
+  background: url('https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/karamay/bg.png');
+  padding: 32rpx;
+  padding-bottom: 0;
+  overflow: hidden;
+}
+
+.date-info {
+  height: 212rpx;
+  position: relative;
+  margin: 4rpx;
+  margin-top: 0;
+}
+
+.bg-img {
+  width: 100%;
+  height: 95%;
+}
+
+.date-content {
+  position: absolute;
+  width: 70%;
+  top: 40rpx;
+  left: 70rpx;
+  background: rgba(255, 255, 255, 0.9);
+  border-radius: 16rpx;
+  text-align: left;
+}
+
+.date-label, .time-label {
+  font-size: 32rpx;
+  color: #333;
+  margin: 10rpx 0;
+}
+
+.title {
+  color: #B1967B;
+}
+
+.action-buttons {
+  display: flex;
+  gap: 20rpx;
+  padding: 0 32rpx;
+  margin-bottom: 40rpx;
+}
+
+.add-new-btn, .select-existing-btn {
+  flex: 1;
+  padding: 24rpx;
+  border: none;
+  border-radius: 12rpx;
+  font-size: 28rpx;
+}
+
+.add-new-btn {
+  background-color: #B1967B;
+  color: #fff;
+}
+
+.select-existing-btn {
+  background-color: transparent;
+  color: #A78A6D;
+  border: 2rpx solid #B1967B;
+}
+
+.visitor-list {
+  height: calc(100vh - 460rpx - 64rpx);
+  overflow-y: auto;
+  padding-bottom: 20rpx;
+}
+
+.visitor-card {
+  background: white;
+  border-radius: 16rpx;
+  padding: 40rpx 40rpx 20rpx 40rpx;
+  margin-bottom: 30rpx;
+  box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.1);
+}
+
+.visitor-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 40rpx;
+  padding-bottom: 20rpx;
+  border-bottom: 2rpx solid #ddd;
+}
+
+.visitor-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #B1967B;
+}
+
+.delete-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: none;
+  border: none;
+  color: #F45151;
+  font-size: 28rpx;
+  line-height: 36rpx;
+}
+.delete-img{
+  width: 32rpx;
+  height: 32rpx;
+  margin-right: 4rpx;
+}
+.visitor-form {
+  padding: 0;
+}
+
+.form-item {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: 32rpx;
+  min-height: 80rpx;
+}
+
+.form-label {
+  width: 160rpx;
+  font-size: 28rpx;
+  color: #333;
+  margin-top: 20rpx;
+  flex-shrink: 0;
+}
+
+.form-input {
+  flex: 1;
+  margin-left: 20rpx;
+}
+
+.bottom-line-input {
+  width: 100%;
+  padding: 20rpx 0;
+  border: none;
+  border-bottom: 2rpx solid #ddd;
+  font-size: 28rpx;
+  background: transparent;
+}
+
+.bottom-line-input.error-line {
+  border-bottom-color: #F45151;
+}
+
+.bottom-line-select {
+  width: 100%;
+  padding: 20rpx 0;
+  border-bottom: 2rpx solid #ddd;
+  font-size: 28rpx;
+}
+
+.picker-text {
+  color: #333;
+}
+
+.error-msg {
+  color: #F45151;
+  font-size: 24rpx;
+  margin-top: 10rpx;
+}
+
+.next-button-container {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  padding: 30rpx;
+}
+
+.next-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 90%;
+  padding: 30rpx;
+  background: #B1967B;
+  color: white;
+  border: none;
+  border-radius: 12rpx;
+  font-size: 32rpx;
+  font-weight: bold;
+}
+
+.next-btn.disabled {
+  background: #ccc;
+}
+
+/* 选择已有参观人页面样式 */
+.existing-page {
+  padding: 32rpx;
+  height: 100vh;
+}
+
+.existing-visitors-list {
+  height: calc(100vh - 200rpx);
+  overflow-y: auto;
+}
+
+.existing-visitor-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  background: white;
+  border-radius: 16rpx;
+  padding: 30rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.1);
+}
+
+.visitor-info {
+  flex: 1;
+}
+
+.visitor-name, .visitor-id, .visitor-phone {
+  font-size: 28rpx;
+  color: #333;
+  margin-bottom: 10rpx;
+}
+
+.checkbox {
+  width: 48rpx;
+  height: 48rpx;
+  border: 2rpx solid #B1967B;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 28rpx;
+  color: white;
+}
+
+.checkbox.checked {
+  background: #B1967B;
+}
+
+.existing-actions {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  padding: 30rpx;
+  background: white;
+  border-top: 2rpx solid #eee;
+}
+
+.confirm-btn {
+  width: 100%;
+  padding: 30rpx;
+  background: #B1967B;
+  color: white;
+  border: none;
+  border-radius: 12rpx;
+  font-size: 32rpx;
+  font-weight: bold;
+}
+
+/* 成功弹窗样式 */
+.modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1000;
+}
+
+.success-modal {
+  background: white;
+  border-radius: 20rpx;
+  padding: 60rpx;
+  text-align: center;
+  width: 500rpx;
+}
+
+.success-icon {
+  width: 80rpx;
+  height: 80rpx;
+  background: #52c41a;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin: 0 auto 30rpx;
+  color: white;
+  font-size: 40rpx;
+  font-weight: bold;
+}
+
+.success-text {
+  font-size: 36rpx;
+  color: #333;
+  margin-bottom: 40rpx;
+}
+
+.success-btn {
+  width: 100%;
+  padding: 24rpx;
+  background: #B1967B;
+  color: white;
+  border: none;
+  border-radius: 12rpx;
+  font-size: 32rpx;
+}

+ 66 - 0
pages/index/active-preview/active-preview.js

@@ -0,0 +1,66 @@
+// 调用预约API
+const { museumApi } = require('../../../utils/api.js');
+
+Page({
+  data: {
+    activityList: [] // 活动列表
+  },
+
+  onLoad(options) {
+    // 页面加载时的逻辑
+    this.loadActivityList();
+  },
+
+  onShow() {
+    // 页面显示时的逻辑
+  },
+
+  // 加载活动列表
+  loadActivityList() {
+    museumApi.getSocialActivityList({
+      pageSize: 100,
+      status: 1
+    })
+    .then(response => {
+      console.log('获取活动列表成功:', response);
+      if (response && response.records) {
+        this.setData({
+          activityList: response.records
+        });
+      }
+    })
+    .catch(error => {
+      console.error('获取活动列表失败:', error);
+    });
+  },
+
+  // 返回首页
+  goBack() {
+    wx.switchTab({
+      url: '/pages/index/index'
+    });
+  },
+
+  // 跳转到用户列表
+  goUserList() {
+    wx.navigateTo({
+      url: '/pages/user/user-list/user-list'
+    });
+  },
+
+  // 跳转到活动页面
+  goToActivePage(e) {
+    const activityId = e.currentTarget.dataset.activityId;
+    console.log('跳转到活动页面,activityId:', activityId);
+    wx.navigateTo({
+      url: `/pages/index/active-page/active-page?activityId=${activityId}&type=2`
+    });
+  },
+
+  onShareAppMessage() {
+    return {
+      title: '克拉玛依博物馆 - 活动预约',
+      path: '/pages/index/active-preview/active-preview'
+    };
+  }
+});

+ 8 - 0
pages/index/active-preview/active-preview.json

@@ -0,0 +1,8 @@
+{
+  "navigationBarTitleText": "活动预约",
+  "navigationBarBackgroundColor": "#E0D2B4",
+  "navigationBarTextStyle": "black",
+  "backgroundColor": "#E0D2B4",
+  "enablePullDownRefresh": false,
+  "usingComponents": {}
+}

+ 48 - 0
pages/index/active-preview/active-preview.wxml

@@ -0,0 +1,48 @@
+<view class="active-preview-container">
+  <!-- <view class="back-button" bindtap="goBack">
+    <image src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/indexPage/icon_back.png" mode="aspectFit"></image>
+  </view> -->
+
+  <view class="preview-content">
+    <view class="activity-cards">
+      <view class="activity-card" wx:for="{{activityList}}" wx:key="id">
+        <image src="{{item.indexImg}}" class="activity-image" mode="aspectFill" />
+        <view class="activity-info">
+          <view class="activity-title">{{item.title}}</view>
+          <view wx:if="{{item.personCount > 0}}" class="btn-reserve" bindtap="goToActivePage" data-activity-id="{{item.activityId}}">活动预约</view>
+        </view>
+      </view>
+    </view>
+
+    <view class="btn-my-reservation" bindtap="goUserList">我的参观人</view>
+
+    <view class="reservation-info">
+      <view class="title">预约须知</view>
+
+      <view class="time-info">
+        <view class="time-section">
+          <text class="time-title">开馆时间</text>
+          <view class="time-value">10:00</view>
+        </view>
+        <view class="line"></view>
+        <view class="time-section">
+          <text class="time-title">闭馆时间</text>
+          <view class="time-value">18:00</view>
+        </view>
+        <view class="divider"></view>
+        <view class="time-section">
+          <view class="stop-time">17:00</view>
+          <view class="stop-desc">停止检票</view>
+        </view>
+      </view>
+
+      <view class="time-note">周一闭馆(法定节假日除外,除夕、正月初一闭馆)。</view>
+
+      <view class="reservation-title">预约参观</view>
+      <view class="reservation-rules">
+        <text class="rule-content">①所有观众(包括享受优待政策的观众及其陪同人员)<text class="notice">均须实名预约</text>。放票时间为<text class="notice">每日18:00,可提前7日(不包括当日)通过官方微信小程序预约</text>,预约时段分为上午(9:00-13:00)和下午(12:00-16:00),当天不再放票;</text>
+        <text class="rule-content">②参观当日须按照预约时段入馆参观,错过预约时段将谢绝入馆。<text class="notice">同一证件号</text>当天可进出馆2次,预约上午票的观众首次入馆时间必须在13:00前;</text>
+      </view>
+    </view>
+  </view>
+</view>

+ 189 - 0
pages/index/active-preview/active-preview.wxss

@@ -0,0 +1,189 @@
+.active-preview-container {
+  min-height: 100vh;
+  background: linear-gradient(180deg, #F5EEE2 0%, #E0D2B4 100%), linear-gradient(180deg, rgba(224, 210, 180, 0) 0%, #E0D2B4 100%);
+  background-size: cover;
+  background-position: center;
+  position: relative;
+  color: #412A12;
+}
+
+.back-button {
+  position: absolute;
+  top: 40rpx;
+  left: 40rpx;
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 50%;
+  z-index: 10;
+}
+
+.back-button image {
+  width: 80rpx;
+  height: 80rpx;
+}
+
+.preview-content {
+  padding: 40rpx 40rpx;
+}
+
+.activity-cards {
+  display: flex;
+  flex-direction: column;
+}
+
+.activity-card {
+  position: relative;
+  background: #fff;
+  border-radius: 20rpx;
+  overflow: hidden;
+  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+  margin-bottom: 16rpx;
+}
+
+.activity-image {
+  width: 100%;
+  height: 206rpx;
+}
+
+.activity-info {
+  position: absolute;
+  top: 0;
+  width: 100%;
+  height: 206rpx;
+  flex-direction: column;
+  display: flex;
+  justify-content: center;
+  align-items: flex-end;
+  padding: 20rpx 30rpx;
+  box-sizing: border-box;
+}
+
+.activity-title {
+  font-size: 32rpx;
+  font-weight: 800;
+  color: #fff;
+  margin-bottom: 20rpx;
+}
+
+.btn-reserve {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 196rpx!important;
+  background: transparent;
+  height: 72rpx;
+  border-radius: 60rpx;
+  border: 2rpx solid #FFFFFF;
+  color: #fff;
+  font-size: 28rpx;
+}
+
+.btn-my-reservation {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 128rpx;
+  background: rgba(255, 255, 255, 0.5);
+  color: #584735;
+  border: 2rpx solid #B1967B;
+  padding: 24rpx;
+  border-radius: 16rpx;
+  font-size: 32rpx;
+  margin-bottom: 40rpx;
+  box-sizing: border-box;
+}
+
+.reservation-info {
+  border-radius: 20rpx;
+}
+
+.title {
+  font-size: 40rpx;
+  margin-bottom: 40rpx;
+  color: #584735;
+  font-weight: bold;
+}
+
+.time-info {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 20rpx;
+}
+
+.time-section {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.time-title {
+  font-size: 28rpx;
+  color: #B1967B;
+  margin-bottom: 10rpx;
+}
+
+.time-value {
+  font-size: 80rpx;
+  font-weight: bold;
+  color: #94765A;
+}
+
+.line {
+  height: 4rpx;
+  width: 26rpx;
+  background: #584735;
+  margin: 48rpx 12rpx 0 12rpx;
+}
+
+.divider {
+  height: 100rpx;
+  width: 4rpx;
+  background: #584735;
+  margin: 0 30rpx;
+}
+
+.stop-time {
+  font-size: 28rpx;
+  color: #B1967B;
+  margin-bottom: 10rpx;
+}
+
+.stop-desc {
+  font-size: 28rpx;
+  color: #B1967B;
+}
+
+.time-note {
+  width: 454rpx;
+  font-size: 28rpx;
+  color: #B1967B;
+  margin-bottom: 40rpx;
+  line-height: 1.5;
+}
+
+.reservation-title {
+  font-size: 32rpx;
+  margin-bottom: 20rpx;
+  color: #584735;
+  font-weight: bold;
+}
+
+.reservation-rules {
+  font-size: 28rpx;
+  line-height: 1.5;
+  color: #B1967B;
+  margin-bottom: 40rpx;
+}
+
+.rule-content {
+  color: #B1967B;
+  display: block;
+  margin-bottom: 20rpx;
+}
+
+.notice {
+  color: #94765A;
+  font-weight: bold;
+}

+ 61 - 0
pages/index/activity/activity.js

@@ -0,0 +1,61 @@
+// pages/index/activity/activity.js
+const { museumApi } = require('../../../utils/api.js');
+const { navigateToWebview } = require('../../../utils/util.js');
+
+Page({
+  data: {
+    activeList: [],
+    loading: false
+  },
+
+  onLoad() {
+    this.getActivityList();
+  },
+
+  onPullDownRefresh() {
+    this.getActivityList();
+  },
+
+  // 获取活动列表
+  async getActivityList() {
+    this.setData({ loading: true });
+    
+    try {
+      const response = await museumApi.getSocialActivityList({
+        pageNum: 1,
+        pageSize: 10
+      });
+      
+      if (response && response.records) {
+        this.setData({
+          activeList: response.records
+        });
+      } else {
+        this.setData({
+          activeList: []
+        });
+      }
+    } catch (error) {
+      console.error('获取活动数据失败:', error);
+      wx.showToast({
+        title: '加载失败',
+        icon: 'none'
+      });
+    } finally {
+      this.setData({ loading: false });
+      wx.stopPullDownRefresh();
+    }
+  },
+
+  // 返回首页
+  goBack() {
+    wx.navigateBack();
+  },
+
+  // 查看活动详情
+  viewActivity(e) {
+    const item = e.currentTarget.dataset.item;
+    console.log('查看活动详情:', item);
+    navigateToWebview(`/allDetailsShow?isFrom=weixin&id=${item.activityId}&type=activity`);
+  }
+});

+ 8 - 0
pages/index/activity/activity.json

@@ -0,0 +1,8 @@
+{
+  "navigationBarTitleText": "社教活动",
+  "navigationBarBackgroundColor": "#ffffff",
+  "navigationBarTextStyle": "black",
+  "backgroundColor": "#f5f5f5",
+  "enablePullDownRefresh": true,
+  "usingComponents": {}
+}

+ 35 - 0
pages/index/activity/activity.wxml

@@ -0,0 +1,35 @@
+<view class="activity-container">
+  <!-- 返回按钮 -->
+  <view class="back-button" bindtap="goBack">
+    <image src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/indexPage/icon_back.png" mode="aspectFit"></image>
+  </view>
+
+  <!-- 标题 -->
+  <view class="section-title">社教活动</view>
+
+  <!-- 活动列表 -->
+  <view wx:if="{{loading}}" class="loading-container">
+    <view class="loading-text">加载中...</view>
+  </view>
+  <view wx:else class="content-section">
+    <view class="collection-list">
+      <view class="collection-item" wx:for="{{activeList}}" wx:key="activityId" bindtap="viewActivity" data-item="{{item}}">
+        <view class="item-image-container">
+          <image src="{{item.indexImg}}" class="item-image" mode="aspectFill" />
+          <view class="view-button">
+            <text>查看</text>
+            <text class="arrow">></text>
+          </view>
+          <view class="item-info">
+            <view class="item-title">{{ item.title || '暂无标题' }}</view>
+            <view class="item-description">{{ item.description || item.content || '暂无描述' }}</view>
+          </view>
+        </view>
+      </view>
+    </view>
+    <!-- 空状态 -->
+    <view wx:if="{{activeList.length === 0}}" class="empty-state">
+      <view class="empty-text">暂无活动数据</view>
+    </view>
+  </view>
+</view>

+ 146 - 0
pages/index/activity/activity.wxss

@@ -0,0 +1,146 @@
+/* pages/index/activity/activity.wxss */
+.activity-container {
+  position: relative;
+  min-height: 100vh;
+  padding: 20rpx;
+  background: url('https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/karamay/bg.png') no-repeat;
+  background-size: cover;
+}
+
+.back-button {
+  position: absolute;
+  top: 20rpx;
+  left: 20rpx;
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 50%;
+  z-index: 10;
+}
+
+.back-button image {
+  width: 80rpx;
+  height: 80rpx;
+}
+
+.section-title {
+  font-size: 40rpx;
+  font-weight: bold;
+  color: #584735;
+  margin: 108rpx 0 40rpx 0;
+  position: relative;
+  padding-bottom: 20rpx;
+}
+
+.section-title::after {
+  content: '';
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  width: 100%;
+  height: 4rpx;
+  background: linear-gradient(90deg, rgba(91, 71, 46, 0.5) 0%, rgba(91, 71, 46, 0) 100%);
+}
+
+.content-section {
+  padding-bottom: 40rpx;
+}
+
+.collection-list {
+  display: flex;
+  flex-direction: column;
+  gap: 30rpx;
+}
+
+.collection-item {
+  background-color: #fff;
+  border-radius: 16rpx;
+  overflow: hidden;
+  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
+}
+
+.item-image-container {
+  position: relative;
+  height: 436rpx;
+  overflow: hidden;
+}
+
+.item-image {
+  width: 100%;
+  height: 100%;
+}
+
+.view-button {
+  position: absolute;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 160rpx;
+  height: 72rpx;
+  top: 24rpx;
+  right: 24rpx;
+  background-color: rgba(0, 0, 0, 0.2);
+  color: #fff;
+  border-radius: 100rpx;
+  font-size: 24rpx;
+  border: 2rpx solid #fff;
+}
+
+.view-button text {
+  font-size: 28rpx;
+}
+
+.arrow {
+  margin-left: 12rpx;
+  font-size: 24rpx;
+}
+
+.item-info {
+  position: absolute;
+  width: 90%;
+  bottom: 32rpx;
+  left: 48rpx;
+}
+
+.item-title {
+  font-size: 40rpx;
+  font-weight: bold;
+  color: #fff;
+  margin-bottom: 8rpx;
+}
+
+.item-description {
+  font-size: 24rpx;
+  color: #fff;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+/* 加载状态样式 */
+.loading-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 400rpx;
+}
+
+.loading-text {
+  font-size: 36rpx;
+  color: #584735;
+  opacity: 0.8;
+}
+
+/* 空状态样式 */
+.empty-state {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 400rpx;
+  margin-top: 100rpx;
+}
+
+.empty-text {
+  font-size: 36rpx;
+  color: #584735;
+  opacity: 0.6;
+}

+ 311 - 0
pages/index/index.js

@@ -0,0 +1,311 @@
+// 小程序首页逻辑
+const { museumApi } = require('../../utils/api.js');
+const { navigateToWebview } = require('../../utils/util.js');
+const app = getApp();
+
+Page({
+  /**
+   * 页面的初始数据
+   */
+  data: {
+    showLoading: false, // 控制是否显示loading组件
+    isFirstOpen: false, // 是否首次打开
+    bannerData: [], // 轮播图数据
+    newsList: [], // 资讯列表
+    exhibitionList: [], // 展览列表
+    activeList: [], // 活动列表
+    loading: false // 加载状态
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad(options) {
+    // 检查是否首次访问
+    const hasVisited = wx.getStorageSync('hasVisited');
+    if (!hasVisited) {
+      // 首次访问,显示loading组件并隐藏导航栏
+      // wx.hideNavigationBarLoading();
+      // wx.setNavigationBarTitle({
+      //   title: ''
+      // });
+      wx.hideTabBar();
+      this.setData({
+        showLoading: true,
+        isFirstOpen: true
+      });
+      
+    } else {
+      // 非首次访问,直接显示内容并初始化数据
+      this.setData({
+        showLoading: false,
+        isFirstOpen: false
+      });
+      // 设置已访问标记
+      wx.setStorageSync('hasVisited', true);
+      this.initAllData();
+    }
+  },
+  
+  // loading组件"开始探索"事件处理
+  onStartExplore() {
+    // 设置已访问标记
+    wx.setStorageSync('hasVisited', true);
+    wx.showTabBar();
+    this.setData({
+      showLoading: false,
+      isFirstOpen: false
+    });
+    // 恢复导航栏
+    // wx.setNavigationBarTitle({
+    //   title: '克拉玛依博物馆'
+    // });
+    // 初始化数据
+    this.initAllData();
+  },
+  
+  // 查看更多
+  viewMore(e) {
+    const section = e.currentTarget.dataset.section;
+    console.log(`查看更多${section}`);
+    
+    switch (section) {
+      case 'exhibition':
+        wx.switchTab({
+          url: '/pages/exhibition/index'
+        });
+        break;
+      case 'recommended':
+        wx.navigateTo({
+          url: '/pages/index/news/news'
+        });
+        break;
+      case 'activity':
+        wx.navigateTo({
+          url: '/pages/index/activity/activity'
+        });
+        break;
+    }
+  },
+
+  /**
+   * 初始化所有数据
+   */
+  async initAllData() {
+    try {
+      this.setData({ loading: true });
+      
+      // 并行请求所有数据
+      const [bannerRes, newsRes, exhibitionRes, activeRes] = await Promise.all([
+        this.getBannerData({ pageNum: 1, pageSize: 10, status: 1 }),
+        this.getNewsList({ pageNum: 1, pageSize: 3, status: 1 }),
+        this.getExhibitionList({ pageNum: 1, pageSize: 5, status: 1 }),
+        this.getActiveList({ pageNum: 1, pageSize: 5, status: 1 })
+      ]);
+      
+      console.log('所有数据加载完成');
+    } catch (error) {
+      console.error('数据加载失败:', error);
+      wx.showToast({
+        title: '数据加载失败',
+        icon: 'none'
+      });
+    } finally {
+      this.setData({ loading: false });
+    }
+  },
+
+  /**
+   * 获取轮播图数据
+   */
+  async getBannerData(params = {}) {
+    try {
+      const response = await museumApi.getCarouselList(params);
+      console.log('轮播图数据:', response);
+      const bannerData = response.records || response.list || response.data || response || [];
+      this.setData({ bannerData });
+      return response;
+    } catch (error) {
+      console.error('获取轮播图数据失败:', error);
+      // 使用默认数据
+      this.setData({ 
+        bannerData: [{ carouselId: 1, title: '轮播图1', img: '' }] 
+      });
+      throw error;
+    }
+  },
+
+  /**
+   * 获取资讯列表
+   */
+  async getNewsList(params = {}) {
+    try {
+      const response = await museumApi.getNewsList(params);
+      console.log('资讯数据:', response);
+      const newsList = response.records || response.list || response.data || response || [];
+      this.setData({ newsList });
+      return response;
+    } catch (error) {
+      console.error('获取资讯数据失败:', error);
+      this.setData({ newsList: [] });
+      throw error;
+    }
+  },
+
+  /**
+   * 获取展览列表
+   */
+  async getExhibitionList(params = {}) {
+    try {
+      const response = await museumApi.getExhibitionList(params);
+      console.log('展览数据:', response);
+      const exhibitionList = response.records || response.list || response.data || response || [];
+      this.setData({ exhibitionList });
+      return response;
+    } catch (error) {
+      console.error('获取展览数据失败:', error);
+      this.setData({ exhibitionList: [] });
+      throw error;
+    }
+  },
+
+  /**
+   * 获取活动列表
+   */
+  async getActiveList(params = {}) {
+    try {
+      const response = await museumApi.getSocialActivityList(params);
+      console.log('活动数据:', response);
+      const activeList = response.records || response.list || response.data || response || [];
+      this.setData({ activeList });
+      return response;
+    } catch (error) {
+      console.error('获取活动数据失败:', error);
+      this.setData({ activeList: [] });
+      throw error;
+    }
+  },
+
+  /**
+   * 功能点击处理
+   */
+  handleFunctionClick(e) {
+    const { type } = e.currentTarget.dataset;
+    console.log(`点击了${type}功能`);
+    
+    // 根据不同功能跳转到webview页面
+    switch (type) {
+      case 'visit':
+        wx.navigateTo({
+          url: '/pages/index/visit-preview/visit-preview'
+        });
+        break;
+      case 'activity':
+        wx.navigateTo({
+          url: '/pages/index/active-preview/active-preview'
+        });
+        break;
+      case 'map':
+        this.handleMapClick();
+        break;
+      case 'introduce':
+        this.navigateToWebview('/allDetailsShow?id=1&type=museum');
+        break;
+    }
+  },
+
+  /**
+   * 查看展览详情
+   */
+  viewExhibition(e) {
+    const { item } = e.currentTarget.dataset;
+    console.log(`查看展览${item.exhibitId}详情`);
+    this.navigateToWebview(`/allDetailsShow?isFrom=weixin&id=${item.exhibitId}&type=exhibition`);
+  },
+
+  /**
+   * 查看活动详情
+   */
+  viewActivity(e) {
+    const { item } = e.currentTarget.dataset;
+    console.log(`查看活动${item.activityId}详情`);
+    this.navigateToWebview(`/allDetailsShow?isFrom=weixin&id=${item.activityId}&type=activity`);
+  },
+
+  /**
+   * 查看资讯详情
+   */
+  viewNews(e) {
+    const { item } = e.currentTarget.dataset;
+    console.log(`查看资讯${item.informationId}详情`);
+    this.navigateToWebview(`/allDetailsShow?isFrom=weixin&id=${item.informationId}&type=information`);
+  },
+
+  /**
+   * 处理地图点击事件
+   */
+  async handleMapClick() {
+    this.navigateToWebview('/indexPage/map?isFrom=weixin');
+  },
+
+  /**
+   * 导航到webview页面
+   */
+  navigateToWebview(path) {
+    navigateToWebview(path);
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload() {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉刷新
+   */
+  onPullDownRefresh() {
+    this.initAllData().finally(() => {
+      wx.stopPullDownRefresh();
+    });
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom() {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage() {
+    return {
+      title: '克拉玛依博物馆',
+      path: '/pages/index/index?isFrom=weixin'
+    };
+  }
+})

+ 8 - 0
pages/index/index.json

@@ -0,0 +1,8 @@
+{
+  "usingComponents": {
+    "loading": "/components/loading/index"
+  },
+  "enablePullDownRefresh": false,
+  "navigationBarTitleText": "克拉玛依博物馆",
+  "navigationStyle": "default"
+}

+ 91 - 0
pages/index/index.wxml

@@ -0,0 +1,91 @@
+<!--pages/index/index.wxml-->
+<!-- 小程序首页 -->
+<view wx:if="{{showLoading}}">
+  <loading bind:startexplore="onStartExplore"></loading>
+</view>
+
+<view wx:else class="home-container {{!isFromParam ? 'home-tabar' : ''}} {{isFirstOpen ? 'home-not-overflow' : ''}}">
+  <!-- 轮播图区域 -->
+  <view class="carousel-section">
+    <swiper class="carousel-swiper" indicator-dots="{{false}}" autoplay="{{true}}" interval="5000" duration="500">
+      <swiper-item wx:for="{{bannerData}}" wx:key="carouselId">
+        <image src="{{item.img}}" alt="{{item.title || '轮播图'}}" class="carousel-img" mode="aspectFill"></image>
+      </swiper-item>
+    </swiper>
+  </view>
+
+  <!-- 功能区域 -->
+  <view class="function-section">
+    <view class="function-item" bindtap="handleFunctionClick" data-type="visit">
+      <image src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/indexPage/visit.png" alt="预约参观" class="function-icon"></image>
+      <text class="function-text">预约参观</text>
+    </view>
+    <view class="function-item" bindtap="handleFunctionClick" data-type="activity">
+      <image src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/indexPage/activity.png" alt="活动预约" class="function-icon"></image>
+      <text class="function-text">活动预约</text>
+    </view>
+    <view class="function-item" bindtap="handleFunctionClick" data-type="map">
+      <image src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/indexPage/map.png" alt="展馆地图" class="function-icon"></image>
+      <text class="function-text">展馆地图</text>
+    </view>
+    <view class="function-item" bindtap="handleFunctionClick" data-type="introduce">
+      <image src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/indexPage/introduce.png" alt="展馆介绍" class="function-icon"></image>
+      <text class="function-text">展馆介绍</text>
+    </view>
+  </view>
+
+  <!-- 展览资讯 -->
+  <view class="section">
+    <view class="section-header">
+      <text class="section-title">展览资讯</text>
+      <text class="view-more" bindtap="viewMore" data-section="recommended">查看更多 +</text>
+    </view>
+    <view class="news-item" wx:for="{{newsList}}" wx:key="informationId" bindtap="viewNews" data-item="{{item}}">
+      <image src="{{item.infoImg}}" alt="展览资讯" class="news-img" mode="aspectFill"></image>
+      <view class="news-content">
+        <text class="news-title">{{item.title}}</text>
+        <text class="news-desc">⇀</text>
+      </view>
+    </view>
+  </view>
+
+  <!-- 推荐展览 -->
+  <view class="section">
+    <view class="section-header">
+      <text class="section-title">推荐展览</text>
+      <text class="view-more" bindtap="viewMore" data-section="exhibition">查看更多 +</text>
+    </view>
+    <scroll-view class="scroll-container" scroll-x="{{true}}">
+      <view class="scroll-wrapper">
+        <view class="exhibition-item" wx:for="{{exhibitionList}}" wx:key="exhibitId" bindtap="viewExhibition" data-item="{{item}}">
+          <image src="{{item.img}}" alt="展览" class="exhibition-img" mode="aspectFill"></image>
+          <view class="exhibition-info">
+            <text class="exhibition-title">{{item.title}}</text>
+          </view>
+        </view>
+      </view>
+    </scroll-view>
+  </view>
+
+  <!-- 社教活动 -->
+  <view class="section">
+    <view class="section-header">
+      <text class="section-title">社教活动</text>
+      <text class="view-more" bindtap="viewMore" data-section="activity">查看更多 +</text>
+    </view>
+    <scroll-view class="scroll-container" scroll-x="{{true}}">
+      <view class="scroll-wrapper">
+        <view class="activity-item" wx:for="{{activeList}}" wx:key="activityId" bindtap="viewActivity" data-item="{{item}}">
+          <view class="active-top">
+            <image src="{{item.indexImg}}" alt="活动" class="activity-img" mode="aspectFill"></image>
+            <text class="active-go">></text>
+          </view>
+          <view class="activity-info">
+            <text class="activity-title">{{item.title}}</text>
+          </view>
+        </view>
+      </view>
+    </scroll-view>
+  </view>
+</view>
+

+ 228 - 0
pages/index/index.wxss

@@ -0,0 +1,228 @@
+/* 小程序首页样式 */
+.home-container {
+  height: 100vh;
+  overflow-y: auto;
+  background: url('https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/karamay/bg.png') no-repeat center top;
+  background-size: 100% auto;
+}
+
+.home-not-overflow {
+  overflow: hidden;
+}
+
+/* 轮播图区域 */
+.carousel-section {
+  width: 100%;
+  margin-bottom: 30rpx;
+}
+
+.carousel-swiper {
+  height: 360rpx;
+}
+
+.carousel-img {
+  width: 100%;
+  height: 100%;
+}
+
+/* 功能区域 */
+.function-section {
+  display: flex;
+  justify-content: space-around;
+  border-radius: 16rpx;
+  margin: 0 30rpx 40rpx;
+}
+
+.function-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  width: 22%;
+}
+
+.function-icon {
+  width: 112rpx;
+  height: 112rpx;
+  margin-bottom: 10rpx;
+}
+
+.function-text {
+  font-size: 32rpx;
+  color: #B1967B;
+}
+
+/* 通用区块样式 */
+.section {
+  margin: 0 30rpx 0;
+  border-radius: 16rpx;
+  padding: 8rpx 20rpx;
+}
+
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 30rpx;
+}
+
+.section-title {
+  font-size: 40rpx;
+  font-weight: bold;
+  color: #584735;
+  position: relative;
+  padding-left: 20rpx;
+}
+
+.view-more {
+  font-size: 28rpx;
+  color: #79ACF9;
+}
+
+/* 展览资讯 */
+.news-item {
+  position: relative;
+  margin-bottom: 20rpx;
+  border-radius: 10rpx;
+  height: 212rpx;
+  overflow: hidden;
+}
+
+.news-img {
+  width: 100%;
+  height: 212rpx;
+}
+
+.news-content {
+  position: absolute;
+  top: 0;
+  width: 100%;
+  height: 212rpx;
+  background: linear-gradient(90deg, rgba(177, 150, 123, 0) 0%, #B1967B 100%);
+}
+
+.news-title {
+  width: 300rpx;
+  text-align: right;
+  font-size: 32rpx;
+  font-weight: 800;
+  margin: 32rpx;
+  float: right;
+  color: #FFFFFF;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+}
+
+.news-desc {
+  position: absolute;
+  bottom: 16rpx;
+  right: 32rpx;
+  font-size: 50rpx;
+  color: #fff;
+  /* transform: rotate(-90deg) scale(1, -1); */
+}
+
+/* 滚动容器 */
+.scroll-container {
+  white-space: nowrap;
+}
+
+.scroll-wrapper {
+  display: inline-flex;
+  padding-bottom: 10rpx;
+}
+
+/* 展览项 */
+.exhibition-item {
+  position: relative;
+  flex: 0 0 auto;
+  width: 690rpx;
+  height: 412rpx;
+  margin-right: 20rpx;
+  border-radius: 16rpx;
+  overflow: hidden;
+}
+
+.exhibition-item:last-child {
+  margin-right: 0;
+}
+
+.exhibition-img {
+  width: 100%;
+  height: 412rpx;
+}
+
+.exhibition-info {
+  width: 690rpx;
+  height: 60rpx;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  opacity: 0.8;
+  position: absolute;
+  bottom: 0;
+  background: #B1967B;
+}
+
+.exhibition-title {
+  font-size: 32rpx;
+  color: #fff;
+  font-weight: bold;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+/* 活动项 */
+.activity-item {
+  flex: 0 0 auto;
+  width: 216rpx;
+  margin-right: 20rpx;
+  border-radius: 16rpx;
+  overflow: hidden;
+}
+
+.activity-item:last-child {
+  margin-right: 0;
+}
+
+.active-top {
+  position: relative;
+  width: 216rpx;
+  height: 248rpx;
+  border-radius: 16rpx;
+  overflow: hidden;
+}
+
+.active-go {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 32rpx;
+  height: 32rpx;
+  position: absolute;
+  bottom: 8rpx;
+  right: 16rpx;
+  border: 2rpx solid #FCE39E;
+  border-radius: 50%;
+  color: #FCE39E;
+  font-size: 24rpx;
+}
+
+.activity-img {
+  width: 216rpx;
+  height: 248rpx;
+}
+
+.activity-info {
+  padding: 16rpx;
+}
+
+.activity-title {
+  font-size: 24rpx;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  color: #584735;
+}

+ 61 - 0
pages/index/news/news.js

@@ -0,0 +1,61 @@
+// pages/index/news/news.js
+const { museumApi } = require('../../../utils/api.js');
+const { navigateToWebview } = require('../../../utils/util.js');
+
+Page({
+  data: {
+    newsList: [],
+    loading: false
+  },
+
+  onLoad() {
+    this.getNewsList();
+  },
+
+  onPullDownRefresh() {
+    this.getNewsList();
+  },
+
+  // 获取资讯列表
+  async getNewsList() {
+    this.setData({ loading: true });
+    
+    try {
+      const response = await museumApi.getNewsList({
+        pageNum: 1,
+        pageSize: 10
+      });
+      
+      if (response && response.records) {
+        this.setData({
+          newsList: response.records
+        });
+      } else {
+        this.setData({
+          newsList: []
+        });
+      }
+    } catch (error) {
+      console.error('获取资讯数据失败:', error);
+      wx.showToast({
+        title: '加载失败',
+        icon: 'none'
+      });
+    } finally {
+      this.setData({ loading: false });
+      wx.stopPullDownRefresh();
+    }
+  },
+
+  // 返回首页
+  goBack() {
+    wx.navigateBack();
+  },
+
+  // 查看资讯详情
+  viewNews(e) {
+    const item = e.currentTarget.dataset.item;
+    console.log('查看资讯详情:', item);
+    navigateToWebview(`/allDetailsShow?isFrom=weixin&id=${item.informationId}&type=information`);
+  }
+});

+ 8 - 0
pages/index/news/news.json

@@ -0,0 +1,8 @@
+{
+  "navigationBarTitleText": "展馆资讯",
+  "navigationBarBackgroundColor": "#ffffff",
+  "navigationBarTextStyle": "black",
+  "backgroundColor": "#f5f5f5",
+  "enablePullDownRefresh": false,
+  "usingComponents": {}
+}

+ 36 - 0
pages/index/news/news.wxml

@@ -0,0 +1,36 @@
+<view class="news-container">
+  <!-- 返回按钮 -->
+  <view class="back-button" bindtap="goBack">
+    <image src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/indexPage/icon_back.png" mode="aspectFit"></image>
+  </view>
+
+  <!-- 标题 -->
+  <view class="section-title">展馆资讯</view>
+
+  <!-- 资讯列表部分 -->
+  <view wx:if="{{loading}}" class="loading-container">
+    <view class="loading-text">加载中...</view>
+  </view>
+  <view wx:else class="scroll-container">
+    <view class="scroll-wrapper">
+      <view class="all-item" wx:for="{{newsList}}" wx:key="informationId">
+        <view class="exhibition-item" bindtap="viewNews" data-item="{{item}}">
+          <image src="{{item.infoImg || item.indexImg}}" class="exhibition-img" mode="aspectFill"></image>
+          <image src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/indexPage/Frame.png" class="bg-img" mode="aspectFit"></image>
+          <view class="exhibition-info">
+            <view class="exhibition-title">{{ item.title || '暂无标题' }}</view>
+          </view>
+        </view>
+        <!-- 查看详情按钮 -->
+        <view class="view-details-button" bindtap="viewNews" data-item="{{item}}">
+          <text>查看详情</text>
+          <text class="arrow">></text>
+        </view>
+      </view>
+    </view>
+    <!-- 空状态 -->
+    <view wx:if="{{newsList.length === 0}}" class="empty-state">
+      <view class="empty-text">暂无资讯数据</view>
+    </view>
+  </view>
+</view>

+ 160 - 0
pages/index/news/news.wxss

@@ -0,0 +1,160 @@
+/* pages/index/news/news.wxss */
+.news-container {
+  position: relative;
+  min-height: 100vh;
+  padding: 20rpx;
+  background: url('https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/karamay/bg.png') no-repeat;
+  background-size: cover;
+}
+
+.back-button {
+  position: absolute;
+  top: 20rpx;
+  left: 20rpx;
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 50%;
+  z-index: 10;
+}
+
+.back-button image {
+  width: 80rpx;
+  height: 80rpx;
+}
+
+.section-title {
+  font-size: 40rpx;
+  font-weight: bold;
+  color: #584735;
+  margin: 108rpx 0 40rpx 0;
+  position: relative;
+  padding-bottom: 20rpx;
+}
+
+.section-title::after {
+  content: '';
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  width: 100%;
+  height: 4rpx;
+  background: linear-gradient(90deg, rgba(91, 71, 46, 0.5) 0%, rgba(91, 71, 46, 0) 100%);
+}
+
+/* 加载状态样式 */
+.loading-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 400rpx;
+}
+
+.loading-text {
+  font-size: 36rpx;
+  color: #584735;
+  opacity: 0.8;
+}
+
+/* 空状态样式 */
+.empty-state {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 400rpx;
+  margin-top: 100rpx;
+}
+
+.empty-text {
+  font-size: 36rpx;
+  color: #584735;
+  opacity: 0.6;
+}
+
+/* 滚动容器 */
+.scroll-container {
+  overflow-x: auto;
+}
+
+.scroll-wrapper {
+  display: flex;
+  padding-bottom: 10rpx;
+}
+
+.all-item {
+  position: relative;
+  flex: 0 0 auto;
+  width: 654rpx;
+  height: 1200rpx;
+  margin-right: 20rpx;
+}
+
+.exhibition-item {
+  position: relative;
+  flex: 0 0 auto;
+  width: 654rpx;
+  height: 918rpx;
+  border-radius: 10rpx;
+  overflow: hidden;
+}
+
+.exhibition-item:last-child {
+  margin-right: 0;
+}
+
+.exhibition-img {
+  width: 100%;
+  height: 100%;
+}
+
+.bg-img {
+  position: absolute;
+  width: 602rpx;
+  height: 862rpx;
+  top: 28rpx;
+  left: 28rpx;
+}
+
+.exhibition-info {
+  width: 690rpx;
+  height: 60rpx;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  opacity: 0.8;
+  position: absolute;
+  bottom: 94rpx;
+}
+
+.exhibition-title {
+  font-size: 48rpx;
+  margin: 0;
+  color: #fff;
+  font-weight: 800;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.view-details-button {
+  position: absolute;
+  bottom: 80rpx;
+  left: 50%;
+  transform: translateX(-50%);
+  width: 430rpx;
+  height: 116rpx;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background: linear-gradient(90deg, #B1967B 0%, #9F6F3F 100%);
+  border-radius: 100rpx;
+  font-size: 40rpx;
+  color: #fff;
+}
+
+.view-details-button text {
+  margin-right: 32rpx;
+}
+
+.arrow {
+  font-size: 32rpx;
+}

+ 161 - 0
pages/index/start-preview/start-preview.js

@@ -0,0 +1,161 @@
+const { museumApi } = require('../../../utils/api.js');
+
+Page({
+  data: {
+    selectedTime: '',
+    selectedDate: null,
+    step: 1,
+    morningAvailability: '余票充足',
+    afternoonAvailability: '余票充足',
+    morningTime: '10:00-14:00',
+    afternoonTime: '14:00-18:00',
+    morningId: null,
+    afternoonId: null
+  },
+
+  onLoad(options) {
+    // 页面加载时的逻辑
+  },
+
+  onShow() {
+    // 页面显示时的逻辑
+  },
+
+  // 日期选择回调
+  onDateChange(e) {
+    console.log('选择的日期:', e.detail);
+    const selectedDate = e.detail.dateString;
+    
+    // 只有当日期可选时才设置selectedDate和selectedTime
+    if (selectedDate) {
+      this.setData({
+        selectedDate: selectedDate,
+        selectedTime: 'morning' // 默认选中上午
+      });
+      
+      // 调用API获取当日时段信息
+      this.getSlotsByDate(selectedDate);
+    } else {
+      // 如果日期不可选,清空选择状态
+      this.setData({
+        selectedDate: null,
+        selectedTime: ''
+      });
+    }
+  },
+
+  // 格式化日期
+  formatDate(date) {
+    const year = date.getFullYear();
+    const month = (date.getMonth() + 1).toString().padStart(2, '0');
+    const day = date.getDate().toString().padStart(2, '0');
+    return `${year}-${month}-${day}`;
+  },
+
+  // 调用API获取指定日期的时段信息
+  getSlotsByDate(dateString) {
+    console.log('调用API获取时段信息,日期:', dateString);
+    
+    museumApi.getSlotsByDate(dateString)
+      .then(res => {
+        console.log('API返回数据:', res);
+        if (res) {
+          this.updateAvailability(res);
+        } else {
+          // API调用失败,使用默认值
+          this.setData({
+            morningAvailability: '余票充足',
+            afternoonAvailability: '余票充足'
+          });
+        }
+      })
+      .catch(error => {
+        console.error('API调用失败:', error);
+        // 使用默认值
+        this.setData({
+          morningAvailability: '余票充足',
+          afternoonAvailability: '余票充足'
+        });
+      });
+  },
+
+  // 更新余票显示
+  updateAvailability(data) {
+    // 根据API返回的数据更新余票显示
+    // 只取前两项数据,第一项为上午,第二项为下午
+    let morningText = '余票充足';
+    let afternoonText = '余票充足';
+    let morningTime = '10:00-14:00';
+    let afternoonTime = '14:00-18:00';
+    let morningId = null;
+    let afternoonId = null;
+    console.log('更新余票显示,数据:', data);
+    if (data && Array.isArray(data) && data.length >= 2) {
+      // 第一项为上午
+      const morningData = data[0];
+      morningText = morningData.pcs > 0 ? `余票${morningData.pcs}张` : '已约满';
+      morningTime = morningData.time;
+      morningId = morningData.id;
+      
+      // 第二项为下午
+      const afternoonData = data[1];
+      afternoonText = afternoonData.pcs > 0 ? `余票${afternoonData.pcs}张` : '已约满';
+      afternoonTime = afternoonData.time;
+      afternoonId = afternoonData.id;
+    }
+    
+    this.setData({
+      morningAvailability: morningText,
+      afternoonAvailability: afternoonText,
+      morningTime: morningTime,
+      afternoonTime: afternoonTime,
+      morningId: morningId,
+      afternoonId: afternoonId
+    });
+  },
+
+  // 选择时间段
+  selectTime(e) {
+    const time = e.currentTarget.dataset.time;
+    this.setData({
+      selectedTime: time
+    });
+    console.log('选择的时间段:', time);
+  },
+
+  // 下一步
+  goNext() {
+    if (this.data.selectedTime && this.data.selectedDate) {
+      console.log('选择的时间段:', this.data.selectedTime);
+      console.log('选择的日期:', this.data.selectedDate);
+      
+      // 根据选择的时间段获取对应的id和时间
+      let appointmentSlotsId = null;
+      let actualTime = '';
+      
+      if (this.data.selectedTime === 'morning') {
+        appointmentSlotsId = this.data.morningId;
+        actualTime = this.data.morningTime;
+      } else if (this.data.selectedTime === 'afternoon') {
+        appointmentSlotsId = this.data.afternoonId;
+        actualTime = this.data.afternoonTime;
+      }
+      
+      wx.navigateTo({
+        url: `/pages/index/visit-people/visit-people?date=${this.data.selectedDate}&time=${actualTime}&appointmentSlotsId=${appointmentSlotsId}&type=1`
+      });
+    } else {
+      wx.showToast({
+        title: '请选择日期和时间段',
+        icon: 'none'
+      });
+    }
+  },
+
+  onShareAppMessage() {
+    return {
+      title: '克拉玛依博物馆 - 开始预约',
+      path: '/pages/index/start-preview/start-preview'
+    };
+  }
+});

+ 11 - 0
pages/index/start-preview/start-preview.json

@@ -0,0 +1,11 @@
+{
+  "navigationBarTitleText": "开始预约",
+  "navigationBarBackgroundColor": "#E0D2B4",
+  "navigationBarTextStyle": "black",
+  "backgroundColor": "#E0D2B4",
+  "enablePullDownRefresh": false,
+  "usingComponents": {
+    "time-select": "/components/time-select/time-select",
+    "visit-people": "/pages/index/visit-people/visit-people"
+  }
+}

+ 28 - 0
pages/index/start-preview/start-preview.wxml

@@ -0,0 +1,28 @@
+<view class="preview-contain">
+  <view class="time-select">
+    <time-select bind:datechange="onDateChange"></time-select>
+    <view class="divider"></view>
+    <view class="select-moon" wx:if="{{selectedDate}}">
+      <text class="moon-time">请选择进馆时间</text>
+      <view class="morning-moon">
+        <view class="time-card {{selectedTime === 'morning' ? 'active' : ''}}" bindtap="selectTime" data-time="morning">
+          <view class="time-period">上午</view>
+          <view class="time-range">({{morningTime}})</view>
+          <view class="availability">{{morningAvailability}}</view>
+          <view class="check-icon" wx:if="{{selectedTime === 'morning'}}">✓</view>
+        </view>
+        
+        <view class="time-card {{selectedTime === 'afternoon' ? 'active' : ''}}" bindtap="selectTime" data-time="afternoon">
+          <view class="time-period">下午</view>
+          <view class="time-range">({{afternoonTime}})</view>
+          <view class="availability">{{afternoonAvailability}}</view>
+          <view class="check-icon" wx:if="{{selectedTime === 'afternoon'}}">✓</view>
+        </view>
+        
+        <view class="next-btn {{!selectedTime ? 'disabled' : ''}}" bindtap="goNext" disabled="{{!selectedTime}}">
+          下一步
+        </view>
+      </view>
+    </view>
+  </view>
+</view>

+ 112 - 0
pages/index/start-preview/start-preview.wxss

@@ -0,0 +1,112 @@
+.preview-contain {
+  height: 100vh;
+  background: url('https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/karamay/bg.png') no-repeat center center;
+  background-size: cover;
+  overflow: auto;
+}
+
+.time-select {
+  padding: 32rpx;
+}
+
+.divider {
+  width: 698rpx;
+  height: 4rpx;
+  background: linear-gradient(90deg, #5B472E 0%, rgba(91, 71, 46, 0) 100%);
+  opacity: 0.5;
+  margin-top: 32rpx;
+}
+
+.select-moon {
+  margin-top: 32rpx;
+}
+
+.moon-time {
+  color: #584735;
+  font-size: 32rpx;
+  font-weight: bold;
+  margin-bottom: 32rpx;
+  display: block;
+}
+
+.morning-moon {
+  display: flex;
+  flex-direction: column;
+  gap: 24rpx;
+}
+
+.time-card {
+  position: relative;
+  background: transparent;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  border: 2rpx solid #B1967B;
+  border-radius: 16rpx;
+  padding: 32rpx 40rpx;
+  transition: all 0.3s ease;
+}
+
+.time-card.active {
+  background: rgba(177, 150, 123, 0.4);
+  border-color: #B1967B;
+}
+
+.time-period {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #584735;
+  margin-bottom: 8rpx;
+}
+
+.time-range {
+  font-size: 32rpx;
+  color: #5B472E;
+  margin-bottom: 16rpx;
+}
+
+.availability {
+  font-size: 24rpx;
+  color: #5B472E;
+}
+
+.check-icon {
+  position: absolute;
+  top: 50%;
+  right: 40rpx;
+  transform: translateY(-50%);
+  width: 48rpx;
+  height: 48rpx;
+  background: #B1967B;
+  color: white;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 28rpx;
+  font-weight: bold;
+}
+
+.next-btn {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background: #B1967B;
+  color: white;
+  border: none;
+  border-radius: 16rpx;
+  padding: 32rpx;
+  font-size: 32rpx;
+  font-weight: bold;
+  margin-top: 40rpx;
+  transition: all 0.3s ease;
+}
+
+.next-btn.disabled {
+  background: #ccc;
+}
+
+.next-btn:not(.disabled):active {
+  background: #9A8169;
+}

+ 365 - 0
pages/index/visit-people/visit-people.js

@@ -0,0 +1,365 @@
+// 调用预约API
+const { museumApi } = require('../../../utils/api.js');
+Page({
+  data: {
+    selectedDate: '',
+    selectedTime: '',
+    appointmentSlotsId: null,
+    activityId: 0, // 活动ID,type=2时需要
+    type: 1, // 预约类型:1-普通预约,2-活动预约
+    visitors: [], // 参观人列表
+    showExistingPage: false,
+    showSuccessModal: false,
+    isFormValid: false,
+    idTypes: [{value: 1, name: '身份证'}],
+    existingVisitors: [] // 我的联系人列表
+  },
+
+  onLoad(options) {
+    this.addNewVisitor();
+    // 获取页面参数
+    if (options.date && options.time) {
+      this.setData({
+        selectedDate: options.date,
+        selectedTime: options.time
+      });
+    }
+    
+    // 保存appointmentSlotsId用于后续提交
+    if (options.appointmentSlotsId) {
+      this.setData({
+        appointmentSlotsId: options.appointmentSlotsId
+      });
+    }
+    
+    // 加载已有参观人数据
+    this.loadExistingVisitors();
+  },
+
+  // 新增参观人
+  addNewVisitor() {
+      const visitors = this.data.visitors;
+      visitors.unshift({
+        id: 0, // 新增参观人ID为0
+        name: '',
+        phone: '',
+        cardType: this.data.idTypes[0].value, // 证件类型
+        idCard: '', // 证件号码
+        idType: this.data.idTypes[0].name, // 用于显示
+        idTypeIndex: 0,
+        idCard: '', // 用于显示
+        nameError: '',
+        phoneError: '',
+        idNumberError: '',
+        isFromContacts: false // 标识是否来自联系人
+      });
+      this.setData({
+        visitors: visitors
+      });
+      this.checkFormValid();
+    },
+
+    // 删除参观人
+    removeVisitor(e) {
+      const index = e.currentTarget.dataset.index;
+      const visitors = this.data.visitors;
+      visitors.splice(index, 1);
+      this.setData({
+        visitors: visitors
+      });
+      this.checkFormValid();
+    },
+
+    // 姓名输入
+    onNameInput(e) {
+      const index = e.currentTarget.dataset.index;
+      const value = e.detail.value;
+      const visitors = this.data.visitors;
+      visitors[index].name = value;
+      this.setData({
+        visitors: visitors
+      });
+    },
+
+    // 电话输入
+    onPhoneInput(e) {
+      const index = e.currentTarget.dataset.index;
+      const value = e.detail.value;
+      const visitors = this.data.visitors;
+      visitors[index].phone = value;
+      this.setData({
+        visitors: visitors
+      });
+    },
+
+    // 证件号码输入
+    onIdNumberInput(e) {
+      const index = e.currentTarget.dataset.index;
+      const value = e.detail.value;
+      const visitors = this.data.visitors;
+      visitors[index].idCard = value;
+      visitors[index].idCard = value; // 同步更新API字段
+      this.setData({
+        visitors: visitors
+      });
+    },
+
+    // 证件类型选择
+    onIdTypeChange(e) {
+      const index = e.currentTarget.dataset.index;
+      const value = e.detail.value;
+      const visitors = this.data.visitors;
+      const selectedIdType = this.data.idTypes[value];
+      visitors[index].idTypeIndex = value;
+      visitors[index].idType = selectedIdType.name;
+      visitors[index].cardType = selectedIdType.value; // 同步更新API字段
+      this.setData({
+        visitors: visitors
+      });
+    },
+
+    // 验证姓名
+    validateName(e) {
+      const index = e.currentTarget.dataset.index;
+      const visitors = this.data.visitors;
+      const visitor = visitors[index];
+      
+      if (!visitor.name.trim()) {
+        visitor.nameError = '请输入姓名';
+      } else if (visitor.name.length < 2) {
+        visitor.nameError = '姓名至少2个字符';
+      } else {
+        visitor.nameError = '';
+      }
+      
+      this.setData({
+        visitors: visitors
+      });
+      this.checkFormValid();
+    },
+
+    // 验证电话
+    validatePhone(e) {
+      const index = e.currentTarget.dataset.index;
+      const visitors = this.data.visitors;
+      const visitor = visitors[index];
+      const phoneRegex = /^1[3-9]\d{9}$/;
+      
+      if (!visitor.phone) {
+        visitor.phoneError = '请输入电话号码';
+      } else if (!phoneRegex.test(visitor.phone)) {
+        visitor.phoneError = '请输入正确的11位手机号码';
+      } else {
+        visitor.phoneError = '';
+      }
+      
+      this.setData({
+        visitors: visitors
+      });
+      this.checkFormValid();
+    },
+
+    // 验证证件号码
+    validateIdNumber(e) {
+      const index = e.currentTarget.dataset.index;
+      const visitors = this.data.visitors;
+      const visitor = visitors[index];
+      const idRegex = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
+      
+      if (!visitor.idCard) {
+        visitor.idNumberError = '请输入证件号码';
+      } else if (!idRegex.test(visitor.idCard)) {
+        visitor.idNumberError = '请输入正确的18位身份证号码';
+      } else {
+        visitor.idNumberError = '';
+      }
+      
+      this.setData({
+        visitors: visitors
+      });
+      this.checkFormValid();
+    },
+
+    // 检查表单是否有效
+    checkFormValid() {
+      const visitors = this.data.visitors;
+      if (visitors.length === 0) {
+        this.setData({ isFormValid: false });
+        return;
+      }
+      
+      const isValid = visitors.every(visitor => 
+        visitor.name && 
+        visitor.phone && 
+        visitor.idCard && 
+        !visitor.nameError && 
+        !visitor.phoneError && 
+        !visitor.idNumberError
+      );
+      
+      this.setData({ isFormValid: isValid });
+    },
+
+    // 加载已有参观人数据
+    loadExistingVisitors() {
+      museumApi.getMyVisitors()
+        .then(response => {
+          console.log('获取参观人列表成功:', response);
+          if (response && response.length > 0) {
+            const existingVisitors = response.map(visitor => ({
+              id: visitor.id,
+              name: visitor.name,
+              idCard: visitor.idCard,
+              phone: visitor.phone,
+              selected: false
+            }));
+            this.setData({ existingVisitors });
+          }
+        })
+        .catch(error => {
+          console.error('获取参观人列表失败:', error);
+          // 如果接口失败,可以使用模拟数据
+          this.setData({
+            existingVisitors: [
+              {
+                name: '周明明',
+                idCard: '210112196705041430',
+                phone: '18416573665',
+                selected: false
+              },
+              {
+                name: '李明',
+                idCard: '621124199504251508',
+                phone: '13902376115',
+                selected: false
+              }
+            ]
+          });
+        });
+    },
+
+    // 显示已有参观人
+    showExistingVisitors() {
+      // 每次显示前重新加载数据
+      this.loadExistingVisitors();
+      this.setData({
+        showExistingPage: true
+      });
+    },
+
+    // 切换已有参观人选择状态
+    toggleExistingVisitor(e) {
+      const index = e.currentTarget.dataset.index;
+      const existingVisitors = this.data.existingVisitors;
+      existingVisitors[index].selected = !existingVisitors[index].selected;
+      this.setData({
+        existingVisitors: existingVisitors
+      });
+    },
+
+    // 确认选择已有参观人
+    confirmExistingVisitors() {
+      const selectedVisitors = this.data.existingVisitors.filter(visitor => visitor.selected);
+      const visitors = this.data.visitors;
+      
+      selectedVisitors.forEach(existingVisitor => {
+        // if (visitors.length < 5) {
+          visitors.unshift({
+            id: existingVisitor.id, // 使用联系人的真实ID
+            name: existingVisitor.name,
+            phone: existingVisitor.phone,
+            cardType: this.data.idTypes[0].value, // 证件类型
+            idCard: existingVisitor.idCard, // 证件号码
+            idType: this.data.idTypes[0].name, // 用于显示
+            idTypeIndex: 0,
+            idCard: existingVisitor.idCard, // 用于显示
+            nameError: '',
+            phoneError: '',
+            idNumberError: '',
+            isFromContacts: true // 标识来自联系人
+          });
+        // }
+      });
+      
+      // 重置选择状态
+      const existingVisitors = this.data.existingVisitors;
+      existingVisitors.forEach(visitor => {
+        visitor.selected = false;
+      });
+      
+      this.setData({
+        visitors: visitors,
+        existingVisitors: existingVisitors,
+        showExistingPage: false
+      });
+      
+      this.checkFormValid();
+    },
+
+    // 提交预约
+    submitReservation() {
+      if (!this.data.isFormValid) {
+        wx.showToast({
+          title: '请完善所有参观人信息',
+          icon: 'none'
+        });
+        return;
+      }
+      
+      wx.showLoading({
+        title: '提交中...',
+        mask: true
+      });
+      
+      // 构建API参数
+      const requestData = {
+        activityId: this.data.type === 2 ? this.data.activityId : 0,
+        appointmentSlotsId: this.data.appointmentSlotsId || 0,
+        appointmentTime: this.data.selectedDate,
+        type: this.data.type,
+        visitors: this.data.visitors.map(visitor => ({
+          cardType: this.data.idTypes[visitor.idTypeIndex].value,
+          id: visitor.id, // 新增参观人为0,联系人为真实ID
+          idCard: visitor.idCard,
+          name: visitor.name,
+          phone: visitor.phone
+        }))
+      };
+      
+      console.log('提交预约参数:', requestData);
+      
+      museumApi.submitReservation(requestData)
+        .then(response => {
+          wx.hideLoading();
+          console.log('预约提交成功:', response);
+          
+          this.setData({
+            showSuccessModal: true
+          });
+        })
+        .catch(error => {
+          wx.hideLoading();
+          console.error('预约提交失败:', error);
+          wx.showToast({
+            title: error.message || '预约失败,请重试',
+            icon: 'none'
+          });
+        });
+    },
+
+    // 关闭成功弹窗
+    closeSuccessModal() {
+      this.setData({
+        showSuccessModal: false
+      });
+      // 跳转回首页
+      wx.switchTab({
+        url: '/pages/index/index'
+      });
+    },
+
+  // 阻止事件冒泡
+  stopPropagation() {
+    // 空函数,用于阻止事件冒泡
+  }
+});

+ 4 - 0
pages/index/visit-people/visit-people.json

@@ -0,0 +1,4 @@
+{
+  "component": false,
+  "usingComponents": {}
+}

+ 135 - 0
pages/index/visit-people/visit-people.wxml

@@ -0,0 +1,135 @@
+<view class="visit-people-container">
+  <!-- 预约日期信息 -->
+  <view class="date-info" wx:if="{{!showExistingPage}}">
+    <image class="bg-img" src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/indexPage/img.png" mode="aspectFill"></image>
+    <view class="date-content">
+      <view class="date-label"><text class="title">预约日期:</text>{{selectedDate}}</view>
+      <view class="time-label"><text class="title">入馆时间:</text>{{selectedTime}}</view>
+    </view>
+  </view>
+
+  <!-- 新增参观人页面 -->
+  <view wx:if="{{!showExistingPage}}" class="main-content">
+    <!-- 新增参观人和选择已有参观人按钮 -->
+    <view class="action-buttons">
+      <button class="add-new-btn" bindtap="addNewVisitor">新增参观人</button>
+      <button class="select-existing-btn" bindtap="showExistingVisitors">选择已有参观人</button>
+    </view>
+
+    <!-- 参观人信息列表 -->
+    <scroll-view class="visitor-list" scroll-y="true">
+      <view wx:for="{{visitors}}" wx:key="index" class="visitor-card">
+        <view class="visitor-header">
+          <text class="visitor-title">参观人信息{{index + 1}}</text>
+          <view class="delete-btn" bindtap="removeVisitor" data-index="{{index}}">
+            <image class="delete-img" src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/indexPage/icon_delete.png" />
+            删除
+          </view>
+        </view>
+        
+        <view class="visitor-form">
+          <view class="form-item">
+            <view class="form-label">姓名</view>
+            <view class="form-input">
+              <input 
+                class="bottom-line-input {{item.nameError ? 'error-line' : ''}}"
+                placeholder="请输入您的姓名"
+                value="{{item.name}}"
+                bindinput="onNameInput"
+                bindblur="validateName"
+                data-index="{{index}}"
+              />
+              <view wx:if="{{item.nameError}}" class="error-msg">{{item.nameError}}</view>
+            </view>
+          </view>
+
+          <view class="form-item">
+            <view class="form-label">电话号码</view>
+            <view class="form-input">
+              <input 
+                class="bottom-line-input {{item.phoneError ? 'error-line' : ''}}"
+                placeholder="请输入11位数字"
+                value="{{item.phone}}"
+                bindinput="onPhoneInput"
+                bindblur="validatePhone"
+                data-index="{{index}}"
+                type="number"
+              />
+              <view wx:if="{{item.phoneError}}" class="error-msg">{{item.phoneError}}</view>
+            </view>
+          </view>
+
+          <view class="form-item">
+            <view class="form-label">证件类型</view>
+            <view class="form-input">
+              <picker 
+                class="bottom-line-select"
+                range="{{idTypes}}"
+                range-key="name"
+                value="{{item.idTypeIndex}}"
+                bindchange="onIdTypeChange"
+                data-index="{{index}}"
+              >
+                <view class="picker-text">{{item.idType || '请选择证件类型'}}</view>
+              </picker>
+            </view>
+          </view>
+
+          <view class="form-item">
+            <view class="form-label">证件号码</view>
+            <view class="form-input">
+              <input 
+                class="bottom-line-input {{item.idNumberError ? 'error-line' : ''}}"
+                placeholder="请输入18位证件编码"
+                value="{{item.idCard}}"
+                bindinput="onIdNumberInput"
+                bindblur="validateIdNumber"
+                data-index="{{index}}"
+              />
+              <view wx:if="{{item.idNumberError}}" class="error-msg">{{item.idNumberError}}</view>
+            </view>
+          </view>
+        </view>
+      </view>
+    </scroll-view>
+
+    <!-- 下一步按钮 -->
+    <view class="next-button-container">
+      <view class="next-btn {{!isFormValid ? 'disabled' : ''}}" bindtap="submitReservation" disabled="{{!isFormValid}}">下一步</view>
+    </view>
+  </view>
+
+  <!-- 选择已有参观人页面 -->
+  <view wx:if="{{showExistingPage}}" class="existing-page">
+    <view class="existing-visitors-list">
+      <view 
+        wx:for="{{existingVisitors}}" 
+        wx:key="index" 
+        class="existing-visitor-item"
+        bindtap="toggleExistingVisitor"
+        data-index="{{index}}"
+      >
+        <view class="visitor-info">
+          <view class="visitor-name">姓名:{{item.name}}</view>
+          <view class="visitor-id">证件号:{{item.idCard}}</view>
+          <view class="visitor-phone">电话号码:{{item.phone}}</view>
+        </view>
+        <view class="checkbox {{item.selected ? 'checked' : ''}}">
+          <text wx:if="{{item.selected}}">✓</text>
+        </view>
+      </view>
+    </view>
+    <view class="existing-actions">
+      <button class="confirm-btn" bindtap="confirmExistingVisitors">确定</button>
+    </view>
+  </view>
+
+  <!-- 成功弹窗 -->
+  <view wx:if="{{showSuccessModal}}" class="modal-overlay" bindtap="closeSuccessModal">
+    <view class="success-modal" catchtap="stopPropagation">
+      <view class="success-icon">✓</view>
+      <view class="success-text">预约成功</view>
+      <button class="success-btn" bindtap="closeSuccessModal">确认</button>
+    </view>
+  </view>
+</view>

+ 310 - 0
pages/index/visit-people/visit-people.wxss

@@ -0,0 +1,310 @@
+.visit-people-container {
+  height: 97vh;
+  background: url('https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/karamay/bg.png');
+  padding: 32rpx;
+  padding-bottom: 0;
+  overflow: hidden;
+}
+
+.date-info {
+  height: 212rpx;
+  position: relative;
+  margin: 4rpx;
+  margin-top: 0;
+}
+
+.bg-img {
+  width: 100%;
+  height: 95%;
+}
+
+.date-content {
+  position: absolute;
+  width: 70%;
+  top: 40rpx;
+  left: 70rpx;
+  background: rgba(255, 255, 255, 0.9);
+  border-radius: 16rpx;
+  text-align: left;
+}
+
+.date-label, .time-label {
+  font-size: 32rpx;
+  color: #333;
+  margin: 10rpx 0;
+}
+
+.title {
+  color: #B1967B;
+}
+
+.action-buttons {
+  display: flex;
+  gap: 20rpx;
+  padding: 0 32rpx;
+  margin-bottom: 40rpx;
+}
+
+.add-new-btn, .select-existing-btn {
+  flex: 1;
+  padding: 24rpx;
+  border: none;
+  border-radius: 12rpx;
+  font-size: 28rpx;
+}
+
+.add-new-btn {
+  background-color: #B1967B;
+  color: #fff;
+}
+
+.select-existing-btn {
+  background-color: transparent;
+  color: #A78A6D;
+  border: 2rpx solid #B1967B;
+}
+
+.visitor-list {
+  height: calc(100vh - 460rpx - 64rpx);
+  overflow-y: auto;
+  padding-bottom: 20rpx;
+}
+
+.visitor-card {
+  background: white;
+  border-radius: 16rpx;
+  padding: 40rpx 40rpx 20rpx 40rpx;
+  margin-bottom: 30rpx;
+  box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.1);
+}
+
+.visitor-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 40rpx;
+  padding-bottom: 20rpx;
+  border-bottom: 2rpx solid #ddd;
+}
+
+.visitor-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #B1967B;
+}
+
+.delete-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: none;
+  border: none;
+  color: #F45151;
+  font-size: 28rpx;
+  line-height: 36rpx;
+}
+.delete-img{
+  width: 32rpx;
+  height: 32rpx;
+  margin-right: 4rpx;
+}
+.visitor-form {
+  padding: 0;
+}
+
+.form-item {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: 32rpx;
+  min-height: 80rpx;
+}
+
+.form-label {
+  width: 160rpx;
+  font-size: 28rpx;
+  color: #333;
+  margin-top: 20rpx;
+  flex-shrink: 0;
+}
+
+.form-input {
+  flex: 1;
+  margin-left: 20rpx;
+}
+
+.bottom-line-input {
+  width: 100%;
+  padding: 20rpx 0;
+  border: none;
+  border-bottom: 2rpx solid #ddd;
+  font-size: 28rpx;
+  background: transparent;
+}
+
+.bottom-line-input.error-line {
+  border-bottom-color: #F45151;
+}
+
+.bottom-line-select {
+  width: 100%;
+  padding: 20rpx 0;
+  border-bottom: 2rpx solid #ddd;
+  font-size: 28rpx;
+}
+
+.picker-text {
+  color: #333;
+}
+
+.error-msg {
+  color: #F45151;
+  font-size: 24rpx;
+  margin-top: 10rpx;
+}
+
+.next-button-container {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  padding: 30rpx;
+}
+
+.next-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 90%;
+  padding: 30rpx;
+  background: #B1967B;
+  color: white;
+  border: none;
+  border-radius: 12rpx;
+  font-size: 32rpx;
+  font-weight: bold;
+}
+
+.next-btn.disabled {
+  background: #ccc;
+}
+
+/* 选择已有参观人页面样式 */
+.existing-page {
+  padding: 32rpx;
+  height: 100vh;
+}
+
+.existing-visitors-list {
+  height: calc(100vh - 200rpx);
+  overflow-y: auto;
+}
+
+.existing-visitor-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  background: white;
+  border-radius: 16rpx;
+  padding: 30rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.1);
+}
+
+.visitor-info {
+  flex: 1;
+}
+
+.visitor-name, .visitor-id, .visitor-phone {
+  font-size: 28rpx;
+  color: #333;
+  margin-bottom: 10rpx;
+}
+
+.checkbox {
+  width: 48rpx;
+  height: 48rpx;
+  border: 2rpx solid #B1967B;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 28rpx;
+  color: white;
+}
+
+.checkbox.checked {
+  background: #B1967B;
+}
+
+.existing-actions {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  padding: 30rpx;
+  background: white;
+  border-top: 2rpx solid #eee;
+}
+
+.confirm-btn {
+  width: 100%;
+  padding: 30rpx;
+  background: #B1967B;
+  color: white;
+  border: none;
+  border-radius: 12rpx;
+  font-size: 32rpx;
+  font-weight: bold;
+}
+
+/* 成功弹窗样式 */
+.modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1000;
+}
+
+.success-modal {
+  background: white;
+  border-radius: 20rpx;
+  padding: 60rpx;
+  text-align: center;
+  width: 500rpx;
+}
+
+.success-icon {
+  width: 80rpx;
+  height: 80rpx;
+  background: #52c41a;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin: 0 auto 30rpx;
+  color: white;
+  font-size: 40rpx;
+  font-weight: bold;
+}
+
+.success-text {
+  font-size: 36rpx;
+  color: #333;
+  margin-bottom: 40rpx;
+}
+
+.success-btn {
+  width: 100%;
+  padding: 24rpx;
+  background: #B1967B;
+  color: white;
+  border: none;
+  border-radius: 12rpx;
+  font-size: 32rpx;
+}

+ 42 - 0
pages/index/visit-preview/visit-preview.js

@@ -0,0 +1,42 @@
+Page({
+  data: {
+    
+  },
+
+  onLoad(options) {
+    // 页面加载时的逻辑
+    console.log(111111)
+  },
+
+  onShow() {
+    // 页面显示时的逻辑
+  },
+
+  // 返回首页
+  goBack() {
+    wx.switchTab({
+      url: '/pages/index/index'
+    });
+  },
+
+  // 开始预约
+  startPreview() {
+    wx.navigateTo({
+      url: '/pages/index/start-preview/start-preview'
+    });
+  },
+
+  // 查看我的参观人
+  viewMyVisitors() {
+    wx.navigateTo({
+      url: '/pages/index/my-visitors/my-visitors'
+    });
+  },
+
+  onShareAppMessage() {
+    return {
+      title: '克拉玛依博物馆 - 预约参观',
+      path: '/pages/index/visitpreview/visitpreview'
+    };
+  }
+});

+ 8 - 0
pages/index/visit-preview/visit-preview.json

@@ -0,0 +1,8 @@
+{
+  "navigationBarTitleText": "预约参观",
+  "navigationBarBackgroundColor": "#E0D2B4",
+  "navigationBarTextStyle": "black",
+  "backgroundColor": "#E0D2B4",
+  "enablePullDownRefresh": false,
+  "usingComponents": {}
+}

+ 43 - 0
pages/index/visit-preview/visit-preview.wxml

@@ -0,0 +1,43 @@
+<view class="visit-preview-container">
+  <!-- <view class="back-button" bindtap="goBack">
+    <image src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/indexPage/icon_back.png" mode="aspectFit"></image>
+  </view> -->
+  
+  <view class="header-image">
+    <image src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/indexPage/zhanweitu.png" class="main-image" mode="aspectFill" />
+  </view>
+  
+  <view class="reservation-info">
+    <view class="title">预约须知</view>
+    
+    <view class="time-info">
+      <view class="time-section">
+        <text class="time-title">开馆时间</text>
+        <view class="time-value">10:00</view>
+      </view>
+      <view class="line"></view>
+      <view class="time-section">
+        <text class="time-title">闭馆时间</text>
+        <view class="time-value">18:00</view>
+      </view>
+      <view class="divider"></view>
+      <view class="time-section">
+        <view class="stop-time">17:00</view>
+        <view class="stop-desc">停止检票</view>
+      </view>
+    </view>
+    
+    <view class="time-note">周一闭馆(法定节假日除外,除夕、正月初一闭馆)。</view>
+    
+    <view class="reservation-title">预约参观</view>
+    <view class="reservation-rules">
+      <text class="rule-content">①所有观众(包括享受优待政策的观众及其陪同人员)<text class="notice">均须实名预约</text>。放票时间为<text class="notice">每日18:00,可提前7日(不包括当日)通过官方微信小程序预约</text>,预约时段分为上午(9:00-13:00)和下午(12:00-16:00),当天不再放票;</text>
+      <text class="rule-content">②参观当日须按照预约时段入馆参观,错过预约时段将谢绝入馆。<text class="notice">同一证件号</text>当天可进出馆2次,预约上午票的观众首次入馆时间必须在13:00前;</text>
+    </view>
+    
+    <view class="reservation-button">
+      <view class="btn-reserve" bindtap="startPreview">参观预约</view>
+      <view class="btn-people" bindtap="viewMyVisitors">我的参观人</view>
+    </view>
+  </view>
+</view>

+ 167 - 0
pages/index/visit-preview/visit-preview.wxss

@@ -0,0 +1,167 @@
+.visit-preview-container {
+  position: relative;
+  min-height: 100vh;
+  background: #fff;
+  color: #412A12;
+}
+
+.back-button {
+  position: absolute;
+  top: 40rpx;
+  left: 40rpx;
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 50%;
+  z-index: 10;
+}
+
+.back-button image {
+  width: 80rpx;
+  height: 80rpx;
+}
+
+.header-image {
+  height: 480rpx;
+}
+
+.main-image {
+  width: 100%;
+  height: 100%;
+}
+
+.reservation-info {
+  position: relative;
+  margin-top: -80rpx;
+  width: 100%;
+  min-height: calc(100vh - 400rpx);
+  background: #E0D2B4;
+  border-top-left-radius: 60rpx;
+  border-top-right-radius: 60rpx;
+  padding: 60rpx;
+  padding-bottom: 60rpx;
+  box-sizing: border-box;
+}
+
+.title {
+  font-size: 40rpx;
+  margin-bottom: 40rpx;
+  color: #584735;
+  font-weight: bold;
+}
+
+.time-info {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 20rpx;
+}
+
+.time-section {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.time-title {
+  font-size: 28rpx;
+  color: #B1967B;
+  margin-bottom: 10rpx;
+}
+
+.time-value {
+  font-size: 80rpx;
+  font-weight: bold;
+  color: #94765A;
+}
+
+.line {
+  height: 4rpx;
+  width: 26rpx;
+  background: #584735;
+  margin: 48rpx 12rpx 0 12rpx;
+}
+
+.divider {
+  height: 100rpx;
+  width: 4rpx;
+  background: #584735;
+  margin: 0 30rpx;
+}
+
+.stop-time {
+  font-size: 28rpx;
+  color: #B1967B;
+  margin-bottom: 10rpx;
+}
+
+.stop-desc {
+  font-size: 28rpx;
+  color: #B1967B;
+}
+
+.time-note {
+  width: 454rpx;
+  font-size: 28rpx;
+  color: #B1967B;
+  margin-bottom: 40rpx;
+  line-height: 1.5;
+}
+
+.reservation-title {
+  font-size: 32rpx;
+  margin-bottom: 20rpx;
+  color: #584735;
+  font-weight: bold;
+}
+
+.reservation-rules {
+  font-size: 28rpx;
+  line-height: 1.5;
+  color: #B1967B;
+  margin-bottom: 40rpx;
+}
+
+.rule-content {
+  color: #B1967B;
+  display: block;
+  margin-bottom: 20rpx;
+}
+
+.notice {
+  color: #94765A;
+  font-weight: bold;
+}
+
+.reservation-button {
+  display: flex;
+  flex-direction: column;
+  margin-top: 40rpx;
+}
+
+.btn-reserve {
+  background: url('https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/karamay/preview-btn.png');
+  width: 660rpx;
+  height: 186rpx;
+  border-radius: 10rpx;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background-size: 100% auto;
+  color: #fff;
+  font-size: 32rpx;
+  font-weight: bold;
+}
+
+.btn-people {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 660rpx;
+  height: 128rpx;
+  background: rgba(255, 255, 255, 0.5);
+  border-radius: 10rpx;
+  border: 2rpx solid #94765A;
+  margin-top: 20rpx;
+  color: #94765A;
+  font-size: 32rpx;
+}

+ 18 - 0
pages/logs/logs.js

@@ -0,0 +1,18 @@
+// logs.js
+const util = require('../../utils/util.js')
+
+Page({
+  data: {
+    logs: []
+  },
+  onLoad() {
+    this.setData({
+      logs: (wx.getStorageSync('logs') || []).map(log => {
+        return {
+          date: util.formatTime(new Date(log)),
+          timeStamp: log
+        }
+      })
+    })
+  }
+})

+ 4 - 0
pages/logs/logs.json

@@ -0,0 +1,4 @@
+{
+  "usingComponents": {
+  }
+}

+ 6 - 0
pages/logs/logs.wxml

@@ -0,0 +1,6 @@
+<!--logs.wxml-->
+<scroll-view class="scrollarea" scroll-y type="list">
+  <block wx:for="{{logs}}" wx:key="timeStamp" wx:for-item="log">
+    <view class="log-item">{{index + 1}}. {{log.date}}</view>
+  </block>
+</scroll-view>

+ 16 - 0
pages/logs/logs.wxss

@@ -0,0 +1,16 @@
+page {
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+}
+.scrollarea {
+  flex: 1;
+  overflow-y: hidden;
+}
+.log-item {
+  margin-top: 20rpx;
+  text-align: center;
+}
+.log-item:last-child {
+  padding-bottom: env(safe-area-inset-bottom);
+}

+ 156 - 0
pages/user/feedback/index.js

@@ -0,0 +1,156 @@
+const { museumApi } = require('../../../utils/api.js');
+
+Page({
+  data: {
+    formData: {
+      title: '',
+      content: '',
+      name: '',
+      email: '',
+      captcha: '',
+      feedbackId: 0
+    }
+  },
+
+  onLoad() {
+    // 页面加载时的初始化
+  },
+
+  // 标题输入
+  onTitleInput(e) {
+    this.setData({
+      'formData.title': e.detail.value
+    });
+  },
+
+  // 内容输入
+  onContentInput(e) {
+    this.setData({
+      'formData.content': e.detail.value
+    });
+  },
+
+  // 姓名输入
+  onNameInput(e) {
+    this.setData({
+      'formData.name': e.detail.value
+    });
+  },
+
+  // 邮箱输入
+  onEmailInput(e) {
+    this.setData({
+      'formData.email': e.detail.value
+    });
+  },
+
+  // 验证码输入
+  onCaptchaInput(e) {
+    this.setData({
+      'formData.captcha': e.detail.value
+    });
+  },
+
+  // 验证表单
+  validateForm() {
+    const { title, content, name, email } = this.data.formData;
+    
+    if (!title.trim()) {
+      wx.showToast({
+        title: '请输入标题',
+        icon: 'none'
+      });
+      return false;
+    }
+    
+    if (!content.trim()) {
+      wx.showToast({
+        title: '请输入反馈内容',
+        icon: 'none'
+      });
+      return false;
+    }
+    
+    if (!name.trim()) {
+      wx.showToast({
+        title: '请输入姓名',
+        icon: 'none'
+      });
+      return false;
+    }
+    
+    if (!email.trim()) {
+      wx.showToast({
+        title: '请输入邮箱',
+        icon: 'none'
+      });
+      return false;
+    }
+    
+    // 简单的邮箱格式验证
+    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+    if (!emailRegex.test(email)) {
+      wx.showToast({
+        title: '请输入正确的邮箱格式',
+        icon: 'none'
+      });
+      return false;
+    }
+    
+    return true;
+  },
+
+  // 提交反馈
+  onSubmit() {
+    if (!this.validateForm()) {
+      return;
+    }
+    
+    wx.showLoading({
+      title: '提交中...'
+    });
+    
+    const submitData = {
+      title: this.data.formData.title.trim(),
+      content: this.data.formData.content.trim(),
+      name: this.data.formData.name.trim(),
+      email: this.data.formData.email.trim(),
+      feedbackId: this.data.formData.feedbackId
+    };
+    
+    museumApi.submitFeedback(submitData)
+      .then((res) => {
+        wx.showToast({
+          title: '提交成功',
+          icon: 'success',
+          duration: 2000
+        });
+        
+        // 清空表单
+        this.setData({
+          formData: {
+            title: '',
+            content: '',
+            name: '',
+            email: '',
+            captcha: '',
+            feedbackId: 0
+          }
+        });
+        
+        // 延迟返回上一页
+        setTimeout(() => {
+          wx.navigateBack();
+        }, 2000);
+        wx.hideLoading();
+      })
+      .catch((error) => {
+        console.error('提交反馈失败:', error);
+        wx.showToast({
+          title: error.message || '提交失败,请重试',
+          icon: 'none'
+        });
+        wx.hideLoading();
+      });
+  }
+});

+ 5 - 0
pages/user/feedback/index.json

@@ -0,0 +1,5 @@
+{
+  "navigationBarTitleText": "意见反馈",
+  "navigationBarBackgroundColor": "#ffffff",
+  "navigationBarTextStyle": "black"
+}

+ 43 - 0
pages/user/feedback/index.wxml

@@ -0,0 +1,43 @@
+<view class="feedback-container">
+  <view class="feedback-title">意见反馈</view>
+  
+  <view class="form-section">
+    <!-- 标题 -->
+    <view class="form-item">
+      <view class="form-label">标题</view>
+      <input class="form-input" placeholder="请输入标题" value="{{formData.title}}" bindinput="onTitleInput" />
+    </view>
+    
+    <!-- 内容 -->
+    <view class="form-item">
+      <view class="form-label">内容</view>
+      <textarea class="form-textarea" placeholder="请输入反馈内容" value="{{formData.content}}" bindinput="onContentInput" maxlength="500"></textarea>
+    </view>
+    
+    <!-- 姓名 -->
+    <view class="form-item">
+      <view class="form-label">姓名</view>
+      <input class="form-input" placeholder="请输入姓名" value="{{formData.name}}" bindinput="onNameInput" />
+    </view>
+    
+    <!-- 邮箱 -->
+    <view class="form-item">
+      <view class="form-label">邮箱</view>
+      <input class="form-input" placeholder="请输入邮箱" value="{{formData.email}}" bindinput="onEmailInput" type="email" />
+    </view>
+    
+    <!-- 验证码 -->
+    <view class="form-item captcha-item">
+      <view class="form-label">验证码</view>
+      <view class="captcha-container">
+        <input class="captcha-input" placeholder="请输入验证码" value="{{formData.captcha}}" bindinput="onCaptchaInput" />
+        <view class="captcha-code">UW6</view>
+      </view>
+    </view>
+  </view>
+  
+  <!-- 提交按钮 -->
+  <view class="submit-section">
+    <view class="submit-btn" bindtap="onSubmit">提交</view>
+  </view>
+</view>

+ 133 - 0
pages/user/feedback/index.wxss

@@ -0,0 +1,133 @@
+.feedback-container {
+  min-height: 100vh;
+  background-image: url('https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/karamay/bg.png');
+  padding: 40rpx;
+}
+
+.feedback-title {
+  font-size: 40rpx;
+  font-weight: bold;
+  color: #584735;
+  text-align: left;
+  margin-bottom: 20rpx;
+}
+
+.form-section {
+  border-radius: 20rpx;
+  /* padding: 40rpx; */
+  margin-bottom: 60rpx;
+}
+
+.form-item {
+  margin-bottom: 40rpx;
+}
+
+.form-item:last-child {
+  margin-bottom: 0;
+}
+
+.form-label {
+  font-size: 32rpx;
+  color: #584735;
+  margin-bottom: 20rpx;
+  font-weight: 400;
+}
+
+.form-input {
+  width: 100%;
+  height: 80rpx;
+  border: 1rpx solid #B1967B;
+  border-radius: 10rpx;
+  padding: 0 20rpx;
+  font-size: 28rpx;
+  color: #333;
+  background-color: #fff;
+  box-sizing: border-box;
+}
+
+.form-input:focus {
+  border-color: #584735;
+}
+
+.form-textarea {
+  width: 100%;
+  min-height: 200rpx;
+  border: 1rpx solid #B1967B;
+  border-radius: 10rpx;
+  padding: 20rpx;
+  font-size: 28rpx;
+  color: #333;
+  background-color: #fff;
+  box-sizing: border-box;
+}
+
+.form-textarea:focus {
+  border-color: #584735;
+}
+
+.captcha-item {
+  position: relative;
+}
+
+.captcha-container {
+  display: flex;
+  align-items: center;
+  gap: 20rpx;
+}
+
+.captcha-input {
+  flex: 1;
+  height: 80rpx;
+  border: 2rpx solid #B1967B;
+  border-radius: 10rpx;
+  padding: 0 20rpx;
+  font-size: 28rpx;
+  color: #333;
+  background-color: #fff;
+  box-sizing: border-box;
+}
+
+.captcha-input:focus {
+  border-color: #584735;
+}
+
+.captcha-code {
+  width: 120rpx;
+  height: 80rpx;
+  background: linear-gradient(135deg, #ff6b9d, #c44569);
+  border-radius: 10rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #fff;
+  font-size: 32rpx;
+  font-weight: bold;
+  letter-spacing: 4rpx;
+}
+
+.submit-section {
+  /* padding: 0 20rpx; */
+}
+
+.submit-btn {
+  width: 100%;
+  height: 88rpx;
+  background: linear-gradient(135deg, #8b7355, #a0896b);
+  border-radius: 10rpx;
+  color: #fff;
+  font-size: 32rpx;
+  font-weight: 500;
+  border: none;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.submit-btn:active {
+  opacity: 0.8;
+}
+
+.submit-btn[disabled] {
+  background: #ccc;
+  color: #999;
+}

+ 178 - 0
pages/user/index.js

@@ -0,0 +1,178 @@
+// index.js
+const defaultAvatarUrl = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0'
+const app = getApp();
+Page({
+  data: {
+    motto: 'Hello World',
+    userInfo: {
+      avatarUrl: defaultAvatarUrl,
+      nickName: '',
+    },
+    hasUserInfo: false,
+    canIUseGetUserProfile: wx.canIUse('getUserProfile'),
+    canIUseNicknameComp: wx.canIUse('input.type.nickname'),
+  },
+  onLoad() {
+    if(wx.getStorageSync('userInfo')){
+      this.setData({
+        userInfo: wx.getStorageSync('userInfo'),
+        hasUserInfo: true
+      })
+    }
+  },
+  bindViewTap() {
+    wx.navigateTo({
+      url: '../logs/logs'
+    })
+  },
+  onChooseAvatar(e) {
+    const { avatarUrl } = e.detail
+    const { nickName } = this.data.userInfo
+    
+    // 更新页面数据
+    this.setData({
+      "userInfo.avatarUrl": avatarUrl,
+      hasUserInfo: nickName && avatarUrl && avatarUrl !== defaultAvatarUrl,
+    })
+    
+    // 更新本地存储和全局数据
+    const updatedUserInfo = {
+      ...this.data.userInfo,
+      avatarUrl: avatarUrl,
+      realUserInfo: true
+    }
+    
+    wx.setStorageSync('userInfo', updatedUserInfo)
+    if (app && app.globalData) {
+      app.globalData.userInfo = updatedUserInfo
+    }
+    
+    console.log('头像已更新:', avatarUrl)
+  },
+  onInputChange(e) {
+    const nickName = e.detail.value
+    const { avatarUrl } = this.data.userInfo
+    
+    // 更新页面数据
+    this.setData({
+      "userInfo.nickName": nickName,
+      hasUserInfo: nickName && avatarUrl && avatarUrl !== defaultAvatarUrl,
+    })
+    
+    // 更新本地存储和全局数据
+    const updatedUserInfo = {
+      ...this.data.userInfo,
+      nickName: nickName,
+      realUserInfo: true
+    }
+    
+    wx.setStorageSync('userInfo', updatedUserInfo)
+    if (app && app.globalData) {
+      app.globalData.userInfo = updatedUserInfo
+    }
+    
+    console.log('昵称已更新:', nickName)
+  },
+  getUserProfile(e) {
+    // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认,开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
+    wx.getUserProfile({
+      desc: '展示用户信息', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
+      success: (res) => {
+        console.log(res)
+        this.setData({
+          userInfo: res.userInfo,
+          hasUserInfo: true
+        })
+      }
+    })
+  },
+  // 跳转到我的参观人页面
+  goToUserList() {
+    wx.navigateTo({
+      url: '/pages/user/userList/index'
+    })
+  },
+
+  // 跳转到我的预约
+  startPreview() {
+    console.log(11111)
+    wx.navigateTo({
+      url: '/pages/user/my-preview/index'
+    })
+  },
+
+  // 意见反馈
+  goToFeedback() {
+    wx.navigateTo({
+      url: '/pages/user/feedback/index'
+    });
+  },
+
+  // 退出登录
+  logout() {
+    const token = wx.getStorageSync('token');
+    if (!token) {
+      wx.showToast({
+        title: '您还未登录',
+        icon: 'none'
+      });
+      return;
+    }
+    
+    wx.showModal({
+      title: '提示',
+      content: '确定要退出登录吗?',
+      success: (res) => {
+        if (res.confirm) {
+          // 清除本地存储的token和用户信息
+          wx.removeStorageSync('token');
+          wx.removeStorageSync('userInfo');
+          if (app && app.globalData) {
+            app.globalData.token = null;
+            app.globalData.userInfo = null;
+          }
+          
+          // 清除页面用户信息,设置为未登录状态
+          this.setData({
+            userInfo: {
+              avatarUrl: defaultAvatarUrl,
+              nickName: '',
+            },
+            hasUserInfo: false,
+          });
+          
+          wx.showToast({
+            title: '已退出登录',
+            icon: 'success',
+            duration: 1500
+          });
+          
+          // // 延迟跳转到首页
+          // setTimeout(() => {
+          //   wx.switchTab({
+          //     url: '/pages/index/index'
+          //   });
+          // }, 1500);
+        }
+      }
+    });
+  },
+  startLogin() {
+    // 触发自动登录
+    if (app && app.wxLogin) {
+      wx.showToast({
+        title: '正在登录',
+        icon: 'none',
+        duration: 1000
+      });
+      app.wxLogin();
+      this.setData({
+        userInfo: {
+          avatarUrl: defaultAvatarUrl,
+          nickName: '微信用户',
+        },
+        hasUserInfo: true,
+      });
+    }
+  }
+})

+ 4 - 0
pages/user/index.json

@@ -0,0 +1,4 @@
+{
+  "usingComponents": {
+  }
+}

+ 43 - 0
pages/user/index.wxml

@@ -0,0 +1,43 @@
+<!--index.wxml-->
+<scroll-view class="scrollarea" scroll-y type="list">
+  <view class="user-container">
+    <view class="userinfo">
+      <block wx:if="{{canIUseNicknameComp && !hasUserInfo}}">
+        <button class="avatar-wrapper" open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar">
+          <image class="avatar" src="{{userInfo.avatarUrl}}"></image>
+        </button>
+        <view class="nickname-wrapper" wx:if="{{userInfo.nickName}}">
+          <input type="nickname" class="nickname-input" placeholder="{{userInfo.nickName || '请登录'}}" bind:change="onInputChange" />
+        </view>
+        <view class="login-in" wx:else bindtap="startLogin">
+          请登录
+        </view>
+      </block>
+      <block wx:elif="{{!hasUserInfo}}">
+        <button wx:if="{{canIUseGetUserProfile}}" bindtap="getUserProfile"> 获取头像昵称 </button>
+      </block>
+      <block wx:else>
+        <image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>
+        <text class="userinfo-nickname">{{userInfo.nickName}}</text>
+      </block>
+    </view>
+    <view class="user-title">
+      我的预约
+    </view>
+    <view class="user-zhanwei">
+      <image class="zhanwei-img" src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/img_03.png"></image>
+    </view>
+    <!-- 功能按钮区域 -->
+    <view class="function-section">
+      <view class="main-buttons">
+        <view class="btn-reserve" bindtap="startPreview">我的预约</view>
+        <view class="btn-people" bindtap="goToUserList">我的参观人</view>
+      </view>
+      
+      <view class="secondary-buttons">
+        <view class="btn-feed" bindtap="goToFeedback">意见反馈</view>
+        <view class="btn-out" bindtap="logout">退出登录</view>
+      </view>
+    </view>
+  </view>
+</scroll-view>

+ 156 - 0
pages/user/index.wxss

@@ -0,0 +1,156 @@
+/**index.wxss**/
+page {
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+}
+.user-container{
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: space-between;
+  padding: 40rpx;
+  box-sizing: border-box;
+
+}
+.scrollarea {
+  flex: 1;
+  overflow-y: hidden;
+}
+
+.userinfo {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  color: #aaa;
+  width: 80%;
+}
+
+.userinfo-avatar {
+  overflow: hidden;
+  width: 128rpx;
+  height: 128rpx;
+  margin: 20rpx;
+  border-radius: 50%;
+}
+
+.usermotto {
+  margin-top: 200px;
+}
+
+.avatar-wrapper {
+  padding: 0;
+  width: 56px !important;
+  border-radius: 8px;
+  margin-top: 40rpx;
+  margin-bottom: 40rpx;
+}
+
+.avatar {
+  display: block;
+  width: 56px;
+  height: 56px;
+}
+
+.nickname-wrapper {
+  display: flex;
+  width: 100%;
+  padding: 16px;
+  box-sizing: border-box;
+  color: black;
+}
+
+.nickname-label {
+  width: 105px;
+}
+
+.nickname-input {
+  text-align: center;
+  flex: 1;
+}
+.login-in{
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+.user-title{
+  width: 100%;
+  font-weight: bold;
+  color: #584735;
+  text-align: left;
+  font-size: 40rpx;
+  margin: 20rpx 0 20rpx 0;
+}
+.user-zhanwei{
+  width: 100%;
+  height: 14px;
+  margin-bottom: 20rpx;
+}
+.zhanwei-img{
+  width: 100%;
+  height: 14px;
+}
+/* 功能按钮区域 */
+.function-section {
+  width: 100%;
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+}
+
+.main-buttons {
+  margin-bottom: 20rpx;
+}
+
+.btn-reserve {
+  background: url('https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/karamay/preview-btn.png');
+  width: 100%;
+  height: 130rpx;
+  border-radius: 10rpx;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background-size: 100% auto;
+  color: #fff;
+  font-size: 32rpx;
+  font-weight: bold;
+}
+
+.btn-people {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 100%;
+  height: 130rpx;
+  background: url('https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/karamay/btn_02.png');
+  background-size: 100% auto;
+  border-radius: 10rpx;
+  border: 2rpx solid #94765A;
+  margin-top: 20rpx;
+  color: #fff;
+  font-size: 32rpx;
+}
+.btn-feed{
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 100%;
+  height: 130rpx;
+  background: #fff;
+  border-radius: 5px 5px 5px 5px;
+  border: 1px solid #FFFFFF;
+  color: #C8583D;
+  margin-bottom: 20rpx;
+}
+.btn-out{
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 100%;
+  height: 130rpx;
+  border-radius: 5px 5px 5px 5px;
+  border: 1px solid #D9D9D9;
+  color: #584735;
+}

+ 220 - 0
pages/user/my-preview/index.js

@@ -0,0 +1,220 @@
+// pages/user/my-preview/index.js
+const { museumApi } = require('../../../utils/api.js');
+
+Page({
+
+  /**
+   * 页面的初始数据
+   */
+  data: {
+    reservations: [],
+    pageNum: 1,
+    pageSize: 10,
+    hasMore: true,
+    loading: false,
+    type: 0 // 预约类型:0-全部,1-参观,2-活动
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad(options) {
+    // 重置数据并加载第一页
+    this.setData({
+      reservations: [],
+      pageNum: 1,
+      hasMore: true
+    });
+    this.loadReservations();
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload() {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh() {
+    // 重置数据并重新加载
+    this.setData({
+      reservations: [],
+      pageNum: 1,
+      hasMore: true
+    });
+    this.loadReservations().finally(() => {
+      wx.stopPullDownRefresh();
+    });
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom() {
+    this.loadMoreReservations();
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage() {
+
+  },
+
+  /**
+   * 加载预约数据
+   */
+  loadReservations() {
+    if (this.data.loading) return Promise.resolve();
+    
+    this.setData({ loading: true });
+    
+    return museumApi.getMyReservations({
+      pageNum: this.data.pageNum,
+      pageSize: this.data.pageSize,
+      type: this.data.type
+    }).then(res => {
+      console.log('预约列表数据:', res);
+      
+      if (res) {
+        const newReservations = this.formatReservationData(res.records || []);
+        const allReservations = this.data.pageNum === 1 ? newReservations : [...this.data.reservations, ...newReservations];
+        
+        this.setData({
+          reservations: allReservations,
+          hasMore: res.records && res.records.length === this.data.pageSize,
+          loading: false
+        });
+      } else {
+        wx.showToast({
+          title: res.message || '获取预约数据失败',
+          icon: 'none'
+        });
+        this.setData({ loading: false });
+      }
+    }).catch(error => {
+      console.error('获取预约数据失败:', error);
+      wx.showToast({
+        title: '网络错误,请重试',
+        icon: 'none'
+      });
+      this.setData({ loading: false });
+    });
+  },
+
+  /**
+   * 加载更多预约数据
+   */
+  loadMoreReservations() {
+    if (!this.data.hasMore || this.data.loading) return;
+    
+    this.setData({
+      pageNum: this.data.pageNum + 1
+    });
+    this.loadReservations();
+  },
+
+  /**
+   * 格式化预约数据
+   */
+  formatReservationData(records) {
+    const currentDate = new Date();
+    currentDate.setHours(0, 0, 0, 0); // 设置为当天0点,便于日期比较
+    
+    return records.map(item => {
+      const appointmentDate = new Date(item.appointmentTime);
+      appointmentDate.setHours(0, 0, 0, 0);
+      const isExpired = appointmentDate < currentDate;
+      
+      return {
+        id: item.id,
+        type: item.type === 1 ? 'normal' : 'activity', // 1-参观,2-活动
+        date: this.formatDate(item.appointmentTime),
+        time: item.time || '',
+        activityName: item.activityId || '',
+        status: item.status,
+        visitors: item.visitors || [],
+        isExpired: isExpired
+      };
+    });
+  },
+
+  /**
+   * 格式化日期
+   */
+  formatDate(dateString) {
+    if (!dateString) return '';
+    const date = new Date(dateString);
+    const year = date.getFullYear();
+    const month = String(date.getMonth() + 1).padStart(2, '0');
+    const day = String(date.getDate()).padStart(2, '0');
+    return `${year}.${month}.${day}`;
+  },
+
+  /**
+   * 取消预约
+   */
+  cancelReservation(e) {
+    const reservationId = e.currentTarget.dataset.id;
+    
+    wx.showModal({
+      title: '确认取消',
+      content: '确定要取消这个预约吗?',
+      success: (res) => {
+        if (res.confirm) {
+          // 这里调用取消预约的API
+          this.performCancelReservation(reservationId);
+        }
+      }
+    });
+  },
+
+  /**
+   * 执行取消预约操作
+   */
+  performCancelReservation(reservationId) {
+    // 模拟API调用
+    wx.showLoading({
+      title: '取消中...'
+    });
+    
+    setTimeout(() => {
+      wx.hideLoading();
+      
+      // 从列表中移除已取消的预约
+      const reservations = this.data.reservations.filter(item => item.id !== reservationId);
+      this.setData({
+        reservations
+      });
+      
+      wx.showToast({
+        title: '取消成功',
+        icon: 'success'
+      });
+    }, 1000);
+  }
+})

+ 7 - 0
pages/user/my-preview/index.json

@@ -0,0 +1,7 @@
+{
+  "usingComponents": {},
+  "navigationBarTitleText": "我的预约",
+  "enablePullDownRefresh": true,
+  "backgroundColor": "#f5f5f5",
+  "backgroundTextStyle": "dark"
+}

+ 82 - 0
pages/user/my-preview/index.wxml

@@ -0,0 +1,82 @@
+<view class="preview-container">
+  <!-- 预约卡片列表 -->
+  <scroll-view class="reservation-list" scroll-y="true">
+    <view wx:for="{{reservations}}" wx:key="id" class="reservation-card">
+      <image class="card-bg" src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/img_05.png" mode="aspectFill"></image>
+      
+      <!-- 预约信息区域 -->
+      <view class="reservation-info">
+        <view class="date-time-section">
+          <view class="info-row">
+            <text class="label">预约日期:</text>
+            <text class="value">{{item.date}}</text>
+          </view>
+          <view class="info-row" wx:if="{{item.type === 'normal'}}">
+            <text class="label">入馆时间:</text>
+            <text class="value">{{item.time}}</text>
+          </view>
+          <view class="info-row" wx:if="{{item.type === 'activity'}}">
+            <text class="label">活动预约:</text>
+            <text class="value">{{item.activityName}}</text>
+          </view>
+        </view>
+        
+        <!-- 取消按钮 (仅普通预约且未过期显示) -->
+        <view wx:if="{{!item.isExpired}}" class="cancel-btn" bindtap="cancelReservation" data-id="{{item.id}}">
+          <text>取消</text>
+        </view>
+        
+        <!-- 印章图标 (活动预约或已过期的普通预约显示) -->
+        <view wx:if="{{item.isExpired}}" class="stamp-icon">
+          <image src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/icon_cancel.png" mode="aspectFit"></image>
+        </view>
+      </view>
+      
+      <!-- 参观人信息区域 -->
+      <view class="visitors-section">
+        <view wx:for="{{item.visitors}}" wx:for-item="visitor" wx:for-index="visitorIndex" wx:key="*this">
+          <view class="section-title">参观人信息{{visitorIndex + 1}}</view>
+          <view class="visitor-info">
+            <view class="visitor-detail">
+              <text class="visitor-label">参观人姓名:</text>
+              <text class="visitor-value">{{visitor.name}}</text>
+            </view>
+            <view class="visitor-detail">
+              <text class="visitor-label">身份证号:</text>
+              <text class="visitor-value">{{visitor.idCard}}</text>
+            </view>
+            <view class="visitor-detail" wx:if="{{visitor.phone}}">
+              <text class="visitor-label">电话号码:</text>
+              <text class="visitor-value">{{visitor.phone}}</text>
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+    
+    <!-- 加载更多提示 -->
+    <view wx:if="{{reservations.length > 0 && hasMore}}" class="load-more">
+      <view wx:if="{{loading}}" class="loading-text">
+        <text>加载中...</text>
+      </view>
+      <view wx:else class="load-more-text">
+        <text>上拉加载更多</text>
+      </view>
+    </view>
+    
+    <!-- 没有更多数据提示 -->
+    <view wx:if="{{reservations.length > 0 && !hasMore}}" class="no-more">
+      <text class="no-more-text">没有更多数据了</text>
+    </view>
+    
+    <!-- 空状态 -->
+    <view wx:if="{{reservations.length === 0 && !loading}}" class="empty-state">
+      <text class="empty-text">暂无预约记录</text>
+    </view>
+    
+    <!-- 首次加载状态 -->
+    <view wx:if="{{reservations.length === 0 && loading}}" class="first-loading">
+      <text class="loading-text">加载中...</text>
+    </view>
+  </scroll-view>
+</view>

+ 217 - 0
pages/user/my-preview/index.wxss

@@ -0,0 +1,217 @@
+/* 容器样式 */
+.preview-container {
+  width: 100%;
+  height: 100vh;
+  background-image: url('https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/karamay/bg.png');
+  overflow: auto;
+}
+
+/* 预约列表容器 */
+.reservation-list {
+  width: 100%;
+  height: 100vh;
+  padding: 40rpx 30rpx;
+  box-sizing: border-box;
+}
+
+/* 预约卡片样式 */
+.reservation-card {
+  position: relative;
+  width: 100%;
+  height: 736rpx;
+  margin-bottom: 40rpx;
+  border-radius: 20rpx;
+  overflow: hidden;
+  box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
+}
+
+/* 卡片背景图 */
+.card-bg {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 1;
+}
+
+/* 预约信息区域 */
+.reservation-info {
+  position: absolute;
+  top: 10rpx;
+  width: 90%;
+  z-index: 1;
+  padding: 40rpx 0 0 40rpx;
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+}
+
+/* 日期时间区域 */
+.date-time-section {
+  flex: 1;
+}
+
+/* 信息行 */
+.info-row {
+  display: flex;
+  align-items: center;
+  margin-bottom: 20rpx;
+}
+
+.info-row:last-child {
+  margin-bottom: 0;
+}
+
+/* 标签样式 */
+.label {
+  font-size: 32rpx;
+  color: #584735;
+  font-weight: bold;
+  margin-right: 10rpx;
+}
+
+/* 值样式 */
+.value {
+  font-size: 32rpx;
+  color: #B1967B;
+}
+
+/* 取消按钮 */
+.cancel-btn {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 80px;
+  height: 40px;
+  border-radius: 6rpx;
+  border: 1px solid #B1967B;
+  color: #B1967B;
+  font-size: 32rpx;
+}
+
+/* 印章图标 */
+.stamp-icon {
+  width: 136rpx;
+  height: 136rpx;
+}
+
+.stamp-icon image {
+  width: 100%;
+  height: 100%;
+}
+
+/* 参观人信息区域 */
+.visitors-section {
+  position: absolute;
+  top: 240rpx;
+  z-index: 1;
+  padding: 0 40rpx 20rpx;
+  max-height: 600rpx;
+  overflow-y: auto;
+}
+
+/* 区域标题 */
+.section-title {
+  font-size: 28rpx;
+  color: #333;
+  font-weight: 600;
+  margin-bottom: 20rpx;
+  margin-top: 30rpx;
+}
+
+.section-title:first-child {
+  margin-top: 0;
+}
+
+/* 参观人信息 */
+.visitor-info {
+  margin-bottom: 20rpx;
+}
+
+/* 参观人详情 */
+.visitor-detail {
+  display: flex;
+  align-items: center;
+  margin-bottom: 15rpx;
+}
+
+.visitor-detail:last-child {
+  margin-bottom: 0;
+}
+
+/* 参观人标签 */
+.visitor-label {
+  font-size: 32rpx;
+  color: #584735;
+  margin-right: 10rpx;
+  min-width: 140rpx;
+}
+
+/* 参观人值 */
+.visitor-value {
+  font-size: 32rpx;
+  color: #B1967B;
+  flex: 1;
+}
+
+/* 加载更多样式 */
+.load-more {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 30rpx 0;
+}
+
+.load-more-text {
+  font-size: 28rpx;
+  color: #999;
+}
+
+.loading-text {
+  font-size: 28rpx;
+  color: #666;
+}
+
+/* 没有更多数据样式 */
+.no-more {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 30rpx 0;
+}
+
+.no-more-text {
+  font-size: 28rpx;
+  color: #ccc;
+}
+
+/* 首次加载样式 */
+.first-loading {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 400rpx;
+  margin-top: 100rpx;
+}
+
+/* 空状态样式 */
+.empty-state {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 400rpx;
+  margin-top: 100rpx;
+}
+
+.empty-text {
+  font-size: 32rpx;
+  color: #999;
+  text-align: center;
+}
+
+/* 滚动条样式 */
+::-webkit-scrollbar {
+  width: 0;
+  background: transparent;
+}

+ 237 - 0
pages/user/userList/index.js

@@ -0,0 +1,237 @@
+const { museumApi } = require('../../../utils/api.js');
+Page({
+  data: {
+    showAddForm: false,
+    contactList: [],
+    newVisitor: {
+      name: '',
+      phone: '',
+      idType: '身份证',
+      idCard: '',
+      nameError: '',
+      phoneError: '',
+      idNumberError: ''
+    },
+    idTypes: ['身份证']
+  },
+
+  onLoad() {
+    this.fetchContactList();
+  },
+
+  onPullDownRefresh() {
+    this.fetchContactList().then(() => {
+      wx.stopPullDownRefresh();
+    });
+  },
+
+  // 显示新增参观人表单
+  showAddVisitor() {
+    this.setData({ showAddForm: true });
+    this.resetForm();
+  },
+
+  // 隐藏新增表单
+  hideAddForm() {
+    this.setData({ showAddForm: false });
+  },
+
+  // 重置表单
+  resetForm() {
+    this.setData({
+      newVisitor: {
+        name: '',
+        phone: '',
+        idType: '身份证',
+        idCard: '',
+        nameError: '',
+        phoneError: '',
+        idNumberError: ''
+      }
+    });
+  },
+
+  // 表单输入处理
+  onNameInput(e) {
+    this.setData({
+      'newVisitor.name': e.detail.value,
+      'newVisitor.nameError': ''
+    });
+  },
+
+  onPhoneInput(e) {
+    this.setData({
+      'newVisitor.phone': e.detail.value,
+      'newVisitor.phoneError': ''
+    });
+  },
+
+  onIdNumberInput(e) {
+    this.setData({
+      'newVisitor.idCard': e.detail.value,
+      'newVisitor.idNumberError': ''
+    });
+  },
+
+  onIdTypeChange(e) {
+    this.setData({
+      'newVisitor.idType': this.data.idTypes[e.detail.value]
+    });
+  },
+
+  // 验证姓名
+  validateName() {
+    const name = this.data.newVisitor.name.trim();
+    if (!name) {
+      this.setData({ 'newVisitor.nameError': '请输入姓名' });
+      return false;
+    }
+    if (!/^[\u4e00-\u9fa5a-zA-Z]+$/.test(name)) {
+      this.setData({ 'newVisitor.nameError': '姓名只能包含中文和英文字母' });
+      return false;
+    }
+    this.setData({ 'newVisitor.nameError': '' });
+    return true;
+  },
+
+  // 验证电话号码
+  validatePhone() {
+    const phone = this.data.newVisitor.phone.trim();
+    if (!phone) {
+      this.setData({ 'newVisitor.phoneError': '请输入电话号码' });
+      return false;
+    }
+    if (!/^1[3-9]\d{9}$/.test(phone)) {
+      this.setData({ 'newVisitor.phoneError': '请输入正确的11位手机号码' });
+      return false;
+    }
+    this.setData({ 'newVisitor.phoneError': '' });
+    return true;
+  },
+
+  // 验证身份证号码
+  validateIdNumber() {
+    const idCard = this.data.newVisitor.idCard.trim();
+    if (!idCard) {
+      this.setData({ 'newVisitor.idNumberError': '请输入证件号码' });
+      return false;
+    }
+    if (!/^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/.test(idCard)) {
+      this.setData({ 'newVisitor.idNumberError': '请输入正确的18位身份证号码' });
+      return false;
+    }
+    this.setData({ 'newVisitor.idNumberError': '' });
+    return true;
+  },
+
+  // 确认添加
+  confirmAdd() {
+    // 验证所有字段
+    const isNameValid = this.validateName();
+    const isPhoneValid = this.validatePhone();
+    const isIdNumberValid = this.validateIdNumber();
+
+    if (!isNameValid || !isPhoneValid || !isIdNumberValid) {
+      return;
+    }
+
+    // 显示加载提示
+    wx.showLoading({
+      title: '添加中...'
+    });
+
+    this.addContactToServer(this.data.newVisitor)
+      .then(() => {
+        wx.showToast({
+          title: '添加成功',
+          icon: 'success'
+        });
+        // 添加成功后回到联系人列表并刷新数据
+        this.setData({ showAddForm: false });
+        this.fetchContactList();
+        wx.hideLoading();
+      })
+      .catch(error => {
+        wx.hideLoading();
+        console.error('添加联系人失败:', error);
+        wx.showToast({
+          title: '添加失败,请重试',
+          icon: 'none'
+        });
+      });
+  },
+
+  // 删除联系人
+  editContact(e) {
+    const index = e.currentTarget.dataset.index;
+    const contact = this.data.contactList[index];
+    
+    wx.showModal({
+      title: '确认删除',
+      content: `确定要删除联系人"${contact.name}"吗?`,
+      success: (res) => {
+        if (res.confirm) {
+          this.deleteContactFromServer(contact.id || contact.visitorId, index);
+        }
+      }
+    });
+  },
+
+  // API调用 - 删除联系人
+  deleteContactFromServer(id, index) {
+    
+    wx.showLoading({
+      title: '删除中...'
+    });
+    
+    museumApi.deleteVisitor(id)
+      .then(() => {
+        wx.hideLoading();
+        wx.showToast({
+          title: '删除成功',
+          icon: 'success'
+        });
+        // 删除成功后刷新列表
+        this.fetchContactList();
+      })
+      .catch(error => {
+        wx.hideLoading();
+        console.error('删除联系人失败:', error);
+        wx.showToast({
+          title: '删除失败,请重试',
+          icon: 'none'
+        });
+      });
+  },
+
+  // API调用 - 添加联系人
+  addContactToServer(contact) {
+    
+    // 构造API参数
+    const params = {
+      cardType: contact.idType === '身份证' ? '1' : '1', // 暂时都设为身份证类型
+      id: 0, // 新增时为0
+      idCard: contact.idCard,
+      name: contact.name,
+      phone: contact.phone
+    };
+    
+    return museumApi.addVisitor(params);
+  },
+
+  // 获取联系人列表
+  fetchContactList() {
+    return museumApi.getMyVisitors()
+      .then(response => {
+        console.log('获取参观人列表成功:', response);
+        // 如果没有数据,使用模拟数据
+        const contactList = response || [];
+        this.setData({ contactList });
+      })
+      .catch(error => {
+        console.error('获取参观人列表失败:', error);
+        // 使用模拟数据
+        this.setData({ contactList: this.getMockContactList() });
+      });
+  },
+});

+ 9 - 0
pages/user/userList/index.json

@@ -0,0 +1,9 @@
+{
+  "navigationBarTitleText": "我的参观人",
+  "navigationBarBackgroundColor": "#ffffff",
+  "navigationBarTextStyle": "black",
+  "backgroundColor": "#f8f8f8",
+  "enablePullDownRefresh": true,
+  "usingComponents": {
+  }
+}

+ 116 - 0
pages/user/userList/index.wxml

@@ -0,0 +1,116 @@
+<view class="user-list-container">
+  <!-- 联系人列表页面 -->
+  <view wx:if="{{!showAddForm}}" class="contact-list-page">
+    <!-- 我的参观人标题 -->
+    <view class="page-title">
+      <text class="title-text">我的参观人</text>
+    </view>
+
+    <!-- 联系人列表 -->
+    <scroll-view class="contact-list" scroll-y>
+      <view wx:for="{{contactList}}" wx:key="index" class="contact-card">
+        <view class="contact-info">
+          <view class="contact-row">
+            <text class="label">姓名</text>
+            <text class="value">{{item.name}}</text>
+            <view class="edit-btn" bindtap="editContact" data-index="{{index}}">
+              <image class="delete-img" src="https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/imgs/indexPage/icon_delete.png" />
+              删除
+            </view>
+          </view>
+          <view class="contact-row">
+            <text class="label">证件号</text>
+            <text class="value">{{item.idCard}}</text>
+          </view>
+          <view class="contact-row">
+            <text class="label">电话号码</text>
+            <text class="value">{{item.phone}}</text>
+          </view>
+        </view>
+      </view>
+      <!-- <view class="tips">温馨提示:每个账号最多添加5个常用参观人</view> -->
+    </scroll-view>
+
+    <!-- 新增参观人按钮 -->
+    <view class="add-button-container">
+      <view class="add-visitor-btn" bindtap="showAddVisitor">新增参观人</view>
+    </view>
+  </view>
+
+  <!-- 新增参观人表单页面 -->
+  <view wx:if="{{showAddForm}}" class="add-form-page">
+    <!-- 新增参观人标题 -->
+    <view class="page-title">
+      <text class="title-text">新增参观人</text>
+    </view>
+
+    <!-- 新增表单 -->
+    <scroll-view class="form-container" scroll-y>
+      <view class="visitor-card">
+        <view class="visitor-form">
+          <view class="form-item">
+            <view class="form-label">姓名</view>
+            <view class="form-input">
+              <input 
+                class="bottom-line-input {{newVisitor.nameError ? 'error-line' : ''}}"
+                placeholder="请输入您的姓名"
+                value="{{newVisitor.name}}"
+                bindinput="onNameInput"
+                bindblur="validateName"
+              />
+              <view wx:if="{{newVisitor.nameError}}" class="error-msg">{{newVisitor.nameError}}</view>
+            </view>
+          </view>
+
+          <view class="form-item">
+            <view class="form-label">电话号码</view>
+            <view class="form-input">
+              <input 
+                class="bottom-line-input {{newVisitor.phoneError ? 'error-line' : ''}}"
+                placeholder="请输入11位数字"
+                value="{{newVisitor.phone}}"
+                type="number"
+                bindinput="onPhoneInput"
+                bindblur="validatePhone"
+              />
+              <view wx:if="{{newVisitor.phoneError}}" class="error-msg">{{newVisitor.phoneError}}</view>
+            </view>
+          </view>
+
+          <view class="form-item">
+            <view class="form-label">证件类型</view>
+            <view class="form-input">
+              <picker 
+                class="bottom-line-select"
+                range="{{idTypes}}"
+                value="{{0}}"
+                bindchange="onIdTypeChange"
+              >
+                <view class="picker-text">{{newVisitor.idType}}</view>
+              </picker>
+            </view>
+          </view>
+
+          <view class="form-item">
+            <view class="form-label">证件号码</view>
+            <view class="form-input">
+              <input 
+                class="bottom-line-input {{newVisitor.idNumberError ? 'error-line' : ''}}"
+                placeholder="请输入18位证件编码"
+                value="{{newVisitor.idCard}}"
+                bindinput="onIdNumberInput"
+                bindblur="validateIdNumber"
+              />
+              <view wx:if="{{newVisitor.idNumberError}}" class="error-msg">{{newVisitor.idNumberError}}</view>
+            </view>
+          </view>
+        </view>
+      </view>
+    </scroll-view>
+
+    <!-- 确认按钮 -->
+    <view class="confirm-button-container">
+      <view class="confirm-btn" bindtap="confirmAdd">确认</view>
+    </view>
+  </view>
+</view>

+ 218 - 0
pages/user/userList/index.wxss

@@ -0,0 +1,218 @@
+/* 主容器 */
+.user-list-container {
+  height: 100vh;
+  background: url('https://swkz-1332577016.cos.ap-guangzhou.myqcloud.com/karamay/bg.png') no-repeat center center;
+  background-size: cover;
+  overflow: hidden;
+  padding: 0 32rpx;
+}
+
+/* 页面标题 */
+.page-title {
+  text-align: left;
+  margin: 40rpx 0;
+}
+
+.title-text {
+  color: #584735;
+  font-size: 40rpx;
+  font-weight: bold;
+}
+
+/* 联系人列表 */
+.contact-list {
+  height: calc(100vh - 360rpx);
+  margin-bottom: 40rpx;
+}
+
+.contact-card {
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 32rpx;
+  margin-bottom: 24rpx;
+  box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.1);
+}
+
+.contact-info .contact-row {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 24rpx;
+}
+
+.contact-info .contact-row:last-child {
+  margin-bottom: 0;
+}
+
+.contact-row .label {
+  color: #B1967B;
+  font-size: 28rpx;
+  min-width: 120rpx;
+}
+
+.contact-row .value {
+  flex: 1;
+  color: #584735;
+  font-size: 28rpx;
+  margin-left: 24rpx;
+}
+
+.contact-row .edit-btn {
+  display: flex;
+  background: none;
+  border: none;
+  color: #F45151;
+  font-size: 28rpx;
+  padding: 0;
+  margin: 0;
+  line-height: 36rpx;
+}
+.delete-img{
+  width: 32rpx;
+  height: 32rpx;
+  margin-right: 4rpx;
+}
+.tips {
+  width: 100%;
+  text-align: center;
+  color: #B1967B;
+  font-size: 24rpx;
+  margin-top: 20rpx;
+}
+
+/* 新增按钮容器 */
+.add-button-container {
+  position: absolute;
+  bottom: 48rpx;
+  left: 32rpx;
+  right: 32rpx;
+}
+
+.add-visitor-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 96rpx;
+  background: #B1967B;
+  color: white;
+  border: none;
+  border-radius: 10rpx;
+  font-size: 32rpx;
+  line-height: 96rpx;
+}
+
+.add-visitor-btn::after {
+  border: none;
+}
+
+/* 表单容器 */
+.form-container {
+  height: calc(100vh - 240rpx);
+  margin-bottom: 40rpx;
+}
+
+.visitor-card {
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 40rpx 40rpx 60rpx;
+  box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.1);
+}
+
+.visitor-form {
+  padding: 0;
+}
+
+.form-item {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: 60rpx;
+  position: relative;
+}
+
+.form-item:last-child {
+  margin-bottom: 0;
+}
+
+.form-label {
+  min-width: 160rpx;
+  color: #B1967B;
+  font-size: 28rpx;
+  line-height: 64rpx;
+  margin-right: 40rpx;
+}
+
+.form-input {
+  flex: 1;
+  position: relative;
+}
+
+/* 输入框样式 */
+.bottom-line-input {
+  width: 100%;
+  height: 64rpx;
+  border: none;
+  border-bottom: 2rpx solid rgba(88, 71, 53, 0.2);
+  background: transparent;
+  text-align: right;
+  font-size: 28rpx;
+  color: #584735;
+}
+
+.bottom-line-input.error-line {
+  border-bottom-color: #F45151;
+}
+
+/* 选择器样式 */
+.bottom-line-select {
+  width: 100%;
+  height: 64rpx;
+  border: none;
+  border-bottom: 2rpx solid rgba(88, 71, 53, 0.2);
+  background: transparent;
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+}
+
+.picker-text {
+  font-size: 28rpx;
+  color: #584735;
+  text-align: right;
+}
+
+/* 错误信息 */
+.error-msg {
+  position: absolute;
+  bottom: -40rpx;
+  right: 0;
+  color: #F45151;
+  font-size: 24rpx;
+}
+
+/* 确认按钮容器 */
+.confirm-button-container {
+  position: absolute;
+  bottom: 48rpx;
+  left: 32rpx;
+  right: 32rpx;
+}
+
+.confirm-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 96rpx;
+  background: #B1967B;
+  color: white;
+  border: none;
+  border-radius: 16rpx;
+  font-size: 32rpx;
+  font-weight: bold;
+  line-height: 96rpx;
+}
+
+.confirm-btn::after {
+  border: none;
+}

+ 12 - 0
pages/webview/index.js

@@ -0,0 +1,12 @@
+Page({
+  data: {
+    url: ''
+  },
+  onLoad: function(options) {
+    if (options.url) {
+      this.setData({
+        url: decodeURIComponent(options.url)
+      });
+    }
+  }
+})

+ 3 - 0
pages/webview/index.json

@@ -0,0 +1,3 @@
+{
+  "pageOrientation": "auto"
+}

+ 1 - 0
pages/webview/index.wxml

@@ -0,0 +1 @@
+<web-view src="{{url}}"></web-view>

+ 0 - 0
pages/webview/index.wxss


+ 41 - 0
project.config.json

@@ -0,0 +1,41 @@
+{
+  "compileType": "miniprogram",
+  "libVersion": "3.8.8",
+  "packOptions": {
+    "ignore": [],
+    "include": []
+  },
+  "setting": {
+    "coverView": true,
+    "es6": true,
+    "postcss": true,
+    "minified": true,
+    "enhance": true,
+    "showShadowRootInWxmlPanel": true,
+    "packNpmRelationList": [],
+    "babelSetting": {
+      "ignore": [],
+      "disablePlugins": [],
+      "outputPath": ""
+    },
+    "compileWorklet": false,
+    "uglifyFileName": false,
+    "uploadWithSourceMap": true,
+    "packNpmManually": false,
+    "minifyWXSS": true,
+    "minifyWXML": true,
+    "localPlugins": false,
+    "condition": false,
+    "swc": false,
+    "disableSWC": true,
+    "disableUseStrict": false,
+    "useCompilerPlugins": false
+  },
+  "condition": {},
+  "editorSetting": {
+    "tabIndent": "auto",
+    "tabSize": 2
+  },
+  "appid": "wx6b8ed0132dae7d3e",
+  "simulatorPluginLibVersion": {}
+}

+ 24 - 0
project.private.config.json

@@ -0,0 +1,24 @@
+{
+  "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
+  "projectname": "karamay",
+  "setting": {
+    "compileHotReLoad": true,
+    "urlCheck": false,
+    "coverView": true,
+    "lazyloadPlaceholderEnable": false,
+    "skylineRenderEnable": false,
+    "preloadBackgroundData": false,
+    "autoAudits": false,
+    "useApiHook": true,
+    "useApiHostProcess": true,
+    "showShadowRootInWxmlPanel": true,
+    "useStaticServer": false,
+    "useLanDebug": false,
+    "showES6CompileOption": false,
+    "bigPackageSizeSupport": false,
+    "checkInvalidKey": true,
+    "ignoreDevUnusedFiles": true
+  },
+  "libVersion": "3.8.7",
+  "condition": {}
+}

+ 7 - 0
sitemap.json

@@ -0,0 +1,7 @@
+{
+  "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
+  "rules": [{
+  "action": "allow",
+  "page": "*"
+  }]
+}

+ 321 - 0
utils/api.js

@@ -0,0 +1,321 @@
+// 小程序API接口模块
+const { request } = require('./request.js');
+
+/**
+ * 工具函数
+ */
+const utils = {
+  // 检查是否有token
+  hasToken() {
+    const app = getApp();
+    return (app && app.globalData && app.globalData.token) || wx.getStorageSync('token');
+  },
+  
+  // 获取当前token
+  getToken() {
+    const app = getApp();
+    if (app && app.globalData && app.globalData.token) {
+      return app.globalData.token;
+    }
+    return wx.getStorageSync('token');
+  },
+  
+  // 模拟登录获取token
+  simulateLogin() {
+    return new Promise((resolve, reject) => {
+      wx.request({
+        url: 'https://sit-kelamayi.4dage.com/api/museum/wxMini/testlogin',
+        method: 'GET',
+        header: {
+          'content-type': 'application/x-www-form-urlencoded'
+        },
+        success: (response) => {
+          console.log('模拟登录接口调用成功:', response.data);
+          
+          if (response.data && response.data.code === 0) {
+            const app = getApp();
+            // 存储token到全局数据
+            if (app && app.globalData) {
+              app.globalData.token = response.data.data.token;
+              app.globalData.userInfo = response.data.data.user;
+            }
+            
+            // 也存储到本地存储
+            wx.setStorageSync('token', response.data.data.token);
+            wx.setStorageSync('userInfo', response.data.data.user);
+            
+            console.log('Token获取成功:', response.data.data.token);
+            resolve(response.data.data);
+          } else {
+            const errorMsg = response.data.message || '模拟登录失败';
+            console.error('模拟登录失败:', errorMsg);
+            reject(new Error(errorMsg));
+          }
+        },
+        fail: (error) => {
+          console.error('模拟登录接口调用失败:', error);
+          reject(error);
+        }
+      });
+    });
+  }
+};
+
+/**
+ * 博物馆相关API
+ */
+const museumApi = {
+  // 获取轮播图列表
+  getCarouselList(params = {}) {
+    return request({
+      url: '/museum/carousel/page',
+      method: 'POST',
+      data: {
+        pageNum: 1,
+        pageSize: 10,
+        status: 1,
+        ...params,
+      },
+    });
+  },
+
+  // 获取资讯列表
+  getNewsList(params = {}) {
+    return request({
+      url: '/museum/information/page',
+      method: 'POST',
+      data: {
+        pageNum: 1,
+        pageSize: 3,
+        status: 1,
+        ...params,
+      },
+    });
+  },
+
+  // 获取展览列表
+  getExhibitionList(params = {}) {
+    return request({
+      url: '/museum/exhibit/page',
+      method: 'POST',
+      data: {
+        pageNum: 1,
+        pageSize: 5,
+        status: 1,
+        ...params,
+      },
+    });
+  },
+
+  // 社教活动列表
+  getSocialActivityList(params = {}) {
+    return request({
+      url: '/museum/activity/page',
+      method: 'POST',
+      data: {
+        pageNum: 1,
+        pageSize: 5,
+        status: 1,
+        ...params,
+      },
+    });
+  },
+
+  // 获取博物馆详情
+  getMuseumDetail(id) {
+    return request({
+      url: `/museum/introContext/info/${id}`,
+      method: 'post',
+    });
+  },
+
+  // 获取展览详情
+  getExhibitionDetail(id) {
+    return request({
+      url: `/museum/exhibit/${id}`,
+      method: 'post',
+    });
+  },
+
+  // 获取展览详情(用于展览页面)
+  getExhibitDetail(id) {
+    return request({
+      url: `/museum/exhibit/${id}`,
+      method: 'post',
+    });
+  },
+
+  // 获取活动详情
+  getActivityDetail(id) {
+    return request({
+      url: `/museum/activity/info/${id}`,
+      method: 'post',
+    });
+  },
+
+  // 获取活动详情(用于活动预约页面)
+  getActivityInfo(id) {
+    return request({
+      url: `/museum/activity/info/${id}`,
+      method: 'post',
+    });
+  },
+
+  // 获取资讯详情
+  getNewsDetail(id) {
+    return request({
+      url: `/museum/information/info/${id}`,
+      method: 'post',
+    });
+  },
+
+  // 获取收藏列表
+  getCollectionList(params = {}) {
+    return request({
+      url: '/museum/artifact/page',
+      method: 'POST',
+      data: {
+        pageNum: 1,
+        pageSize: 10,
+        status: 1,
+        ...params,
+      },
+      useToken: true // 收藏相关接口需要token
+    });
+  },
+
+  // 获取收藏详情
+  getCollectionDetail(id) {
+    return request({
+      url: `/museum/artifact/${id}`,
+      method: 'post',
+      useToken: true // 收藏相关接口需要token
+    });
+  },
+
+  // 添加收藏
+  addCollection(id) {
+    return request({
+      url: `/museum/artifact/collect/${id}`,
+      method: 'post',
+      useToken: true
+    });
+  },
+
+  // 取消收藏
+  removeCollection(id) {
+    return request({
+      url: `/museum/artifact/uncollect/${id}`,
+      method: 'post',
+      useToken: true
+    });
+  },
+
+  // 获取用户收藏状态
+  getCollectionStatus(id) {
+    return request({
+      url: `/museum/artifact/collectStatus/${id}`,
+      method: 'get',
+      useToken: true
+    });
+  },
+
+  // 获取用户信息
+  getUserInfo() {
+    return request({
+      url: '/museum/user/info',
+      method: 'get',
+      useToken: true
+    });
+  },
+
+  // 更新用户信息
+  updateUserInfo(data) {
+    return request({
+      url: '/museum/user/update',
+      method: 'post',
+      data,
+      useToken: true
+    });
+  },
+
+  // 获取指定日期的预约时段信息
+  getSlotsByDate(dateString) {
+    return request({
+      url: '/museum/appointmentSlots/getSlotsByDate',
+      method: 'POST',
+      data: {
+        time: dateString
+      },
+      useToken: true
+    });
+  },
+
+  // 获取我的参观人列表
+  getMyVisitors() {
+    return request({
+      url: '/museum/appointmentVisitors/listMyVisitors',
+      method: 'post',
+      useToken: true
+    });
+  },
+
+  // 提交预约
+  submitReservation(params) {
+    return request({
+      url: '/museum/appointment/reservation',
+      method: 'POST',
+      data: params,
+      useToken: true
+    });
+  },
+
+  // 获取我的预约列表
+  getMyReservations(params = {}) {
+    return request({
+      url: '/museum/appointment/pageMy',
+      method: 'POST',
+      data: {
+        pageNum: 1,
+        pageSize: 10,
+        type: 0, // 预约类型:0-全部,1-参观,2-活动
+        ...params
+      },
+      useToken: true
+    });
+  },
+
+  // 新增参观人
+  addVisitor(params) {
+    return request({
+      url: '/museum/appointmentVisitors/add',
+      method: 'POST',
+      data: params,
+      useToken: true
+    });
+  },
+
+  // 删除参观人
+  deleteVisitor(id) {
+    return request({
+      url: `/museum/appointmentVisitors/del/${id}`,
+      method: 'POST',
+      useToken: true
+    });
+  },
+
+  // 提交意见反馈
+  submitFeedback(params) {
+    return request({
+      url: '/museum/feedback/add',
+      method: 'POST',
+      data: params,
+      useToken: true
+    });
+  }
+};
+
+module.exports = {
+  museumApi,
+  utils
+};

+ 74 - 0
utils/htmlProcessor.js

@@ -0,0 +1,74 @@
+/**
+ * HTML内容处理工具 - 小程序版本
+ * 用于处理富文本内容中的格式问题
+ */
+
+/**
+ * 处理HTML内容
+ * @param {string} htmlContent - 原始HTML内容
+ * @returns {string} 处理后的HTML内容
+ */
+function processHtmlContent(htmlContent) {
+  if (!htmlContent) return ''
+
+  let processedContent = htmlContent
+
+  // 1. 处理段落中的前导空格,转换为缩进
+  processedContent = processedContent.replace(/<p>\s+([^<]*)<\/p>/g, '<p class="indent">$1</p>')
+
+  // 2. 处理段落内部的多个连续空格
+  processedContent = processedContent.replace(
+    /<p>([^<]*?)\s{2,}([^<]*?)<\/p>/g,
+    '<p><span class="indent-text">$1</span>$2</p>',
+  )
+
+  // 3. 处理图片容器,添加统一的样式类
+  processedContent = processedContent.replace(
+    /<div class="media-wrap image-wrap"><img src="([^"]*)"/g,
+    '<div class="media-wrap image-wrap processed-image"><img src="$1" class="content-image"',
+  )
+
+  // 4. 处理音频容器(如果有的话)
+  processedContent = processedContent.replace(
+    /<div class="media-wrap audio-wrap">/g,
+    '<div class="media-wrap audio-wrap processed-audio">',
+  )
+
+  // 5. 处理视频容器(如果有的话)
+  processedContent = processedContent.replace(
+    /<div class="media-wrap video-wrap">/g,
+    '<div class="media-wrap video-wrap processed-video">',
+  )
+
+  // 6. 移除空的段落标签
+  processedContent = processedContent.replace(/<p><\/p>/g, '')
+
+  // 7. 处理图片的alt属性
+  processedContent = processedContent.replace(
+    /<img([^>]*?)src="([^"]*?)"([^>]*?)\/?>/g,
+    '<img$1src="$2" alt="内容图片" loading="lazy"$3/>',
+  )
+
+  return processedContent
+}
+
+/**
+ * 获取处理HTML内容的CSS样式类名映射
+ * @returns {object} 样式类名映射对象
+ */
+function getHtmlContentStyleClasses() {
+  return {
+    indent: 'html-indent',
+    indentText: 'html-indent-text',
+    processedImage: 'html-processed-image',
+    contentImage: 'html-content-image',
+    processedAudio: 'html-processed-audio',
+    processedVideo: 'html-processed-video',
+    mediaWrap: 'html-media-wrap'
+  }
+}
+
+module.exports = {
+  processHtmlContent,
+  getHtmlContentStyleClasses
+}

+ 118 - 0
utils/request.js

@@ -0,0 +1,118 @@
+// 小程序网络请求封装
+const baseURL = 'https://sit-kelamayi.4dage.com/api'; // 根据环境配置
+
+/**
+ * 封装wx.request
+ * @param {Object} options 请求配置
+ * @param {Boolean} options.useToken 是否使用token,默认false
+ * @returns {Promise}
+ */
+function request(options) {
+  return new Promise((resolve, reject) => {
+    const {
+      url,
+      method = 'GET',
+      data = {},
+      header = {},
+      useToken = false
+    } = options;
+
+    // 设置默认请求头
+    const defaultHeader = {
+      'Content-Type': 'application/json;charset=UTF-8',
+      ...header
+    };
+
+    // 如果需要使用token,则添加到请求头
+    if (useToken) {
+      const app = getApp();
+      let token = null;
+      
+      // 优先从全局数据获取token
+      if (app && app.globalData && app.globalData.token) {
+        token = app.globalData.token;
+      } else {
+        // 如果全局数据没有,尝试从本地存储获取
+        token = wx.getStorageSync('token');
+      }
+      
+      if (token) {
+        defaultHeader['token'] = `${token}`;
+        console.log('添加token到请求头:', token);
+      } else {
+        console.warn('需要token但未获取到token');
+      }
+    }
+
+    // 完整的请求URL
+    const fullUrl = url.startsWith('http') ? url : baseURL + url;
+
+    console.log('发送请求:', {
+      url: fullUrl,
+      method,
+      data
+    });
+
+    wx.request({
+      url: fullUrl,
+      method: method.toUpperCase(),
+      data: data,
+      header: defaultHeader,
+      success: (res) => {
+        console.log('请求成功:', res);
+        
+        // 统一处理响应数据
+        const { data: responseData } = res;
+        
+        if (res.statusCode === 200) {
+          // 根据后端返回的数据结构调整
+          if (responseData.code === 200 || responseData.success === true) {
+            resolve(responseData.data || responseData);
+          } else if (responseData.code === 4008) {
+            // 登录失效,提示并延迟跳转到用户页面
+            console.log('登录失效,跳转到用户页面');
+            wx.showToast({
+              title: '未登录,即将跳转',
+              icon: 'none',
+              duration: 1000
+            });
+            
+            // 延迟1秒后跳转到用户页面并触发自动登录
+            setTimeout(() => {
+              wx.switchTab({
+                url: '/pages/user/index'
+              });
+            }, 1000);
+            
+            reject(new Error('登录失效,请重新登录'));
+          } else {
+            // 处理业务错误
+            const errorMsg = responseData.message || responseData.msg || '请求失败';
+            console.error('业务错误:', errorMsg);
+            reject(new Error(errorMsg));
+          }
+        } else {
+          reject(new Error(`HTTP ${res.statusCode}: ${res.errMsg || '网络错误'}`));
+        }
+      },
+      fail: (error) => {
+        console.error('请求失败:', error);
+        let errorMsg = '网络错误';
+        
+        if (error.errMsg) {
+          if (error.errMsg.includes('timeout')) {
+            errorMsg = '请求超时';
+          } else if (error.errMsg.includes('fail')) {
+            errorMsg = '网络连接失败';
+          }
+        }
+        
+        reject(new Error(errorMsg));
+      }
+    });
+  });
+}
+
+module.exports = {
+  request
+};

+ 58 - 0
utils/util.js

@@ -0,0 +1,58 @@
+const formatTime = date => {
+  // 确保date是Date对象
+  const dateObj = date instanceof Date ? date : new Date(date)
+  
+  const year = dateObj.getFullYear()
+  const month = dateObj.getMonth() + 1
+  const day = dateObj.getDate()
+  const hour = dateObj.getHours()
+  const minute = dateObj.getMinutes()
+  const second = dateObj.getSeconds()
+
+  return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}`
+}
+
+const formatNumber = n => {
+  n = n.toString()
+  return n[1] ? n : `0${n}`
+}
+
+/**
+ * 导航到webview页面
+ * @param {string} path - 页面路径
+ */
+const navigateToWebview = (path) => {
+  // 配置webview链接
+  const envConfig = {
+    // 开发环境和测试环境
+    development: 'https://sit-kelamayi.4dage.com/mini/#',
+    // 正式环境
+    production: 'https://kelamayi.4dage.com/mini/#'
+  };
+  
+  // 获取当前环境
+  const currentEnv = wx.getAccountInfoSync().miniProgram.envVersion || 'develop';
+  let baseUrl = envConfig.development;
+  
+  if (currentEnv === 'release') {
+    baseUrl = envConfig.production;
+  }
+  
+  // 检查path中是否已经包含isFrom参数,避免重复添加
+  let fullUrl;
+  if (path.includes('isFrom=weixin')) {
+    fullUrl = `${baseUrl}${path}`;
+  } else {
+    const separator = path.includes('?') ? '&' : '?';
+    fullUrl = `${baseUrl}${path}${separator}isFrom=weixin`;
+  }
+  
+  wx.navigateTo({
+    url: `/pages/webview/index?url=${encodeURIComponent(fullUrl)}`
+  });
+};
+
+module.exports = {
+  formatTime,
+  navigateToWebview
+}