index.vue 8.71 KB
<!--
 * @Date: 2022-09-19 14:11:06
 * @LastEditors: hookehuyr hookehuyr@gmail.com
 * @LastEditTime: 2025-07-17 12:19:40
 * @FilePath: /jgdl/src/pages/feedBackList/index.vue
 * @Description: 意见反馈列表页面
-->
<template>
  <view class="feedback-list-page">
    <!-- 滚动视图 -->
    <scroll-view
      class="scroll-view"
      :scroll-y="true"
      :style="scrollStyle"
      @scrolltolower="loadMore"
      :lower-threshold="50"
    >
      <!-- 反馈列表 -->
      <view class="feedback-list" v-if="feedbackList.length > 0">
        <view
          v-for="(item, index) in feedbackList"
          :key="index"
          class="feedback-card"
        >
          <!-- 卡片头部:时间和类型 -->
          <view class="card-header">
            <view class="feedback-time">{{ formatTime(item.created_at) }}</view>
            <view class="feedback-type">{{ getCategoryName(item.category) }}</view>
          </view>

          <!-- 反馈内容 -->
          <view class="feedback-content">
            <text class="content-text">{{ item.note }}</text>
          </view>

          <!-- 图片展示 -->
          <view class="feedback-images" v-if="item.images && item.images.length > 0">
            <view
              v-for="(image, imgIndex) in item.images"
              :key="imgIndex"
              class="image-item"
              @click="previewImage(item.images, imgIndex)"
            >
              <image :src="image" class="feedback-image" mode="aspectFill" />
            </view>
          </view>

          <!-- 联系方式 -->
          <view class="contact-info" v-if="item.contact">
            <text class="contact-label">联系方式:</text>
            <text class="contact-text">{{ item.contact }}</text>
          </view>

          <!-- 回复内容 -->
          <view class="reply-section" v-if="item.reply_content">
            <view class="reply-header">
              <text class="reply-label">回复内容:</text>
              <text class="reply-time">{{ formatTime(item.reply_time) }}</text>
            </view>
            <view class="reply-content">
              <text class="reply-text">{{ item.reply_content }}</text>
            </view>
          </view>
        </view>
      </view>

      <!-- 空状态 -->
      <view class="empty-state" v-if="!loading && feedbackList.length === 0">
        <view class="empty-icon">📝</view>
        <view class="empty-text">暂无反馈记录</view>
        <view class="empty-desc">您还没有提交过任何反馈</view>
      </view>

      <!-- 加载状态 -->
      <view class="loading-container" v-if="loading">
        <text class="loading-text">加载中...</text>
      </view>

      <!-- 没有更多数据 -->
      <view class="no-more-container" v-if="!hasMore && feedbackList.length > 0">
        <text class="no-more-text">没有更多数据了</text>
      </view>
    </scroll-view>

    <!-- 底部固定按钮 -->
    <view class="fixed-bottom">
      <button class="feedback-btn" @click="goToFeedback">
        我要反馈
      </button>
    </view>

    <!-- 图片预览 -->
    <nut-image-preview
      v-model:show="previewVisible"
      :images="previewImages"
      :init-no="previewIndex"
      @close="closePreview"
    />
  </view>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue'
import Taro from '@tarojs/taro'
import { Toast } from '@nutui/nutui-taro'
// import { getFeedbackListAPI } from '@/api/other'
import './index.less'

// 反馈类型映射
const categoryMap = {
  '1': '功能建议',
  '3': '界面设计',
  '5': '车辆信息',
  '7': '其他问题'
}

// 响应式数据
const loading = ref(false)
const refreshing = ref(false)
const hasMore = ref(true)
const currentPage = ref(0)
const pageSize = ref(10)
const feedbackList = ref([])

// 图片预览相关
const previewVisible = ref(false)
const previewImages = ref([])
const previewIndex = ref(0)

// 计算滚动视图样式
const scrollStyle = computed(() => {
  const systemInfo = Taro.getSystemInfoSync()
  const windowHeight = systemInfo.windowHeight

  // 动态计算底部区域高度:padding + 按钮高度 + 边框 + 安全区域
  // 转换rpx到px: rpx值 / 750 * 屏幕宽度
  const rpxToPx = systemInfo.screenWidth / 750
  const topPadding = 20 * rpxToPx // 顶部padding
  const bottomPadding = 20 * rpxToPx // 底部基础padding
  const buttonHeight = 88 * rpxToPx // 按钮高度
  const borderHeight = 1 * rpxToPx // 边框高度

  // 获取安全区域底部高度
  const safeAreaInsetBottom = systemInfo.safeArea
    ? Math.max(0, systemInfo.screenHeight - systemInfo.safeArea.bottom)
    : 0

  // 总的底部高度 = 顶部padding + 底部padding + 按钮高度 + 边框高度 + 安全区域
  const totalBottomHeight = topPadding + bottomPadding + buttonHeight + borderHeight + safeAreaInsetBottom

  const scrollHeight = Math.max(200, windowHeight - totalBottomHeight)

  return `height: ${scrollHeight}px;`
})

