Files
smartDriveEEUniApp/pages-subpackage/customer/components/ConversationSummaryFurniture.vue
2026-01-31 21:10:40 +08:00

755 lines
24 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<scroll-view class="detail-content" scroll-y>
<!-- 加载状态 -->
<view class="loading-container" v-if="loading">
<text class="loading-text">加载中...</text>
</view>
<!-- 空状态 -->
<view class="empty-container" v-else-if="!loading && summaryList.length === 0 && !summarySentence && !dealKeyPoints && productCategoryList.length === 0">
<text class="empty-text">暂无会话总结数据</text>
</view>
<!-- 客户类型家庭结构等总结信息在产品清单上方 -->
<view
class="summary-item"
v-for="(item, index) in summaryList"
:key="index"
@click="viewSummaryDetail(item)">
<view class="summary-label">
<text>{{ item.label }}</text>
</view>
<view class="summary-value">
<text>{{ item.value }}</text>
</view>
<uni-icons type="right" size="16" color="#999"></uni-icons>
</view>
<!-- 意向产品清单显示产品类别名称列表 -->
<view class="summary-item" v-if="productCategoryList.length > 0">
<view class="summary-label">
<text>意向产品清单</text>
</view>
<view class="summary-value">
<text>{{ getProductCategoryNames() }}</text>
</view>
</view>
<!-- 一句话总结 -->
<view class="summary-item" v-if="summarySentence">
<view class="summary-label">
<text>一句话总结</text>
</view>
<view class="summary-value">
<text>{{ summarySentence }}</text>
</view>
</view>
<!-- 成交关键点 -->
<view class="summary-item" v-if="dealKeyPoints">
<view class="summary-label">
<text>成交关键点</text>
</view>
<view class="summary-value">
<text>{{ dealKeyPoints }}</text>
</view>
</view>
<!-- 产品类别标签页 -->
<view class="product-category-tabs" v-if="productCategoryList.length > 0">
<scroll-view class="category-tabs-scroll" scroll-x>
<view class="category-tabs-wrapper">
<view
class="category-tab-item"
:class="{ active: activeProductCategory === category.key }"
v-for="(category, index) in productCategoryList"
:key="index"
@click="switchProductCategory(category.key)">
<text>{{ category.name }}</text>
</view>
</view>
</scroll-view>
</view>
<!-- 产品需求清单表格 -->
<view class="product-requirement-table" v-if="activeProductCategory && currentProductRequirement">
<view class="requirement-title">
<text>{{ getProductCategoryName(activeProductCategory) }}</text>
</view>
<view class="requirement-table">
<view
class="requirement-row"
v-for="(row, index) in currentProductRequirement.rows"
:key="index">
<view class="requirement-label">
<text>{{ row.label }}</text>
</view>
<view class="requirement-content">
<text>{{ row.content }}</text>
</view>
</view>
</view>
</view>
<!-- 如果没有产品类别数据显示提示确保categoryList已初始化后显示 -->
<view class="product-requirement-table" v-else-if="productCategoryList.length === 0 && furnitureAnalysisList.length > 0">
<view class="requirement-title">
<text>产品需求</text>
</view>
<view class="requirement-table">
<view class="requirement-row">
<view class="requirement-label">
<text>提示</text>
</view>
<view class="requirement-content">
<text>暂无产品需求信息</text>
</view>
</view>
</view>
</view>
</scroll-view>
</template>
<script>
import { getApiUrl } from '@/common/config.js';
import { initSummaryList } from '../utils/dataInit.js';
export default {
name: 'ConversationSummaryFurniture',
props: {
// 服务记录项
serviceItem: {
type: Object,
default: () => ({})
}
},
data() {
return {
summaryList: [],
furnitureAnalysisList: [], // 家具意向分析数据
productCategoryList: [], // 产品类别列表
activeProductCategory: '', // 当前选中的产品类别
productRequirementMap: {}, // 产品需求映射 {categoryKey: {rows: [...]}}
summarySentence: '', // 一句话总结
dealKeyPoints: '', // 成交关键点
loading: false // 加载状态
}
},
computed: {
// 当前选中的产品需求
currentProductRequirement() {
if (!this.activeProductCategory) return null;
return this.productRequirementMap[this.activeProductCategory] || null;
}
},
watch: {
serviceItem: {
handler(newVal, oldVal) {
// 如果新值和旧值相同,不重复处理
if (newVal === oldVal) {
return;
}
// 检查新值是否有效
if (newVal && typeof newVal === 'object') {
const newId = newVal.id || newVal.rawData?.id;
const oldId = oldVal?.id || oldVal?.rawData?.id;
// 如果ID相同不重复加载
if (newId && newId === oldId) {
return;
}
if (newId) {
this.initData();
this.loadFurnitureAnalysis(newVal);
} else {
// 如果没有ID重置所有状态
this.initData();
}
} else {
// 如果新值无效,重置所有状态
this.initData();
}
},
immediate: true,
deep: true
}
},
mounted() {
// 组件挂载时如果已有serviceItem初始化数据
if (this.serviceItem && (this.serviceItem.id || this.serviceItem.rawData?.id)) {
this.initData();
this.loadFurnitureAnalysis(this.serviceItem);
}
},
methods: {
// 初始化数据
initData() {
try {
// 只有当serviceItem有效时才初始化summaryList
if (this.serviceItem && (this.serviceItem.id || this.serviceItem.rawData?.id)) {
this.summaryList = initSummaryList(this.serviceItem);
} else {
this.summaryList = [];
}
} catch (error) {
console.error('初始化总结数据失败:', error);
this.summaryList = [];
}
// 重置产品需求清单和相关数据
this.productCategoryList = [];
this.activeProductCategory = '';
this.productRequirementMap = {};
this.summarySentence = '';
this.dealKeyPoints = '';
this.furnitureAnalysisList = [];
this.loading = false;
},
// 加载家具意向分析数据
async loadFurnitureAnalysis(item) {
if (!item) {
return;
}
try {
this.loading = true;
// 获取parentId从rawData中获取id
const parentId = item.rawData?.id || item.id;
if (!parentId) {
console.warn('无法获取parentId');
this.furnitureAnalysisList = [];
this.loading = false;
return;
}
// 统一使用 getApiUrl根据 env.js 配置自动切换环境
const url = `${getApiUrl('/api/audioTextAnalysisFurniture/byParentId')}?parentId=${parentId}`;
const res = await uni.request({
url: url,
method: 'GET',
timeout: 10000
});
if (res.statusCode === 200 && res.data && res.data.success) {
this.furnitureAnalysisList = res.data.data || [];
// 如果有数据更新summaryList显示家具意向分析
if (this.furnitureAnalysisList.length > 0) {
this.updateSummaryWithFurnitureData();
}
} else {
console.warn('获取家具意向分析失败:', res.data?.message || '未知错误');
this.furnitureAnalysisList = [];
}
} catch (error) {
console.error('获取家具意向分析失败:', error);
this.furnitureAnalysisList = [];
} finally {
this.loading = false;
}
},
// 用家具意向分析数据更新会话总结
updateSummaryWithFurnitureData() {
if (this.furnitureAnalysisList.length === 0) return;
// 从第一个家具意向分析数据中提取关键信息
const firstItem = this.furnitureAnalysisList[0];
// 构建会话总结列表,显示客户类型、家庭结构、一句话总结
const summaryItems = [];
// 客户类型
if (firstItem.customerType) {
summaryItems.push({
label: '客户类型',
value: firstItem.customerType,
rawData: firstItem
});
}
// 家庭结构
if (firstItem.familyStructure) {
summaryItems.push({
label: '家庭结构',
value: firstItem.familyStructure,
rawData: firstItem
});
}
// 一句话总结单独存储不在summaryList中
if (firstItem.summarySentence) {
this.summarySentence = firstItem.summarySentence;
}
// 成交关键点(可以从数据中提取或生成)
if (firstItem.dealKeyPoints) {
this.dealKeyPoints = firstItem.dealKeyPoints;
} else if (firstItem.summarySentence) {
// 如果数据中没有成交关键点,可以从一句话总结中提取关键信息
// 提取关键词:活动优惠、搭配、环保、安全、交付、退换、服务等
const keyWords = [];
const summary = firstItem.summarySentence;
if (summary.includes('优惠') || summary.includes('活动')) {
keyWords.push('活动优惠');
}
if (summary.includes('搭配')) {
keyWords.push('整套搭配');
}
if (summary.includes('环保')) {
keyWords.push('环保');
}
if (summary.includes('安全')) {
keyWords.push('安全');
}
if (summary.includes('交付') || summary.includes('周期')) {
keyWords.push('快速交付');
}
if (summary.includes('退换') || summary.includes('服务')) {
keyWords.push('无忧退换服务');
}
if (keyWords.length > 0) {
this.dealKeyPoints = keyWords.join('');
}
}
// 如果提取到了数据更新summaryList
if (summaryItems.length > 0) {
this.summaryList = summaryItems;
}
// 构建产品需求清单
this.buildProductRequirementList(firstItem);
},
// 构建产品需求清单(固定显示所有产品类别)
buildProductRequirementList(data) {
const categories = [
{ key: 'sofa', name: '沙发', field: 'sofa' },
{ key: 'bed', name: '床', field: 'bedAndMattress' },
{ key: 'mattress', name: '床垫', field: 'bedAndMattress' },
{ key: 'dining_table', name: '餐桌椅', field: 'diningTable' },
{ key: 'tea_table', name: '茶几', field: 'teaTable' },
{ key: 'tv_cabinet', name: '电视柜', field: 'tvCabinet' },
{ key: 'study_desk', name: '学习桌', field: 'studyDesk' },
{ key: 'cabinet', name: '橱柜', field: 'cabinet' },
{ key: 'wine_cabinet', name: '酒柜', field: 'wineCabinet' },
{ key: 'master_bedroom_cabinet', name: '主卧衣柜', field: 'masterBedroomCabinet' },
{ key: 'secondary_bedroom_cabinet', name: '次卧衣柜', field: 'secondaryBedroomCabinet' },
{ key: 'shoe_cabinet', name: '鞋柜', field: 'shoeCabinet' },
{ key: 'custom', name: '定制家居', field: null }
];
const requirementMap = {};
const categoryList = [];
categories.forEach(category => {
// 检查是否有该产品的数据
let hasData = false;
let productData = null;
if (category.key === 'bed' || category.key === 'mattress') {
// 床和床垫共用bedAndMattress字段
const fieldValue = data.bedAndMattress || data.bed_and_mattress || '';
if (fieldValue && fieldValue.trim()) {
hasData = true;
productData = this.parseProductJson(fieldValue);
}
} else if (category.key === 'custom') {
// 定制家居:暂时跳过,因为各个定制产品已有单独类别
// 如果需要显示定制家居汇总,可以在这里添加逻辑
hasData = false;
} else {
// 尝试驼峰命名和蛇形命名
const fieldValue = data[category.field] || data[this.toSnakeCase(category.field)] || '';
if (fieldValue && fieldValue.trim()) {
hasData = true;
productData = this.parseProductJson(fieldValue);
}
}
// 只有当有数据时才添加到列表中
if (hasData) {
categoryList.push({ key: category.key, name: category.name });
// 构建产品需求行
const rows = this.buildProductDimensionRows(productData, data, category.key);
requirementMap[category.key] = { rows };
}
});
this.productRequirementMap = requirementMap;
this.productCategoryList = categoryList;
if (categoryList.length > 0) {
this.activeProductCategory = categoryList[0].key;
}
},
// 解析产品JSON字符串
parseProductJson(jsonString) {
if (!jsonString || typeof jsonString !== 'string') {
return null;
}
try {
const parsed = JSON.parse(jsonString);
return parsed;
} catch (error) {
console.warn('解析产品JSON失败:', error, jsonString);
return null;
}
},
// 驼峰转蛇形命名
toSnakeCase(str) {
if (!str) return '';
// 在大写字母前加下划线,然后转小写,最后去掉开头的下划线
return str.replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, '');
},
// 切换产品类别
switchProductCategory(categoryKey) {
this.activeProductCategory = categoryKey;
},
// 获取产品类别名称
getProductCategoryName(categoryKey) {
const category = this.productCategoryList.find(item => item.key === categoryKey);
return category ? category.name : '';
},
// 获取所有产品类别名称(用于意向产品清单显示)
getProductCategoryNames() {
return this.productCategoryList.map(item => item.name).join('');
},
parseProductAttributes(productKey, requirementText, data) {
const attributes = {};
const text = (requirementText || '').trim();
if (!text) return attributes;
const matchFirst = (pattern) => {
const result = text.match(pattern);
return result ? result[0] : '';
};
switch (productKey) {
case 'sofa':
attributes.type = matchFirst(/(L型|L形|一字型|U型|U形|带贵妃榻|贵妃榻|三人位|四人位|转角|组合)/);
attributes.material = matchFirst(/(真皮|科技布|布艺|布|皮质|皮革|实木|板式|棉麻|绒布)/);
attributes.size = matchFirst(/(\d+\.?\d*)\s*(米|m|平米|平方米)/);
attributes.color = matchFirst(/(米白|白色|灰色|黑色|棕色|蓝色|绿色|暖色调|冷色调|灰白色系)/);
attributes.style = matchFirst(/(现代简约|简约现代|现代|北欧|中式|美式|欧式|工业风|原木风|简约|极简)/);
if (text.includes('储物') || text.includes('收纳')) attributes.feature = '储物';
if (text.includes('好打理')) attributes.remark = '要求好打理';
break;
case 'bed':
attributes.size = matchFirst(/(\d+\.?\d*)\s*(米|m)/);
attributes.material = matchFirst(/(实木|板式|软包|皮质|布艺)/);
attributes.style = data.decorationStyle || '';
if (text.includes('主卧')) attributes.remark = '主卧';
break;
case 'mattress':
attributes.size = matchFirst(/(\d+\.?\d*)\s*(米|m)/);
attributes.material = matchFirst(/(弹簧|记忆棉|乳胶|椰棕|海绵)/);
if (text.includes('分区支撑')) attributes.softness = '分区支撑(兼顾软硬需求)';
else if (text.includes('偏软') || text.includes('软')) attributes.softness = '偏软';
else if (text.includes('偏硬') || text.includes('硬')) attributes.softness = '偏硬';
break;
case 'dining_table':
if (text.includes('伸缩')) attributes.type = '可伸缩';
const rangeMatch = text.match(/(\d+\.?\d*)\s*[-~到]\s*(\d+\.?\d*)\s*米/);
if (rangeMatch) attributes.size = `${rangeMatch[1]}-${rangeMatch[2]}`;
else attributes.size = matchFirst(/(\d+\.?\d*)\s*米/);
attributes.chairCount = matchFirst(/(\d+)\s*把/);
if (text.includes('舒适')) attributes.feature = '舒适型';
attributes.style = data.decorationStyle || '';
break;
case 'tea_table':
if (text.includes('抽屉')) attributes.type = '带抽屉';
if (text.includes('储物') || text.includes('收纳')) attributes.feature = '储物';
attributes.style = data.decorationStyle || '';
break;
case 'tv_cabinet':
if (text.includes('组合')) attributes.type = '组合式';
if (text.includes('储物')) attributes.feature = '储物';
attributes.style = data.decorationStyle || '';
break;
case 'study_desk':
if (text.includes('可调节') || text.includes('调节高度')) attributes.type = '可调节高度';
if (text.includes('成长')) attributes.feature = '适应孩子成长';
break;
case 'wine_cabinet':
if (text.includes('恒温')) attributes.feature = '恒温';
const capacity = text.match(/(\d+)[-~到](\d+)\s*瓶|(\d+)\s*瓶/);
if (capacity) {
if (capacity[1] && capacity[2]) attributes.capacity = `${capacity[1]}-${capacity[2]}`;
else if (capacity[3]) attributes.capacity = `${capacity[3]}`;
}
break;
case 'master_bedroom_cabinet':
case 'secondary_bedroom_cabinet':
if (text.includes('定制') || text.includes('整墙')) attributes.type = text.includes('整墙') ? '整墙定制' : '定制';
attributes.size = matchFirst(/(\d+\.?\d*)\s*(米|m)/);
if (text.includes('衣帽间')) attributes.feature = '带衣帽间';
if (text.includes('主卧')) attributes.remark = '主卧';
if (text.includes('次卧')) attributes.remark = '次卧';
break;
case 'shoe_cabinet':
if (text.includes('窄款') || text.includes('高型')) attributes.type = '窄款高型';
attributes.size = matchFirst(/(\d+\.?\d*)\s*(米|m)/);
if (text.includes('带座') || text.includes('可坐')) attributes.feature = '带座功能';
if (text.includes('收纳空间')) attributes.remark = '大容量收纳';
break;
default:
attributes.description = text;
break;
}
if (!attributes.style && data && data.decorationStyle) attributes.style = data.decorationStyle;
if (!attributes.color && data && data.decorationStyle) {
const color = data.decorationStyle.match(/(米白|白色|灰色|黑色|棕色|蓝色|暖色调|冷色调|灰白色系|原木色)/);
if (color) attributes.color = color[0];
}
if (!attributes.description && text) attributes.description = text;
return attributes;
},
buildProductDimensionRows(productData = null, data = {}, categoryKey = '') {
const dimensionLabels = [
{ key: 'styleColor', label: '偏好风格与颜色', field: 'preferenceStyleAndColor' },
{ key: 'material', label: '材质偏好', field: 'materialPreference' },
{ key: 'focus', label: '客户重点关注', field: 'customerFocusAreas' },
{ key: 'price', label: '价格敏感度', field: 'priceSensitivity' },
{ key: 'campaign', label: '活动参与度', field: 'activityParticipation' },
{ key: 'ordered', label: '客户疑虑点', field: 'customerConcerns' },
{ key: 'service', label: '交付服务', field: 'deliveryService' },
{ key: 'competitor', label: '竞品反馈', field: 'competitorFeedback' },
{ key: 'delivery', label: '交付周期', field: 'deliveryCycle' },
{ key: 'appearance', label: '产品颜值', field: 'productAppearance' }
];
// 判断productData是否是解析后的JSON对象
// 通过检查是否包含后端返回的JSON字段来判断
const isParsedJson = productData &&
typeof productData === 'object' &&
!Array.isArray(productData) &&
productData !== null &&
('preferenceStyleAndColor' in productData ||
'materialPreference' in productData ||
'customerConcerns' in productData);
const getValue = (field) => {
if (isParsedJson) {
// 从解析后的JSON对象中获取字段
const value = productData[field];
// 如果值是字符串,去除首尾空格;如果是其他类型,转为字符串
if (value === null || value === undefined || value === '') {
return '暂无信息';
}
if (typeof value === 'string') {
const trimmed = value.trim();
return trimmed || '暂无信息';
}
return String(value) || '暂无信息';
} else {
// 向后兼容从data对象中获取或使用默认值
// 这里productData可能是旧的attributes对象或null
const attributes = productData || {};
switch (field) {
case 'preferenceStyleAndColor':
const parts = [];
if (attributes.style) parts.push(attributes.style);
if (attributes.color) parts.push(attributes.color);
if (parts.length === 0 && data.decorationStyle) parts.push(data.decorationStyle);
return parts.join(' / ') || '暂无信息';
case 'materialPreference':
return attributes.material || '暂无信息';
case 'customerFocusAreas':
return attributes.feature || attributes.remark || '暂无信息';
case 'priceSensitivity':
return data.priceSensitivity || '暂无信息';
case 'activityParticipation':
return data.campaignPreference || data.activityParticipation || '暂无信息';
case 'customerConcerns':
return attributes.description || '暂无信息';
case 'deliveryService':
return data.serviceExpectation || '暂无信息';
case 'competitorFeedback':
return data.competitorFeedback || '暂无信息';
case 'deliveryCycle':
return data.deliveryCycle || '暂无信息';
case 'productAppearance':
return data.appearancePreference || attributes.style || '暂无信息';
default:
return '暂无信息';
}
}
};
return dimensionLabels.map(item => ({
label: item.label,
content: getValue(item.field) || '暂无信息'
}));
},
viewSummaryDetail(item) {
// 查看总结详情
console.log('查看总结详情', item);
}
}
}
</script>
<style scoped>
/* 详情内容 */
.detail-content {
flex: 1;
padding: 24rpx 32rpx;
background-color: #FFFFFF;
overflow-y: auto;
min-height: 0;
}
.summary-item {
padding: 24rpx 0;
border-bottom: 1px solid #F0F0F0;
display: flex;
flex-direction: column;
position: relative;
padding-right: 40rpx;
}
.summary-item:last-child {
border-bottom: none;
}
.summary-label {
margin-bottom: 12rpx;
}
.summary-label text {
font-size: 28rpx;
color: #666;
font-weight: 500;
}
.summary-value {
flex: 1;
}
.summary-value text {
font-size: 28rpx;
color: #333;
line-height: 1.6;
}
.summary-item uni-icons {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
}
/* 产品类别标签页 */
.product-category-tabs {
background-color: #FFFFFF;
border-bottom: 1px solid #E0E0E0;
margin-bottom: 24rpx;
padding: 0;
}
.category-tabs-scroll {
white-space: nowrap;
width: 100%;
}
.category-tabs-wrapper {
display: inline-flex;
padding: 0 32rpx;
}
.category-tab-item {
padding: 24rpx 16rpx;
position: relative;
margin-right: 32rpx;
white-space: nowrap;
}
.category-tab-item text {
font-size: 28rpx;
color: #666;
}
.category-tab-item.active text {
color: #333;
font-weight: 500;
}
.category-tab-item.active::after {
content: '';
position: absolute;
bottom: 0;
left: 16rpx;
right: 16rpx;
height: 4rpx;
background-color: #007AFF;
border-radius: 2rpx;
}
/* 产品需求清单表格 */
.product-requirement-table {
background-color: #FFFFFF;
padding: 0;
}
.requirement-title {
padding: 24rpx 32rpx;
font-size: 32rpx;
font-weight: 600;
color: #333;
border-bottom: 1px solid #F0F0F0;
}
.requirement-table {
padding: 0 32rpx 24rpx;
}
.requirement-row {
display: flex;
padding: 24rpx 0;
border-bottom: 1px solid #F0F0F0;
}
.requirement-row:last-child {
border-bottom: none;
}
.requirement-label {
min-width: 200rpx;
width: 200rpx;
padding-right: 24rpx;
}
.requirement-label text {
font-size: 28rpx;
color: #666;
font-weight: 500;
}
.requirement-content {
flex: 1;
}
.requirement-content text {
font-size: 28rpx;
color: #333;
line-height: 1.6;
}
/* 加载和空状态 */
.loading-container,
.empty-container {
padding: 80rpx 0;
text-align: center;
}
.loading-text,
.empty-text {
font-size: 28rpx;
color: #999;
}
</style>