Files
smartDriveEEUniApp/pages/reception/reception.vue
2025-11-28 10:11:10 +08:00

870 lines
19 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>
<view class="page">
<!-- #ifdef APP -->
<statusBar></statusBar>
<!-- #endif -->
<!-- 导航栏 -->
<uni-nav-bar
:fixed="true"
:statusBar="true"
title="AI销售管理"
leftIcon="left"
@clickLeft="goBack"
color="#333"
backgroundColor="#FFFFFF">
</uni-nav-bar>
<view class="content">
<!-- AI功能横幅 -->
<view class="ai-banner">
<text class="banner-text">AI让销售过程和团队管理更透明</text>
</view>
<!-- 标签页 -->
<view class="tabs">
<view
class="tab-item"
:class="{ active: activeTab === 'status' }"
@click="switchTab('status')">
<text>服务状态</text>
</view>
<view
class="tab-item"
:class="{ active: activeTab === 'performance' }"
@click="switchTab('performance')">
<text>服务表现</text>
</view>
<view
class="tab-item"
:class="{ active: activeTab === 'reception' }"
@click="switchTab('reception')">
<text>开始接待</text>
</view>
</view>
<!-- 服务状态列表 -->
<scroll-view class="service-status-list" scroll-y v-if="activeTab === 'status'">
<view
class="service-card"
v-for="(item, index) in serviceStatusList"
:key="index"
@click="viewServiceDetail(item)">
<view class="card-header">
<view class="staff-info">
<view class="staff-avatar">
<text class="avatar-text">{{ item.staffName.charAt(0) }}</text>
</view>
<text class="staff-name">{{ item.staffName }}</text>
</view>
<view class="service-status">
<uni-icons type="bars" size="16" color="#007AFF"></uni-icons>
<text>服务中</text>
</view>
</view>
<view class="customer-tags">
<view
class="tag-item"
:class="'tag-' + tag.color"
v-for="(tag, tagIndex) in item.tags"
:key="tagIndex">
<text>{{ tag.text }}</text>
</view>
</view>
<!-- AI提醒 -->
<view class="ai-alert" v-if="item.alert" :class="item.alert.type === 'risk' ? 'alert-risk-box' : 'alert-reminder-box'">
<view class="alert-header">
<view class="alert-icon">
<view class="icon-circle"></view>
</view>
<text class="alert-title">{{ item.alert.title }}</text>
</view>
<view class="alert-content" :class="item.alert.type === 'risk' ? 'alert-risk-text' : 'alert-reminder-text'">
<text v-if="item.alert.type === 'risk'">{{ item.alert.message }}</text>
<view v-else class="alert-list">
<view class="alert-item" v-for="(msg, msgIndex) in item.alert.messages" :key="msgIndex">
<text>{{ msg }}</text>
</view>
</view>
</view>
</view>
<view class="service-duration">
<text>已服务 {{ item.duration }} 分钟</text>
</view>
</view>
</scroll-view>
<!-- 服务表现内容 -->
<view class="performance-content" v-if="activeTab === 'performance'">
<text class="empty-text">服务表现内容</text>
</view>
<!-- 开始接待表单 -->
<scroll-view class="reception-form" scroll-y v-if="activeTab === 'reception'">
<view class="form-card">
<view class="form-item">
<text class="form-item__label">服务次数</text>
<view class="form-item__text">
<text>{{ receptionForm.contactCount || 0 }}</text>
</view>
</view>
<view class="form-item">
<text class="form-item__label">客户姓名</text>
<input
class="form-item__input"
v-model="receptionForm.customerName"
placeholder="请输入客户姓名"
placeholder-style="color: #9ca3af"
/>
</view>
<view class="form-item">
<text class="form-item__label">客户电话</text>
<input
class="form-item__input"
v-model="receptionForm.contact"
@blur="onContactBlur"
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="receptionForm.customerSource"
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="receptionForm.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="receptionForm.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="receptionForm.salesName"
placeholder="请输入销售姓名"
placeholder-style="color: #9ca3af"
/>
</view>
<view class="form-item">
<text class="form-item__label">销售电话</text>
<input
class="form-item__input"
v-model="receptionForm.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="receptionForm.detailedAddress"
placeholder="请输入详细住址"
placeholder-style="color: #9ca3af"
/>
</view>
</view>
<view class="form-actions form-actions--triple">
<view class="form-btn form-btn--cancel" @click="onCancel">
<text class="form-btn__text">取消</text>
</view>
<view class="form-btn form-btn--save" @click="onSave('save')">
<text class="form-btn__text">保存</text>
</view>
<view class="form-btn form-btn--start" @click="onSave('start')">
<text class="form-btn__text">开始接待</text>
</view>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
// #ifdef APP
import statusBar from "@/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-status-bar";
// #endif
import { getApiUrl } from "@/common/config.js";
export default {
// #ifdef APP
components: {
statusBar
},
// #endif
data() {
return {
activeTab: 'status',
genderOptions: ["男", "女"],
isFetchingContact: false,
receptionForm: {
customerName: "",
contact: "",
customerSource: "",
gender: "女",
age: "",
dealershipId: "",
dealershipName: "",
salesId: "",
salesName: "",
salesPhone: "",
recordingCount: 0,
intendedModel: "",
infoCard: "",
remark: "",
detailedAddress: "",
contactCount: 0
},
serviceStatusList: [
{
staffName: '三多',
status: '服务中',
duration: 60,
tags: [
{ text: '首次到访', color: 'blue' },
{ text: '新房交付', color: 'blue' },
{ text: '想买床', color: 'orange' }
],
alert: {
type: 'risk',
title: '投诉风险',
message: '客户情绪激动,有投诉风险,请及时介入'
}
},
{
staffName: '雅秋',
status: '服务中',
duration: 55,
tags: [
{ text: '新婚夫妇', color: 'blue' },
{ text: '收纳需求', color: 'blue' },
{ text: '毛坯房', color: 'blue' },
{ text: '关注环保', color: 'orange' }
],
alert: {
type: 'reminder',
title: '关单提醒',
messages: [
'服务已超过2小时,有关单意向',
'但价格敏感,提起多次竞品品牌'
]
}
}
]
}
},
methods: {
goBack() {
uni.navigateBack();
},
switchTab(tab) {
this.activeTab = tab;
},
viewServiceDetail(item) {
// 查看服务详情
console.log('查看服务详情', item);
},
onGenderChange(e) {
this.receptionForm.gender = e.detail.value;
},
onContactBlur() {
this.fetchCustomerByContact();
},
async fetchCustomerByContact() {
const contact = this.receptionForm.contact?.trim();
if (!contact || this.isFetchingContact) {
return;
}
try {
this.isFetchingContact = true;
// 统一使用 getApiUrl根据 env.js 配置自动切换环境
const url = `${getApiUrl('/api/customerManagement/getByContact')}?contact=${encodeURIComponent(contact)}`;
const res = await uni.request({
url: url,
method: 'GET',
timeout: 8000
});
const list = res.data?.data;
if (res.statusCode === 200 && res.data?.success && Array.isArray(list) && list.length) {
const customer = list[0];
this.receptionForm = {
...this.receptionForm,
customerName: customer.customerName || this.receptionForm.customerName,
customerSource: customer.customerSource || this.receptionForm.customerSource,
dealershipId: customer.dealershipId || "",
dealershipName: customer.dealershipName || "",
salesId: customer.salesId || "",
salesName: customer.salesName || "",
salesPhone: customer.salesPhone || "",
recordingCount: customer.recordingCount ?? 0,
intendedModel: customer.intendedModel || "",
infoCard: customer.infoCard || "",
remark: customer.remark || "",
detailedAddress: customer.detailedAddress || "",
contactCount: customer.contactCount ?? 0
};
uni.showToast({
title: "已填充客户信息",
icon: "none"
});
}
// 未获取到数据时,不提示任何信息
} catch (error) {
// 查询失败时,也不提示任何信息
console.error("查询客户信息失败:", error);
} finally {
this.isFetchingContact = false;
}
},
onCancel() {
// 重置表单
this.receptionForm = {
customerName: "",
contact: "",
customerSource: "",
gender: "女",
age: "",
dealershipId: "",
dealershipName: "",
salesId: "",
salesName: "",
salesPhone: "",
recordingCount: 0,
intendedModel: "",
infoCard: "",
remark: "",
detailedAddress: "",
contactCount: 0
};
uni.showToast({
title: "已取消",
icon: "none"
});
},
async onSave(action = 'save') {
// 表单验证
if (!this.receptionForm.customerName) {
uni.showToast({
title: "请输入客户姓名",
icon: "none"
});
return;
}
if (!this.receptionForm.contact) {
uni.showToast({
title: "请输入客户电话",
icon: "none"
});
return;
}
// 构建请求参数
const params = {
customerName: this.receptionForm.customerName?.trim(),
contact: this.receptionForm.contact?.trim(),
customerSource: this.receptionForm.customerSource?.trim() || "",
dealershipId: this.receptionForm.dealershipId?.trim() || "",
dealershipName: this.receptionForm.dealershipName?.trim() || "",
salesId: this.receptionForm.salesId?.trim() || "",
salesName: this.receptionForm.salesName?.trim() || "",
recordingCount: Number(this.receptionForm.recordingCount) || 0,
intendedModel: this.receptionForm.intendedModel?.trim() || "",
infoCard: this.receptionForm.infoCard?.trim() || "",
remark: this.receptionForm.remark?.trim() || "",
detailedAddress: this.receptionForm.detailedAddress?.trim() || "",
salesPhone: this.receptionForm.salesPhone?.trim() || "",
contactCount: Number(this.receptionForm.contactCount) || 0,
operationType: action
};
try {
uni.showLoading({
title: "保存中..."
});
const res = await uni.request({
url: getApiUrl('/api/customerManagement/add'),
method: "POST",
data: params,
timeout: 10000
});
uni.hideLoading();
if (res.statusCode === 200 && res.data && res.data.success) {
uni.showToast({
title: "保存成功",
icon: "success"
});
// 保存成功后重置表单
this.onCancel();
} else {
uni.showToast({
title: res.data?.message || "保存失败",
icon: "none"
});
}
} catch (error) {
uni.hideLoading();
console.error("保存失败:", error);
uni.showToast({
title: "保存失败,请重试",
icon: "none"
});
}
}
}
}
</script>
<style>
page {
background-color: #F5F5F5;
}
.page {
min-height: 100vh;
background-color: #F5F5F5;
}
.content {
padding-top: 88rpx;
}
/* AI功能横幅 */
.ai-banner {
background-color: #E3F2FD;
padding: 24rpx 32rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
.banner-text {
font-size: 28rpx;
color: #1976D2;
flex: 1;
}
.banner-btn {
font-size: 28rpx;
color: #1976D2;
font-weight: 500;
margin-left: 16rpx;
}
/* 标签页 */
.tabs {
display: flex;
background-color: #FFFFFF;
border-bottom: 1px solid #E0E0E0;
padding: 0 32rpx;
}
.tab-item {
padding: 24rpx 32rpx;
position: relative;
}
.tab-item text {
font-size: 30rpx;
color: #666;
}
.tab-item.active text {
color: #333;
font-weight: 500;
}
.tab-item.active::after {
content: '';
position: absolute;
bottom: 0;
left: 32rpx;
right: 32rpx;
height: 4rpx;
background-color: #007AFF;
border-radius: 2rpx;
}
/* 服务状态列表 */
.service-status-list {
height: calc(100vh - 300rpx);
background-color: #F5F5F5;
padding: 24rpx 32rpx;
}
.service-card {
background-color: #FFFFFF;
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.staff-info {
display: flex;
align-items: center;
}
.staff-avatar {
width: 64rpx;
height: 64rpx;
background-color: #2196F3;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16rpx;
}
.avatar-text {
font-size: 32rpx;
color: #FFFFFF;
font-weight: 500;
}
.staff-name {
font-size: 32rpx;
font-weight: 500;
color: #333;
}
.service-status {
display: flex;
align-items: center;
}
.service-status text {
font-size: 26rpx;
color: #007AFF;
margin-left: 8rpx;
}
.customer-tags {
display: flex;
flex-wrap: wrap;
margin-bottom: 20rpx;
gap: 12rpx;
}
.tag-item {
padding: 8rpx 16rpx;
border-radius: 8rpx;
}
.tag-blue {
background-color: #E3F2FD;
}
.tag-blue text {
font-size: 24rpx;
color: #1976D2;
}
.tag-orange {
background-color: #FFF3E0;
}
.tag-orange text {
font-size: 24rpx;
color: #F57C00;
}
/* AI提醒 */
.ai-alert {
border-radius: 8rpx;
padding: 20rpx;
margin-bottom: 20rpx;
}
.alert-risk-box {
background-color: #FFF5F5;
}
.alert-reminder-box {
background-color: #F5F5F5;
}
.alert-header {
display: flex;
align-items: center;
margin-bottom: 12rpx;
}
.alert-icon {
margin-right: 8rpx;
}
.icon-circle {
width: 24rpx;
height: 24rpx;
border: 2rpx solid #333;
border-radius: 50%;
position: relative;
}
.icon-circle::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 8rpx;
height: 8rpx;
background-color: #333;
border-radius: 50%;
}
.alert-title {
font-size: 28rpx;
font-weight: 500;
color: #333;
}
.alert-content {
font-size: 26rpx;
line-height: 1.6;
}
.alert-risk-text {
color: #FF5722;
}
.alert-reminder-text {
color: #666;
}
.alert-list {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.alert-item {
display: flex;
align-items: flex-start;
}
.alert-item::before {
content: '•';
margin-right: 8rpx;
color: #666;
}
.alert-item text {
font-size: 26rpx;
color: #666;
line-height: 1.6;
}
.service-duration {
padding-top: 16rpx;
border-top: 1px solid #F0F0F0;
}
.service-duration text {
font-size: 26rpx;
color: #999;
}
/* 服务表现内容 */
.performance-content {
padding: 40rpx;
text-align: center;
}
.empty-text {
font-size: 28rpx;
color: #999;
}
/* 开始接待表单样式 */
.reception-form {
height: calc(100vh - 300rpx);
background-color: #F5F5F5;
padding: 24rpx 32rpx;
}
.form-card {
background-color: #ffffff;
border-radius: 16rpx;
padding: 32rpx 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
margin-bottom: 32rpx;
}
.form-item {
display: flex;
align-items: center;
margin-bottom: 32rpx;
}
.gender-radio-group {
flex: 1;
display: flex;
gap: 32rpx;
flex-wrap: wrap;
}
.gender-radio {
display: flex;
align-items: center;
gap: 12rpx;
}
.gender-radio__text {
color: #374151;
font-size: 28rpx;
}
.form-item:last-child {
margin-bottom: 0;
}
.form-item__label {
font-size: 28rpx;
color: #333;
font-weight: 500;
width: 160rpx;
flex-shrink: 0;
}
.form-item__input {
flex: 1;
height: 88rpx;
background-color: #f9fafb;
border-radius: 12rpx;
padding: 0 24rpx;
font-size: 28rpx;
color: #333;
box-sizing: border-box;
}
.form-item__text {
flex: 1;
height: 88rpx;
background-color: #f9fafb;
border-radius: 12rpx;
padding: 0 24rpx;
display: flex;
align-items: center;
box-sizing: border-box;
}
.form-item__text text {
font-size: 28rpx;
color: #333;
}
.form-item__picker {
flex: 1;
height: 88rpx;
background-color: #f9fafb;
border-radius: 12rpx;
padding: 0 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
}
.form-item__picker-text {
font-size: 28rpx;
color: #333;
}
.form-item__picker-placeholder {
font-size: 28rpx;
color: #9ca3af;
}
.form-item__picker-icon {
font-size: 24rpx;
color: #9ca3af;
}
.form-actions {
display: flex;
gap: 24rpx;
padding-bottom: 40rpx;
}
.form-actions--triple {
gap: 16rpx;
}
.form-btn {
flex: 1;
height: 88rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
}
.form-btn--cancel {
background-color: #f3f4f6;
}
.form-btn--save {
background-color: #007AFF;
}
.form-btn--start {
background-color: #10B981;
}
.form-btn__text {
font-size: 32rpx;
font-weight: 500;
}
.form-btn--cancel .form-btn__text {
color: #6b7280;
}
.form-btn--save .form-btn__text {
color: #ffffff;
}
</style>