feat(topOne): 重构核心业绩指标组件并添加用户下拉菜单
- 重构KpiMetrics组件以使用API获取的真实数据 - 添加UserDropdown组件到topOne页面 - 简化http工具中的get方法 - 更新KPI卡片显示逻辑和标签文本
This commit is contained in:
@@ -147,12 +147,10 @@ service.interceptors.response.use(
|
|||||||
// 封装常用的请求方法
|
// 封装常用的请求方法
|
||||||
const http = {
|
const http = {
|
||||||
// GET请求
|
// GET请求
|
||||||
get(url, params = {}, config = {}) {
|
get(url) {
|
||||||
return service({
|
return service({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url,
|
url
|
||||||
params,
|
|
||||||
...config
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 2. 活跃组数 -->
|
<!-- 2. 定金转化率 -->
|
||||||
<div class="kpi-card">
|
<div class="kpi-card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span class="card-label">定金转化率</span>
|
<span class="card-label">定金转化率</span>
|
||||||
@@ -41,10 +41,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<span class="card-value">{{ kpiData.activeTeams.value }}</span>
|
<span class="card-value">{{ kpiData.activeTeams.value }}</span>
|
||||||
<span class="card-unit">组</span>
|
<span class="card-unit">%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
总人数:{{ kpiData.activeTeams.totalMembers }}人
|
上月转化率:{{ kpiData.activeTeams.totalMembers }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
<!-- 5. 中心转化率 -->
|
<!-- 5. 中心转化率 -->
|
||||||
<div class="kpi-card">
|
<div class="kpi-card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<span class="card-label">平均转化率</span>
|
<span class="card-label">中心转化率</span>
|
||||||
<span class="card-trend" :class="getTrendClass(kpiData.conversionRate.trend)">
|
<span class="card-trend" :class="getTrendClass(kpiData.conversionRate.trend)">
|
||||||
{{ formatTrend(kpiData.conversionRate.trend, true) }} vs 上期
|
{{ formatTrend(kpiData.conversionRate.trend, true) }} vs 上期
|
||||||
</span>
|
</span>
|
||||||
@@ -94,61 +94,70 @@
|
|||||||
<span class="card-value">{{ kpiData.conversionRate.value }}</span>
|
<span class="card-value">{{ kpiData.conversionRate.value }}</span>
|
||||||
<span class="card-unit">%</span>
|
<span class="card-unit">%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
|
||||||
行业平均:{{ kpiData.conversionRate.industryAvg }}%
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, computed, watch } from 'vue';
|
||||||
|
|
||||||
// 1. 定义内部响应式状态
|
// 定义props
|
||||||
const kpiData = ref({
|
const props = defineProps({
|
||||||
// 定义一个骨架结构,防止模板在初始渲染时出错
|
kpiData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
formatNumber: {
|
||||||
|
type: Function,
|
||||||
|
default: (num) => num?.toLocaleString() || '0'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const error = ref(null);
|
||||||
|
|
||||||
|
// 计算属性:将API数据转换为组件需要的格式
|
||||||
|
const kpiData = computed(() => {
|
||||||
|
const data = props.kpiData;
|
||||||
|
|
||||||
|
if (!data || Object.keys(data).length === 0) {
|
||||||
|
return {
|
||||||
totalSales: {},
|
totalSales: {},
|
||||||
activeTeams: {},
|
activeTeams: {},
|
||||||
conversionRate: {},
|
conversionRate: {},
|
||||||
totalCalls: {},
|
totalCalls: {},
|
||||||
newCustomers: {},
|
newCustomers: {},
|
||||||
});
|
|
||||||
const isLoading = ref(true); // 加载状态,默认为true
|
|
||||||
const error = ref(null); // 错误状态,默认为null
|
|
||||||
|
|
||||||
// 2. 模拟从API获取数据的函数
|
|
||||||
async function fetchData() {
|
|
||||||
isLoading.value = true;
|
|
||||||
error.value = null;
|
|
||||||
try {
|
|
||||||
// 模拟网络延迟
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
||||||
|
|
||||||
// 模拟成功获取的数据。在实际应用中,这里会是 fetch() 或 axios.get()
|
|
||||||
const responseData = {
|
|
||||||
totalSales: { value: 552000, trend: 12, targetCompletion: 56 },
|
|
||||||
activeTeams: { value: 5, total: 5, totalMembers: 40 },
|
|
||||||
conversionRate: { value: 5.2, trend: 0.3, industryAvg: 4.8 },
|
|
||||||
totalCalls: { value: 1247, trend: -8, effectiveCalls: 892 }, // 示例:负向趋势
|
|
||||||
newCustomers: { value: 117, trend: 15, interestedCustomers: 89 },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 更新组件的内部数据
|
|
||||||
kpiData.value = responseData;
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
// 如果发生错误,更新错误状态
|
|
||||||
error.value = e.message || '未知错误';
|
|
||||||
} finally {
|
|
||||||
// 无论成功或失败,最后都设置加载完成
|
|
||||||
isLoading.value = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 在组件挂载后调用数据获取函数
|
return {
|
||||||
onMounted(() => {
|
totalSales: {
|
||||||
fetchData();
|
value: data.totalDeals?.company_monthly_deal_count || 0,
|
||||||
|
trend: parseFloat(data.totalDeals?.company_monthly_vs_previous_month_deals_comparison) || 0,
|
||||||
|
targetCompletion: parseFloat(data.totalDeals?.company_monthly_target_completion_rate) || 0
|
||||||
|
},
|
||||||
|
activeTeams: {
|
||||||
|
value: parseFloat(data.conversionRate?.company_current_deposit_conversion_rate) || 0,
|
||||||
|
trend: parseFloat(data.conversionRate?.company_monthly_vs_last_month_rate_comparison) || 0,
|
||||||
|
totalMembers: data.conversionRate?.company_last_month_deposit_conversion_rate || '0.00%'
|
||||||
|
},
|
||||||
|
conversionRate: {
|
||||||
|
value: parseFloat(data.conversionRate?.company_conversion_rate) || 0,
|
||||||
|
trend: parseFloat(data.conversionRate?.center_monthly_vs_previous_deals) || 0,
|
||||||
|
industryAvg: 4.8
|
||||||
|
},
|
||||||
|
totalCalls: {
|
||||||
|
value: data.totalCallCount?.company_total_call_count || 0,
|
||||||
|
trend: parseFloat(data.totalCallCount?.company_total_call_count_vs_last) || 0,
|
||||||
|
effectiveCalls: data.totalCallCount?.company_effective_call_count || 0
|
||||||
|
},
|
||||||
|
newCustomers: {
|
||||||
|
value: data.newCustomer?.company_new_leads_count || 0,
|
||||||
|
trend: parseFloat(data.newCustomer?.company_new_leads_vs_previous_period) || 0,
|
||||||
|
interestedCustomers: data.newCustomer?.company_new_v_customer_count || 0
|
||||||
|
}
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- 以下是辅助函数,保持不变 ---
|
// --- 以下是辅助函数,保持不变 ---
|
||||||
|
|||||||
@@ -8,12 +8,14 @@
|
|||||||
<i class="icon-refresh"></i> 刷新数据
|
<i class="icon-refresh"></i> 刷新数据
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 头像 -->
|
||||||
|
<UserDropdown />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 第一行:核心业绩指标、销售实时进度、下发任务 -->
|
<!-- 第一行:核心业绩指标、销售实时进度、下发任务 -->
|
||||||
<div class="dashboard-row row-1">
|
<div class="dashboard-row row-1">
|
||||||
<!-- 核心业绩指标 -->
|
<!-- 核心业绩指标 -->
|
||||||
<kpi-metrics :kpi-data="kpiData" :format-number="formatNumber" />
|
<kpi-metrics :kpi-data="totalDeals" :format-number="formatNumber" />
|
||||||
|
|
||||||
<!-- 销售实时进度 -->
|
<!-- 销售实时进度 -->
|
||||||
<sales-progress :sales-data="salesData" />
|
<sales-progress :sales-data="salesData" />
|
||||||
@@ -144,6 +146,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed, onMounted, nextTick } from "vue";
|
import { ref, reactive, computed, onMounted, nextTick } from "vue";
|
||||||
|
import UserDropdown from "@/components/UserDropdown.vue";
|
||||||
import KpiMetrics from "./components/KpiMetrics.vue";
|
import KpiMetrics from "./components/KpiMetrics.vue";
|
||||||
import SalesProgress from "./components/SalesProgress.vue";
|
import SalesProgress from "./components/SalesProgress.vue";
|
||||||
import TaskList from "./components/TaskList.vue";
|
import TaskList from "./components/TaskList.vue";
|
||||||
@@ -160,6 +163,9 @@ import DataDetail from "./components/DataDetail.vue";
|
|||||||
import CampManagement from "./components/CampManagement.vue";
|
import CampManagement from "./components/CampManagement.vue";
|
||||||
import DetailedDataTable from "./components/DetailedDataTable.vue";
|
import DetailedDataTable from "./components/DetailedDataTable.vue";
|
||||||
import DataDetailCard from "./components/DataDetailCard.vue";
|
import DataDetailCard from "./components/DataDetailCard.vue";
|
||||||
|
import { getOverallCompanyPerformance,getCompanyDepositConversionRate,getCompanyTotalCallCount,getCompanyNewCustomer,getCompanyConversionRate,getCompanyRealTimeProgress
|
||||||
|
,getCompanyConversionRateVsLast,getSalesMonthlyPerformance,getCenterPerformanceRank,getCustomerTypeDistribution,getUrgentNeedToAddress,getLevelTree,getDetailedDataTable
|
||||||
|
} from "@/api/top";
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const kpiData = reactive({
|
const kpiData = reactive({
|
||||||
@@ -575,8 +581,38 @@ const createTask = () => {
|
|||||||
showTaskModal.value = false;
|
showTaskModal.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
// 核心数据
|
||||||
|
const totalDeals = ref({});
|
||||||
|
// 核心数据--总成交金额
|
||||||
|
async function getTotalDeals() {
|
||||||
|
try {
|
||||||
|
const res1 = await getOverallCompanyPerformance()
|
||||||
|
const res2=await getCompanyDepositConversionRate()
|
||||||
|
const res3=await getCompanyTotalCallCount()
|
||||||
|
const res4=await getCompanyNewCustomer()
|
||||||
|
const res5=await getCompanyConversionRate()
|
||||||
|
totalDeals.value={
|
||||||
|
totalDeals:res1.data,
|
||||||
|
totalAmount:res1.data,
|
||||||
|
conversionRate:res2.data,
|
||||||
|
totalCallCount:res3.data,
|
||||||
|
newCustomer:res4.data,
|
||||||
|
conversionRate:res5.data,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取总成交金额失败:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(async() => {
|
||||||
// 页面初始化逻辑
|
// 页面初始化逻辑
|
||||||
|
await getTotalDeals()
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user