312 lines
6.7 KiB
Vue
312 lines
6.7 KiB
Vue
<template>
|
||
<view class="feedback-page">
|
||
<view class="form">
|
||
<view class="form-item">
|
||
<view class="label">反馈类型</view>
|
||
<picker :range="types" range-key="label" @change="onTypeChange">
|
||
<view class="picker">
|
||
{{ selectedType.label || '请选择' }}
|
||
<text class="arrow">›</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<view class="label">反馈内容</view>
|
||
<textarea
|
||
v-model="form.content"
|
||
placeholder="请详细描述您的问题或建议"
|
||
maxlength="500"
|
||
:show-confirm-bar="false"
|
||
/>
|
||
<view class="count">{{ form.content.length }}/500</view>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<view class="label">上传图片(选填)</view>
|
||
<view class="image-list">
|
||
<view
|
||
v-for="(img, index) in form.images"
|
||
:key="index"
|
||
class="image-item"
|
||
>
|
||
<image :src="img" mode="aspectFill" />
|
||
<view class="delete" @click="deleteImage(index)">×</view>
|
||
</view>
|
||
<view
|
||
v-if="form.images.length < 9"
|
||
class="image-item upload"
|
||
@click="chooseImage"
|
||
>
|
||
<text>+</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<view class="label">联系方式(选填)</view>
|
||
<input
|
||
v-model="form.contact"
|
||
placeholder="手机号或微信号,方便我们联系您"
|
||
maxlength="50"
|
||
/>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="submit-btn">
|
||
<button @click="submit" :disabled="!canSubmit">提交反馈</button>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { feedbackApi } from '@/api/index.js'
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
types: [
|
||
{ label: '功能建议', value: 'suggestion' },
|
||
{ label: '问题反馈', value: 'problem' },
|
||
{ label: '投诉', value: 'complaint' }
|
||
],
|
||
form: {
|
||
type: '',
|
||
content: '',
|
||
images: [],
|
||
contact: ''
|
||
}
|
||
}
|
||
},
|
||
computed: {
|
||
selectedType() {
|
||
return this.types.find(t => t.value === this.form.type) || {}
|
||
},
|
||
|
||
canSubmit() {
|
||
return this.form.type && this.form.content.trim().length >= 10
|
||
}
|
||
},
|
||
methods: {
|
||
onTypeChange(e) {
|
||
this.form.type = this.types[e.detail.value].value
|
||
},
|
||
|
||
chooseImage() {
|
||
const self = this
|
||
uni.chooseImage({
|
||
count: 9 - this.form.images.length,
|
||
sizeType: ['compressed'], // 使用压缩图
|
||
sourceType: ['album', 'camera'],
|
||
success: async (res) => {
|
||
uni.showLoading({ title: '上传中...' })
|
||
|
||
try {
|
||
// 逐个上传,避免并发过多
|
||
for (const filePath of res.tempFilePaths) {
|
||
const url = await self.uploadSingleImage(filePath)
|
||
if (url) {
|
||
self.form.images.push(url)
|
||
}
|
||
}
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '上传成功', icon: 'success' })
|
||
} catch (error) {
|
||
console.error('上传失败', error)
|
||
uni.hideLoading()
|
||
uni.showToast({ title: '上传失败', icon: 'none' })
|
||
}
|
||
}
|
||
})
|
||
},
|
||
|
||
// 单张图片上传
|
||
uploadSingleImage(filePath) {
|
||
return new Promise((resolve, reject) => {
|
||
uni.uploadFile({
|
||
url: 'http://localhost:8089/api/upload/image',
|
||
filePath: filePath,
|
||
name: 'file',
|
||
success: (res) => {
|
||
if (res.statusCode === 200) {
|
||
try {
|
||
const data = JSON.parse(res.data)
|
||
if (data.code === 200) {
|
||
resolve(data.data.fileUrl || data.data.url || data.data)
|
||
} else {
|
||
reject(data.message || '上传失败')
|
||
}
|
||
} catch (e) {
|
||
reject('解析响应失败')
|
||
}
|
||
} else {
|
||
reject('上传失败')
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
reject(err)
|
||
}
|
||
})
|
||
})
|
||
},
|
||
|
||
deleteImage(index) {
|
||
this.form.images.splice(index, 1)
|
||
},
|
||
|
||
async submit() {
|
||
if (!this.canSubmit) return
|
||
|
||
uni.showLoading({ title: '提交中...' })
|
||
|
||
try {
|
||
await feedbackApi.submit({
|
||
...this.form,
|
||
images: JSON.stringify(this.form.images)
|
||
})
|
||
|
||
uni.showToast({
|
||
title: '提交成功',
|
||
icon: 'success',
|
||
duration: 2000
|
||
})
|
||
|
||
setTimeout(() => {
|
||
uni.navigateBack()
|
||
}, 2000)
|
||
} catch (error) {
|
||
uni.showToast({ title: '提交失败', icon: 'none' })
|
||
} finally {
|
||
uni.hideLoading()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.feedback-page {
|
||
min-height: 100vh;
|
||
background: #f5f5f5;
|
||
padding: 20rpx;
|
||
}
|
||
|
||
.form {
|
||
background: #fff;
|
||
border-radius: 16rpx;
|
||
padding: 20rpx;
|
||
}
|
||
|
||
.form-item {
|
||
margin-bottom: 40rpx;
|
||
|
||
.label {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.picker {
|
||
height: 80rpx;
|
||
line-height: 80rpx;
|
||
padding: 0 20rpx;
|
||
background: #f5f5f5;
|
||
border-radius: 8rpx;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
|
||
.arrow {
|
||
font-size: 40rpx;
|
||
color: #999;
|
||
}
|
||
}
|
||
|
||
textarea {
|
||
width: 100%;
|
||
min-height: 300rpx;
|
||
padding: 20rpx;
|
||
background: #f5f5f5;
|
||
border-radius: 8rpx;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.count {
|
||
text-align: right;
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
margin-top: 10rpx;
|
||
}
|
||
|
||
input {
|
||
height: 80rpx;
|
||
padding: 0 20rpx;
|
||
background: #f5f5f5;
|
||
border-radius: 8rpx;
|
||
font-size: 28rpx;
|
||
}
|
||
}
|
||
|
||
.image-list {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.image-item {
|
||
width: 200rpx;
|
||
height: 200rpx;
|
||
border-radius: 8rpx;
|
||
overflow: hidden;
|
||
position: relative;
|
||
|
||
image {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.delete {
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
width: 50rpx;
|
||
height: 50rpx;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
color: #fff;
|
||
text-align: center;
|
||
line-height: 50rpx;
|
||
font-size: 40rpx;
|
||
}
|
||
|
||
&.upload {
|
||
background: #f5f5f5;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
text {
|
||
font-size: 80rpx;
|
||
color: #999;
|
||
}
|
||
}
|
||
}
|
||
|
||
.submit-btn {
|
||
margin-top: 40rpx;
|
||
|
||
button {
|
||
width: 100%;
|
||
height: 88rpx;
|
||
background: #5677fc;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 8rpx;
|
||
font-size: 32rpx;
|
||
|
||
&[disabled] {
|
||
background: #ccc;
|
||
}
|
||
}
|
||
}
|
||
</style>
|