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,21 @@
import request from '@/utils/request'
import upload from '@/utils/upload'
// 提交意见反馈H5/小程序端)
export function submitFeedback(data) {
return request({
url: '/system/feedback/submit',
method: 'post',
data
})
}
// 上传反馈截图
export function uploadFeedbackImage(filePath) {
return upload({
url: '/file/upload',
filePath,
name: 'file',
formData: {}
})
}

View File

@@ -187,6 +187,12 @@
"navigationBarTitleText": "常见问题"
}
},
{
"path": "feedback/index",
"style": {
"navigationBarTitleText": "意见反馈"
}
},
{
"path": "about/index",
"style": {

View File

@@ -37,12 +37,22 @@
</view>
<text class="action-text">交流社区</text>
</view>
<!-- #ifdef MP-WEIXIN -->
<button class="action-card action-button" open-type="contact" @contact="handleContact">
<view class="action-icon" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">
<uni-icons type="contact" size="24" color="#ffffff"></uni-icons>
</view>
<text class="action-text">联系客服</text>
</button>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view class="action-card" @click="handleService">
<view class="action-icon" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">
<uni-icons type="contact" size="24" color="#ffffff"></uni-icons>
</view>
<text class="action-text">联系客服</text>
</view>
<!-- #endif -->
<view class="action-card" @click="handleBuilding">
<view class="action-icon" style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);">
<uni-icons type="compose" size="24" color="#ffffff"></uni-icons>
@@ -53,7 +63,7 @@
<view class="action-icon" style="background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);">
<uni-icons type="star" size="24" color="#ffffff"></uni-icons>
</view>
<text class="action-text">给个好评</text>
<text class="action-text">支持我们</text>
</view>
</view>
@@ -134,6 +144,9 @@ const userStore = useUserStore()
const name = userStore.name;
const userId = computed(() => userStore.userId || '');
const version = config.appInfo.version;
const communityWechat = 'intc126';
const servicePhone = '17753252359';
const officialSite = config.appInfo.site_url;
const popup = ref(null);
function handleToInfo() {
@@ -182,35 +195,73 @@ function handleAbout() {
});
};
function handleJiaoLiuQun() {
uni.showToast({
title: 'intc126',
mask: false,
icon: "none",
duration: 5000
uni.showModal({
title: '加入交流群',
content: `复制微信号 ${communityWechat},添加后备注“智聪网络科技”,获取使用交流、问题答疑和功能更新。`,
confirmText: '复制微信号',
cancelText: '稍后再说',
success: (res) => {
if (res.confirm) {
copyText(communityWechat, '微信号已复制');
}
}
});
};
function handleBuilding() {
uni.showToast({
title: '谢谢反馈~',
mask: false,
icon: "none",
duration: 1000
uni.navigateTo({
url: '/pages_mine/pages/feedback/index'
});
}
function handleService() {
uni.showToast({
title: '请电联或加客服微信17753252359',
mask: false,
icon: "none",
duration: 5000
uni.showActionSheet({
itemList: ['拨打客服电话', '复制客服微信', '查看关于我们'],
success: ({ tapIndex }) => {
if (tapIndex === 0) {
uni.makePhoneCall({ phoneNumber: servicePhone });
} else if (tapIndex === 1) {
copyText(servicePhone, '客服微信已复制');
} else if (tapIndex === 2) {
handleAbout();
}
}
});
}
function handleContact() {
// 微信小程序端由 open-type="contact" 直接打开原生客服会话
}
function handleUs() {
uni.showToast({
title: '谢谢点赞!',
mask: false,
icon: "none",
duration: 1000
uni.showActionSheet({
itemList: ['推荐给朋友', '复制官网链接', '意见反馈'],
success: ({ tapIndex }) => {
if (tapIndex === 0) {
uni.showModal({
title: '感谢支持',
content: '如果觉得好用,可以点击右上角“···”分享给朋友,也欢迎把官网链接发给需要的人。',
confirmText: '复制官网',
cancelText: '知道了',
success: (res) => {
if (res.confirm) {
copyText(officialSite, '官网链接已复制');
}
}
});
} else if (tapIndex === 1) {
copyText(officialSite, '官网链接已复制');
} else if (tapIndex === 2) {
handleBuilding();
}
}
});
}
function copyText(data, title = '复制成功') {
uni.setClipboardData({
data,
success: () => {
uni.showToast({
title,
icon: 'none'
});
}
});
}
</script>
@@ -403,6 +454,18 @@ page {
font-size: 24rpx;
}
}
.action-button {
padding: 0;
margin: 0;
border: 0;
background: transparent;
line-height: 1;
&::after {
border: none;
}
}
}
.menu-section {

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>