feat(FeedbackForm): 在反馈提交中添加项目字段

refactor(CustomerDetail): 重命名并启用总通话分析功能
重构客户详情组件,将"客户诉求分析"改为"总通话分析"并启用相关功能,优化分析逻辑和UI显示

style(sale): 移除冗余标题并调整布局样式
删除客户详情区域的冗余标题,调整主布局的宽度和边距

perf(CustomerDetail): 优化分析请求和错误处理
移除调试日志,优化API请求参数和错误处理逻辑
This commit is contained in:
2025-10-13 11:45:27 +08:00
parent 575a08ed3a
commit 9555bb66fd
3 changed files with 219 additions and 232 deletions

View File

@@ -124,7 +124,7 @@ const handleSubmit = async () => {
const token = localStorage.getItem('token') || ''; const token = localStorage.getItem('token') || '';
// 发送 POST 请求到后端接口 // 发送 POST 请求到后端接口
const response = await axios.post('https://mldash.nycjy.cn/api/v1/submit_feedback', {type: formData.type, content: formData.content}, { const response = await axios.post('https://mldash.nycjy.cn/api/v1/submit_feedback', {project:'mldash',type: formData.type, content: formData.content}, {
headers: { headers: {
'Authorization': `Bearer ${token}` 'Authorization': `Bearer ${token}`
} }

View File

@@ -19,13 +19,13 @@
> >
{{ hasCallData ? 'SOP通话分析' : '暂无20分钟通话数据'}} {{ hasCallData ? 'SOP通话分析' : '暂无20分钟通话数据'}}
</button> </button>
<!-- <button <button
@click="startDemandAnalysis" @click="startDemandAnalysis"
class="analysis-button demand-button" class="analysis-button demand-button"
:disabled="isDemandAnalysisLoading" :disabled="isDemandAnalysisLoading"
> >
{{ isDemandAnalysisLoading ? '诉求分析中...' : '客户诉求分析' }} {{ isDemandAnalysisLoading ? '数据分析中...' : '总通话分析' }}
</button> --> </button>
</div> </div>
</div> </div>
@@ -65,21 +65,21 @@
</div> </div>
<!-- 下方整行区域 --> <!-- 下方整行区域 -->
<!-- <div class="bottom-row"> <div class="bottom-row">
<div class="analysis-section demand-analysis"> <div class="analysis-section demand-analysis">
<div class="section-header"> <div class="section-header">
<h4>客户诉求分析</h4> <h4>总通话分析</h4>
</div> </div>
<div class="section-content"> <div class="section-content">
<div class="text-content" v-if="demandAnalysisResult"> <div class="text-content" v-if="demandAnalysisResult">
<div class="analysis-text" v-html="formattedDemandAnalysis"></div> <div class="analysis-text" v-html="formattedDemandAnalysis"></div>
</div> </div>
<div class="placeholder-text" v-else> <div class="placeholder-text" v-else>
<p>点击"客户诉求分析"按钮开始深度分析客户需求和诉求</p> <p>点击"总通话分析"按钮开始深度分析客户的所有通话</p>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> -->
</div> </div>
</div> </div>
@@ -117,7 +117,6 @@ const props = defineProps({
} }
}); });
console.log(999999999,props.selectedContact);
// 分析结果状态 // 分析结果状态
const basicAnalysisResult = ref(''); // 基础信息分析结果 const basicAnalysisResult = ref(''); // 基础信息分析结果
const sopAnalysisResult = ref(''); // SOP通话分析结果 const sopAnalysisResult = ref(''); // SOP通话分析结果
@@ -130,7 +129,7 @@ const isDemandAnalysisLoading = ref(false); // 诉求分析加载状态
// Dify API配置 // Dify API配置
const DIFY_API_KEY_01 = 'app-h4uBo5kOGoiYhjuBF1AHZi8b'; //基础信息分析 const DIFY_API_KEY_01 = 'app-h4uBo5kOGoiYhjuBF1AHZi8b'; //基础信息分析
const DIFY_API_KEY = 'app-ZIJSFWbcdZLufkwCp9RrvpUR'; const DIFY_API_KEY = 'app-ZIJSFWbcdZLufkwCp9RrvpUR'; // 总通话分析
// 初始化ChatService // 初始化ChatService
const chatService_01 = new SimpleChatService(DIFY_API_KEY_01); const chatService_01 = new SimpleChatService(DIFY_API_KEY_01);
const chatService = new SimpleChatService(DIFY_API_KEY); const chatService = new SimpleChatService(DIFY_API_KEY);
@@ -198,7 +197,6 @@ const startBasicAnalysis = async () => {
let formInfoText = '暂无表单信息'; let formInfoText = '暂无表单信息';
if (Object.keys(formData).length > 0) { if (Object.keys(formData).length > 0) {
console.log(888888,formData);
const allInfo = []; const allInfo = [];
// 处理第一种格式基础信息和additional_info // 处理第一种格式基础信息和additional_info
@@ -206,7 +204,6 @@ const startBasicAnalysis = async () => {
const basicInfo = []; const basicInfo = [];
const additionalInfo = []; const additionalInfo = [];
// 处理基础信息字段
const basicFields = { const basicFields = {
name: '姓名', name: '姓名',
mobile: '手机号', mobile: '手机号',
@@ -224,7 +221,6 @@ const startBasicAnalysis = async () => {
} }
}); });
// 处理 additional_info 数组
if (formData.additional_info && Array.isArray(formData.additional_info)) { if (formData.additional_info && Array.isArray(formData.additional_info)) {
formData.additional_info.forEach(item => { formData.additional_info.forEach(item => {
if (item.topic && item.answer) { if (item.topic && item.answer) {
@@ -233,13 +229,11 @@ const startBasicAnalysis = async () => {
}); });
} }
// 添加基础信息
if (basicInfo.length > 0) { if (basicInfo.length > 0) {
allInfo.push('=== 基础信息 ==='); allInfo.push('=== 基础信息 ===');
allInfo.push(...basicInfo); allInfo.push(...basicInfo);
} }
// 添加问卷信息
if (additionalInfo.length > 0) { if (additionalInfo.length > 0) {
allInfo.push('\n=== 问卷信息 ==='); allInfo.push('\n=== 问卷信息 ===');
allInfo.push(...additionalInfo); allInfo.push(...additionalInfo);
@@ -251,36 +245,29 @@ const startBasicAnalysis = async () => {
const expandInfo = []; const expandInfo = [];
const map = formData.customerExpandFieldMap; const map = formData.customerExpandFieldMap;
// 处理所有expand字段
Object.entries(map).forEach(([key, value]) => { Object.entries(map).forEach(([key, value]) => {
// 跳过原型链上的属性
if (!map.hasOwnProperty(key)) return; if (!map.hasOwnProperty(key)) return;
// 如果是对象类型包含key和typeCode的字段
if (value && typeof value === 'object' && value.key) { if (value && typeof value === 'object' && value.key) {
const question = value.key; const question = value.key;
let answer = ''; let answer = '';
// 根据不同的类型处理答案
if (value.typeCode === 'SINGLE_SELECT' || value.typeCode === 'MULTIPLE_SELECT') { if (value.typeCode === 'SINGLE_SELECT' || value.typeCode === 'MULTIPLE_SELECT') {
if (value.expandValueList && value.expandValueList.length > 0) { if (value.expandValueList && value.expandValueList.length > 0) {
answer = value.expandValueList.map(item => item.itemName).join('、'); answer = value.expandValueList.map(item => item.itemName).join('、');
} }
} else if (value.typeCode === 'TEXT' || value.typeCode === 'TEXTAREA' || value.typeCode === 'NUMBER') { } else if (value.typeCode === 'TEXT' || value.typeCode === 'TEXTAREA' || value.typeCode === 'NUMBER') {
// 直接从formData中获取对应的expand值
answer = formData[key] || ''; answer = formData[key] || '';
} }
// 只添加有答案的问题
if (answer && answer !== '暂无' && answer !== '') { if (answer && answer !== '暂无' && answer !== '') {
expandInfo.push(`${question}\n答案: ${answer}`); expandInfo.push(`${question}\n答案: ${answer}`);
} }
} }
}); });
// 添加扩展信息
if (expandInfo.length > 0) { if (expandInfo.length > 0) {
if (allInfo.length > 0) allInfo.push('\n'); // 如果已有其他信息,添加分隔行 if (allInfo.length > 0) allInfo.push('\n');
allInfo.push('=== 问卷详细信息 ==='); allInfo.push('=== 问卷详细信息 ===');
allInfo.push(...expandInfo); allInfo.push(...expandInfo);
} }
@@ -291,7 +278,7 @@ const startBasicAnalysis = async () => {
// 构建聊天记录信息 // 构建聊天记录信息
const chatData = props.chatRecords || []; const chatData = props.chatRecords || [];
const chatInfoText = chatData.messages.length > 0 ? const chatInfoText = chatData.messages && chatData.messages.length > 0 ?
`聊天记录数量: ${chatData.messages.length}\n最近聊天内容: ${JSON.stringify(chatData.messages.slice(-3), null, 2)}` : `聊天记录数量: ${chatData.messages.length}\n最近聊天内容: ${JSON.stringify(chatData.messages.slice(-3), null, 2)}` :
'暂无聊天记录'; '暂无聊天记录';
@@ -317,7 +304,6 @@ ${callData.length > 0 && callData[0].record_context ? callData[0].record_context
请基于以上客户的表单信息、聊天记录和通话记录,分析客户的基本情况、背景信息和初步画像。`; 请基于以上客户的表单信息、聊天记录和通话记录,分析客户的基本情况、背景信息和初步画像。`;
console.log(888888,formInfoText);
try { try {
await chatService_01.sendMessage( await chatService_01.sendMessage(
query, query,
@@ -338,38 +324,19 @@ console.log(888888,formInfoText);
// SOP通话分析 // SOP通话分析
const startSopAnalysis = async () => { const startSopAnalysis = async () => {
console.log(888888888777777,props.selectedContact.wechat_id)
if (!props.selectedContact) return; if (!props.selectedContact) return;
isSopAnalysisLoading.value = true; isSopAnalysisLoading.value = true;
sopAnalysisResult.value = ''; sopAnalysisResult.value = '';
// 构建通话记录信息
const callData = props.callRecords || [];
const callInfoText = callData.length > 0 ?
`通话记录数量: ${callData.length}\n通话记录详情: ${JSON.stringify(callData, null, 2)}` :
'暂无通话记录';
const query = `=== 通话记录 ===
${callData.length > 0 && callData[0].record_context ? callData[0].record_context : callInfoText}`;
try { try {
// await chatService.sendMessage( const res = await axios.get('https://analysis.api.nycjy.cn/api/v1/call', {
// query,
// (update) => {
// sopAnalysisResult.value = update.content;
// },
// () => {
// isSopAnalysisLoading.value = false;
// console.log('SOP通话分析完成');
// }
// );
const res= await axios.get('https://analysis.api.nycjy.cn/api/v1/call',
{
params: { params: {
wechat_id: props.selectedContact.wechat_id wechat_id: props.selectedContact.wechat_id
} }
}) });
sopAnalysisResult.value = res.data.report_content; sopAnalysisResult.value = res.data.report_content;
isSopAnalysisLoading.value = false;
} catch (error) { } catch (error) {
console.error('SOP通话分析失败:', error); console.error('SOP通话分析失败:', error);
sopAnalysisResult.value = `分析失败: ${error.message}`; sopAnalysisResult.value = `分析失败: ${error.message}`;
@@ -377,30 +344,31 @@ ${callData.length > 0 && callData[0].record_context ? callData[0].record_context
} }
}; };
// 客户诉求分析 // 总通话分析
const startDemandAnalysis = async () => { const startDemandAnalysis = async () => {
console.log("所有通话记录:", props.callRecords);
if (!props.selectedContact) return; if (!props.selectedContact) return;
isDemandAnalysisLoading.value = true; isDemandAnalysisLoading.value = true;
demandAnalysisResult.value = ''; demandAnalysisResult.value = '';
const query = `请对客户 ${props.selectedContact.name} 进行深度诉求分析: // 1. 检查并拼接所有通话记录,为每次通话添加清晰的标签和区分
let allCallTexts = '暂无通话记录';
if (props.callRecords && props.callRecords.length > 0) {
allCallTexts = props.callRecords
.map((call, index) => {
// 为每段通话记录构建一个结构化的“信息头”,包含序号、标签和时间
return `--- 通话记录 ${index + 1} ---\n标签: ${call.record_tag || '无'}\n时间: ${call.record_create_time || '未知'}\n\n内容:\n${call.record_context}`;
})
.join('\n\n'); // 使用两个换行符清晰地分隔不同的通话记录
}
请从以下维度分析客户的真实需求和诉求: // 2. 构建一个详细的、结构化的分析指令
1. 显性需求分析(客户明确表达的需求) const query = `
2. 隐性需求挖掘(潜在的、未明确表达的需求) 请基于以下该客户的所有通话记录,进行全面深入的分析:
3. 痛点识别(客户面临的主要问题和挑战) === 全部通话记录 ===
4. 决策因素分析(影响客户决策的关键因素) ${allCallTexts}
5. 价值期望(客户期望获得的价值和收益) `;
6. 风险顾虑(客户可能的担忧和顾虑)
7. 个性化建议(针对性的解决方案建议)
客户信息:
姓名:${props.selectedContact.name}
公司:${props.selectedContact.company || '未提供'}
职位:${props.selectedContact.position || '未提供'}
销售阶段:${props.selectedContact.salesStage || '未知'}
健康度:${props.selectedContact.health || '未知'}%`;
try { try {
await chatService.sendMessage( await chatService.sendMessage(
@@ -410,11 +378,11 @@ const startDemandAnalysis = async () => {
}, },
() => { () => {
isDemandAnalysisLoading.value = false; isDemandAnalysisLoading.value = false;
console.log('客户诉求分析完成'); console.log('总通话分析完成');
} }
); );
} catch (error) { } catch (error) {
console.error('客户诉求分析失败:', error); console.error('总通话分析失败:', error);
demandAnalysisResult.value = `分析失败: ${error.message}`; demandAnalysisResult.value = `分析失败: ${error.message}`;
isDemandAnalysisLoading.value = false; isDemandAnalysisLoading.value = false;
} }
@@ -891,8 +859,14 @@ $purple: #a855f7;
// Markdown样式 // Markdown样式
.analysis-text { .analysis-text {
// Markdown样式 // Markdown样式
h1, h2, h3, h4, h5, h6 { :deep(h1),
:deep(h2),
:deep(h3),
:deep(h4),
:deep(h5),
:deep(h6) {
margin: 1rem 0 0.5rem 0; margin: 1rem 0 0.5rem 0;
font-weight: 600; font-weight: 600;
color: $slate-800; color: $slate-800;
@@ -902,14 +876,31 @@ $purple: #a855f7;
} }
} }
h1 { font-size: 1.25rem; } :deep(h1) {
h2 { font-size: 1.125rem; } font-size: 1.25rem;
h3 { font-size: 1rem; } }
h4 { font-size: 0.875rem; }
h5 { font-size: 0.75rem; }
h6 { font-size: 0.75rem; }
p { :deep(h2) {
font-size: 1.125rem;
}
:deep(h3) {
font-size: 1rem;
}
:deep(h4) {
font-size: 0.875rem;
}
:deep(h5) {
font-size: 0.75rem;
}
:deep(h6) {
font-size: 0.75rem;
}
:deep(p) {
margin: 0.5rem 0; margin: 0.5rem 0;
&:first-child { &:first-child {
@@ -921,7 +912,8 @@ $purple: #a855f7;
} }
} }
ul, ol { :deep(ul),
:deep(ol) {
margin: 0.5rem 0; margin: 0.5rem 0;
padding-left: 1.5rem; padding-left: 1.5rem;
@@ -930,23 +922,24 @@ $purple: #a855f7;
} }
} }
blockquote { :deep(blockquote) {
margin: 1rem 0; margin: 1rem 0;
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
border-left: 4px solid $blue; border-left: 4px solid $blue;
background: rgba(59, 130, 246, 0.05); background: rgba(59, 130, 246, 0.05);
font-style: italic; color: $slate-600;
} }
code { :deep(code) {
background: $slate-100; background: $slate-100;
padding: 0.125rem 0.25rem; padding: 0.125rem 0.25rem;
border-radius: 0.25rem; border-radius: 0.25rem;
font-family: 'Courier New', monospace; font-family: 'Courier New', monospace;
font-size: 0.8rem; font-size: 0.8rem;
color: $indigo;
} }
pre { :deep(pre) {
background: $slate-100; background: $slate-100;
padding: 1rem; padding: 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
@@ -959,16 +952,16 @@ $purple: #a855f7;
} }
} }
strong { :deep(strong) {
font-weight: 600; font-weight: 600;
color: $slate-800; color: $slate-800;
} }
em { :deep(em) {
font-style: italic; font-style: italic;
} }
a { :deep(a) {
color: $blue; color: $blue;
text-decoration: none; text-decoration: none;
@@ -977,18 +970,19 @@ $purple: #a855f7;
} }
} }
hr { :deep(hr) {
margin: 1.5rem 0; margin: 1.5rem 0;
border: none; border: none;
border-top: 1px solid $slate-200; border-top: 1px solid $slate-200;
} }
table { :deep(table) {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
margin: 1rem 0; margin: 1rem 0;
th, td { th,
td {
padding: 0.5rem; padding: 0.5rem;
border: 1px solid $slate-200; border: 1px solid $slate-200;
text-align: left; text-align: left;
@@ -1085,8 +1079,6 @@ h4 {
background-color: $white; background-color: $white;
border-radius: 0.5rem; border-radius: 0.5rem;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
// padding: 1rem;
// margin-top: 12px;
} }
// 分析区域布局优化 // 分析区域布局优化
@@ -1125,5 +1117,4 @@ h4 {
min-height: 250px; min-height: 250px;
} }
} }
</style> </style>

View File

@@ -98,9 +98,6 @@
<main class="main-content"> <main class="main-content">
<!-- 客户详情区域 --> <!-- 客户详情区域 -->
<section v-if="cardVisibility.customerDetail && selectedContact" class="detail-section"> <section v-if="cardVisibility.customerDetail && selectedContact" class="detail-section">
<div class="section-header">
<h2>客户详情</h2>
</div>
<div class="section-content"> <div class="section-content">
<CustomerDetail <CustomerDetail
ref="customerDetailRef" ref="customerDetailRef"
@@ -638,7 +635,6 @@ async function getCustomerCall() {
const kpiData = computed(() => kpiDataState); const kpiData = computed(() => kpiDataState);
// COMPUTED PROPERTIES // COMPUTED PROPERTIES
const selectedContact = computed(() => { const selectedContact = computed(() => {
console.log(999999999,formattedCustomersList.value);
// 优先从API数据中查找 // 优先从API数据中查找
if (formattedCustomersList.value.length > 0) { if (formattedCustomersList.value.length > 0) {
return formattedCustomersList.value.find((c) => c.id === selectedContactId.value) || null; return formattedCustomersList.value.find((c) => c.id === selectedContactId.value) || null;
@@ -1077,9 +1073,9 @@ $primary: #3b82f6;
} }
// 主要布局 // 主要布局
.main-layout { .main-layout {
width: 100vw; width: 99vw;
margin: 0 auto; margin-bottom: 1rem;
padding: 1rem; // padding: 1rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1rem; gap: 1rem;