upload project source code
This commit is contained in:
477
前端源码/uni-app/components/screens/ProfileFeedbackScreen.vue
Normal file
477
前端源码/uni-app/components/screens/ProfileFeedbackScreen.vue
Normal file
@@ -0,0 +1,477 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user