478 lines
12 KiB
Vue
478 lines
12 KiB
Vue
<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>
|