客户接待主包的一些代码移动到子包内
This commit is contained in:
@@ -0,0 +1,755 @@
|
||||
<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 '@/pages/furniture_customer/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>
|
||||
@@ -401,7 +401,7 @@
|
||||
import CustomerList from '@/pages/furniture_customer/components/CustomerList.vue';
|
||||
import ServiceList from '@/pages/furniture_customer/components/ServiceList.vue';
|
||||
import CustomerProfileModal from '@/pages/furniture_customer/components/CustomerProfileModal.vue';
|
||||
import ConversationSummaryFurniture from '@/pages/furniture_customer/components/ConversationSummaryFurniture.vue';
|
||||
import ConversationSummaryFurniture from './components/ConversationSummaryFurniture.vue';
|
||||
import { initCustomerProfile, initSummaryList, initPerformanceData } from '@/pages/furniture_customer/utils/dataInit.js';
|
||||
import { getApiUrl, API_BASE_URL } from '@/common/config.js';
|
||||
|
||||
@@ -2496,7 +2496,6 @@
|
||||
|
||||
// 路径映射
|
||||
this.urlPathMap = {
|
||||
'pages/furniture_customer/furniture_customer': '/customer',
|
||||
'pages-subpackage/furniture_customer/furniture_customer': '/customer',
|
||||
'pages/furniture_reception/furniture_reception': '/reception',
|
||||
'pages/ai_qa/ai_qa': '/ai-qa',
|
||||
@@ -2574,7 +2573,7 @@
|
||||
}
|
||||
|
||||
// 只为当前页面的路径执行URL隐藏
|
||||
if ((currentPath === 'pages/furniture_customer/furniture_customer' || currentPath === 'pages-subpackage/furniture_customer/furniture_customer') && this.urlPathMap[currentPath]) {
|
||||
if (currentPath === 'pages-subpackage/furniture_customer/furniture_customer' && this.urlPathMap[currentPath]) {
|
||||
const shortPath = this.urlPathMap[currentPath];
|
||||
const currentPathname = window.location.pathname;
|
||||
|
||||
|
||||
894
pages-subpackage/furniture_reception/common_begin_reception.vue
Normal file
894
pages-subpackage/furniture_reception/common_begin_reception.vue
Normal file
@@ -0,0 +1,894 @@
|
||||
<template>
|
||||
<view class="reception-form-wrapper">
|
||||
<scroll-view class="reception-form" scroll-y enable-back-to-top>
|
||||
<view class="form-card">
|
||||
<view class="form-item">
|
||||
<text class="form-item__label">服务次数</text>
|
||||
<view class="form-item__text">
|
||||
<text>{{ formData.recordingCount || 0 }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-item__label">客户姓名</text>
|
||||
<input
|
||||
class="form-item__input"
|
||||
v-model="formData.customerName"
|
||||
placeholder="请输入客户姓名"
|
||||
placeholder-style="color: #9ca3af"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-item__label">客户电话</text>
|
||||
<input
|
||||
class="form-item__input"
|
||||
v-model="formData.contact"
|
||||
@blur="onContactBlur"
|
||||
type="number"
|
||||
placeholder="请输入客户电话"
|
||||
placeholder-style="color: #9ca3af"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-item__label">客户来源</text>
|
||||
<view class="customer-source-select-wrapper">
|
||||
<view
|
||||
ref="customerSourceSelect"
|
||||
class="customer-source-select"
|
||||
@click.stop="toggleCustomerSourceDropdown"
|
||||
>
|
||||
<text
|
||||
:class="formData.customerSource ? 'form-item__picker-text' : 'form-item__picker-placeholder'"
|
||||
>
|
||||
{{ formData.customerSource || "请选择客户来源" }}
|
||||
</text>
|
||||
<uni-icons type="bottom" size="16" color="#9ca3af"></uni-icons>
|
||||
</view>
|
||||
<view
|
||||
ref="customerSourceDropdown"
|
||||
v-if="showCustomerSourceDropdown"
|
||||
class="customer-source-select-dropdown"
|
||||
@click.stop
|
||||
>
|
||||
<view
|
||||
class="customer-source-select-dropdown__item"
|
||||
v-for="option in customerSourceOptions"
|
||||
:key="option"
|
||||
@click="selectCustomerSource(option)"
|
||||
>
|
||||
<text :class="{'active': option === formData.customerSource}">
|
||||
{{ option }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-item__label">门店名称</text>
|
||||
<input
|
||||
class="form-item__input"
|
||||
v-model="formData.dealershipName"
|
||||
placeholder="请输入门店名称"
|
||||
placeholder-style="color: #9ca3af"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-item__label">性别</text>
|
||||
<radio-group
|
||||
class="gender-radio-group"
|
||||
@change="onGenderChange">
|
||||
<label
|
||||
class="gender-radio"
|
||||
v-for="(option, index) in genderOptions"
|
||||
:key="index">
|
||||
<radio
|
||||
:value="option"
|
||||
:checked="formData.gender === option"
|
||||
color="#2563eb" />
|
||||
<text class="gender-radio__text">{{ option }}</text>
|
||||
</label>
|
||||
</radio-group>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-item__label">年龄</text>
|
||||
<input
|
||||
class="form-item__input"
|
||||
v-model="formData.age"
|
||||
type="number"
|
||||
placeholder="请输入年龄"
|
||||
placeholder-style="color: #9ca3af"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-item__label">销售姓名</text>
|
||||
<input
|
||||
class="form-item__input"
|
||||
v-model="formData.salesName"
|
||||
placeholder="请输入销售姓名"
|
||||
placeholder-style="color: #9ca3af"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-item__label">销售电话</text>
|
||||
<input
|
||||
class="form-item__input"
|
||||
v-model="formData.salesPhone"
|
||||
type="number"
|
||||
placeholder="请输入销售电话"
|
||||
placeholder-style="color: #9ca3af"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="form-item__label">住址</text>
|
||||
<input
|
||||
class="form-item__input"
|
||||
v-model="formData.remarks"
|
||||
placeholder="请输入详细住址"
|
||||
placeholder-style="color: #9ca3af"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="form-actions form-actions--triple">
|
||||
<view class="form-btn form-btn--cancel" @click="handleCancel">
|
||||
<view class="form-btn__icon-circle">
|
||||
<uni-icons type="close" size="16" color="#007AFF"></uni-icons>
|
||||
</view>
|
||||
<text class="form-btn__text">取消</text>
|
||||
</view>
|
||||
<view class="form-btn form-btn--save" @click="handleSave('save')">
|
||||
<view class="form-btn__icon-circle">
|
||||
<uni-icons type="checkmarkempty" size="16" color="#007AFF"></uni-icons>
|
||||
</view>
|
||||
<text class="form-btn__text">保存</text>
|
||||
</view>
|
||||
<view class="form-btn form-btn--start" @click="handleSave('start')">
|
||||
<view class="form-btn__icon-circle">
|
||||
<uni-icons type="checkmarkempty" size="16" color="#007AFF"></uni-icons>
|
||||
</view>
|
||||
<text class="form-btn__text">开始接待</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 遮罩层,点击关闭下拉框 -->
|
||||
<view
|
||||
v-if="showCustomerSourceDropdown"
|
||||
class="customer-source-select-mask"
|
||||
@click="closeCustomerSourceDropdown"
|
||||
></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getApiUrl } from "@/common/config.js";
|
||||
|
||||
export default {
|
||||
name: 'ReceptionForm',
|
||||
props: {
|
||||
// 表单初始数据
|
||||
initialData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
genderOptions: ["男", "女"],
|
||||
customerSourceOptions: ["自然到店", "网络媒体", "其他类型"],
|
||||
showCustomerSourceDropdown: false,
|
||||
isFetchingContact: false,
|
||||
formData: {
|
||||
id: "",
|
||||
customerName: "",
|
||||
contact: "",
|
||||
customerSource: "",
|
||||
gender: "女",
|
||||
age: "",
|
||||
dealershipId: "",
|
||||
dealershipName: "",
|
||||
salesId: "",
|
||||
salesName: "",
|
||||
salesPhone: "",
|
||||
recordingCount: 0,
|
||||
intendedModel: "",
|
||||
infoCard: "",
|
||||
remark: "",
|
||||
detailedAddress: "",
|
||||
contactCount: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
initialData: {
|
||||
handler(newVal) {
|
||||
if (newVal && Object.keys(newVal).length > 0) {
|
||||
this.formData = { ...this.formData, ...newVal };
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 加载当前登录用户信息,填充销售姓名和电话
|
||||
this.loadCurrentUserInfo();
|
||||
// 监听窗口大小变化,重新定位下拉框
|
||||
uni.onWindowResize(() => {
|
||||
if (this.showCustomerSourceDropdown) {
|
||||
this.updateDropdownPosition();
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 加载当前登录用户信息,填充销售姓名和电话
|
||||
*/
|
||||
loadCurrentUserInfo() {
|
||||
try {
|
||||
// 从本地存储获取登录响应信息
|
||||
const loginResponse = uni.getStorageSync('backend-login-response') || {};
|
||||
|
||||
// 填充销售姓名
|
||||
if (loginResponse.userName) {
|
||||
this.formData.salesName = loginResponse.userName;
|
||||
}
|
||||
|
||||
// 填充销售电话:如果电话为空,则使用姓名作为电话
|
||||
if (loginResponse.phone) {
|
||||
this.formData.salesPhone = loginResponse.phone;
|
||||
} else if (loginResponse.userName) {
|
||||
// 如果电话为空,使用姓名作为电话
|
||||
this.formData.salesPhone = loginResponse.userName;
|
||||
}
|
||||
|
||||
// 如果有 userId,也可以填充到 salesId
|
||||
if (loginResponse.userId) {
|
||||
this.formData.salesId = String(loginResponse.userId);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载当前登录用户信息失败:', e);
|
||||
}
|
||||
},
|
||||
onGenderChange(e) {
|
||||
this.formData.gender = e.detail.value;
|
||||
},
|
||||
toggleCustomerSourceDropdown() {
|
||||
this.showCustomerSourceDropdown = !this.showCustomerSourceDropdown;
|
||||
if (this.showCustomerSourceDropdown) {
|
||||
this.$nextTick(() => {
|
||||
this.updateDropdownPosition();
|
||||
});
|
||||
}
|
||||
},
|
||||
updateDropdownPosition() {
|
||||
const selectEl = this.$refs.customerSourceSelect;
|
||||
if (selectEl) {
|
||||
const rect = selectEl.getBoundingClientRect();
|
||||
const dropdownEl = this.$refs.customerSourceDropdown;
|
||||
if (dropdownEl) {
|
||||
dropdownEl.style.top = (rect.bottom + 8) + 'px';
|
||||
dropdownEl.style.left = rect.left + 'px';
|
||||
dropdownEl.style.width = rect.width + 'px';
|
||||
}
|
||||
}
|
||||
},
|
||||
selectCustomerSource(option) {
|
||||
this.formData.customerSource = option;
|
||||
this.showCustomerSourceDropdown = false;
|
||||
},
|
||||
closeCustomerSourceDropdown() {
|
||||
this.showCustomerSourceDropdown = false;
|
||||
},
|
||||
onContactBlur() {
|
||||
const contact = this.formData.contact?.trim();
|
||||
if (!contact) {
|
||||
return;
|
||||
}
|
||||
if (!this.isValidPhoneNumber(contact)) {
|
||||
uni.showToast({
|
||||
title: "请输入有效的手机号",
|
||||
icon: "none"
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.fetchCustomerByContact();
|
||||
},
|
||||
isValidPhoneNumber(phone) {
|
||||
const normalizedPhone = phone?.trim();
|
||||
if (!normalizedPhone) {
|
||||
return false;
|
||||
}
|
||||
return /^1[3-9]\d{9}$/.test(normalizedPhone);
|
||||
},
|
||||
async fetchCustomerByContact() {
|
||||
const contact = this.formData.contact?.trim();
|
||||
if (!contact || this.isFetchingContact) {
|
||||
return;
|
||||
}
|
||||
if (!this.isValidPhoneNumber(contact)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.isFetchingContact = true;
|
||||
// 统一使用 getApiUrl,根据 env.js 配置自动切换环境
|
||||
const url = `${getApiUrl('/api/customerManagement/getByContact')}?contact=${encodeURIComponent(contact)}`;
|
||||
|
||||
// 获取认证信息
|
||||
let tenantId = '';
|
||||
let token = '';
|
||||
try {
|
||||
tenantId = uni.getStorageSync('backend-tenant-id') || '';
|
||||
token = uni.getStorageSync('backend-token') || '';
|
||||
} catch (e) {
|
||||
console.error('获取认证信息失败:', e);
|
||||
}
|
||||
|
||||
// 构建请求头
|
||||
const headers = {};
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
if (tenantId) {
|
||||
headers['X-Tenant-Id'] = tenantId;
|
||||
}
|
||||
|
||||
const res = await uni.request({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
header: headers,
|
||||
timeout: 30000 // 增加超时时间到30秒
|
||||
});
|
||||
const list = res.data?.data;
|
||||
if (res.statusCode === 200 && res.data?.success && Array.isArray(list) && list.length) {
|
||||
const customer = list[0];
|
||||
const retrievedId = customer.id || customer.customerId || "";
|
||||
const resolvedSalesPhone = customer.salesPhone
|
||||
|| customer.salesMobile
|
||||
|| customer.salesTel
|
||||
|| this.formData.salesPhone
|
||||
|| "";
|
||||
this.formData = {
|
||||
...this.formData,
|
||||
id: retrievedId ? String(retrievedId) : this.formData.id,
|
||||
customerName: customer.customerName || this.formData.customerName,
|
||||
customerSource: customer.customerSource || this.formData.customerSource,
|
||||
dealershipId: customer.dealershipId ? String(customer.dealershipId) : this.formData.dealershipId,
|
||||
dealershipName: customer.dealershipName || this.formData.dealershipName,
|
||||
salesId: customer.salesId ? String(customer.salesId) : this.formData.salesId,
|
||||
salesName: customer.salesName || this.formData.salesName,
|
||||
salesPhone: String(resolvedSalesPhone),
|
||||
recordingCount: Number(customer.recordingCount) || 0,
|
||||
intendedModel: customer.intendedModel || this.formData.intendedModel,
|
||||
infoCard: customer.infoCard || this.formData.infoCard,
|
||||
remark: customer.remark || this.formData.remark,
|
||||
detailedAddress: customer.detailedAddress || this.formData.detailedAddress,
|
||||
contactCount: customer.contactCount ?? this.formData.contactCount
|
||||
};
|
||||
uni.showToast({
|
||||
title: "已填充客户信息",
|
||||
icon: "none"
|
||||
});
|
||||
}
|
||||
// 未获取到数据时,不提示任何信息
|
||||
} catch (error) {
|
||||
// 查询失败时,也不提示任何信息
|
||||
console.error("查询客户信息失败:", error);
|
||||
} finally {
|
||||
this.isFetchingContact = false;
|
||||
}
|
||||
},
|
||||
handleCancel() {
|
||||
// 重置表单
|
||||
this.formData = {
|
||||
id: "",
|
||||
customerName: "",
|
||||
contact: "",
|
||||
customerSource: "",
|
||||
gender: "女",
|
||||
age: "",
|
||||
dealershipId: "",
|
||||
dealershipName: "",
|
||||
salesId: "",
|
||||
salesName: "",
|
||||
salesPhone: "",
|
||||
recordingCount: 0,
|
||||
intendedModel: "",
|
||||
infoCard: "",
|
||||
remark: "",
|
||||
detailedAddress: "",
|
||||
contactCount: 0
|
||||
};
|
||||
// 重置后重新填充当前登录用户信息
|
||||
this.loadCurrentUserInfo();
|
||||
// 触发取消事件
|
||||
this.$emit('cancel');
|
||||
uni.showToast({
|
||||
title: "已取消",
|
||||
icon: "none"
|
||||
});
|
||||
},
|
||||
async handleSave(action = 'save') {
|
||||
// 表单验证
|
||||
// 客户电话格式验证(如果填写了电话,则验证格式)
|
||||
const trimmedContact = this.formData.contact?.trim();
|
||||
if (trimmedContact && !this.isValidPhoneNumber(trimmedContact)) {
|
||||
uni.showToast({
|
||||
title: "请输入有效的客户电话",
|
||||
icon: "none"
|
||||
});
|
||||
return;
|
||||
}
|
||||
const trimmedSalesPhone = this.formData.salesPhone?.trim();
|
||||
if (trimmedSalesPhone && !this.isValidPhoneNumber(trimmedSalesPhone)) {
|
||||
uni.showToast({
|
||||
title: "请输入有效的销售电话",
|
||||
icon: "none"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
const normalizedCustomerId = this.formData.id?.toString().trim() || "";
|
||||
const params = {
|
||||
id: normalizedCustomerId,
|
||||
customerName: this.formData.customerName?.trim(),
|
||||
contact: trimmedContact,
|
||||
customerSource: this.formData.customerSource?.trim() || "",
|
||||
dealershipId: String(this.formData.dealershipId || '').trim() || "",
|
||||
dealershipName: this.formData.dealershipName?.trim() || "",
|
||||
salesId: String(this.formData.salesId || '').trim() || "",
|
||||
salesName: this.formData.salesName?.trim() || "",
|
||||
recordingCount: Number(this.formData.recordingCount) || 0,
|
||||
intendedModel: this.formData.intendedModel?.trim() || "",
|
||||
infoCard: this.formData.infoCard?.trim() || "",
|
||||
remark: this.formData.remark?.trim() || "",
|
||||
detailedAddress: this.formData.detailedAddress?.trim() || "",
|
||||
salesPhone: trimmedSalesPhone || "",
|
||||
contactCount: Number(this.formData.contactCount) || 0,
|
||||
operationType: action,
|
||||
scenario: "furniture"
|
||||
};
|
||||
|
||||
try {
|
||||
uni.showLoading({
|
||||
title: "保存中..."
|
||||
});
|
||||
|
||||
// 获取认证信息
|
||||
let tenantId = '';
|
||||
let token = '';
|
||||
try {
|
||||
tenantId = uni.getStorageSync('backend-tenant-id') || '';
|
||||
token = uni.getStorageSync('backend-token') || '';
|
||||
} catch (e) {
|
||||
console.error('获取认证信息失败:', e);
|
||||
}
|
||||
|
||||
// 构建请求头
|
||||
const headers = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
if (tenantId) {
|
||||
headers['X-Tenant-Id'] = tenantId;
|
||||
}
|
||||
|
||||
const res = await uni.request({
|
||||
url: getApiUrl('/api/customerManagement/add'),
|
||||
method: "POST",
|
||||
data: params,
|
||||
header: headers,
|
||||
timeout: 30000 // 增加超时时间到30秒
|
||||
});
|
||||
|
||||
uni.hideLoading();
|
||||
|
||||
if (res.statusCode === 200 && res.data && res.data.success) {
|
||||
uni.showToast({
|
||||
title: "保存成功",
|
||||
icon: "success"
|
||||
});
|
||||
// 保存成功后重置表单
|
||||
this.handleCancel();
|
||||
// 触发保存成功事件
|
||||
this.$emit('save-success', { action, data: params });
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.data?.message || "保存失败",
|
||||
icon: "none"
|
||||
});
|
||||
// 触发保存失败事件
|
||||
this.$emit('save-error', { action, error: res.data?.message || "保存失败" });
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
console.error("保存失败:", error);
|
||||
uni.showToast({
|
||||
title: "保存失败,请重试",
|
||||
icon: "none"
|
||||
});
|
||||
// 触发保存失败事件
|
||||
this.$emit('save-error', { action, error: error.message || "保存失败,请重试" });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.reception-form-wrapper {
|
||||
/* 使用绝对定位,从tab页底部开始,高度计算:100vh - 导航栏(88rpx) - tab页(72rpx) - 底部导航栏(96rpx) */
|
||||
position: absolute;
|
||||
top: 160rpx; /* 导航栏(88rpx) + tab页(72rpx) = 160rpx */
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 96rpx; /* 底部导航栏高度 */
|
||||
background-color: #F5F5F5;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
z-index: 0; /* 确保在标签页下方,但可以显示内容 */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden; /* 防止内容溢出 */
|
||||
}
|
||||
|
||||
.reception-form {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
padding: 24rpx 16rpx;
|
||||
box-sizing: border-box;
|
||||
background-color: #F5F5F5;
|
||||
overflow-y: auto;
|
||||
/* 确保可以滚动 */
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 40rpx 24rpx 32rpx 24rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||
margin-bottom: 24rpx;
|
||||
overflow: visible; /* 确保内容不被裁剪 */
|
||||
}
|
||||
|
||||
.form-card .form-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 第一个表单项特殊处理,确保顶部空间 */
|
||||
.form-card .form-item:first-child {
|
||||
padding-top: 24rpx;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex !important;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 32rpx;
|
||||
min-height: 88rpx;
|
||||
padding-top: 8rpx;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
flex-wrap: nowrap !important;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
justify-content: center;
|
||||
flex-direction: row !important;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
/* 对于包含 textarea 的表单项,label 顶部对齐 */
|
||||
.form-item--textarea {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.gender-radio-group {
|
||||
flex: 1;
|
||||
flex-shrink: 1;
|
||||
display: flex;
|
||||
gap: 32rpx;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
height: 88rpx;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.gender-radio {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
flex-shrink: 0;
|
||||
height: 88rpx;
|
||||
}
|
||||
|
||||
.gender-radio__text {
|
||||
color: #374151;
|
||||
font-size: 28rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
.form-item__label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
width: 140rpx;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
margin-right: 24rpx;
|
||||
margin-left: 0;
|
||||
padding: 12rpx 0 12rpx 16rpx;
|
||||
white-space: nowrap;
|
||||
box-sizing: border-box;
|
||||
min-height: 88rpx;
|
||||
line-height: 1.4;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
/* 确保标签可见且不被遮挡 */
|
||||
}
|
||||
|
||||
.form-item--half {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.form-item__input {
|
||||
flex: 1 !important;
|
||||
flex-shrink: 1 !important;
|
||||
flex-grow: 1 !important;
|
||||
min-width: 0 !important;
|
||||
max-width: none !important;
|
||||
width: auto !important;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
background-color: #f9fafb;
|
||||
border-radius: 12rpx;
|
||||
padding: 0 24rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
box-sizing: border-box;
|
||||
border: 2rpx solid #e5e7eb;
|
||||
transition: all 0.2s ease;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.form-item__input:focus {
|
||||
border-color: #007AFF;
|
||||
background-color: #FFFFFF;
|
||||
box-shadow: 0 0 0 4rpx rgba(0, 122, 255, 0.1);
|
||||
}
|
||||
|
||||
.form-item__text {
|
||||
flex: 1;
|
||||
flex-shrink: 1;
|
||||
min-width: 0;
|
||||
height: 88rpx;
|
||||
background-color: #f9fafb;
|
||||
border-radius: 12rpx;
|
||||
padding: 0 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.form-item__text text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
padding: 24rpx 16rpx;
|
||||
padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
|
||||
background-color: #F5F5F5;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
border-top: 1px solid #E5E7EB;
|
||||
}
|
||||
|
||||
.form-actions--triple {
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.form-btn {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
transition: all 0.2s ease;
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #e5e7eb;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-btn--cancel:active,
|
||||
.form-btn--save:active,
|
||||
.form-btn--start:active {
|
||||
background-color: #f0f0f0;
|
||||
border-color: #d1d5db;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.form-btn__icon-circle {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(0, 122, 255, 0.08);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.form-btn__text {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #007AFF;
|
||||
}
|
||||
|
||||
.form-btn--cancel .form-btn__text,
|
||||
.form-btn--save .form-btn__text,
|
||||
.form-btn--start .form-btn__text {
|
||||
color: #007AFF;
|
||||
}
|
||||
|
||||
/* 客户来源下拉框样式 */
|
||||
.customer-source-select-wrapper {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
flex-shrink: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
height: 88rpx;
|
||||
}
|
||||
|
||||
.customer-source-select {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 88rpx;
|
||||
background-color: #f9fafb;
|
||||
border-radius: 12rpx;
|
||||
padding: 0 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid transparent;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.customer-source-select uni-icons {
|
||||
flex-shrink: 0;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
.customer-source-select-dropdown {
|
||||
position: fixed;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 12rpx 30rpx rgba(15, 23, 42, 0.1);
|
||||
z-index: 9999;
|
||||
padding: 12rpx 0;
|
||||
box-sizing: border-box;
|
||||
max-height: 400rpx;
|
||||
overflow-y: auto;
|
||||
min-width: 200rpx;
|
||||
}
|
||||
|
||||
.customer-source-select-dropdown__item {
|
||||
padding: 20rpx 32rpx;
|
||||
}
|
||||
|
||||
.customer-source-select-dropdown__item text {
|
||||
font-size: 28rpx;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.customer-source-select-dropdown__item text.active {
|
||||
color: #2A68FF;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.customer-source-select-dropdown__item:active {
|
||||
background-color: #f5f7fb;
|
||||
}
|
||||
|
||||
.customer-source-select-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: transparent;
|
||||
z-index: 9998;
|
||||
}
|
||||
|
||||
.form-item__picker-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.form-item__picker-placeholder {
|
||||
font-size: 28rpx;
|
||||
color: #9ca3af;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 小屏幕适配 */
|
||||
@media (max-width: 750rpx) {
|
||||
.form-item__label {
|
||||
width: 120rpx;
|
||||
font-size: 26rpx;
|
||||
margin-right: 16rpx;
|
||||
margin-left: 0;
|
||||
padding: 10rpx 0 10rpx 12rpx;
|
||||
min-height: 80rpx;
|
||||
line-height: 1.4;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
min-height: 80rpx;
|
||||
padding-top: 6rpx;
|
||||
}
|
||||
|
||||
.form-item__input,
|
||||
.form-item__text,
|
||||
.customer-source-select-wrapper,
|
||||
.gender-radio-group,
|
||||
.gender-radio {
|
||||
height: 80rpx;
|
||||
}
|
||||
|
||||
.form-item__input,
|
||||
.form-item__text,
|
||||
.customer-source-select-wrapper,
|
||||
.gender-radio-group {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.form-item__input,
|
||||
.customer-source-select {
|
||||
font-size: 26rpx;
|
||||
padding: 0 16rpx;
|
||||
}
|
||||
|
||||
.form-item__text {
|
||||
padding: 0 16rpx;
|
||||
}
|
||||
|
||||
.form-item__text text {
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.gender-radio-group {
|
||||
gap: 24rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1785
pages-subpackage/furniture_reception/furniture_reception-impl.vue
Normal file
1785
pages-subpackage/furniture_reception/furniture_reception-impl.vue
Normal file
File diff suppressed because it is too large
Load Diff
1184
pages-subpackage/furniture_reception/serviceListFurniture.vue
Normal file
1184
pages-subpackage/furniture_reception/serviceListFurniture.vue
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user