diff --git a/my-vue-app/src/api/api.js b/my-vue-app/src/api/api.js index ada2a25..01b0da4 100644 --- a/my-vue-app/src/api/api.js +++ b/my-vue-app/src/api/api.js @@ -7,7 +7,7 @@ export const getProblemDistribution = (params) => { // 今日通话 /api/v1/more_level_screening/today_call export const getTodayCall = (params) => { - return https.post('/api/v1/sales/today_call', params) + return https.post('/api/v1/sales/current_camp_call', params) } // 表格填写率 /api/v1/more_level_screening/table_filling_rate diff --git a/my-vue-app/src/api/senorManger.js b/my-vue-app/src/api/senorManger.js index 6ae7d33..4936e8d 100644 --- a/my-vue-app/src/api/senorManger.js +++ b/my-vue-app/src/api/senorManger.js @@ -74,7 +74,10 @@ export const getAbnormalResponseRate = (params) => { export const getHistoryCamps = (params) => { return https.post('/api/v1/level_three/overview/get_history_camps', params) } - +// 数据对比 /api/v1/level_three/overview/get_team_many_target +export const getTeamManyTarget = (params) => { + return https.post('/api/v1/level_three/overview/get_team_many_target', params) +} diff --git a/my-vue-app/src/utils/https.js b/my-vue-app/src/utils/https.js index 07a1086..a845c4b 100644 --- a/my-vue-app/src/utils/https.js +++ b/my-vue-app/src/utils/https.js @@ -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' diff --git a/my-vue-app/src/views/person/components/CustomerDetail.vue b/my-vue-app/src/views/person/components/CustomerDetail.vue index 1c2f6e6..4a2ffc8 100644 --- a/my-vue-app/src/views/person/components/CustomerDetail.vue +++ b/my-vue-app/src/views/person/components/CustomerDetail.vue @@ -104,8 +104,8 @@ const props = defineProps({ default: null }, formInfo: { - type: Object, - default: () => ({}) + type: Array, + default: () => [] }, chatRecords: { type: Array, @@ -193,89 +193,34 @@ const startBasicAnalysis = async () => { basicAnalysisResult.value = ''; // 构建表单信息 - const formData = props.formInfo || {}; + const formData = props.formInfo || []; let formInfoText = '暂无表单信息'; - if (Object.keys(formData).length > 0) { + // ** 适配新的 formInfo 数组格式 ** + if (Array.isArray(formData) && formData.length > 0) { const allInfo = []; - // 处理第一种格式:基础信息和additional_info - if (formData.name || formData.mobile || formData.additional_info) { - const basicInfo = []; - const additionalInfo = []; - - const basicFields = { - name: '姓名', - mobile: '手机号', - occupation: '职业', - territory: '地区', - child_name: '孩子姓名', - child_gender: '孩子性别', - child_education: '孩子教育阶段', - child_relation: '与孩子关系' - }; - - Object.entries(basicFields).forEach(([key, label]) => { - if (formData[key] && formData[key] !== '暂无' && formData[key] !== '') { - basicInfo.push(`${label}: ${formData[key]}`); - } - }); - - if (formData.additional_info && Array.isArray(formData.additional_info)) { - formData.additional_info.forEach(item => { - if (item.topic && item.answer) { - additionalInfo.push(`${item.topic}\n答案: ${item.answer}`); - } - }); + // 遍历新格式: [{ question_label: "...", answer: "..." }, ...] + formData.forEach(item => { + // 检查字段是否存在且答案有效 + if ( + item.question_label && + item.answer && + item.answer !== '暂无' && + item.answer !== '' + ) { + // 格式化为 "问题标签: 答案" + allInfo.push(`${item.question_label.trim()}: ${item.answer.trim()}`); } + }); - if (basicInfo.length > 0) { - allInfo.push('=== 基础信息 ==='); - allInfo.push(...basicInfo); - } - - if (additionalInfo.length > 0) { - allInfo.push('\n=== 问卷信息 ==='); - allInfo.push(...additionalInfo); - } - } - - // 处理第二种格式:customerExpandFieldMap - if (formData.customerExpandFieldMap) { - const expandInfo = []; - const map = formData.customerExpandFieldMap; - - Object.entries(map).forEach(([key, value]) => { - if (!map.hasOwnProperty(key)) return; - - if (value && typeof value === 'object' && value.key) { - const question = value.key; - let answer = ''; - - if (value.typeCode === 'SINGLE_SELECT' || value.typeCode === 'MULTIPLE_SELECT') { - if (value.expandValueList && value.expandValueList.length > 0) { - answer = value.expandValueList.map(item => item.itemName).join('、'); - } - } else if (value.typeCode === 'TEXT' || value.typeCode === 'TEXTAREA' || value.typeCode === 'NUMBER') { - answer = formData[key] || ''; - } - - if (answer && answer !== '暂无' && answer !== '') { - expandInfo.push(`${question}\n答案: ${answer}`); - } - } - }); - - if (expandInfo.length > 0) { - if (allInfo.length > 0) allInfo.push('\n'); - allInfo.push('=== 问卷详细信息 ==='); - allInfo.push(...expandInfo); - } - } - - formInfoText = allInfo.length > 0 ? allInfo.join('\n') : '暂无表单信息'; + // 格式化表单信息文本 + formInfoText = allInfo.length > 0 + ? `=== 问卷/表单信息 ===\n${allInfo.join('\n')}` + : '暂无有效问卷/表单信息'; } - + // ** 适配结束 ** + // 构建聊天记录信息 const chatData = props.chatRecords || []; const chatInfoText = chatData.messages && chatData.messages.length > 0 ? diff --git a/my-vue-app/src/views/person/components/RawDataCards.vue b/my-vue-app/src/views/person/components/RawDataCards.vue index f36c180..b31dfcb 100644 --- a/my-vue-app/src/views/person/components/RawDataCards.vue +++ b/my-vue-app/src/views/person/components/RawDataCards.vue @@ -143,7 +143,7 @@ const props = defineProps({ }, formInfo: { type: Object, - default: () => ({}) + default: () => [] }, chatInfo: { type: Object, diff --git a/my-vue-app/src/views/person/sale.vue b/my-vue-app/src/views/person/sale.vue index dd9efa9..2bfd4a2 100644 --- a/my-vue-app/src/views/person/sale.vue +++ b/my-vue-app/src/views/person/sale.vue @@ -369,7 +369,7 @@ async function getCoreKpi() { // 今日通话数据 const res = await getTodayCall(hasParams ? params : undefined) if (res.code === 200) { - kpiDataState.totalCalls = res.data.today_call + kpiDataState.totalCalls = res.data.call_count } // 转化率、分配数据量、加微率 const conversionRes = await getConversionRateAndAllocatedData(hasParams ? params : undefined) @@ -582,8 +582,9 @@ async function getCustomerForm() { } try { const res = await getCustomerFormInfo(params) + console.log('获取客户表单数据:', res) if(res.code === 200) { - formInfo.value = res.data + formInfo.value = res.data[0].answer || [] } } catch (error) { // 静默处理错误 diff --git a/my-vue-app/src/views/senorManger/components/PerformanceComparison.vue b/my-vue-app/src/views/senorManger/components/PerformanceComparison.vue index c3b2024..d9f9359 100644 --- a/my-vue-app/src/views/senorManger/components/PerformanceComparison.vue +++ b/my-vue-app/src/views/senorManger/components/PerformanceComparison.vue @@ -60,42 +60,41 @@ import { ref, computed, onMounted, watch } from 'vue'; // 假设你有一个API服务来获取对比数据 // import { getPerformanceComparisonData } from '@/api/senorManger.js'; -// 模拟API调用 +// **MODIFIED**: 更新模拟API以返回新的数据结构 const getPerformanceComparisonData = async (params) => { - console.log('模拟API请求:', params); + console.log('模拟API请求 (新数据结构):', params); return new Promise(resolve => { setTimeout(() => { - // 模拟不同周期返回不同数据 let mockData; if (params.period === 'last_week') { mockData = { - assignedLeads: 480, - wechatAdds: 390, - calls: 1450, - callDuration: 11500, - deposits: 38, - deals: 50, - conversionRate: 10.4, + "allocated_data_volume": 650, + "wechat_added_volume": 410, + "call_volume_after_classification": { "加微通话": 10, "20分钟通话": 50, "未分类": 150, "无效通话": 100, "促到课": 40 }, + "call_avg_duration_after_classification": { "加微通话": 4.5, "20分钟通话": 15.0, "未分类": 1.5, "无效通话": 1.0, "促到课": 2.0 }, + "deposit_volume": 40, + "transaction_volume": 52, + "conversion_rate": "8.00%" }; } else if (params.period === 'last_month') { mockData = { - assignedLeads: 2000, - wechatAdds: 1500, - calls: 5800, - callDuration: 48000, - deposits: 150, - deals: 210, - conversionRate: 10.5, + "allocated_data_volume": 2800, + "wechat_added_volume": 1800, + "call_volume_after_classification": { "加微通话": 40, "20分钟通话": 220, "未分类": 600, "视频通话": 50, "无效通话": 450, "促到课": 180 }, + "call_avg_duration_after_classification": { "加微通话": 5.0, "20分钟通话": 16.0, "未分类": 1.8, "视频通话": 6.0, "无效通话": 1.1, "促到课": 1.5 }, + "deposit_volume": 180, + "transaction_volume": 230, + "conversion_rate": "8.21%" }; - } else { + } else { // last_quarter mockData = { - assignedLeads: 6500, - wechatAdds: 5200, - calls: 18000, - callDuration: 150000, - deposits: 450, - deals: 600, - conversionRate: 9.2, + "allocated_data_volume": 8500, + "wechat_added_volume": 5500, + "call_volume_after_classification": { "加微通话": 120, "20分钟通话": 650, "未分类": 1800, "视频通话": 150, "无效通话": 1200, "促到课": 500 }, + "call_avg_duration_after_classification": { "加微通话": 4.8, "20分钟通话": 16.5, "未分类": 1.7, "视频通话": 5.5, "无效通话": 1.0, "促到课": 1.8 }, + "deposit_volume": 550, + "transaction_volume": 700, + "conversion_rate": "8.24%" }; } resolve({ code: 200, data: mockData }); @@ -125,12 +124,11 @@ const selectedPeriodLabel = computed(() => periodLabels[selectedPeriod.value]); const fetchComparisonData = async () => { isLoading.value = true; try { - // 真实场景中,这里应该调用API const res = await getPerformanceComparisonData({ period: selectedPeriod.value }); if (res.code === 200) { previousPeriodData.value = res.data; } else { - previousPeriodData.value = null; // API出错或无数据 + previousPeriodData.value = null; } } catch (error) { console.error('获取对比数据失败:', error); @@ -144,14 +142,54 @@ onMounted(() => { fetchComparisonData(); }); -// 如果本期数据可能变化,可以监听props来刷新 watch(() => props.currentPeriodData, () => { fetchComparisonData(); }, { deep: true }); +// **NEW**: 新增一个工具函数,用于处理API返回的复杂数据结构 +// 并将其转换为表格渲染所需的扁平化结构 +const processRawData = (rawData) => { + if (!rawData) return null; + + // 1. 计算通话总量 + const totalCalls = Object.values(rawData.call_volume_after_classification || {}) + .reduce((sum, count) => sum + count, 0); + + // 2. 计算通话总时长 (分钟) + // 逻辑: (分类通话数 * 分类平均时长) 的总和 + const callVolumes = rawData.call_volume_after_classification || {}; + const avgDurations = rawData.call_avg_duration_after_classification || {}; + const totalDurationInMinutes = Object.keys(callVolumes).reduce((sum, key) => { + const volume = callVolumes[key] || 0; + const avgDuration = avgDurations[key] || 0; + return sum + (volume * avgDuration); + }, 0); + + // 3. 将转化率字符串 "7.98%" 转为数字 7.98 + const conversionRateValue = parseFloat(rawData.conversion_rate) || 0; + + // 4. 返回一个与旧版组件兼容的对象结构 + return { + assignedLeads: rawData.allocated_data_volume, + wechatAdds: rawData.wechat_added_volume, + calls: totalCalls, + // 组件内部格式化时会除以60,因此这里需要乘以60,将分钟转为秒 + callDuration: totalDurationInMinutes * 60, + deposits: rawData.deposit_volume, + deals: rawData.transaction_volume, + conversionRate: conversionRateValue, + }; +}; + + const comparedMetrics = computed(() => { - if (!props.currentPeriodData || !previousPeriodData.value) return []; + // **MODIFIED**: 在计算前,先使用 processRawData 对数据进行处理 + const processedCurrentData = processRawData(props.currentPeriodData); + const processedPreviousData = processRawData(previousPeriodData.value); + + if (!processedCurrentData || !processedPreviousData) return []; + // 指标配置保持不变,因为数据已经被处理成它期望的格式 const metricsConfig = [ { key: 'assignedLeads', label: '分配数据量', unit: '个' }, { key: 'wechatAdds', label: '加微量', unit: '个' }, @@ -163,8 +201,8 @@ const comparedMetrics = computed(() => { ]; return metricsConfig.map(metric => { - const current = props.currentPeriodData[metric.key]; - const previous = previousPeriodData.value[metric.key]; + const current = processedCurrentData[metric.key]; + const previous = processedPreviousData[metric.key]; return { ...metric, current, @@ -198,6 +236,7 @@ const calculateChange = (current, previous) => { const formatValue = (value, unit) => { if (value === null || value === undefined) return '-'; if (unit === '分钟') { + // 假设传入的value是秒,转换为分钟显示 return `${Math.round(value / 60)} 分钟`; } if (unit === '%') { @@ -212,6 +251,7 @@ const formatChange = (diff, unit) => { const absDiff = Math.abs(diff); if (unit === '分钟') { + // 假设diff是秒,转换为分钟显示 return `${prefix}${Math.round(absDiff / 60)} 分钟`; } if (unit === '%') { @@ -229,12 +269,12 @@ const getChangeClass = (trend) => {