fix: 意见反馈功能提交。
This commit is contained in:
413
src/pages_mine/pages/feedback/index.vue
Normal file
413
src/pages_mine/pages/feedback/index.vue
Normal 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>
|
||||
Reference in New Issue
Block a user