Files
spring-festival-greetings/pages/feedback/index.vue
2026-01-31 22:23:39 +08:00

361 lines
7.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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";
import { getBavBarHeight, getDeviceInfo } from "@/utils/system";
import { sendFeedback } from "@/api/mine.js";
import { uploadImage } from "@/utils/common.js";
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 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
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>