fix: 修复表单数据处理逻辑并更新UI文本显示

修复表单数据从对象到数组的格式兼容处理,确保新旧数据格式都能正确显示
更新多处UI文本描述,包括"今日通话"改为"本期通话"、"月度总业绩"改为"本月成交单数"
调整客户阶段筛选逻辑,排除'待加微'客户对'待填表单'和'待到课'的影响
修复未分配通话记录API端点错误
This commit is contained in:
2025-10-22 16:22:51 +08:00
parent 094f655634
commit d75fd9beb8
7 changed files with 75 additions and 58 deletions

View File

@@ -32,7 +32,7 @@
</div>
<div class="report-card">
<div class="card-header">
<span class="card-title">月度总业绩 <i class="info-icon" @mouseenter="showTooltip('monthlyRevenue', $event)" @mouseleave="hideTooltip"></i></span>
<span class="card-title">本月成交单数 <i class="info-icon" @mouseenter="showTooltip('monthlyRevenue', $event)" @mouseleave="hideTooltip"></i></span>
<span class="card-trend positive">+8% vs 上月</span>
</div>
<div class="card-value">{{ formatCurrency(weekTotalData.week_add_fee_total?.total_add_fee || 0) }} </div>
@@ -122,8 +122,8 @@ const metricDescriptions = {
description: '本期新增的成交订单数量,已确认付款或签约的客户订单。'
},
monthlyRevenue: {
title: '月度总业绩',
description: '本月团队累计完成的销售业绩总额,包括所有已确认的订单金额。'
title: '本月成交单数',
description: '本月团队累计完成的销售订单数量,包括所有已确认的订单。'
},
conversionRate: {
title: '定金转化率',

View File

@@ -14,7 +14,7 @@
<div class="kpi-grid">
<div class="kpi-item">
<div class="kpi-value">{{ props.kpiData.totalCalls }}</div>
<p>今日通话 <i class="info-icon" @mouseenter="showTooltip('totalCalls', $event)" @mouseleave="hideTooltip"></i></p>
<p>本期通话 <i class="info-icon" @mouseenter="showTooltip('totalCalls', $event)" @mouseleave="hideTooltip"></i></p>
</div>
<div class="kpi-item">
<div class="kpi-value">{{ props.kpiData.successRate }}</div>
@@ -282,8 +282,8 @@ const tooltip = reactive({
// 指标说明配置
const kpiDescriptions = {
totalCalls: {
title: '今日通话',
description: '今日总共通话的次数。'
title: '本期通话',
description: '本期总共通话的次数。'
},
successRate: {
title: '电话接通率',

View File

@@ -70,7 +70,7 @@
<span class="content-time">最新: {{ chatData.lastMessage }}</span>
</div>
<div class="message-list">
<div v-for="(message, index) in props.chatInfo.messages" :key="index" class="message-item">
<div v-for="(message, index) in props.chatInfo?.messages" :key="index" class="message-item">
<div class="message-header">
<span class="message-sender">{{ message.format_direction }}</span>
<span class="message-time">{{ message.format_add_time }}</span>
@@ -142,7 +142,7 @@ const props = defineProps({
default: () => ({})
},
formInfo: {
type: Object,
type: Array,
default: () => []
},
chatInfo: {
@@ -166,10 +166,25 @@ const chatMessages = computed(() => {
return props.chatInfo?.messages || []
})
// 表单字段数据
// 表单字段数据 (已更新)
const formFields = computed(() => {
const formData = props.formInfo
if (!formData || Object.keys(formData).length === 0) {
console.log(8888888,formData)
// --- NEW LOGIC: 处理新的数组格式(问答列表) ---
if (Array.isArray(formData) && formData.length > 0) {
// 检查数组项结构,确保是新的问答格式
if (formData[0].question_label && formData[0].answer) {
// 直接将问答列表映射为 { label: question_label, value: answer } 格式
return formData.map(item => ({
label: item.question_label,
value: item.answer || '暂无回答'
}))
}
}
// --------------------------------------------------
// Fallback: 如果数据为空、null 或旧的空对象格式
if (!formData || (typeof formData === 'object' && Object.keys(formData).length === 0)) {
return [
{ label: '姓名', value: '暂无数据' },
{ label: '联系方式', value: '暂无数据' },
@@ -178,6 +193,7 @@ const formFields = computed(() => {
]
}
// --- OLD LOGIC: 处理旧的对象格式(保持兼容性) ---
let fields = []
// 检查是否为第一种格式包含name, mobile等字段
@@ -223,6 +239,7 @@ const formFields = computed(() => {
return allFields
})
// 聊天数据
const chatData = computed(() => ({
count: props.chatInfo?.messages?.length || 0,

View File

@@ -432,7 +432,7 @@ const handleUnassignedRecordingsClick = async () => {
// 优先使用路由参数,其次是 Pinia store 中的用户信息,最后是备用值
const user_name = routeParams.user_name || userStore.userInfo?.username || 'example_user';
const response = await axios.post('https://mldash.nycjy.cn/api/v1/sales_timeline/get_sale_call_info', {
const response = await axios.post('http://192.168.15.121:8890/api/v1/sales_timeline/get_sale_unassigned_call_info', {
user_name: user_name
});
@@ -545,7 +545,26 @@ const totalCustomers = computed(() => {
return Math.max(...baseStages, 1);
});
const getAttendedLessons = (classSituation, classNum) => {
if (classNum && Array.isArray(classNum) && classNum.length > 0) {
const filtered = classNum.filter(n => n !== -1);
return filtered.length > 0 ? filtered.sort((a, b) => a - b).join(' ') : '未到课';
}
if (!classSituation) return '未到课';
if (Array.isArray(classSituation)) {
const filtered = classSituation.filter(i => i !== -1);
return filtered.length > 0 ? filtered.join(' ') : '未到课';
}
if (typeof classSituation === 'object') {
const lessons = Object.keys(classSituation).map(k => parseInt(k)).filter(n => !isNaN(n) && n !== -1).sort((a, b) => a - b);
return lessons.length > 0 ? lessons.join(' ') : '未到课';
}
return '未到课';
};
// **修改 getStageCount 函数**
const getStageCount = (stageType) => {
if (stageType === '成交') {
return props.payMoneyCustomersCount || (props.payMoneyCustomersList?.length || 0);
}
@@ -558,31 +577,24 @@ const getStageCount = (stageType) => {
}
return 0;
}
const allCustomers = props.customersList || [];
// 筛选掉 '待加微' 的客户列表,只有这批客户才需要关注 '待填表单' 和 '待到课'
const nonPendingWechatCustomers = allCustomers.filter(c => c.type !== '待加微');
if (stageType === '待填表单') {
if (props.customersList?.length) {
return props.customersList.filter(customer => (customer.customer_occupation === '未知' || !customer.customer_occupation) || (customer.customer_child_education === '未知' || !customer.customer_child_education)).length;
}
return 0;
// 对非 '待加微' 的客户进行筛选
return nonPendingWechatCustomers.filter(customer => (customer.customer_occupation === '未知' || !customer.customer_occupation) || (customer.customer_child_education === '未知' || !customer.customer_child_education)).length;
}
if (stageType === '待到课') {
if (props.customersList?.length) {
return props.customersList.filter(customer => {
const classNum = customer.class_num;
const classSituation = customer.class_situation;
if (classNum && Array.isArray(classNum) && classNum.length > 0) return false;
if (!classSituation) return true;
if (Array.isArray(classSituation)) return classSituation.length === 0;
if (typeof classSituation === 'object') {
const lessonNumbers = Object.keys(classSituation).map(key => parseInt(key)).filter(num => !isNaN(num));
return lessonNumbers.length === 0;
}
return true;
}).length;
}
return 0;
// 对非 '待加微' 的客户进行筛选,并使用 getAttendedLessons
return nonPendingWechatCustomers.filter(customer => getAttendedLessons(customer.class_situation, customer.class_num) === '未到课').length;
}
if (props.customersList?.length) {
return props.customersList.filter(customer => customer.type === stageType).length;
// 对于其他普通阶段(待加微、待入群、待联系),使用全部客户列表
if (allCustomers.length > 0) {
return allCustomers.filter(customer => customer.type === stageType).length;
}
return props.data[stageType] || 0;
};
@@ -615,6 +627,7 @@ const getPercentage = (count, stageId, stageName) => {
return Math.round((totalCustomers.value - count) / totalCustomers.value * 100);
};
// **修改 selectStage 函数**
const selectStage = (stageName) => {
let filteredCustomers = [];
if (stageName === '全部') {
@@ -631,9 +644,13 @@ const selectStage = (stageName) => {
filteredCustomers = props.courseCustomers['课1-4'].filter(c => c.type === stageName);
}
} else if (stageName === '待填表单') {
filteredCustomers = props.customersList.filter(c => (c.customer_occupation === '未知' || !c.customer_occupation) || (c.customer_child_education === '未知' || !c.customer_child_education));
// 筛选掉 '待加微' 的客户列表
const customersToFilter = props.customersList.filter(c => c.type !== '待加微') || [];
filteredCustomers = customersToFilter.filter(c => (c.customer_occupation === '未知' || !c.customer_occupation) || (c.customer_child_education === '未知' || !c.customer_child_education));
} else if (stageName === '待到课') {
filteredCustomers = props.customersList.filter(c => getAttendedLessons(c.class_situation, c.class_num) === '未到课');
// 筛选掉 '待加微' 的客户列表
const customersToFilter = props.customersList.filter(c => c.type !== '待加微') || [];
filteredCustomers = customersToFilter.filter(c => getAttendedLessons(c.class_situation, c.class_num) === '未到课');
} else {
filteredCustomers = props.customersList.filter(c => c.type === stageName);
}
@@ -679,23 +696,6 @@ const getCourseConversionRate = (courseNumber) => {
return Math.round((depositCount / courseStageCount) * 100);
};
const getAttendedLessons = (classSituation, classNum) => {
if (classNum && Array.isArray(classNum) && classNum.length > 0) {
const filtered = classNum.filter(n => n !== -1);
return filtered.length > 0 ? filtered.sort((a, b) => a - b).join(' ') : '未到课';
}
if (!classSituation) return '未到课';
if (Array.isArray(classSituation)) {
const filtered = classSituation.filter(i => i !== -1);
return filtered.length > 0 ? filtered.join(' ') : '未到课';
}
if (typeof classSituation === 'object') {
const lessons = Object.keys(classSituation).map(k => parseInt(k)).filter(n => !isNaN(n) && n !== -1).sort((a, b) => a - b);
return lessons.length > 0 ? lessons.join(' ') : '未到课';
}
return '未到课';
};
const getHealthClass = (health) => {
if (!health && health !== 0) return '';
const healthValue = parseFloat(health);
@@ -882,7 +882,7 @@ $indigo: #4f46e5;
font-weight: bold;
}
.clickable { cursor: pointer; color: #1976d2; text-decoration: underline; &:hover { color: #1565c0; } }
.messages-list { display: flex; flex-direction: column; gap: 12px; .message-item { display: flex; gap: 8px; padding: 12px; background-color: #f9fafb; border-radius: 6px; border-left: 3px solid $primary; } .message-number { font-weight: 600; color: $primary; } }
.messages-list { display: flex; flex-direction: column; gap: 12px; .message-item { display: flex; gap: 8px; padding: 12px; background-color: #f9fafb; border-left: 3px solid $primary; } .message-number { font-weight: 600; color: $primary; } }
.course-sub-timeline { margin: 16px 0; padding: 12px; background-color: #f8fafc; border-radius: 8px; border: 1px solid #e2e8f0; }
.sub-timeline-container { display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; gap: 8px; }
.course-timeline { background: white; border-radius: 6px; padding: 8px 12px; border: 1px solid #e5e7eb; }

View File

@@ -323,7 +323,7 @@ const payMoneyCustomersList = ref([]);
const payMoneyCustomersCount = ref(0);
// 表单信息
const formInfo = ref({});
const formInfo = ref([]);
// 通话记录
const callRecords = ref([]);
// 聊天记录
@@ -584,7 +584,7 @@ async function getCustomerForm() {
const res = await getCustomerFormInfo(params)
console.log('获取客户表单数据:', res)
if(res.code === 200) {
formInfo.value = res.data[0].answer || []
formInfo.value = res.data[0].answers || []
}
} catch (error) {
// 静默处理错误

View File

@@ -24,10 +24,10 @@
团队总业绩
<span class="info-icon" @mouseenter="showTooltip($event, 'teamPerformance')" @mouseleave="hideTooltip"></span>
</span>
<span class="card-trend positive">{{ totalPerformance.team_current_vs_previous_deals }} vs 上期</span>
<span class="card-trend positive">{{ totalPerformance.team_current_vs_previous_period_deals_comparison }} vs 上期</span>
</div>
<div class="card-value">{{ totalPerformance.current_team_odd_numbers||0 }}</div>
<div class="card-subtitle">月目标完成率: {{ totalPerformance.team_monthly_performance }}</div>
<div class="card-subtitle">月目标完成率: {{ totalPerformance.team_monthly_target_completion_rate }}</div>
</div>
<div class="overview-card">
@@ -48,7 +48,7 @@
团队转化率
<span class="info-icon" @mouseenter="showTooltip($event, 'conversionRate')" @mouseleave="hideTooltip"></span>
</span>
<span class="card-trend positive">{{ conversionRate.team_current_vs_previous_deals }} vs 上期</span>
<span class="card-trend positive">{{ conversionRate.team_current_vs_previous_conversion_rate }} vs 上期</span>
</div>
<div class="card-value">{{ conversionRate.center_conversion_rate }}</div>
<div class="card-subtitle">团队平均转化率: {{ conversionRate.average_conversion_rate }}</div>

View File

@@ -65,7 +65,7 @@
:formCompletionRate="statisticalIndicators.formCompletionRate"
/>
<!-- 新增业绩周期对比组件 -->
<div v-if="cardVisibility.performanceComparison" class="performance-comparison-section">
<div v-if="false" class="performance-comparison-section">
<PerformanceComparison :current-period-data="currentPeriodMetrics" />
</div>
<!-- Bottom Section -->