You need to sign in or sign up before continuing.
index.vue 7.55 KB
<!--
 * @Date: 2026-01-29 22:26:13
 * @LastEditors: hookehuyr hookehuyr@gmail.com
 * @LastEditTime: 2026-01-31 14:40:32
 * @FilePath: /manulife-weapp/src/pages/feedback/index.vue
 * @Description: 意见反馈页面
-->
<template>
  <view class="min-h-screen bg-gray-50 pb-[200rpx]">
    <NavHeader title="反馈问题" />
    <view class="p-[32rpx]">
      <!-- Feedback Type -->
      <view class="bg-white rounded-[24rpx] p-[32rpx] mb-[24rpx] shadow-sm">
        <view class="text-[30rpx] font-bold text-gray-900 mb-[24rpx]">反馈类型</view>
        <view class="flex flex-wrap gap-[24rpx]">
          <view
            v-for="type in types"
            :key="type.value"
            class="px-[32rpx] py-[16rpx] rounded-full text-[26rpx] transition-colors"
            :class="selectedType === type.value ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-600'"
            @tap="selectedType = type.value"
          >
            {{ type.label }}
          </view>
        </view>
      </view>

      <!-- Description -->
      <view class="bg-white rounded-[24rpx] p-[28rpx] mb-[24rpx] shadow-sm">
        <view class="text-[30rpx] font-bold text-gray-900 mb-[24rpx]">问题描述</view>
        <view class="feedback-textarea-wrapper border border-[#E5E7EB] rounded-[16rpx] overflow-hidden relative">
          <textarea
            v-model="description"
            placeholder="请详细描述您遇到的问题或建议..."
            :maxlength="200"
            class="w-full h-[240rpx] p-[24rpx] text-[28rpx] leading-normal bg-transparent resize-none"
            auto-height
          />
          <view class="absolute bottom-[16rpx] right-[24rpx] text-[22rpx] text-gray-400">
            {{ description.length }}/200
          </view>
        </view>
      </view>

      <!-- Screenshots -->
      <view class="bg-white rounded-[24rpx] p-[32rpx] mb-[40rpx] shadow-sm">
        <view class="text-[30rpx] font-bold text-gray-900 mb-[24rpx]">添加截图(可选)</view>
        <view class="flex gap-[24rpx]">
          <!-- Uploaded Images -->
          <view
            v-for="(img, index) in uploadedImages"
            :key="index"
            class="relative w-[160rpx] h-[160rpx] rounded-[16rpx] overflow-hidden"
          >
            <image :src="img" mode="aspectFill" class="w-full h-full" />
            <view
              class="absolute top-0 right-0 bg-red-500 text-white w-[40rpx] h-[40rpx] flex items-center justify-center"
              @tap="removeImage(index)"
            >
              <text class="text-[24rpx]">×</text>
            </view>
          </view>

          <!-- Upload Button -->
          <view
            v-if="uploadedImages.length < 3"
            class="w-[160rpx] h-[160rpx] rounded-[16rpx] border-2 border-dashed border-gray-300 flex flex-col items-center justify-center"
            @tap="chooseImage"
          >
            <text class="text-[48rpx] text-gray-400">+</text>
            <text class="text-[24rpx] text-gray-400 mt-[8rpx]">上传图片</text>
          </view>
        </view>
        <view class="text-[22rpx] text-gray-400 mt-[16rpx]">最多上传3张图片,每张不超过5MB</view>
      </view>

      <!-- Submit Button -->
      <nut-button type="primary" block class="!h-[88rpx] !rounded-[44rpx] !text-[32rpx]" @click="onSubmit">提交反馈</nut-button>
    </view>

    <!-- TabBar -->
    <!-- <TabBar current="me" /> -->
  </view>
</template>

<script setup>
import { ref } from 'vue'
import TabBar from '@/components/navigation/TabBar.vue'
import NavHeader from '@/components/navigation/NavHeader.vue'
import Taro from '@tarojs/taro'
import { addAPI } from '@/api/feedback'
import BASE_URL from '@/utils/config'
import eventBus, { Events } from '@/utils/eventBus'

