index.vue 8.85 KB
<!--
 * @Date: 2022-09-19 14:11:06
 * @LastEditors: hookehuyr hookehuyr@gmail.com
 * @LastEditTime: 2025-07-21 11:47:19
 * @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"
      :refresher-enabled="true"
      :refresher-triggered="refreshing"
      @refresherrefresh="onRefresh"
      @refresherrestore="onRefreshRestore"
    >
      <!-- 反馈列表 -->
      <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="header-left">
              <view class="feedback-time">{{ item.created_time }}</view>
              <view class="feedback-status" :class="getStatusClass(item.status)">{{ getStatusText(item.status) }}</view>
            </view>
            <view class="header-right">
              <view class="feedback-type">{{ getCategoryName(item.category) }}</view>
              <!-- <view class="feedback-status" :class="getStatusClass(item.status)">{{ getStatusText(item.status) }}</view> -->
            </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" @tap="copyContact(item.contact)">{{ item.contact }}</text>
          </view>

          <!-- 回复内容 -->
          <view class="reply-section" v-if="item.reply">
            <view class="reply-header">
              <text class="reply-label">回复内容:</text>
              <text class="reply-time">{{ item.reply_time }}</text>
            </view>
            <view class="reply-content">
              <text class="reply-text">{{ item.reply }}</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"
      :autoplay="0"
      :show-index="false"
      @close="closePreview"
    />
  </view>
</template>

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

// 反馈类型映射
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 getStatusText = (status) => {
  const statusMap = {
    3: '待处理',
    5: '已处理'
  }
  return statusMap[status] || '未知状态'
}

/**
 * 获取状态样式类
 */
const getStatusClass = (status) => {
  return {
    'status-pending': status === 3,
    'status-processed': status === 5
  }
}

/**
 * 格式化时间
 */
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}`
}

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

  loading.value = true

  try {
    const page = isRefresh ? 0 : currentPage.value
    const params = {
      page: page,
      limit: pageSize.value
    }

    const response = await getFeedbackListAPI(params)

    if (response && response.code === 1 && response.data) {
      const newData = response.data.list || []

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

      // 判断是否还有更多数据
      const totalLoaded = isRefresh ? newData.length : feedbackList.value.length
      hasMore.value = totalLoaded < response.data.total

      if (!isRefresh) {
        currentPage.value++
      }
    } else {
      console.error('API返回错误:', response)
      Toast.fail(response?.msg || '获取数据失败')
    }
  } catch (error) {
    console.error('获取反馈列表失败:', error)
    Toast.fail(error.message || '网络错误,请重试')
  } finally {
    loading.value = false
    refreshing.value = false
  }
}

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

/**
 * 下拉刷新恢复
 */
const onRefreshRestore = () => {
  refreshing.value = false
}

/**
 * 加载更多
 */
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'
  })
}

/**
 * 复制联系方式
 * @param contact 联系方式
 */
const copyContact = (contact) => {
  Taro.setClipboardData({
    data: contact,
    success: () => {
      Taro.showToast({
        title: '联系方式已复制',
        icon: 'success'
      })
    }
  })
}

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

// 页面onShow时获取数据
useDidShow(() => {
  // 如果不是首次加载,重新获取数据
  if (feedbackList.value.length) {
    getFeedbackList(true)
  }
})
</script>

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