354 lines
7.0 KiB
Vue
354 lines
7.0 KiB
Vue
<template>
|
||
<view class="feedback-page" >
|
||
<NavBar title="意见反馈" />
|
||
|
||
<view class="content-wrap">
|
||
<!-- Type Selector -->
|
||
<view class="form-item">
|
||
<view class="label">反馈类型</view>
|
||
<view class="type-list">
|
||
<view
|
||
v-for="(item, index) in feedbackTypes"
|
||
:key="index"
|
||
class="type-chip"
|
||
:class="{ active: formData.type === item.value }"
|
||
@tap="formData.type = item.value"
|
||
>
|
||
{{ item.label }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Content Input -->
|
||
<view class="form-item">
|
||
<view class="label">反馈内容 <text class="required">*</text></view>
|
||
<view class="textarea-box">
|
||
<textarea
|
||
v-model="formData.content"
|
||
class="textarea"
|
||
placeholder="请输入您的反馈意见,我们将为您不断改进..."
|
||
placeholder-class="placeholder"
|
||
maxlength="200"
|
||
/>
|
||
<text class="counter">{{ formData.content.length }}/200</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Image Upload -->
|
||
<view class="form-item">
|
||
<view class="label"
|
||
>图片上传 <text class="optional">(选填,最多3张)</text></view
|
||
>
|
||
<view class="image-grid">
|
||
<view
|
||
v-for="(img, index) in formData.images"
|
||
:key="index"
|
||
class="image-item"
|
||
>
|
||
<image
|
||
:src="img"
|
||
mode="aspectFill"
|
||
class="thumb"
|
||
@tap="previewImage(index)"
|
||
/>
|
||
<view class="delete-btn" @tap.stop="deleteImage(index)">×</view>
|
||
</view>
|
||
<view
|
||
v-if="formData.images.length < 3"
|
||
class="upload-btn"
|
||
@tap="chooseImage"
|
||
>
|
||
<text class="plus">+</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Submit Button -->
|
||
<view class="submit-wrap">
|
||
<button
|
||
class="submit-btn"
|
||
:class="{ disabled: !isValid }"
|
||
:disabled="!isValid"
|
||
@tap="submitFeedback"
|
||
>
|
||
提交反馈
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed } from "vue";
|
||
import { getDeviceInfo } from "@/utils/system";
|
||
import { sendFeedback } from "@/api/mine.js";
|
||
import { uploadImage } from "@/utils/common.js";
|
||
import NavBar from "@/components/NavBar/NavBar.vue";
|
||
|
||
const feedbackTypes = [
|
||
{ label: "功能建议", value: 1 },
|
||
{ label: "问题反馈", value: 2 },
|
||
{ label: "投诉", value: 3 },
|
||
{ label: "其他", value: 4 },
|
||
];
|
||
|
||
const formData = ref({
|
||
type: 1,
|
||
content: "",
|
||
images: [],
|
||
});
|
||
|
||
const isValid = computed(() => {
|
||
return formData.value.content.trim().length > 0;
|
||
});
|
||
|
||
const chooseImage = () => {
|
||
uni.chooseImage({
|
||
count: 3 - formData.value.images.length,
|
||
sizeType: ["compressed"],
|
||
sourceType: ["album", "camera"],
|
||
success: (res) => {
|
||
formData.value.images = [...formData.value.images, ...res.tempFilePaths];
|
||
},
|
||
});
|
||
};
|
||
|
||
const deleteImage = (index) => {
|
||
formData.value.images.splice(index, 1);
|
||
};
|
||
|
||
const previewImage = (index) => {
|
||
uni.previewImage({
|
||
urls: formData.value.images,
|
||
current: index,
|
||
});
|
||
};
|
||
|
||
const submitFeedback = async () => {
|
||
if (!isValid.value) return;
|
||
|
||
uni.showLoading({ title: "提交中..." });
|
||
|
||
try {
|
||
const uploadedImages = [];
|
||
// Upload all images first
|
||
for (const img of formData.value.images) {
|
||
const url = await uploadImage(img);
|
||
uploadedImages.push(url);
|
||
}
|
||
|
||
// Submit with real URLs
|
||
const deviceInfo = getDeviceInfo();
|
||
|
||
sendFeedback({
|
||
type: formData.value.type,
|
||
content: formData.value.content,
|
||
images: uploadedImages,
|
||
deviceInfo: deviceInfo,
|
||
});
|
||
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: "感谢您的反馈",
|
||
icon: "success",
|
||
duration: 2000,
|
||
});
|
||
setTimeout(() => {
|
||
uni.navigateBack();
|
||
}, 1000);
|
||
} catch (err) {
|
||
uni.hideLoading();
|
||
uni.showToast({ title: "提交失败", icon: "none" });
|
||
console.error(err);
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.feedback-page {
|
||
min-height: 100vh;
|
||
background: #f9f9f9;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.nav-bar {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
z-index: 100;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 24rpx;
|
||
background: #fff;
|
||
}
|
||
.back {
|
||
font-size: 50rpx;
|
||
margin-right: 24rpx;
|
||
line-height: 1;
|
||
color: #333;
|
||
padding: 20rpx;
|
||
margin-left: -20rpx;
|
||
}
|
||
.nav-title {
|
||
font-size: 34rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
flex: 1;
|
||
text-align: center;
|
||
margin-right: 50rpx;
|
||
}
|
||
|
||
.content-wrap {
|
||
padding: 32rpx;
|
||
}
|
||
|
||
.form-item {
|
||
margin-bottom: 48rpx;
|
||
}
|
||
|
||
.label {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #333;
|
||
margin-bottom: 20rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.required {
|
||
color: #ff3b30;
|
||
margin-left: 8rpx;
|
||
}
|
||
.optional {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
font-weight: normal;
|
||
margin-left: 8rpx;
|
||
}
|
||
|
||
/* Type Selector */
|
||
.type-list {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 20rpx;
|
||
}
|
||
.type-chip {
|
||
padding: 12rpx 32rpx;
|
||
background: #fff;
|
||
border-radius: 999rpx;
|
||
font-size: 26rpx;
|
||
color: #666;
|
||
border: 2rpx solid transparent;
|
||
transition: all 0.2s;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.02);
|
||
}
|
||
.type-chip.active {
|
||
background: #fff0f0;
|
||
color: #ff3b30;
|
||
border-color: #ff3b30;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* Textarea */
|
||
.textarea-box {
|
||
background: #fff;
|
||
border-radius: 24rpx;
|
||
padding: 24rpx;
|
||
position: relative;
|
||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.02);
|
||
}
|
||
.textarea {
|
||
width: 100%;
|
||
height: 240rpx;
|
||
font-size: 28rpx;
|
||
line-height: 1.5;
|
||
color: #333;
|
||
}
|
||
.placeholder {
|
||
color: #ccc;
|
||
}
|
||
.counter {
|
||
position: absolute;
|
||
bottom: 16rpx;
|
||
right: 24rpx;
|
||
font-size: 22rpx;
|
||
color: #999;
|
||
}
|
||
|
||
/* Image Grid */
|
||
.image-grid {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 24rpx;
|
||
}
|
||
.image-item {
|
||
width: 160rpx;
|
||
height: 160rpx;
|
||
position: relative;
|
||
border-radius: 16rpx;
|
||
overflow: hidden;
|
||
}
|
||
.thumb {
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 16rpx;
|
||
}
|
||
.delete-btn {
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
color: #fff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-bottom-left-radius: 12rpx;
|
||
font-size: 32rpx;
|
||
line-height: 1;
|
||
}
|
||
.upload-btn {
|
||
width: 160rpx;
|
||
height: 160rpx;
|
||
background: #fff;
|
||
border-radius: 16rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border: 2rpx dashed #ddd;
|
||
}
|
||
.plus {
|
||
font-size: 60rpx;
|
||
color: #ddd;
|
||
font-weight: 300;
|
||
margin-top: -8rpx;
|
||
}
|
||
|
||
/* Submit Button */
|
||
.submit-wrap {
|
||
margin-top: 60rpx;
|
||
}
|
||
.submit-btn {
|
||
background: #ff3b30;
|
||
color: #fff;
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
height: 88rpx;
|
||
border-radius: 999rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 10rpx 20rpx rgba(255, 59, 48, 0.2);
|
||
transition: all 0.3s;
|
||
}
|
||
.submit-btn.disabled {
|
||
background: #ffccc7;
|
||
box-shadow: none;
|
||
opacity: 0.8;
|
||
}
|
||
.submit-btn::after {
|
||
border: none;
|
||
}
|
||
</style>
|