fix: 意见反馈功能提交。

This commit is contained in:
tianyongbao
2026-07-14 08:05:24 +08:00
parent 3294695dec
commit 65239f57ba
4 changed files with 524 additions and 21 deletions

View File

@@ -0,0 +1,413 @@
<template>
<view class="feedback-container">
<view class="intro-card">
<view class="intro-icon">
<uni-icons type="compose" size="28" color="#ffffff"></uni-icons>
</view>
<view class="intro-content">
<text class="intro-title">意见反馈</text>
<text class="intro-desc">请描述遇到的问题或功能建议</text>
</view>
</view>
<view class="form-card">
<view class="form-item">
<text class="label">反馈类型</text>
<picker mode="selector" :range="feedbackTypes" :value="feedbackTypeIndex" @change="handleTypeChange">
<view class="picker-row">
<text>{{ currentType }}</text>
<uni-icons type="right" size="16" color="#c0c4cc"></uni-icons>
</view>
</picker>
</view>
<view class="form-item">
<text class="label required">反馈内容</text>
<textarea
v-model="content"
class="content-input"
maxlength="500"
placeholder="请尽量说明操作步骤、看到的问题或希望增加的功能"
placeholder-class="placeholder"
/>
<text class="count-text">{{ content.length }}/500</text>
</view>
<view class="form-item">
<text class="label">联系方式</text>
<input
v-model="contact"
class="contact-input"
maxlength="100"
placeholder="选填,便于客服进一步沟通"
placeholder-class="placeholder"
/>
</view>
<view class="form-item">
<view class="label-row">
<text class="label">截图</text>
<text class="tip-text">选填最多 3 </text>
</view>
<view class="image-list">
<view v-for="(image, index) in imageList" :key="image" class="image-item">
<image :src="image" mode="aspectFill"></image>
<view class="remove-image" @click="removeImage(index)">
<uni-icons type="closeempty" size="18" color="#ffffff"></uni-icons>
</view>
</view>
<view v-if="imageList.length < 3" class="add-image" @click="chooseImages">
<uni-icons type="plusempty" size="28" color="#909399"></uni-icons>
<text>添加截图</text>
</view>
</view>
</view>
</view>
<view class="submit-section">
<button class="submit-btn" :loading="submitting" :disabled="submitting" @click="handleSubmit">
{{ submitting ? '提交中...' : '提交反馈' }}
</button>
</view>
</view>
</template>
<script setup>
import { computed, ref } from 'vue'
import appConfig from '@/config'
import { submitFeedback, uploadFeedbackImage } from '@/api/system/feedback'
const feedbackTypes = ['功能建议', '问题反馈', '数据异常', '其他']
const feedbackTypeIndex = ref(0)
const content = ref('')
const contact = ref('')
const imageList = ref([])
const submitting = ref(false)
const serviceWechat = '17753252359'
const currentType = computed(() => feedbackTypes[feedbackTypeIndex.value])
function handleTypeChange(e) {
feedbackTypeIndex.value = Number(e.detail.value)
}
function chooseImages() {
const restCount = 3 - imageList.value.length
if (restCount <= 0 || submitting.value) return
uni.chooseImage({
count: restCount,
sizeType: ['compressed'],
success: (res) => {
imageList.value = imageList.value.concat(res.tempFilePaths || []).slice(0, 3)
}
})
}
function removeImage(index) {
if (submitting.value) return
imageList.value.splice(index, 1)
}
async function handleSubmit() {
if (submitting.value) return
const feedbackContent = content.value.trim()
if (!feedbackContent) {
uni.showToast({
title: '请先填写反馈内容',
icon: 'none'
})
return
}
submitting.value = true
uni.showLoading({ title: imageList.value.length ? '上传截图中...' : '提交中...' })
try {
const imageUrls = await uploadImages(imageList.value)
uni.showLoading({ title: '提交中...' })
await submitFeedback({
feedbackType: currentType.value,
content: feedbackContent,
contact: contact.value.trim(),
images: imageUrls.join(','),
source: getFeedbackSource()
})
content.value = ''
contact.value = ''
imageList.value = []
feedbackTypeIndex.value = 0
uni.hideLoading()
uni.showModal({
title: '提交成功',
content: '您的反馈已提交,我们会尽快处理。',
confirmText: '知道了',
showCancel: false
})
} catch (error) {
uni.hideLoading()
if (error !== '500') {
uni.showToast({
title: '提交失败,请稍后重试',
icon: 'none'
})
}
} finally {
submitting.value = false
}
}
async function uploadImages(paths) {
const urls = []
for (const path of paths) {
const uploadRes = await uploadFeedbackImage(path)
urls.push(normalizeUploadUrl(uploadRes))
}
return urls
}
function normalizeUploadUrl(uploadRes) {
const data = uploadRes?.data || {}
const url = data.url || data.fileName || uploadRes?.url || uploadRes?.fileName || uploadRes?.imgUrl
if (!url) {
throw new Error('上传截图失败')
}
if (/^https?:\/\//.test(url)) {
return url
}
return `${appConfig.baseUrl}${url.startsWith('/') ? '' : '/'}${url}`
}
function getFeedbackSource() {
// #ifdef MP-WEIXIN
return '小程序'
// #endif
// #ifdef H5
return 'H5'
// #endif
return 'App'
}
function copyServiceWechat() {
uni.setClipboardData({
data: serviceWechat,
success: () => {
uni.showToast({
title: '客服微信已复制',
icon: 'none'
})
}
})
}
</script>
<style lang="scss" scoped>
page {
display: flex;
flex-direction: column;
box-sizing: border-box;
background-color: #f5f7fa;
min-height: 100%;
}
.feedback-container {
padding: 24rpx;
}
.intro-card {
display: flex;
align-items: center;
padding: 28rpx;
border-radius: 24rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
box-shadow: 0 8rpx 32rpx rgba(102, 126, 234, 0.22);
margin-bottom: 24rpx;
}
.intro-icon {
width: 72rpx;
height: 72rpx;
border-radius: 20rpx;
background: rgba(255, 255, 255, 0.18);
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
flex-shrink: 0;
}
.intro-content {
display: flex;
flex-direction: column;
}
.intro-title {
color: #ffffff;
font-size: 34rpx;
font-weight: 700;
margin-bottom: 8rpx;
}
.intro-desc {
color: rgba(255, 255, 255, 0.86);
font-size: 24rpx;
line-height: 36rpx;
}
.form-card {
background: linear-gradient(135deg, #ffffff 0%, #f8f9fb 100%);
border-radius: 24rpx;
padding: 8rpx 28rpx 28rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.08);
}
.form-item {
padding-top: 28rpx;
}
.label-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.label {
display: block;
color: #303133;
font-size: 28rpx;
font-weight: 600;
margin-bottom: 16rpx;
}
.required::after {
content: '*';
color: #f56c6c;
margin-left: 4rpx;
}
.tip-text,
.count-text {
color: #909399;
font-size: 22rpx;
}
.count-text {
display: block;
text-align: right;
margin-top: 8rpx;
}
.picker-row,
.contact-input,
.content-input {
width: 100%;
box-sizing: border-box;
background: #f7f8fb;
border: 1rpx solid #e8edf3;
border-radius: 18rpx;
color: #303133;
font-size: 26rpx;
}
.picker-row {
min-height: 88rpx;
padding: 0 22rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.contact-input {
height: 88rpx;
padding: 0 22rpx;
}
.content-input {
min-height: 220rpx;
padding: 22rpx;
line-height: 38rpx;
}
.placeholder {
color: #c0c4cc;
}
.image-list {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.image-item,
.add-image {
width: 152rpx;
height: 152rpx;
border-radius: 18rpx;
overflow: hidden;
position: relative;
}
.image-item image {
width: 100%;
height: 100%;
}
.remove-image {
position: absolute;
top: 0;
right: 0;
width: 42rpx;
height: 42rpx;
border-bottom-left-radius: 18rpx;
background: rgba(0, 0, 0, 0.48);
display: flex;
align-items: center;
justify-content: center;
}
.add-image {
border: 2rpx dashed #dcdfe6;
background: #f7f8fb;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #909399;
font-size: 22rpx;
}
.submit-section {
padding: 32rpx 0 16rpx;
}
.submit-btn,
.service-btn {
height: 88rpx;
border-radius: 44rpx;
font-size: 28rpx;
margin: 0 0 20rpx;
display: flex;
align-items: center;
justify-content: center;
&::after {
border: none;
}
}
.submit-btn {
color: #ffffff;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.28);
}
.service-btn {
color: #667eea;
background: #ffffff;
border: 1rpx solid rgba(102, 126, 234, 0.28);
}
</style>