Compare commits

...

2 Commits

Author SHA1 Message Date
676b213a7d feat(反馈表单): 在多个视图添加反馈表单功能及样式
为topOne、seniorManager和secondTop视图添加反馈表单控制逻辑和按钮样式
2025-10-09 20:17:21 +08:00
600684570a feat(反馈系统): 添加用户反馈功能组件
在多个视图页面中添加反馈按钮和FeedbackForm组件,允许用户提交反馈意见。主要变更包括:
1. 创建FeedbackForm.vue组件实现反馈表单
2. 在topone、seniorManager、secondTop等视图添加反馈按钮
3. 实现表单提交逻辑和样式
4. 修复manager.vue中Sale组件路径大小写问题
5. 将index.html语言设置为中文
2025-09-30 15:59:39 +08:00
7 changed files with 509 additions and 28 deletions

View File

@@ -1,5 +1,5 @@
<!doctype html>
<html lang="en">
<html lang="zh">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />

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>

View File

@@ -74,7 +74,7 @@ import TeamReport from "./components/TeamReport.vue";
import SalesFunnel from "./components/SalesFunnel.vue";
import PerformanceRanking from "./components/PerformanceRanking.vue";
import MemberDetails from "./components/MemberDetails.vue";
import Sale from "../person/Sale.vue";
import Sale from "../person/sale.vue";
import SalesTimelineWithTaskList from "../person/components/SalesTimelineWithTaskList.vue";
import RawDataCards from "../person/components/RawDataCards.vue";
import CustomerDetail from "../person/components/CustomerDetail.vue";

View File

