feat(反馈系统): 添加用户反馈功能组件

在多个视图页面中添加反馈按钮和FeedbackForm组件,允许用户提交反馈意见。主要变更包括:
1. 创建FeedbackForm.vue组件实现反馈表单
2. 在topone、seniorManager、secondTop等视图添加反馈按钮
3. 实现表单提交逻辑和样式
4. 修复manager.vue中Sale组件路径大小写问题
5. 将index.html语言设置为中文
This commit is contained in:
2025-09-30 15:59:39 +08:00
parent 6f0d10b881
commit 600684570a
7 changed files with 410 additions and 12 deletions

View File

@@ -0,0 +1,334 @@
<template>
<div v-if="isVisible" class="modal-overlay" @click="closeModal">
<div class="modal-container" @click.stop>
<div class="modal-header">
<h3>意见反馈</h3>
<button class="modal-close-btn" @click="closeModal">×</button>
</div>
<div class="feedback-form-container">
<h2>意见反馈</h2>
<p class="subtitle">我们期待听到您的声音不断改进我们的产品</p>
<!-- 提交成功或失败的提示信息 -->
<div v-if="submitStatus === 'success'" class="feedback-message success">
感谢您的反馈我们已经收到您的宝贵意见
</div>
<div v-if="submitStatus === 'error'" class="feedback-message error">
提交失败请检查网络或稍后重试
</div>
<form v-if="submitStatus !== 'success'" @submit.prevent="handleSubmit">
<!-- 反馈类型 -->
<div class="form-group">
<label for="feedback-type">反馈类型</label>
<select id="feedback-type" v-model="formData.type">
<option>功能建议</option>
<option>界面优化</option>
<option>Bug 反馈</option>
<option>其他</option>
</select>
</div>
<!-- 反馈内容 -->
<div class="form-group">
<label for="feedback-content">反馈内容 (必填)</label>
<textarea
id="feedback-content"
v-model="formData.content"
placeholder="请详细描述您的问题或建议..."
rows="6"
required
></textarea>
</div>
<!-- 提交按钮 -->
<div class="form-actions">
<button type="submit" :disabled="isSubmitting">
<span v-if="isSubmitting">正在提交...</span>
<span v-else>提交反馈</span>
</button>
</div>
</form>
</div>
</div>
</div>
</template>
<script setup>
import axios from 'axios';
import { ref, reactive } from 'vue';
// 定义组件的props
const props = defineProps({
isVisible: {
type: Boolean,
default: false
}
});
// 定义组件要触发的事件
const emit = defineEmits(['submit-feedback', 'close']);
// 使用 reactive 创建响应式表单数据对象
const formData = reactive({
type: '功能建议', // 默认值
content: '',
contact: '',
screenshot: null, // 存储文件对象
});
// ref 用于独立的响应式值
const isSubmitting = ref(false); // 是否正在提交
const submitStatus = ref(null); // 'success', 'error', or null
const imagePreviewUrl = ref(null); // 图片预览 URL
const fileInputRef = ref(null); // 用于引用文件输入元素
// 文件选择变化时的处理函数
const handleFileChange = (event) => {
const file = event.target.files[0];
if (file) {
formData.screenshot = file;
// 创建一个临时的 URL 用于图片预览
imagePreviewUrl.value = URL.createObjectURL(file);
}
};
// 移除已选图片
const removeImage = () => {
formData.screenshot = null;
imagePreviewUrl.value = null;
// 清空文件输入框的值,以便用户可以再次选择相同的文件
if (fileInputRef.value) {
fileInputRef.value.value = '';
}
};
// 关闭模态框
const closeModal = () => {
emit('close');
};
// 表单提交处理函数
const handleSubmit = async () => {
// 简单验证
if (!formData.content.trim()) {
alert('反馈内容不能为空!');
return;
}
isSubmitting.value = true;
submitStatus.value = null;
try {
// 创建 FormData 对象
// 获取 token (假设存储在 localStorage 中)
const token = localStorage.getItem('token') || '';
// 发送 POST 请求到后端接口
const response = await axios.post('https://mldash.nycjy.cn/api/v1/submit_feedback', {type: formData.type, content: formData.content}, {
headers: {
'Authorization': `Bearer ${token}`
}
});
console.log('响应状态8888:', response.data.message);
// 提交成功
submitStatus.value = 'success';
// 触发父组件的事件,并传递数据
emit('submit-feedback', { ...formData });
} catch (error) {
console.error('提交反馈失败:', error);
// 提交失败
submitStatus.value = 'error';
} finally {
// 请求结束
isSubmitting.value = false;
}
};
</script>
<style scoped>
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-container {
background-color: #ffffff;
border-radius: 10px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
max-width: 600px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
position: relative;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 2rem;
border-bottom: 1px solid #e2e8f0;
}
.modal-header h3 {
margin: 0;
color: #1a202c;
font-size: 1.25rem;
}
.modal-close-btn {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #718096;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
}
.modal-close-btn:hover {
color: #1a202c;
}
.feedback-form-container {
padding: 2rem;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
h2 {
text-align: center;
color: #1a202c;
margin-top: 0;
margin-bottom: 0.5rem;
}
.subtitle {
text-align: center;
color: #718096;
margin-bottom: 2rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: #4a5568;
}
input[type="text"],
select,
textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid #cbd5e0;
border-radius: 6px;
font-size: 1rem;
color: #2d3748;
transition: border-color 0.2s, box-shadow 0.2s;
}
input[type="text"]:focus,
select:focus,
textarea:focus {
outline: none;
border-color: #4299e1;
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5);
}
input[type="file"] {
width: 100%;
padding: 0.5rem;
}
.image-preview {
position: relative;
margin-top: 1rem;
max-width: 200px;
}
.image-preview img {
width: 100%;
height: auto;
border-radius: 6px;
border: 1px solid #e2e8f0;
}
.remove-image-btn {
position: absolute;
top: -10px;
right: -10px;
background-color: #e53e3e;
color: white;
border: none;
border-radius: 50%;
width: 24px;
height: 24px;
font-size: 16px;
line-height: 24px;
text-align: center;
cursor: pointer;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.form-actions {
text-align: center;
}
button[type="submit"] {
padding: 0.75rem 2rem;
background-color: #4299e1;
color: white;
border: none;
border-radius: 6px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.2s;
width: 100%;
}
button[type="submit"]:hover:not(:disabled) {
background-color: #3182ce;
}
button[type="submit"]:disabled {
background-color: #a0aec0;
cursor: not-allowed;
}
.feedback-message {
padding: 1rem;
border-radius: 6px;
text-align: center;
margin-bottom: 1.5rem;
font-weight: 500;
}
.feedback-message.success {
background-color: #c6f6d5;
color: #2f855a;
border: 1px solid #9ae6b4;
}
.feedback-message.error {
background-color: #fed7d7;
color: #c53030;
border: 1px solid #feb2b2;
}
</style>