fix(api): 修正二阶分析报告接口路径错误
refactor(views): 重构阶段分析报告展示逻辑,使用新接口数据格式 feat(analytics): 添加umami网站统计脚本 feat(api): 新增优秀录音文件获取接口 style(views): 优化分析报告样式和布局
This commit is contained in:
@@ -84,5 +84,5 @@ export const getCallSuccessRate = (params) => {
|
||||
|
||||
// 二阶分析报告
|
||||
export const getSecondOrderAnalysisReport = (params) => {
|
||||
return https.post('/api/v1/sales/get_call_text', params)
|
||||
return https.post('/api/v1/sales/get_second_analysis_report', params)
|
||||
}
|
||||
@@ -44,9 +44,9 @@ export const getGroupDetail = (params) => {
|
||||
export const getGroupCallDuration = (params) => {
|
||||
return https.post('/api/v1/manager/group_call_duration', params)
|
||||
}
|
||||
// 二二阶分析报告 /api/v1/sales/get_call_text
|
||||
// 二阶分析报告 /api/v1/sales/get_call_text
|
||||
export const GetSecondOrderAnalysisReport = (params) => {
|
||||
return https.post('/api/v1/manager/group_call_text', params)
|
||||
return https.post('/api/v1/manager/group_second_report', params)
|
||||
}
|
||||
|
||||
// 通话分类数据 /api/v1/manager/get_member_call_classify
|
||||
|
||||
@@ -79,6 +79,9 @@ export const getTeamManyTarget = (params) => {
|
||||
return https.post('/api/v1/level_three/overview/get_team_many_target', params)
|
||||
}
|
||||
|
||||
|
||||
// 优秀录音 /api/v1/level_three/overview/get_current_center_excellent_record_file
|
||||
export const getExcellentRecordFile = (params) => {
|
||||
return https.post('/api/v1/level_three/overview/get_current_center_excellent_record_file', params)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -85,11 +85,6 @@
|
||||
<div class="guidance-section">
|
||||
<div class="guidance-header" @click="toggleGuidanceCollapse">
|
||||
<h3>💡 指导建议</h3>
|
||||
<div class="period-switcher">
|
||||
<button @click="switchAnalysisPeriod('day')" :class="{ active: analysisPeriod === 'day' }">当日</button>
|
||||
<button @click="switchAnalysisPeriod('camp')" :class="{ active: analysisPeriod === 'camp' }">当期</button>
|
||||
<button @click="switchAnalysisPeriod('month')" :class="{ active: analysisPeriod === 'month' }">当月</button>
|
||||
</div>
|
||||
<div class="collapse-toggle" :class="{ 'collapsed': isGuidanceCollapsed }">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M8 4l4 4H4l4-4z"/>
|
||||
@@ -99,27 +94,17 @@
|
||||
<div class="guidance-content" v-show="!isGuidanceCollapsed" :class="{ 'collapsing': isGuidanceCollapsed }">
|
||||
<!-- 分析报告内容 -->
|
||||
<div class="analysis-report">
|
||||
<div v-if="isReportLoading" class="loading">正在生成分析报告...</div>
|
||||
<div v-else class="report-content">{{ analysisReport }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 原有指导建议内容 -->
|
||||
<div class="guidance-cards">
|
||||
<div class="guidance-card" v-if="getGuidanceForMember(selectedMember).length > 0">
|
||||
<div class="guidance-item" v-for="(guidance, index) in getGuidanceForMember(selectedMember)" :key="index">
|
||||
<div class="guidance-icon" :class="guidance.type">
|
||||
{{ guidance.icon }}
|
||||
</div>
|
||||
<div class="guidance-content">
|
||||
<h4 class="guidance-title">{{ guidance.title }}</h4>
|
||||
<p class="guidance-description">{{ guidance.description }}</p>
|
||||
<div class="guidance-action" v-if="guidance.action">
|
||||
<span class="action-label">建议行动:</span>
|
||||
<span class="action-text">{{ guidance.action }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isReportLoading" class="loading-message">正在生成分析报告...</div>
|
||||
<div v-else-if="!analysisReport || (Array.isArray(analysisReport) && analysisReport.length === 0)" class="empty-message">
|
||||
暂无分析报告数据
|
||||
</div>
|
||||
<div v-else-if="Array.isArray(analysisReport)">
|
||||
<div v-for="(report, index) in analysisReport" :key="index" class="report-section">
|
||||
<h4 class="report-title">{{ report.name }} ({{ report.start_time }} - {{ report.end_time }})</h4>
|
||||
<div class="report-content" v-html="report.report.replace(/\n/g, '<br>')"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="report-content">{{ analysisReport }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -224,53 +209,6 @@ const processedCallStats = computed(() => {
|
||||
})).sort((a, b) => b.count - a.count); // 按通话次数降序排列
|
||||
});
|
||||
|
||||
|
||||
// 切换分析周期
|
||||
const switchAnalysisPeriod = (period) => {
|
||||
analysisPeriod.value = period
|
||||
CenterGetSecondOrderAnalysisReport(analysisPeriod.value)
|
||||
}
|
||||
|
||||
// 获取二阶分析报告
|
||||
async function CenterGetSecondOrderAnalysisReport(time) {
|
||||
if (!chatService_02.value) {
|
||||
chatService_02.value = new SimpleChatService('app-MGaBOx5QFblsMZ7dSkxKJDKm')
|
||||
}
|
||||
|
||||
isReportLoading.value = true
|
||||
analysisReport.value = ''
|
||||
|
||||
try {
|
||||
const params = {
|
||||
user_name: props.memberDetails?.user_name,
|
||||
time: time
|
||||
}
|
||||
const response = await GetSecondOrderAnalysisReport(params)
|
||||
|
||||
if (!response.data.records || response.data.records.length === 0) {
|
||||
analysisReport.value = '当前周期暂无数据可供分析。'
|
||||
isReportLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
const records = response.data.records.join('\n')
|
||||
|
||||
chatService_02.value.sendMessage(
|
||||
records,
|
||||
(update) => {
|
||||
analysisReport.value = update.content
|
||||
},
|
||||
() => {
|
||||
isReportLoading.value = false
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('获取二阶分析报告失败:', error)
|
||||
analysisReport.value = '获取分析报告失败,请稍后重试。'
|
||||
isReportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 切换指导建议折叠状态
|
||||
const toggleGuidanceCollapse = () => {
|
||||
isGuidanceCollapsed.value = !isGuidanceCollapsed.value
|
||||
@@ -293,31 +231,6 @@ const hideTooltip = () => {
|
||||
tooltip.visible = false
|
||||
}
|
||||
|
||||
// 获取成员指导建议
|
||||
const getGuidanceForMember = (member) => {
|
||||
const guidance = []
|
||||
if (!member) return guidance;
|
||||
|
||||
// 业绩相关建议
|
||||
if (member.month_order_count === 0) {
|
||||
guidance.push({ type: 'urgent', icon: '🚨', title: '业绩突破', description: '当前还未有成交记录,需要重点关注转化技巧和客户跟进。', action: '建议参加销售技巧培训,加强客户需求挖掘' })
|
||||
} else if (member.month_order_count < 5) {
|
||||
guidance.push({ type: 'warning', icon: '📈', title: '业绩提升', description: '业绩有提升空间,可以通过优化沟通策略来提高转化率。', action: '分析高业绩同事的沟通技巧,制定个人提升计划' })
|
||||
}
|
||||
|
||||
// 转化率相关建议
|
||||
if (member.conversion < 3.0) {
|
||||
guidance.push({ type: 'urgent', icon: '🎯', title: '转化率优化', description: '转化率偏低,需要提升客户沟通和需求挖掘能力。', action: '重点学习客户心理分析和异议处理技巧' })
|
||||
}
|
||||
|
||||
// 通话相关建议
|
||||
if (member.call_count < 100) {
|
||||
guidance.push({ type: 'warning', icon: '📞', title: '通话量提升', description: '通话量偏少,增加客户接触频次有助于提升业绩。', action: '制定每日通话计划,确保充足的客户接触量' })
|
||||
}
|
||||
|
||||
return guidance.slice(0, 3);
|
||||
}
|
||||
|
||||
// 【修改】函数现在从API获取数据并更新内部状态
|
||||
async function updateCallClassificationData() {
|
||||
if (props.selectedMember && props.selectedMember.user_name) {
|
||||
@@ -325,6 +238,7 @@ async function updateCallClassificationData() {
|
||||
const response = await getMemberCallClassify({
|
||||
user_name: props.selectedMember.user_name
|
||||
});
|
||||
console.log('获取通话分类数据:', response.data);
|
||||
// 将获取到的数据赋值给内部状态
|
||||
callClassificationData.value = {
|
||||
call_count_by_tag: response.call_count_by_tag || {},
|
||||
@@ -341,14 +255,33 @@ async function updateCallClassificationData() {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取二阶分析报告数据
|
||||
async function fetchAnalysisReport() {
|
||||
if (props.selectedMember && props.selectedMember.user_name) {
|
||||
isReportLoading.value = true;
|
||||
try {
|
||||
const response = await GetSecondOrderAnalysisReport({
|
||||
user_name: props.selectedMember.user_name
|
||||
});
|
||||
console.log('获取分析报告数据:', response.data);
|
||||
// 将获取到的数据赋值给分析报告变量
|
||||
analysisReport.value = response.data;
|
||||
} catch (error) {
|
||||
console.error('获取分析报告失败:', error);
|
||||
analysisReport.value = '';
|
||||
} finally {
|
||||
isReportLoading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 监听selectedMember变化
|
||||
watch(() => props.selectedMember, (newMember) => {
|
||||
if (newMember) {
|
||||
// 成员变化时,获取新的通话分类数据
|
||||
updateCallClassificationData();
|
||||
// 同时获取新的分析报告
|
||||
CenterGetSecondOrderAnalysisReport(analysisPeriod.value);
|
||||
|
||||
updateCallClassificationData();
|
||||
// 获取分析报告数据
|
||||
fetchAnalysisReport();
|
||||
// 重置滚动位置
|
||||
nextTick(() => {
|
||||
const container = document.querySelector('.member-details')
|
||||
@@ -656,14 +589,80 @@ watch(() => props.selectedMember, (newMember) => {
|
||||
border: 1px solid #e2e8f0;
|
||||
min-height: 100px;
|
||||
|
||||
.loading {
|
||||
.loading-message {
|
||||
text-align: center;
|
||||
color: #64748b;
|
||||
font-style: italic;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.report-content {
|
||||
.empty-message {
|
||||
text-align: center;
|
||||
color: #94a3b8;
|
||||
padding: 2rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.report-section {
|
||||
margin-bottom: 1.5rem;
|
||||
padding-bottom: 1.5rem;
|
||||
border-bottom: 1px dashed #e2e8f0;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.report-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
margin: 0 0 0.75rem 0;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: #ffffff;
|
||||
border-left: 3px solid #3b82f6;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.report-content {
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.6;
|
||||
color: #334155;
|
||||
font-size: 0.9rem;
|
||||
padding: 0 0.5rem;
|
||||
|
||||
h1, h2, h3 {
|
||||
color: #1e293b;
|
||||
margin: 1.25rem 0 0.75rem 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.1rem;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin: 0.75rem 0;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.report-content:not(.report-section .report-content) {
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.5;
|
||||
color: #1e293b;
|
||||
|
||||
@@ -111,33 +111,19 @@
|
||||
<div class="modal-container">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title">阶段分析报告</h3>
|
||||
<div class="period-switcher">
|
||||
<button @click="switchAnalysisPeriod('day')" :class="{ active: analysisPeriod === 'day' }">当日</button>
|
||||
<button @click="switchAnalysisPeriod('camp')" :class="{ active: analysisPeriod === 'camp' }">当期</button>
|
||||
<button @click="switchAnalysisPeriod('month')" :class="{ active: analysisPeriod === 'month' }">当月</button>
|
||||
</div>
|
||||
<button class="modal-close-btn" @click="closeAnalysisModal">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="analysis-content">
|
||||
<div v-if="analysisPeriod === 'day'">
|
||||
<h4>当日分析报告</h4>
|
||||
<div v-if="analysisReport.day === '数据为空'" class="error-message">数据为空</div>
|
||||
<div v-else-if="analysisReport.day" v-html="analysisReport.day.replace(/\n/g, '<br>')"></div>
|
||||
<p v-else>正在生成分析报告...</p>
|
||||
</div>
|
||||
<div v-if="analysisPeriod === 'camp'">
|
||||
<h4>当期分析报告</h4>
|
||||
<div v-if="analysisReport.camp === '数据为空'" class="error-message">数据为空</div>
|
||||
<div v-else-if="analysisReport.camp" v-html="analysisReport.camp.replace(/\n/g, '<br>')"></div>
|
||||
<p v-else>正在生成分析报告...</p>
|
||||
</div>
|
||||
<div v-if="analysisPeriod === 'month'">
|
||||
<h4>当月分析报告</h4>
|
||||
<div v-if="analysisReport.month === '数据为空'" class="error-message">数据为空</div>
|
||||
<div v-else-if="analysisReport.month" v-html="analysisReport.month.replace(/\n/g, '<br>')"></div>
|
||||
<p v-else>正在生成分析报告...</p>
|
||||
<div v-if="!analysisReport || Object.keys(analysisReport).length === 0" class="loading-message">正在生成分析报告...</div>
|
||||
<div v-else-if="Array.isArray(analysisReport) && analysisReport.length === 0" class="error-message">数据为空</div>
|
||||
<div v-else-if="Array.isArray(analysisReport)">
|
||||
<div v-for="(report, index) in analysisReport" :key="index" class="report-section">
|
||||
<h4>{{ report.name }} ({{ report.start_time }} 至 {{ report.end_time }})</h4>
|
||||
<div v-html="report.report.replace(/\n/g, '<br>')"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="error-message">数据格式错误</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -220,44 +206,12 @@ const props = defineProps({
|
||||
}
|
||||
});
|
||||
|
||||
async function CenterGetSecondOrderAnalysisReport(time) {
|
||||
const params = getRequestParams()
|
||||
const hasParams = {...params,time:time}
|
||||
const res = await getSecondOrderAnalysisReport(hasParams)
|
||||
if (res.code === 200) {
|
||||
|
||||
const records = res.data.records.join('\n')
|
||||
|
||||
// 检查数据是否为空
|
||||
if (!records) {
|
||||
console.error('数据为空')
|
||||
// 将错误信息存储到对应的响应式变量中
|
||||
analysisReport.value[time] = '数据为空'
|
||||
return
|
||||
}
|
||||
|
||||
const prompt = `请分析以下数据:\n${records}\n请提供一个阶段分析报告。`
|
||||
console.log(prompt)
|
||||
// 使用sendMessage方法替代chat方法
|
||||
try {
|
||||
await chatService_02.sendMessage(
|
||||
prompt,
|
||||
(update) => {
|
||||
// 实时更新回调
|
||||
if (!update.isStreaming) {
|
||||
console.log('阶段分析报告:', update.content)
|
||||
// 将结果存储到对应的响应式变量中
|
||||
analysisReport.value[time] = update.content
|
||||
}
|
||||
},
|
||||
() => {
|
||||
// 流结束回调
|
||||
console.log('阶段分析报告生成完成')
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('获取阶段分析报告失败:', error)
|
||||
}
|
||||
async function CenterGetSecondOrderAnalysisReport() {
|
||||
const params = getRequestParams()
|
||||
const res = await getSecondOrderAnalysisReport(params)
|
||||
if (res.code === 200) {
|
||||
console.log(11111,res.data)
|
||||
analysisReport.value = res.data
|
||||
}
|
||||
}
|
||||
// Chart.js 实例
|
||||
@@ -427,17 +381,12 @@ const hideTooltip = () => {
|
||||
|
||||
// 阶段分析报告模态框状态
|
||||
const showAnalysisModal = ref(false);
|
||||
const analysisPeriod = ref('day'); // 'day', 'camp', 'month'
|
||||
const analysisReport = ref({
|
||||
day: '',
|
||||
camp: '',
|
||||
month: ''
|
||||
});
|
||||
|
||||
// 阶段分析报告数据
|
||||
const analysisReport = ref({});
|
||||
// 显示阶段分析报告模态框
|
||||
const showSecondOrderAnalysisReport = () => {
|
||||
showAnalysisModal.value = true;
|
||||
CenterGetSecondOrderAnalysisReport(analysisPeriod.value)
|
||||
CenterGetSecondOrderAnalysisReport()
|
||||
};
|
||||
|
||||
// 关闭阶段分析报告模态框
|
||||
@@ -445,14 +394,6 @@ const closeAnalysisModal = () => {
|
||||
showAnalysisModal.value = false;
|
||||
};
|
||||
|
||||
// 切换分析周期
|
||||
const switchAnalysisPeriod = (period) => {
|
||||
analysisPeriod.value = period;
|
||||
CenterGetSecondOrderAnalysisReport(period)
|
||||
};
|
||||
|
||||
|
||||
|
||||
watch(() => props.contactTimeData, () => {
|
||||
renderContactTimeChart();
|
||||
}, { deep: true });
|
||||
@@ -1021,4 +962,31 @@ $white: #ffffff;
|
||||
border-radius: 4px;
|
||||
background-color: #fef0f0;
|
||||
}
|
||||
|
||||
.loading-message {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.report-section {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
background-color: #f9fafc;
|
||||
}
|
||||
|
||||
.report-section:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.report-section h4 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 15px;
|
||||
color: #303133;
|
||||
font-size: 16px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -84,7 +84,7 @@
|
||||
@select-group="selectGroup"
|
||||
/>
|
||||
</div>
|
||||
<GoodMusic style="height: 300px;" :abnormalData="teamAlerts" />
|
||||
<GoodMusic style="height: 300px;" :qualityCalls="excellentRecord" />
|
||||
<!-- Right Section - Group Comparison -->
|
||||
<div v-if="cardVisibility.groupComparison" class="right-section">
|
||||
<GroupComparison
|
||||
@@ -212,8 +212,8 @@ import Loading from '@/components/Loading.vue'
|
||||
import PerformanceComparison from './components/PerformanceComparison.vue'; // 1. 导入新组件
|
||||
import { getOverallTeamPerformance,getTotalGroupCount,getConversionRate,getTotalCallCount,
|
||||
getNewCustomer,getDepositConversionRate,getActiveCustomerCommunicationRate,getAverageAnswerTime,
|
||||
getTimeoutRate,getTableFillingRate,getUrgentNeedToAddress,getTeamRanking,getTeamRankingInfo,getAbnormalResponseRate,getTeamSalesFunnel } from '@/api/senorManger.js'
|
||||
import { getExcellentRecordFile } from '@/api/top.js'
|
||||
getTimeoutRate,getTableFillingRate,getUrgentNeedToAddress,getTeamRanking,getTeamRankingInfo,
|
||||
getAbnormalResponseRate,getTeamSalesFunnel,getExcellentRecordFile } from '@/api/senorManger.js'
|
||||
import { useUserStore } from '@/stores/user.js'
|
||||
import FeedbackForm from "@/components/FeedbackForm.vue";
|
||||
|
||||
@@ -274,6 +274,7 @@ async function CenterExcellentRecord() {
|
||||
const cacheKey = getCacheKey('CenterExcellentRecord', params);
|
||||
const result = await withCache(cacheKey, async () => {
|
||||
const res = await getExcellentRecordFile(params);
|
||||
console.log(6666666666,res);
|
||||
return res.data;
|
||||
});
|
||||
excellentRecord.value = result;
|
||||
|
||||
Reference in New Issue
Block a user