upload project source code

This commit is contained in:
2026-04-30 18:49:43 +08:00
commit 9b394ba682
2277 changed files with 660945 additions and 0 deletions

View 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>