/**
 * 反馈类型选项(对应后端 category 值)
 * 1=功能建议, 3=问题反馈, 7=其他问题
 * @type {Array<{label: string, value: string}>}
 */
const types = [
  { label: '功能建议', value: '1' },
  { label: '问题反馈', value: '3' },
  { label: '其他', value: '7' }
]

/** @type {import('vue').Ref<string>} 选中的反馈类型 */
const selectedType = ref('1')

/** @type {import('vue').Ref<string>} 问题描述 */
const description = ref('')

/** @type {import('vue').Ref<Array<string>>} 已上传的图片 URL 列表 */
const uploadedImages = ref([])

/**
 * @description 选择图片并上传(参考头像上传逻辑)
 */
const chooseImage = () => {
  Taro.chooseImage({
    count: 3 - uploadedImages.value.length, // 剩余可上传数量
    sizeType: ['compressed'], // 使用压缩图
    sourceType: ['album', 'camera'],
    success: async (res) => {
      const tempFile = res.tempFiles[0]

      // 检查文件大小(5MB限制)
      if (tempFile.size > 5 * 1024 * 1024) {
        Taro.showToast({
          title: '图片大小不能超过5MB',
          icon: 'none',
        })
        return
      }

      // 显示上传进度
      Taro.showLoading({ title: '上传中...', mask: true })

      // 使用 Taro.uploadFile 上传到服务器(参考头像上传)
      Taro.uploadFile({
        url: BASE_URL + '/admin/?m=srv&a=upload&image_audit=1',
        filePath: tempFile.path,
        name: 'file',
        success: (uploadRes) => {
          Taro.hideLoading()

          const data = JSON.parse(uploadRes.data)

          // 检查是否为审核不通过
          if (data.data && data.data.audit_code == -1) {
            Taro.showModal({
              title: '温馨提示',
              content: data.msg || '图片审核不通过',
              showCancel: false
            })
            return
          }

          if (data.code === 0) {  // 注意:后端 code=0 表示成功
            uploadedImages.value.push(data.data.src)
            Taro.showToast({
              title: '上传成功',
              icon: 'success'
            })
          } else {
            Taro.showToast({
              title: data.msg || '上传失败',
              icon: 'none'
            })
          }
        },
        fail: () => {
          Taro.hideLoading()
          Taro.showToast({
            title: '上传失败,请稍后重试',
            icon: 'none'
          })
        }
      })
    }
  })
}

/**
 * @description 移除图片
 * @param {number} index 图片索引
 */
const removeImage = (index) => {
  uploadedImages.value.splice(index, 1)
}

/**
 * @description 提交意见反馈
 */
const onSubmit = async () => {
  // 验证必填项
  if (!description.value) {
    Taro.showToast({ title: '请输入问题描述', icon: 'none' })
    return
  }

  Taro.showLoading({ title: '提交中...', mask: true })

  try {
    // 调用 API 提交反馈
    const res = await addAPI({
      category: selectedType.value,     // 反馈类别:1=功能建议, 3=问题反馈, 7=其他问题
      note: description.value,           // 反馈内容
      images: uploadedImages.value      // 图片 URL 数组(可选)
    })

    Taro.hideLoading()

    if (res.code === 1) {
      Taro.showToast({ title: '提交成功', icon: 'success' })

      // 重置表单
      description.value = ''
      uploadedImages.value = []
      selectedType.value = '1'

      // 发送事件通知反馈列表页刷新
      eventBus.emit(Events.FEEDBACK_SUBMIT, {
        timestamp: Date.now()
      })

      // 延迟返回
      setTimeout(() => {
        Taro.navigateBack()
      }, 1500)
    } else {
      Taro.showToast({ title: res.msg || '提交失败', icon: 'none' })
    }
  } catch (err) {
    Taro.hideLoading()
    console.error('提交反馈失败:', err)
    Taro.showToast({ title: '网络异常,请重试', icon: 'none' })
  }
}
</script>

<style lang="less">
</style>