Files
spring-festival-greetings/pages/feedback/index.vue

353 lines
7.1 KiB
Vue
Raw Normal View History

2026-01-26 11:18:24 +08:00
<template>
<view class="feedback-page" :style="{ paddingTop: getBavBarHeight() + 'px' }">
<!-- Custom Navbar -->
<view class="nav-bar">
<view class="back" @tap="goBack"></view>
<text class="nav-title">意见反馈</text>
</view>
<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";
2026-01-26 17:16:52 +08:00
import { getBavBarHeight, getDeviceInfo } from "@/utils/system";
2026-01-26 11:18:24 +08:00
import { sendFeedback } from "@/api/mine.js";
import { uploadImage } from "@/utils/common.js";
const feedbackTypes = [
{ label: "功能建议", value: 1 },
{ label: "问题反馈", value: 2 },
{ label: "投诉", value: 3 },
2026-01-26 17:16:52 +08:00
{ label: "其他", value: 4 },
2026-01-26 11:18:24 +08:00
];
const formData = ref({
2026-01-26 17:16:52 +08:00
type: 1,
2026-01-26 11:18:24 +08:00
content: "",
images: [],
});
const isValid = computed(() => {
return formData.value.content.trim().length > 0;
});
const goBack = () => {
uni.navigateBack();
};
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
2026-01-26 17:16:52 +08:00
const deviceInfo = getDeviceInfo();
sendFeedback({
type: formData.value.type,
content: formData.value.content,
2026-01-26 11:18:24 +08:00
images: uploadedImages,
2026-01-26 17:16:52 +08:00
deviceInfo: deviceInfo,
2026-01-26 11:18:24 +08:00
});
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 {
display: flex;
align-items: center;
padding: 16rpx 24rpx;
background: #fff;
position: sticky;
top: 0;
z-index: 100;
}
.back {
font-size: 40rpx;
margin-right: 24rpx;
line-height: 1;
}
.nav-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.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>