@@ -29,11 +29,21 @@
style="display: flex; align-items: center; gap: 30px"
>
</div>
<div style="display: flex; align-items: center; gap: 20px;">
<button @click="showFeedbackFormModal" class="feedback-btn">意见反馈</button>
<FeedbackForm
:is-visible="showFeedbackForm"
@close="closeFeedbackFormModal"
@submit-feedback="closeFeedbackFormModal"
/>
<UserDropdown
:card-visibility="cardVisibility"
@update-card-visibility="updateCardVisibility"
/>
</div>
</div>
</template>
</div>
<div class="section-content">
@@ -182,6 +192,7 @@ import RawDataCards from "./components/RawDataCards.vue";
import WeekAnalize from "./components/WeekAnalize.vue";
import UserDropdown from "@/components/UserDropdown.vue";
import Loading from "@/components/Loading.vue";
import FeedbackForm from "@/components/FeedbackForm.vue";
import {getCustomerAttendance,getTodayCall,getProblemDistribution,getTableFillingRate,getAverageResponseTime,
getWeeklyActiveCommunicationRate,getTimeoutResponseRate,getCustomerCallInfo,getCustomerChatInfo,getCustomerFormInfo,
getConversionRateAndAllocatedData,getCustomerAttendanceAfterClass4,getPayMoneyCustomers,getSalesFunnel,getGoldContactTime,
@@ -294,6 +305,9 @@ const showModal = ref(false)
const modalContent = ref('')
const modalTitle = ref('')
// FeedbackForm 弹框状态
const showFeedbackForm = ref(false)
// 下载弹框状态
const showDownloadModal = ref(false)
const downloadModalContent = ref('')
@@ -891,6 +905,16 @@ const closeDownloadModal = () => {
downloadModalTitle.value = ''
}
// 显示 FeedbackForm
const showFeedbackFormModal = () => {
showFeedbackForm.value = true
}
// 关闭 FeedbackForm
const closeFeedbackFormModal = () => {
showFeedbackForm.value = false
}
// // 处理SOP分析事件
// const handleAnalyzeSop = (analyzeData) => {
// console.log('handleAnalyzeSop', analyzeData)
@@ -1944,6 +1968,22 @@ $primary: #3b82f6;
}
}
// 意见反馈按钮样式
.feedback-btn {
background-color: #4299e1;
color: white;
border: none;
border-radius: 6px;
padding: 0.5rem 1rem;
font-size: 0.9rem;
cursor: pointer;
transition: background-color 0.2s;
}
.feedback-btn:hover {
background-color: #3182ce;
}
// 弹框响应式样式
@media (max-width: 768px) {
.modal-container {

View File

@@ -28,11 +28,19 @@
<div v-if="!isRouteNavigation">
<!-- 用户下拉菜单 -->
<div style="display: flex; align-items: center; gap: 20px;">
<button @click="showFeedbackFormModal" class="feedback-btn">意见反馈</button>
<FeedbackForm
:is-visible="showFeedbackForm"
@close="closeFeedbackFormModal"
@submit-feedback="closeFeedbackFormModal"
/>
<UserDropdown
:card-visibility="cardVisibility"
@update-card-visibility="updateCardVisibility"
/>
</div>
</div>
</div>
</div>
@@ -176,7 +184,7 @@
<script setup>
import { ref, onMounted, computed,reactive } from 'vue'
import FeedbackForm from "@/components/FeedbackForm.vue";
// 30分钟数据缓存系统
const cache = new Map()
const CACHE_DURATION = 30 * 60 * 1000 // 30分钟
@@ -272,11 +280,23 @@
teamDetail: true
})
// FeedbackForm 控制变量
const showFeedbackForm = ref(false)
// 更新卡片显示状态
const updateCardVisibility = (newVisibility) => {
Object.assign(cardVisibility.value, newVisibility)
console.log('卡片显示状态已更新:', cardVisibility.value)
}
// FeedbackForm 控制方法
const showFeedbackFormModal = () => {
showFeedbackForm.value = true
}
const closeFeedbackFormModal = () => {
showFeedbackForm.value = false
}
// 营期调控逻辑
// This would ideally come from a prop or API call based on the logged-in user
const centerData = ref({
@@ -1646,6 +1666,22 @@ const hideTooltip = () => {
border-radius: 0.5rem;
border: 1px solid rgba(0, 0, 0, 0.1);
}
/* 意见反馈按钮样式 */
.feedback-btn {
background-color: #4299e1;
color: white;
border: none;
border-radius: 6px;
padding: 0.5rem 1rem;
font-size: 0.9rem;
cursor: pointer;
transition: background-color 0.2s;
&:hover {
background-color: #3182ce;
}
}
}
.stage-control {
margin-left: 20px;

View File

@@ -27,15 +27,21 @@
<div v-if="!isRouteNavigation">
<!-- 用户下拉菜单 -->
<div style="display: flex; align-items: center; gap: 20px;">
<button @click="showFeedbackFormModal" class="feedback-btn">意见反馈</button>
<FeedbackForm
:is-visible="showFeedbackForm"
@close="closeFeedbackFormModal"
@submit-feedback="closeFeedbackFormModal"
/>
<UserDropdown
class="header-ringht"
style="margin-left: auto;"
:card-visibility="cardVisibility"
@update-card-visibility="updateCardVisibility"
/>
</div>
</div>
</div>
</div>
</header>
<!-- Main Content -->
<main class="dashboard-main">
@@ -198,6 +204,7 @@ import { getOverallTeamPerformance,getTotalGroupCount,getConversionRate,getTotal
getTimeoutRate,getTableFillingRate,getUrgentNeedToAddress,getTeamRanking,getTeamRankingInfo,getAbnormalResponseRate } from '@/api/senorManger.js'
import { useUserStore } from '@/stores/user.js'
import FeedbackForm from "@/components/FeedbackForm.vue";
// 缓存系统
const cache = new Map()
@@ -307,6 +314,8 @@ const timeoutResponseRate = ref(5)
const severeTimeoutRate = ref(2)
const formCompletionRate = ref(90)
const CheckType = ref('month')
// FeedbackForm 控制变量
const showFeedbackForm = ref(false)
// 更新CheckType的方法
const updateCheckType = async (newValue) => {
@@ -318,6 +327,15 @@ const updateCheckType = async (newValue) => {
console.log('数据已根据新的统计模式重新加载')
}
// FeedbackForm 控制方法
const showFeedbackFormModal = () => {
showFeedbackForm.value = true
}
const closeFeedbackFormModal = () => {
showFeedbackForm.value = false
}
// 卡片显示状态
const cardVisibility = ref({
centerOverview: true,
@@ -1490,4 +1508,20 @@ const hideTooltip = () => {
border: 1px solid rgba(0, 0, 0, 0.1);
}
}
// 意见反馈按钮样式
.feedback-btn {
background-color: #4299e1;
color: white;
border: none;
border-radius: 6px;
padding: 0.5rem 1rem;
font-size: 0.9rem;
cursor: pointer;
transition: background-color 0.2s;
}
.feedback-btn:hover {
background-color: #3182ce;
}
</style>

View File

@@ -4,11 +4,19 @@
<div class="dashboard-header">
<h1>管理者数据看板</h1>
<!-- 头像 -->
<div style="display: flex; align-items: center; gap: 20px;">
<button @click="showFeedbackFormModal" class="feedback-btn">意见反馈</button>
<FeedbackForm
:is-visible="showFeedbackForm"
@close="closeFeedbackFormModal"
@submit-feedback="closeFeedbackFormModal"
/>
<UserDropdown
:card-visibility="cardVisibility"
@update-card-visibility="updateCardVisibility"
/>
</div>
</div>
<!-- 第一行核心业绩指标销售实时进度 -->
<div class="dashboard-row row-1">
@@ -111,6 +119,7 @@ import PeriodStage from "./components/PeriodStage.vue";
import { getOverallCompanyPerformance,getCompanyDepositConversionRate,getCompanyTotalCallCount,getCompanyNewCustomer,getCompanyConversionRate,getCompanyRealTimeProgress
,getCompanyConversionRateVsLast,getSalesMonthlyPerformance,getCustomerTypeDistribution,getUrgentNeedToAddress,getLevelTree,getDetailedDataTable,getExcellentRecordFile } from "@/api/top";
import { useUserStore } from "@/stores/user.js";
import FeedbackForm from "@/components/FeedbackForm.vue";
// 缓存系统
const cache = new Map();
@@ -236,6 +245,18 @@ const cardVisibility = ref({
detailedDataTable: true
});
// FeedbackForm 控制变量
const showFeedbackForm = ref(false);
// FeedbackForm 控制方法
const showFeedbackFormModal = () => {
showFeedbackForm.value = true;
};
const closeFeedbackFormModal = () => {
showFeedbackForm.value = false;
};
// 更新卡片显示状态
const updateCardVisibility = (newVisibility) => {
Object.assign(cardVisibility.value, newVisibility);
@@ -2164,4 +2185,20 @@ button {
-ms-user-select: text;
user-select: text;
}
/* 意见反馈按钮样式 */
.feedback-btn {
background-color: #4299e1;
color: white;
border: none;
border-radius: 6px;
padding: 0.5rem 1rem;
font-size: 0.9rem;
cursor: pointer;
transition: background-color 0.2s;
}
.feedback-btn:hover {
background-color: #3182ce;
}
</style>