Compare commits

..

2 Commits

Author SHA1 Message Date
575a08ed3a feat(会员详情): 添加二阶分析报告功能并优化指导建议布局
重构会员详情组件,添加二阶分析报告功能,包括周期切换和报告加载状态显示。优化指导建议部分的布局结构,将分析报告与原有指导建议分开显示。调整样式以提升用户体验,并修复部分代码格式问题。
2025-10-10 21:56:02 +08:00
b3f5178470 feat(个人仪表盘): 添加实时分析报告功能并更新API基础路径
- 在PersonalDashboard组件中实现实时分析报告功能,包括数据为空和加载状态处理
- 添加SimpleChatService集成用于生成分析报告
- 将API基础路径从本地开发环境切换到生产环境
- 优化分析报告模态框样式和错误消息显示
2025-10-10 21:32:27 +08:00
4 changed files with 284 additions and 86 deletions

View File

@@ -45,7 +45,7 @@ export const getGroupCallDuration = (params) => {
return https.post('/api/v1/manager/group_call_duration', params)
}
// 二二阶分析报告 /api/v1/sales/get_call_text
export const getSecondOrderAnalysisReport = (params) => {
export const GetSecondOrderAnalysisReport = (params) => {
return https.post('/api/v1/manager/group_call_text', params)
}

View File

@@ -5,8 +5,8 @@ import { useUserStore } from '@/stores/user'
// 创建axios实例
const service = axios.create({
// baseURL: 'https://mldash.nycjy.cn/' || '', // API基础路径支持完整URL
baseURL: 'http://192.168.15.121:8890' || '', // API基础路径支持完整URL
baseURL: 'https://mldash.nycjy.cn/' || '', // API基础路径支持完整URL
// baseURL: 'http://192.168.15.121:8890' || '', // API基础路径支持完整URL
timeout: 100000, // 请求超时时间
headers: {
'Content-Type': 'application/json;charset=UTF-8'

View File

@@ -63,9 +63,17 @@
</svg>
</div>
</div>
<div class="guidance-cards" v-show="!isGuidanceCollapsed" :class="{ 'collapsing': isGuidanceCollapsed }">
<div class="guidance-card" v-if="getGuidanceForMember(memberDetails).length > 0">
<div class="guidance-item" v-for="(guidance, index) in getGuidanceForMember(memberDetails)" :key="index">
<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>
@@ -79,10 +87,6 @@
</div>
</div>
</div>
<div class="no-guidance" v-else>
<div class="celebration-icon">🎉</div>
<h4>表现优秀</h4>
<p>{{ memberDetails?.user_name || selectedMember?.user_name || selectedMember?.name }} 的各项指标都很不错继续保持这种状态</p>
</div>
</div>
</div>
@@ -137,8 +141,10 @@
</template>
<script setup>
import { ref, defineProps, watch, nextTick, reactive } from 'vue'
import { ref, defineProps, watch, nextTick, reactive, onMounted } from 'vue'
import Tooltip from '@/components/Tooltip.vue'
import { GetSecondOrderAnalysisReport } from '@/api/manager.js'
import { SimpleChatService } from '@/utils/ChatService.js'
// 定义props
const props = defineProps({
@@ -159,7 +165,13 @@ const isDetailsCollapsed = ref(false)
const isGuidanceCollapsed = ref(false)
// 录音列表折叠状态(默认展开)
const isRecordingsCollapsed = ref(false)
const isRecordingsCollapsed = ref(true)
// 分析周期
const analysisPeriod = ref('day') // 默认周期为当日
const analysisReport = ref('') // 分析报告内容
const isReportLoading = ref(false) // 报告加载状态
const chatService_02 = ref(null) // 用于生成分析报告的聊天服务实例
// 工具提示状态
const tooltip = reactive({
@@ -199,6 +211,64 @@ const toggleDetailsCollapse = () => {
isDetailsCollapsed.value = !isDetailsCollapsed.value
}
// 切换分析周期
const switchAnalysisPeriod = (period) => {
analysisPeriod.value = period
// 切换周期后重新获取分析报告
CenterGetSecondOrderAnalysisReport(analysisPeriod.value )
}
// 获取二阶分析报告
async function CenterGetSecondOrderAnalysisReport(time) {
// 如果聊天服务实例不存在,则创建一个新的实例
if (!chatService_02.value) {
// 注意这里使用与PersonalDashboard.vue相同的API密钥
chatService_02.value = new SimpleChatService('app-MGaBOx5QFblsMZ7dSkxKJDKm')
}
// 设置加载状态
isReportLoading.value = true
analysisReport.value = ''
try {
// 准备请求参数
const params = {
user_name:props.memberDetails?.user_name,
time: time // 使用当前选择的周期
}
// 调用API获取分析报告数据
const response = await GetSecondOrderAnalysisReport(params)
// 检查数据是否为空
if (!response.data.records || response.data.records.length === 0) {
console.error('数据为空')
analysisReport.value = '数据为空'
isReportLoading.value = false
return
}
// 将记录数组转换为字符串
const records = response.data.records.join('\n')
// 使用聊天服务发送消息并处理流式响应
chatService_02.value.sendMessage(
records, // 使用API返回的文本数据
(update) => {
// 实时更新报告内容
analysisReport.value = update.content
},
() => {
// 流结束时的操作
isReportLoading.value = false
}
)
} catch (error) {
console.error('获取二阶分析报告失败:', error)
analysisReport.value = '获取分析报告失败,请稍后重试。'
isReportLoading.value = false
}
}
// 切换指导建议折叠状态
const toggleGuidanceCollapse = () => {
isGuidanceCollapsed.value = !isGuidanceCollapsed.value
@@ -339,20 +409,24 @@ const getGuidanceForMember = (member) => {
return guidance.slice(0, 3) // 最多显示3个建议
}
// 监听selectedMember变化,重置滚动条位置
watch(() => props.selectedMember, () => {
// 监听selectedMember变化
watch(() => props.selectedMember, (newMember) => {
if (newMember) {
// 重置滚动位置
nextTick(() => {
// 获取member-details容器元素并重置滚动位置
const memberDetailsEl = document.querySelector('.member-details')
if (memberDetailsEl) {
// 使用平滑滚动动画
memberDetailsEl.scrollTo({
top: 0,
behavior: 'smooth'
})
const container = document.querySelector('.member-details')
if (container) {
container.scrollTop = 0
}
})
}, { immediate: false })
}
})
// 组件挂载时初始化分析报告
onMounted(() => {
// 初始化时获取当日的分析报告
CenterGetSecondOrderAnalysisReport()
})
</script>
<style lang="scss" scoped>
@@ -698,12 +772,69 @@ watch(() => props.selectedMember, () => {
}
}
// 分析报告样式
.analysis-report {
background: #f8fafc;
border-radius: 8px;
padding: 1rem;
margin-bottom: 1.25rem;
border: 1px solid #e2e8f0;
min-height: 100px;
.loading {
text-align: center;
color: #64748b;
font-style: italic;
padding: 1rem;
}
.report-content {
white-space: pre-wrap;
line-height: 1.5;
color: #1e293b;
font-size: 0.9rem;
}
}
.guidance-cards {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
// 周期切换按钮样式
.period-switcher {
display: flex;
gap: 0.5rem;
margin-left: auto;
button {
padding: 0.25rem 0.75rem;
border: 1px solid #cbd5e1;
background: white;
border-radius: 16px;
font-size: 0.85rem;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
background: #f1f5f9;
border-color: #94a3b8;
}
&.active {
background: #3b82f6;
color: white;
border-color: #3b82f6;
&:hover {
background: #2563eb;
border-color: #2563eb;
}
}
}
}
.guidance-card {
background: white;
border-radius: 8px;
@@ -911,7 +1042,7 @@ watch(() => props.selectedMember, () => {
}
// 指导建议适配
.guidance-section {
.guidance-section {
margin-top: 1rem;
.guidance-header {
@@ -920,6 +1051,14 @@ watch(() => props.selectedMember, () => {
}
}
.analysis-report {
padding: 0.75rem;
.report-content {
font-size: 0.8rem;
}
}
.guidance-item {
flex-direction: column;
align-items: flex-start;
@@ -951,7 +1090,7 @@ watch(() => props.selectedMember, () => {
padding: 0.4rem;
}
}
}
}
}
@media (max-width: 480px) {

View File

@@ -122,15 +122,21 @@
<div class="analysis-content">
<div v-if="analysisPeriod === 'day'">
<h4>当日分析报告</h4>
<p>这里是当日分析报告的占位文本</p>
<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>
<p>这里是当期分析报告的占位文本</p>
<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>
<p>这里是当月分析报告的占位文本</p>
<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>
</div>
</div>
@@ -149,12 +155,16 @@ import Chart from 'chart.js/auto';
import {getSecondOrderAnalysisReport} from "@/api/api.js"
import { useUserStore } from "@/stores/user";
import { useRouter } from "vue-router";
import { SimpleChatService } from '@/utils/ChatService.js';
// 用户store
const userStore = useUserStore();
// 路由实例
const router = useRouter();
const Dify_API_Key_02 = 'app-MGaBOx5QFblsMZ7dSkxKJDKm'
const chatService_02= new SimpleChatService(Dify_API_Key_02)
// 获取通用请求参数的函数
const getRequestParams = () => {
const params = {}
@@ -214,7 +224,41 @@ async function CenterGetSecondOrderAnalysisReport(time) {
const params = getRequestParams()
const hasParams = {...params,time:time}
const res = await getSecondOrderAnalysisReport(hasParams)
console.log(res)
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)
}
}
}
// Chart.js 实例
const chartInstances = {};
@@ -373,7 +417,12 @@ const hideTooltip = () => {
// 阶段分析报告模态框状态
const showAnalysisModal = ref(false);
const analysisPeriod = ref('day'); // 'day', 'current', 'month'
const analysisPeriod = ref('day'); // 'day', 'camp', 'month'
const analysisReport = ref({
day: '',
camp: '',
month: ''
});
// 显示阶段分析报告模态框
const showSecondOrderAnalysisReport = () => {
@@ -849,7 +898,7 @@ $white: #ffffff;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
width: 90%;
max-width: 600px;
max-height: 80vh;
max-height: 60vh;
overflow: hidden;
display: flex;
flex-direction: column;
@@ -950,4 +999,14 @@ $white: #ffffff;
color: #606266;
line-height: 1.6;
}
.error-message {
color: #f56c6c;
font-weight: bold;
text-align: center;
padding: 20px;
border: 1px solid #f56c6c;
border-radius: 4px;
background-color: #fef0f0;
}
</style>