feat(反馈系统): 添加用户反馈功能组件
在多个视图页面中添加反馈按钮和FeedbackForm组件,允许用户提交反馈意见。主要变更包括: 1. 创建FeedbackForm.vue组件实现反馈表单 2. 在topone、seniorManager、secondTop等视图添加反馈按钮 3. 实现表单提交逻辑和样式 4. 修复manager.vue中Sale组件路径大小写问题 5. 将index.html语言设置为中文
This commit is contained in:
334
my-vue-app/src/components/FeedbackForm.vue
Normal file
334
my-vue-app/src/components/FeedbackForm.vue
Normal 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>
|
||||
Reference in New Issue
Block a user