feat: 添加加载组件并优化团队详情页加载体验
- 新增Loading组件用于全局加载状态显示 - 在团队详情页添加加载状态提示 - 优化API请求时的加载状态管理 - 更新axios基础URL配置
This commit is contained in:
107
my-vue-app/src/components/Loading.vue
Normal file
107
my-vue-app/src/components/Loading.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div class="loading-overlay" v-if="visible">
|
||||
<div class="loading-container">
|
||||
<div class="loading-spinner">
|
||||
<div class="spinner-ring"></div>
|
||||
<div class="spinner-ring"></div>
|
||||
<div class="spinner-ring"></div>
|
||||
<div class="spinner-ring"></div>
|
||||
</div>
|
||||
<div class="loading-text">{{ text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps } from 'vue'
|
||||
|
||||
defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
default: '加载中...'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.loading-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 9999;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
position: relative;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.spinner-ring {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 3px solid transparent;
|
||||
border-top: 3px solid #3b82f6;
|
||||
border-radius: 50%;
|
||||
animation: spin 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||
}
|
||||
|
||||
.spinner-ring:nth-child(1) {
|
||||
animation-delay: -0.45s;
|
||||
}
|
||||
|
||||
.spinner-ring:nth-child(2) {
|
||||
animation-delay: -0.3s;
|
||||
}
|
||||
|
||||
.spinner-ring:nth-child(3) {
|
||||
animation-delay: -0.15s;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 1rem;
|
||||
color: #64748b;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.loading-spinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -5,7 +5,7 @@ import { useUserStore } from '@/stores/user'
|
||||
|
||||
// 创建axios实例
|
||||
const service = axios.create({
|
||||
baseURL: 'http://192.168.15.53:8890' || '', // API基础路径,支持完整URL
|
||||
baseURL: 'http://192.168.15.51:8890' || '', // API基础路径,支持完整URL
|
||||
timeout: 30000, // 请求超时时间
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8'
|
||||
|
||||
@@ -55,72 +55,81 @@
|
||||
</div>
|
||||
<!-- Team Members Detail Section -->
|
||||
<div class="team-detail-section" v-if="selectedGroup">
|
||||
<div class="team-detail-header">
|
||||
<h2>{{ selectedGroup.name }} - 团队成员详情</h2>
|
||||
<div class="team-summary">
|
||||
<div class="summary-item">
|
||||
<span class="label">组长:</span>
|
||||
<span class="value">{{ selectedGroup.leader }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="label">成员数:</span>
|
||||
<span class="value">{{ teamPerformanceDetail.group_details.length }}人</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="label">今日业绩:</span>
|
||||
<span class="value">{{ formatCurrency(selectedGroup.todayPerformance) }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="label">转化率:</span>
|
||||
<span class="value">{{ selectedGroup.conversionRate }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 团队详情加载状态 -->
|
||||
<div v-if="isTeamDetailLoading" class="team-loading">
|
||||
<div class="loading-spinner"></div>
|
||||
<div class="loading-text">正在加载团队详情...</div>
|
||||
</div>
|
||||
|
||||
<div class="members-grid">
|
||||
<div
|
||||
v-for="member in teamPerformanceDetail.group_details"
|
||||
:key="member.id"
|
||||
class="member-card"
|
||||
>
|
||||
<div class="member-header">
|
||||
<div class="member-info">
|
||||
<h3 class="member-name">{{ member.name }}</h3>
|
||||
<!-- 团队详情内容 -->
|
||||
<div v-else>
|
||||
<div class="team-detail-header">
|
||||
<h2>{{ selectedGroup.name }} - 团队成员详情</h2>
|
||||
<div class="team-summary">
|
||||
<div class="summary-item">
|
||||
<span class="label">组长:</span>
|
||||
<span class="value">{{ selectedGroup.leader }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="member-metrics">
|
||||
<div class="metric-row">
|
||||
<div class="metric-item">
|
||||
<span class="metric-label">今日业绩</span>
|
||||
<span class="metric-value">{{ member.today_performance }}</span>
|
||||
<div class="summary-item">
|
||||
<span class="label">成员数:</span>
|
||||
<span class="value">{{ teamPerformanceDetail.group_details?.length || 0 }}人</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="label">今日业绩:</span>
|
||||
<span class="value">{{ formatCurrency(selectedGroup.todayPerformance) }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="label">转化率:</span>
|
||||
<span class="value">{{ selectedGroup.conversionRate }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="members-grid">
|
||||
<div
|
||||
v-for="member in teamPerformanceDetail.group_details"
|
||||
:key="member.id"
|
||||
class="member-card"
|
||||
>
|
||||
<div class="member-header">
|
||||
<div class="member-info">
|
||||
<h3 class="member-name">{{ member.name }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="member-metrics">
|
||||
<div class="metric-row">
|
||||
<div class="metric-item">
|
||||
<span class="metric-label">今日业绩</span>
|
||||
<span class="metric-value">{{ member.today_performance }}</span>
|
||||
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span class="metric-label">月度业绩</span>
|
||||
<span class="metric-value">{{ member.monthly_performance }}</span>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span class="metric-label">月度业绩</span>
|
||||
<span class="metric-value">{{ member.monthly_performance }}</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-row">
|
||||
<div class="metric-item">
|
||||
<span class="metric-label">转化率</span>
|
||||
<span class="metric-value">{{ member.conversion_rate_this_period }}</span>
|
||||
|
||||
<div class="metric-row">
|
||||
<div class="metric-item">
|
||||
<span class="metric-label">转化率</span>
|
||||
<span class="metric-value">{{ member.conversion_rate_this_period }}</span>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span class="metric-label">通话次数</span>
|
||||
<span class="metric-value">{{ member.call_count_this_period }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span class="metric-label">通话次数</span>
|
||||
<span class="metric-value">{{ member.call_count_this_period }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-row">
|
||||
<div class="metric-item">
|
||||
<span class="metric-label">新增客户</span>
|
||||
<span class="metric-value">{{ member.new_customers_this_period }}</span>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span class="metric-label">成交订单</span>
|
||||
<span class="metric-value">{{ member.deals_this_period }}</span>
|
||||
|
||||
<div class="metric-row">
|
||||
<div class="metric-item">
|
||||
<span class="metric-label">新增客户</span>
|
||||
<span class="metric-value">{{ member.new_customers_this_period }}</span>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span class="metric-label">成交订单</span>
|
||||
<span class="metric-value">{{ member.deals_this_period }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -131,6 +140,9 @@
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Loading组件 -->
|
||||
<Loading :visible="isLoading" text="正在加载数据..." />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -144,6 +156,7 @@ import TeamAlerts from '../maneger/components/TeamAlerts.vue'
|
||||
import ProblemRanking from './components/ProblemRanking.vue'
|
||||
import StatisticalIndicators from './components/StatisticalIndicators.vue'
|
||||
import UserDropdown from '@/components/UserDropdown.vue'
|
||||
import Loading from '@/components/Loading.vue'
|
||||
import { getOverallTeamPerformance,getTotalGroupCount,getConversionRate,getTotalCallCount,
|
||||
getNewCustomer,getDepositConversionRate,getActiveCustomerCommunicationRate,getAverageAnswerTime,
|
||||
getTimeoutRate,getTableFillingRate,getUrgentNeedToAddress,getTeamRanking,getTeamRankingInfo } from '@/api/senorManger.js'
|
||||
@@ -158,6 +171,11 @@ const formCompletionRate = ref(90)
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
// Loading状态
|
||||
const isLoading = ref(false)
|
||||
// 团队详情加载状态
|
||||
const isTeamDetailLoading = ref(false)
|
||||
|
||||
// 整体概览
|
||||
const overallTeamPerformance = ref({
|
||||
totalPerformance: {},
|
||||
@@ -331,7 +349,39 @@ async function fetchUrgentNeedToAddress() {
|
||||
"少玩手机": "0.00%",
|
||||
"回归学校": "0.00%",
|
||||
"心理健康": "0.00%"
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 团队详情加载样式
|
||||
.team-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem;
|
||||
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #3498db;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); },
|
||||
"urgent_issue_consultations": {
|
||||
"成绩提升": 0,
|
||||
"少玩手机": 0,
|
||||
@@ -349,7 +399,6 @@ async function fetchUrgentNeedToAddress() {
|
||||
const teamRanking = ref({})
|
||||
|
||||
async function fetchTeamRanking() {
|
||||
console.log(555555,userStore.userInfo)
|
||||
const params={
|
||||
user_name: userStore.userInfo.username,
|
||||
user_level: userStore.userInfo.user_level.toString(),
|
||||
@@ -357,31 +406,6 @@ async function fetchTeamRanking() {
|
||||
try {
|
||||
const response = await getTeamRanking(params)
|
||||
teamRanking.value = response.data
|
||||
/**
|
||||
* "data": {
|
||||
"user_name": "陈盼良",
|
||||
"user_level": 3,
|
||||
"formal_plural": {
|
||||
"巅峰五部-时永帅": 2,
|
||||
"技术部": 0,
|
||||
"星火一部--张瑾": 4,
|
||||
"美团业务支持部": 0,
|
||||
"星耀三部-周毅": 3,
|
||||
"亮剑二部-田贵星": 2,
|
||||
"巅峰三部--刘东洋": 0
|
||||
},
|
||||
"composition_transformation": {
|
||||
"巅峰五部-时永帅": "0.00%",
|
||||
"技术部": "0.00%",
|
||||
"星火一部--张瑾": "11.43%",
|
||||
"美团业务支持部": "0.00%",
|
||||
"星耀三部-周毅": "15.00%",
|
||||
"亮剑二部-田贵星": "5.71%",
|
||||
"巅峰三部--刘东洋": "0.00%"
|
||||
}
|
||||
}
|
||||
*/
|
||||
console.log('团队业绩排名:', response.data)
|
||||
} catch (error) {
|
||||
console.error('获取团队业绩排名失败:', error)
|
||||
}
|
||||
@@ -399,26 +423,31 @@ async function fetchTeamPerformanceDetail(department) {
|
||||
teamPerformanceDetail.value = {}
|
||||
const response = await getTeamRankingInfo(params)
|
||||
teamPerformanceDetail.value = response.data
|
||||
console.log('团队业绩详情:', response.data)
|
||||
} catch (error) {
|
||||
console.error('获取团队业绩详情失败:', error)
|
||||
}
|
||||
}
|
||||
// 初始化时获取数据
|
||||
onMounted(async ()=>{
|
||||
// await fetchOverallTeamPerformance()
|
||||
// await fetchActiveGroups()
|
||||
// await fetchConversionRate()
|
||||
// await fetchTotalCallCount()
|
||||
// await fetchNewCustomers()
|
||||
// await fetchDepositConversions()
|
||||
// await fetchCustomerCommunicationRate()
|
||||
// await fetchAverageResponseTime()
|
||||
// await fetchTimeoutRate()
|
||||
// await fetchTableFillingRate()
|
||||
await fetchUrgentNeedToAddress()
|
||||
await fetchTeamRanking()
|
||||
|
||||
try {
|
||||
isLoading.value = true
|
||||
await fetchOverallTeamPerformance()
|
||||
await fetchActiveGroups()
|
||||
await fetchConversionRate()
|
||||
await fetchTotalCallCount()
|
||||
await fetchNewCustomers()
|
||||
await fetchDepositConversions()
|
||||
await fetchCustomerCommunicationRate()
|
||||
await fetchAverageResponseTime()
|
||||
await fetchTimeoutRate()
|
||||
await fetchTableFillingRate()
|
||||
await fetchUrgentNeedToAddress()
|
||||
await fetchTeamRanking()
|
||||
} catch (error) {
|
||||
console.error('数据加载失败:', error)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
// 组别数据
|
||||
@@ -427,7 +456,7 @@ const groups=[]
|
||||
const selectedGroup = ref(groups[0])
|
||||
|
||||
// 选择组别函数
|
||||
const selectGroup = (group) => {
|
||||
const selectGroup = async (group) => {
|
||||
selectedGroup.value = group
|
||||
// 获取部门名称并调用团队业绩详情接口
|
||||
// 从teamRanking数据中查找对应的原始部门名称
|
||||
@@ -445,7 +474,16 @@ const selectGroup = (group) => {
|
||||
}
|
||||
}
|
||||
console.log('选中的部门:', group.name, '-> 发送的部门名称:', department)
|
||||
fetchTeamPerformanceDetail(department)
|
||||
|
||||
// 设置团队详情加载状态
|
||||
isTeamDetailLoading.value = true
|
||||
try {
|
||||
await fetchTeamPerformanceDetail(department)
|
||||
} catch (error) {
|
||||
console.error('获取团队详情失败:', error)
|
||||
} finally {
|
||||
isTeamDetailLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化货币
|
||||
@@ -860,6 +898,35 @@ const getStatusText = (status) => {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
// 团队详情加载状态
|
||||
.team-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem;
|
||||
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid #e2e8f0;
|
||||
border-top: 3px solid #3b82f6;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
color: #64748b;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
// 移动端适配
|
||||
@media (max-width: 768px) {
|
||||
.dashboard-header {
|
||||
|
||||
Reference in New Issue
Block a user