/**
 * 获取反馈类型名称
 */
const getCategoryName = (category) => {
  return categoryMap[category] || '未知类型'
}

/**
 * 格式化时间
 */
const formatTime = (timestamp) => {
  if (!timestamp) return ''
  const date = new Date(timestamp * 1000)
  const year = date.getFullYear()
  const month = String(date.getMonth() + 1).padStart(2, '0')
  const day = String(date.getDate()).padStart(2, '0')
  const hours = String(date.getHours()).padStart(2, '0')
  const minutes = String(date.getMinutes()).padStart(2, '0')
  return `${year}-${month}-${day} ${hours}:${minutes}`
}

/**
 * 获取反馈列表数据 - 使用Mock数据
 */
const getFeedbackList = async (isRefresh = false) => {
  if (loading.value) return

  loading.value = true

  try {
    // Mock数据
    const mockData = [
      {
        id: 1,
        category: '1',
        note: '希望能增加夜间模式功能,这样在晚上使用时眼睛会更舒服一些。',
        images: ['https://picsum.photos/200/200?random=1', 'https://picsum.photos/200/200?random=2'],
        contact: '138****8888',
        created_at: Math.floor(Date.now() / 1000) - 86400,
        reply_content: '感谢您的建议,我们已将夜间模式功能加入开发计划,预计下个版本上线。',
        reply_time: Math.floor(Date.now() / 1000) - 3600
      },
      {
        id: 2,
        category: '3',
        note: '首页的搜索框位置有点偏上,建议调整到更容易点击的位置。',
        images: [],
        contact: 'wechat_user123',
        created_at: Math.floor(Date.now() / 1000) - 172800,
        reply_content: null,
        reply_time: null
      },
      {
        id: 3,
        category: '5',
        note: '发现有些车辆信息显示不完整,价格和配置信息缺失。',
        images: ['https://picsum.photos/200/200?random=3'],
        contact: '159****6666',
        created_at: Math.floor(Date.now() / 1000) - 259200,
        reply_content: '我们已经修复了车辆信息显示问题,感谢您的反馈!',
        reply_time: Math.floor(Date.now() / 1000) - 86400
      },
      {
        id: 4,
        category: '7',
        note: '应用启动速度有点慢,希望能优化一下性能。',
        images: [],
        contact: null,
        created_at: Math.floor(Date.now() / 1000) - 345600,
        reply_content: null,
        reply_time: null
      }
    ]

    // 模拟网络延迟
    await new Promise(resolve => setTimeout(resolve, 1000))

    const startIndex = isRefresh ? 0 : currentPage.value * pageSize.value
    const endIndex = startIndex + pageSize.value
    const newData = mockData.slice(startIndex, endIndex)

    if (isRefresh) {
      feedbackList.value = newData
      currentPage.value = 0
    } else {
      feedbackList.value.push(...newData)
    }

    // 判断是否还有更多数据
    hasMore.value = endIndex < mockData.length

    if (!isRefresh) {
      currentPage.value++
    }

  } catch (error) {
    console.error('获取反馈列表失败:', error)
    Toast.fail(error.message || '获取数据失败')
  } finally {
    loading.value = false
    refreshing.value = false
  }
}

/**
 * 下拉刷新
 */
const onRefresh = () => {
  refreshing.value = true
  getFeedbackList(true)
}

/**
 * 加载更多
 */
const loadMore = () => {
  if (!hasMore.value || loading.value) return
  getFeedbackList()
}

/**
 * 预览图片
 */
const previewImage = (images, index) => {
  previewImages.value = images.map(src => ({ src }))
  previewIndex.value = index
  previewVisible.value = true
}

/**
 * 关闭图片预览
 */
const closePreview = () => {
  previewVisible.value = false
}

/**
 * 跳转到反馈页面
 */
const goToFeedback = () => {
  Taro.navigateTo({
    url: '/pages/feedBack/index'
  })
}

// 页面加载时获取数据
onMounted(() => {
  getFeedbackList(true)
})
</script>

<script>
export default {
  name: 'FeedbackListPage'
}
</script>