Files
----/前端源码/uni-app/components/screens/ProfileFeedbackScreen.vue

478 lines
12 KiB
Vue
Raw Permalink 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-screen">
<view class="feedback-bg"></view>
<!-- 状态栏占位 -->
<view class="status-bar-placeholder"></view>
<!-- Header -->
<view class="feedback-header">
<view class="feedback-back-btn" @click="handleBack">
<text class="feedback-back-icon"></text>
</view>
<text class="feedback-title">意见反馈</text>
<view class="feedback-header-placeholder"></view>
</view>
<!-- Content -->
<scroll-view scroll-y class="feedback-content">
<view class="feedback-content-inner">
<!-- 反馈类型 -->
<view class="feedback-section">
<text class="feedback-section-title">反馈类型</text>
<view class="feedback-type-list">
<view v-for="type in feedbackTypes" :key="type.value" class="feedback-type-item"
:class="{ 'feedback-type-item-active': selectedType === type.value }"
@click="selectType(type.value)">
<text class="feedback-type-label">{{ type.label }}</text>
</view>
</view>
</view>
<!-- 反馈内容 -->
<view class="feedback-section">
<text class="feedback-section-title">反馈内容</text>
<textarea class="feedback-textarea" v-model="feedbackContent"
placeholder="请详细描述您遇到的问题或建议,我们会认真对待每一条反馈..." :maxlength="500"
placeholder-class="feedback-textarea-placeholder" />
<view class="feedback-textarea-counter">
<text class="feedback-textarea-counter-text">{{ feedbackContent.length }}/500</text>
</view>
</view>
<!-- 上传图片 -->
<view class="feedback-section">
<text class="feedback-section-title">上传图片选填</text>
<view class="feedback-images">
<view v-for="(img, index) in uploadedImages" :key="index" class="feedback-image-item">
<image :src="img" class="feedback-image" mode="aspectFill" />
<view class="feedback-image-delete" @click="deleteImage(index)">
<text class="feedback-image-delete-icon">×</text>
</view>
</view>
<view v-if="uploadedImages.length < 3" class="feedback-image-upload" @click="chooseImage">
<text class="feedback-image-upload-icon">+</text>
<text class="feedback-image-upload-text">添加图片</text>
</view>
</view>
<text class="feedback-images-tip">最多上传3张图片每张不超过5MB</text>
</view>
<!-- 联系方式 -->
<view class="feedback-section">
<text class="feedback-section-title">联系方式选填</text>
<input class="feedback-input" v-model="contactInfo" placeholder="请输入手机号或微信号,方便我们联系您"
placeholder-class="feedback-input-placeholder" />
</view>
<!-- 提交按钮 -->
<view class="feedback-submit-btn" @click="submitFeedback">
<text class="feedback-submit-btn-text">提交反馈</text>
</view>
<!-- 温馨提示 -->
<view class="feedback-tips">
<text class="feedback-tips-title">温馨提示</text>
<text class="feedback-tips-text"> 我们会在1-3个工作日内处理您的反馈</text>
<text class="feedback-tips-text"> 如需回复请留下您的联系方式</text>
<text class="feedback-tips-text"> 感谢您对壹梵起名的支持与建议</text>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { userApi } from "@/api";
declare const uni: any;
const emit = defineEmits<{
back: [];
}>();
// 反馈类型
const feedbackTypes = [
{ value: 'suggestion', label: '功能建议', icon: '💡' },
{ value: 'bug', label: '问题反馈', icon: '🐛' },
{ value: 'complaint', label: '投诉建议', icon: '📢' },
{ value: 'other', label: '其他', icon: '💬' },
];
const selectedType = ref<'suggestion' | 'bug' | 'complaint' | 'other'>('suggestion');
const feedbackContent = ref("");
const uploadedImages = ref<string[]>([]);
const contactInfo = ref("");
const selectType = (type: 'suggestion' | 'bug' | 'complaint' | 'other') => {
selectedType.value = type;
};
const chooseImage = async () => {
uni.chooseImage({
count: 3 - uploadedImages.value.length,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: async (res: any) => {
const tempFilePaths = res.tempFilePaths;
uni.showLoading({ title: '上传中...' });
try {
// 逐个上传图片到服务器
for (const filePath of tempFilePaths) {
const result = await userApi.uploadImage(filePath);
// 使用服务器返回的file_url
uploadedImages.value.push(result.file_url);
}
uni.hideLoading();
} catch (error: any) {
uni.hideLoading();
uni.showToast({
title: error.msg || '图片上传失败',
icon: 'none'
});
}
}
});
};
const deleteImage = (index: number) => {
uploadedImages.value.splice(index, 1);
};
const submitFeedback = async () => {
if (!feedbackContent.value.trim()) {
uni.showToast({ title: "请输入反馈内容", icon: "none" });
return;
}
try {
await userApi.submitFeedback({
content: feedbackContent.value.trim(),
images: uploadedImages.value.join(','),
contact: contactInfo.value.trim(),
feedback_type: selectedType.value
});
uni.showToast({
title: "提交成功,感谢您的反馈!",
icon: "success",
duration: 2000
});
// 延迟返回,让用户看到成功提示
setTimeout(() => {
handleBack();
}, 2000);
} catch (error: any) {
uni.showToast({
title: error.msg || "提交失败,请稍后重试",
icon: "none"
});
}
};
const handleBack = () => {
emit('back');
};
</script>
<style scoped>
.feedback-screen {
height: 100%;
display: flex;
flex-direction: column;
background-color: #f0efe9;
position: relative;
}
.status-bar-placeholder {
height: var(--status-bar-height, 0);
width: 100%;
flex-shrink: 0;
}
.feedback-bg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
opacity: 0.3;
background-image: url("https://www.transparenttextures.com/patterns/rice-paper.png");
}
/* Header */
.feedback-header {
position: relative;
z-index: 10;
height: 88rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 32rpx;
border-bottom: 1rpx solid #dcd3c9;
background-color: rgba(253, 251, 247, 0.8);
backdrop-filter: blur(10rpx);
}
.feedback-back-btn {
width: 64rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
padding: 0;
margin-left: -16rpx;
}
.feedback-back-icon {
font-size: 48rpx;
color: #5a5a5a;
font-weight: 300;
}
.feedback-title {
font-size: 32rpx;
font-weight: 700;
color: #2c2c2c;
letter-spacing: 0.2em;
}
.feedback-header-placeholder {
width: 64rpx;
}
/* Content */
.feedback-content {
flex: 1;
height: 0;
position: relative;
z-index: 10;
}
.feedback-content-inner {
padding: 32rpx;
padding-bottom: calc(32rpx + env(safe-area-inset-bottom, 0px));
}
/* Section */
.feedback-section {
margin-bottom: 32rpx;
}
.feedback-section-title {
font-size: 28rpx;
font-weight: 700;
color: #2c2c2c;
margin-bottom: 16rpx;
display: block;
}
/* Type List */
.feedback-type-list {
display: flex;
gap: 16rpx;
flex-wrap: wrap;
}
.feedback-type-item {
flex: 1;
min-width: 150rpx;
padding: 20rpx 16rpx;
background-color: #fffdf9;
border: 2rpx solid #e5e5e5;
border-radius: 16rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
transition: all 0.3s;
}
.feedback-type-item-active {
background-color: #8b2323;
border-color: #8b2323;
}
.feedback-type-icon {
font-size: 32rpx;
}
.feedback-type-label {
font-size: 24rpx;
color: #2c2c2c;
}
.feedback-type-item-active .feedback-type-label {
color: #f2e6d8;
}
/* Textarea */
.feedback-textarea {
width: 100%;
min-height: 240rpx;
background-color: #fffdf9;
border: 1rpx solid #e5e5e5;
border-radius: 16rpx;
padding: 24rpx;
font-size: 28rpx;
color: #2c2c2c;
box-sizing: border-box;
line-height: 1.6;
}
.feedback-textarea-placeholder {
color: #ccc;
}
.feedback-textarea-counter {
margin-top: 8rpx;
text-align: right;
}
.feedback-textarea-counter-text {
font-size: 20rpx;
color: #999;
}
/* Images */
.feedback-images {
display: flex;
gap: 16rpx;
flex-wrap: wrap;
margin-bottom: 8rpx;
}
.feedback-image-item {
position: relative;
width: 160rpx;
height: 160rpx;
}
.feedback-image {
width: 100%;
height: 100%;
border-radius: 16rpx;
border: 1rpx solid #e5e5e5;
}
.feedback-image-delete {
position: absolute;
top: -8rpx;
right: -8rpx;
width: 40rpx;
height: 40rpx;
background-color: #8b2323;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2);
}
.feedback-image-delete-icon {
font-size: 32rpx;
color: #fff;
line-height: 1;
}
.feedback-image-upload {
width: 160rpx;
height: 160rpx;
background-color: #fffdf9;
border: 2rpx dashed #e5e5e5;
border-radius: 16rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8rpx;
}
.feedback-image-upload-icon {
font-size: 48rpx;
color: #ccc;
line-height: 1;
}
.feedback-image-upload-text {
font-size: 20rpx;
color: #999;
}
.feedback-images-tip {
font-size: 20rpx;
color: #999;
display: block;
}
/* Input */
.feedback-input {
width: 100%;
height: 88rpx;
background-color: #fffdf9;
border: 1rpx solid #e5e5e5;
border-radius: 16rpx;
padding: 0 24rpx;
font-size: 28rpx;
color: #2c2c2c;
box-sizing: border-box;
}
.feedback-input-placeholder {
color: #ccc;
}
/* Submit Button */
.feedback-submit-btn {
width: 100%;
padding: 28rpx 0;
background-color: #8b2323;
border-radius: 16rpx;
display: flex;
justify-content: center;
margin-bottom: 32rpx;
transition: opacity 0.3s;
}
.feedback-submit-btn:active {
opacity: 0.8;
}
.feedback-submit-btn-text {
font-size: 32rpx;
font-weight: 700;
color: #f2e6d8;
}
/* Tips */
.feedback-tips {
background-color: rgba(255, 253, 249, 0.6);
border-radius: 16rpx;
padding: 24rpx;
display: flex;
flex-direction: column;
gap: 12rpx;
}
.feedback-tips-title {
font-size: 24rpx;
font-weight: 700;
color: #8b2323;
margin-bottom: 4rpx;
}
.feedback-tips-text {
font-size: 20rpx;
color: #666;
line-height: 1.6;
}
</style>