diff --git a/pages/customer/components/ServiceList.vue b/pages/customer/components/ServiceList.vue
index 01df97d..2e31cf3 100644
--- a/pages/customer/components/ServiceList.vue
+++ b/pages/customer/components/ServiceList.vue
@@ -19,7 +19,7 @@
刷新
-
+
+ @filterClick="refreshServiceList"
+ @reachBottom="onServiceListReachBottom" />
= this.serviceListTotal) {
+ return;
+ }
+ // 下一页
+ this.serviceListPage.current += 1;
+ this.loadServiceList();
+ },
async viewDetail(item) {
// 查看详情
this.currentServiceDetail = { ...item };
@@ -1645,12 +1656,8 @@
});
if (res.statusCode === 200 && res.data && res.data.success) {
- uni.showToast({
- title: '获取服务记录 成功!',
- icon: 'none'
- });
// 转换后端数据为页面需要的格式
- this.serviceList = res.data.data.map(item => {
+ const mappedList = res.data.data.map(item => {
// 格式化时间:将 ISO 格式转换为 YYYY-MM-DD HH:mm:ss
let formattedTime = '';
if (item.createTime) {
@@ -1674,6 +1681,12 @@
rawData: item
};
});
+ // 分页:第一页或刷新时重置,其他情况追加
+ if (this.serviceListPage.current === 1) {
+ this.serviceList = mappedList;
+ } else {
+ this.serviceList = this.serviceList.concat(mappedList);
+ }
this.serviceListTotal = res.data.total || 0;
} else {
uni.showToast({
diff --git a/pages/insight/insight.vue b/pages/insight/insight.vue
deleted file mode 100644
index 175ab6d..0000000
--- a/pages/insight/insight.vue
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-
-
- 洞察
- 洞察页面内容
-
-
-
-
-
-
-
-
-
-
diff --git a/pages/reception/reception.vue b/pages/reception/reception.vue
index 7e308da..bf43474 100644
--- a/pages/reception/reception.vue
+++ b/pages/reception/reception.vue
@@ -29,22 +29,51 @@
@click="switchTab('status')">
服务状态
-
- 服务表现
-
开始接待
+
+ 标签管理
+
-
+
+
+ 共{{ serviceStatusTotal }}条
+
+
+
+
+
+ 刷新
+
+
+
+
+ 客户:{{ item.customerName || '未知客户' }}
+ 电话:{{ item.customerPhone }}
+
+
- 已服务 {{ item.duration }} 分钟
+ {{ item.durationText }}
+
+ 暂无服务中记录
+
+
+ 正在加载更多...
+
-
-
- 服务表现内容
-
-
@@ -139,6 +174,15 @@
placeholder="请输入客户来源"
placeholder-style="color: #9ca3af"
/>
+
+
+ 门店名称
+
性别
@@ -208,6 +252,134 @@
+
+
+
+
+
+
+
+
+
+
+ 详情:
+ {{ item.detail }}
+
+
+ 备注:
+ {{ item.remark }}
+
+
+
+
+ 暂无标签
+
+
+
+
+
+
+
+ 所属行业
+
+
+
+ 标签分类
+
+
+ {{ tagForm.tagType || "请选择标签分类" }}
+
+
+
+
+
+
+ {{ option }}
+
+
+
+
+
+ 标签名
+
+
+
+ 详情
+
+
+
+ 备注
+
+
+
+
+
+ 取消
+
+
+ 保存
+
+
+
+
+
@@ -229,7 +401,18 @@ import { getApiUrl } from "@/common/config.js";
activeTab: 'status',
genderOptions: ["男", "女"],
isFetchingContact: false,
+ serviceStatusLoading: false,
+ serviceStatusTotal: 0,
+ serviceStatusPage: {
+ current: 1,
+ size: 10
+ },
+ serviceStatusQuery: {
+ salesName: '',
+ customerName: ''
+ },
receptionForm: {
+ id: "",
customerName: "",
contact: "",
customerSource: "",
@@ -247,50 +430,148 @@ import { getApiUrl } from "@/common/config.js";
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小时,有关单意向',
- '但价格敏感,提起多次竞品品牌'
- ]
- }
- }
- ]
+ serviceStatusList: [],
+ // 标签管理
+ tagViewMode: 'list', // 'list' 或 'add'
+ tagList: [],
+ tagForm: {
+ industry: '家居行业',
+ tagType: '',
+ name: '',
+ detail: '',
+ remark: ''
+ },
+ tagTypeOptions: ["接待客户", "质检SOP", "客户画像"],
+ showTagTypeDropdown: false
}
},
+ onLoad() {
+ this.fetchServiceStatusList();
+ },
methods: {
goBack() {
uni.navigateBack();
},
switchTab(tab) {
this.activeTab = tab;
+ if (tab === 'status') {
+ this.fetchServiceStatusList();
+ } else if (tab === 'tag') {
+ this.tagViewMode = 'list';
+ this.fetchTagList();
+ }
+ },
+ onServiceStatusSearch() {
+ if (this.activeTab !== 'status') {
+ this.activeTab = 'status';
+ }
+ this.serviceStatusPage.current = 1;
+ this.fetchServiceStatusList({ force: true });
+ },
+ onServiceStatusRefresh() {
+ this.serviceStatusPage.current = 1;
+ this.fetchServiceStatusList({ force: true });
+ },
+ onServiceStatusReachBottom() {
+ // 已在加载中或全部加载完成则不再触发
+ if (this.serviceStatusLoading) return;
+ if (this.serviceStatusList.length >= this.serviceStatusTotal) return;
+ this.serviceStatusPage.current += 1;
+ this.fetchServiceStatusList();
+ },
+ async fetchServiceStatusList({ force = false } = {}) {
+ if (this.serviceStatusLoading && !force) {
+ return;
+ }
+ this.serviceStatusLoading = true;
+ try {
+ const queryParams = {
+ current: this.serviceStatusPage.current,
+ size: this.serviceStatusPage.size,
+ syncStatus: '服务中'
+ };
+ const trimmedSalesName = this.serviceStatusQuery?.salesName?.trim();
+ const trimmedCustomerName = this.serviceStatusQuery?.customerName?.trim();
+ if (trimmedSalesName) {
+ queryParams.salesName = trimmedSalesName;
+ }
+ if (trimmedCustomerName) {
+ queryParams.customerName = trimmedCustomerName;
+ }
+ const queryString = Object.keys(queryParams)
+ .map(key => `${key}=${encodeURIComponent(queryParams[key])}`)
+ .join('&');
+ const url = `${getApiUrl('/api/audioManagement/list')}?${queryString}`;
+ const res = await uni.request({
+ url,
+ method: 'GET',
+ timeout: 10000
+ });
+ if (res.statusCode === 200 && res.data && res.data.success) {
+ const records = Array.isArray(res.data.data) ? res.data.data : [];
+ const mapped = records.map(item => this.buildServiceStatusItem(item));
+ // 第一页或强制刷新时重置列表,否则追加
+ if (this.serviceStatusPage.current === 1 || force) {
+ this.serviceStatusList = mapped;
+ } else {
+ this.serviceStatusList = this.serviceStatusList.concat(mapped);
+ }
+ this.serviceStatusTotal = Number(res.data.total) || 0;
+ } else {
+ this.serviceStatusList = [];
+ this.serviceStatusTotal = 0;
+ uni.showToast({
+ title: res.data?.message || '获取服务中列表失败',
+ icon: 'none'
+ });
+ }
+ } catch (error) {
+ console.error('获取服务中列表失败:', error);
+ uni.showToast({
+ title: '获取服务状态失败,请稍后重试',
+ icon: 'none'
+ });
+ } finally {
+ this.serviceStatusLoading = false;
+ }
+ },
+ buildServiceStatusItem(record = {}) {
+ const formattedUpdateTime = this.formatDateTime(record.updateTime || record.createTime);
+ const tags = [];
+ if (record.recordingName) {
+ tags.push({ text: `录音:${record.recordingName}`, color: 'blue' });
+ }
+ if (record.intentionLevel) {
+ tags.push({ text: `意向:${record.intentionLevel}`, color: 'orange' });
+ }
+ if (record.projectName) {
+ tags.push({ text: `项目:${record.projectName}`, color: 'blue' });
+ }
+ return {
+ id: record.id,
+ staffName: record.salesName || '未分配销售',
+ status: record.syncStatus || '服务中',
+ customerName: record.customerName || '',
+ customerPhone: record.customerPhone || '',
+ tags,
+ durationText: formattedUpdateTime ? `最近更新时间:${formattedUpdateTime}` : '最近暂无更新时间'
+ };
+ },
+ formatDateTime(value) {
+ if (!value) {
+ return '';
+ }
+ const date = new Date(value);
+ if (Number.isNaN(date.getTime())) {
+ return '';
+ }
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, '0');
+ const day = String(date.getDate()).padStart(2, '0');
+ const hours = String(date.getHours()).padStart(2, '0');
+ const minutes = String(date.getMinutes()).padStart(2, '0');
+ const seconds = String(date.getSeconds()).padStart(2, '0');
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
},
viewServiceDetail(item) {
// 查看服务详情
@@ -300,13 +581,34 @@ import { getApiUrl } from "@/common/config.js";
this.receptionForm.gender = e.detail.value;
},
onContactBlur() {
+ const contact = this.receptionForm.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.receptionForm.contact?.trim();
if (!contact || this.isFetchingContact) {
return;
}
+ if (!this.isValidPhoneNumber(contact)) {
+ return;
+ }
try {
this.isFetchingContact = true;
// 统一使用 getApiUrl,根据 env.js 配置自动切换环境
@@ -319,21 +621,28 @@ import { getApiUrl } from "@/common/config.js";
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.receptionForm.salesPhone
+ || "";
this.receptionForm = {
...this.receptionForm,
+ id: retrievedId ? String(retrievedId) : this.receptionForm.id,
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
+ dealershipId: customer.dealershipId || this.receptionForm.dealershipId,
+ dealershipName: customer.dealershipName || this.receptionForm.dealershipName,
+ salesId: customer.salesId || this.receptionForm.salesId,
+ salesName: customer.salesName || this.receptionForm.salesName,
+ salesPhone: String(resolvedSalesPhone),
+ recordingCount: customer.recordingCount ?? this.receptionForm.recordingCount,
+ intendedModel: customer.intendedModel || this.receptionForm.intendedModel,
+ infoCard: customer.infoCard || this.receptionForm.infoCard,
+ remark: customer.remark || this.receptionForm.remark,
+ detailedAddress: customer.detailedAddress || this.receptionForm.detailedAddress,
+ contactCount: customer.contactCount ?? this.receptionForm.contactCount
};
uni.showToast({
title: "已填充客户信息",
@@ -351,6 +660,7 @@ import { getApiUrl } from "@/common/config.js";
onCancel() {
// 重置表单
this.receptionForm = {
+ id: "",
customerName: "",
contact: "",
customerSource: "",
@@ -389,11 +699,29 @@ import { getApiUrl } from "@/common/config.js";
});
return;
}
+ const trimmedContact = this.receptionForm.contact?.trim();
+ if (!this.isValidPhoneNumber(trimmedContact)) {
+ uni.showToast({
+ title: "请输入有效的客户电话",
+ icon: "none"
+ });
+ return;
+ }
+ const trimmedSalesPhone = this.receptionForm.salesPhone?.trim();
+ if (trimmedSalesPhone && !this.isValidPhoneNumber(trimmedSalesPhone)) {
+ uni.showToast({
+ title: "请输入有效的销售电话",
+ icon: "none"
+ });
+ return;
+ }
// 构建请求参数
+ const normalizedCustomerId = this.receptionForm.id?.toString().trim() || "";
const params = {
+ id: normalizedCustomerId,
customerName: this.receptionForm.customerName?.trim(),
- contact: this.receptionForm.contact?.trim(),
+ contact: trimmedContact,
customerSource: this.receptionForm.customerSource?.trim() || "",
dealershipId: this.receptionForm.dealershipId?.trim() || "",
dealershipName: this.receptionForm.dealershipName?.trim() || "",
@@ -404,7 +732,7 @@ import { getApiUrl } from "@/common/config.js";
infoCard: this.receptionForm.infoCard?.trim() || "",
remark: this.receptionForm.remark?.trim() || "",
detailedAddress: this.receptionForm.detailedAddress?.trim() || "",
- salesPhone: this.receptionForm.salesPhone?.trim() || "",
+ salesPhone: trimmedSalesPhone || "",
contactCount: Number(this.receptionForm.contactCount) || 0,
operationType: action
};
@@ -444,6 +772,99 @@ import { getApiUrl } from "@/common/config.js";
icon: "none"
});
}
+ },
+ // 标签管理相关方法
+ fetchTagList() {
+ // TODO: 从API获取标签列表
+ // 目前使用模拟数据
+ this.tagList = [];
+ },
+ showAddTag() {
+ this.tagViewMode = 'add';
+ this.showTagTypeDropdown = false;
+ this.tagForm = {
+ industry: '家居行业',
+ tagType: '',
+ name: '',
+ detail: '',
+ remark: ''
+ };
+ },
+ cancelAddTag() {
+ this.tagViewMode = 'list';
+ this.showTagTypeDropdown = false;
+ this.tagForm = {
+ industry: '家居行业',
+ tagType: '',
+ name: '',
+ detail: '',
+ remark: ''
+ };
+ },
+ toggleTagTypeDropdown() {
+ this.showTagTypeDropdown = !this.showTagTypeDropdown;
+ },
+ selectTagType(option) {
+ this.tagForm.tagType = option;
+ this.showTagTypeDropdown = false;
+ },
+ closeTagTypeDropdown() {
+ this.showTagTypeDropdown = false;
+ },
+ async saveTag() {
+ // 表单验证
+ if (!this.tagForm.name || !this.tagForm.name.trim()) {
+ uni.showToast({
+ title: "请输入标签名",
+ icon: "none"
+ });
+ return;
+ }
+
+ try {
+ uni.showLoading({
+ title: "保存中..."
+ });
+
+ const payload = {
+ industry: this.tagForm.industry?.trim() || '',
+ tagType: this.tagForm.tagType?.trim() || '',
+ tagName: this.tagForm.name.trim(),
+ tagDetail: this.tagForm.detail?.trim() || '',
+ remark: this.tagForm.remark?.trim() || ''
+ };
+
+ const res = await uni.request({
+ url: getApiUrl('/api/industryTags/add'),
+ method: "POST",
+ data: payload,
+ header: {
+ "Content-Type": "application/json"
+ },
+ timeout: 10000
+ });
+
+ const { statusCode, data } = res;
+ if (statusCode === 200 && data && (data.success || data.code === 200)) {
+ uni.showToast({
+ title: data?.message || "保存成功",
+ icon: "success"
+ });
+ // 保存成功后返回列表并刷新
+ this.tagViewMode = 'list';
+ this.fetchTagList();
+ } else {
+ throw new Error(data?.message || "保存失败");
+ }
+ } catch (error) {
+ console.error("保存标签失败:", error);
+ uni.showToast({
+ title: error?.message || "保存失败,请重试",
+ icon: "none"
+ });
+ } finally {
+ uni.hideLoading();
+ }
}
}
}
@@ -525,6 +946,52 @@ import { getApiUrl } from "@/common/config.js";
background-color: #F5F5F5;
padding: 24rpx 32rpx;
}
+
+ .service-status-toolbar {
+ display: flex;
+ flex-wrap: nowrap;
+ align-items: center;
+ gap: 16rpx;
+ padding: 16rpx 24rpx;
+ margin-bottom: 24rpx;
+ background-color: #F8F8FA;
+ border-radius: 16rpx;
+ border: 1px solid #EFEFF2;
+ }
+
+ .toolbar-total {
+ font-size: 26rpx;
+ color: #666;
+ margin-right: 16rpx;
+ }
+
+ .toolbar-input {
+ flex: 1;
+ min-width: 150rpx;
+ background-color: #F5F6FA;
+ border-radius: 12rpx;
+ padding: 16rpx 24rpx;
+ font-size: 28rpx;
+ border: 1px solid transparent;
+ }
+
+ .toolbar-actions {
+ display: flex;
+ align-items: center;
+ margin-left: auto;
+ }
+
+ .toolbar-btn {
+ padding: 0 16rpx;
+ height: 72rpx;
+ color: #2A68FF;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8rpx;
+ font-size: 28rpx;
+ font-weight: 400;
+ }
.service-card {
background-color: #FFFFFF;
@@ -586,6 +1053,19 @@ import { getApiUrl } from "@/common/config.js";
margin-bottom: 20rpx;
gap: 12rpx;
}
+
+ .customer-info {
+ margin-bottom: 16rpx;
+ display: flex;
+ flex-direction: column;
+ gap: 8rpx;
+ }
+
+ .customer-name,
+ .customer-phone {
+ font-size: 28rpx;
+ color: #555;
+ }
.tag-item {
padding: 8rpx 16rpx;
@@ -706,16 +1186,12 @@ import { getApiUrl } from "@/common/config.js";
font-size: 26rpx;
color: #999;
}
-
- /* 服务表现内容 */
- .performance-content {
- padding: 40rpx;
+
+ .service-status-empty {
+ padding: 48rpx 0;
text-align: center;
- }
-
- .empty-text {
- font-size: 28rpx;
color: #999;
+ font-size: 28rpx;
}
/* 开始接待表单样式 */
@@ -769,6 +1245,10 @@ import { getApiUrl } from "@/common/config.js";
flex-shrink: 0;
}
+ .form-item--half {
+ width: 50%;
+ }
+
.form-item__input {
flex: 1;
height: 88rpx;
@@ -823,6 +1303,67 @@ import { getApiUrl } from "@/common/config.js";
color: #9ca3af;
}
+ .tag-select-wrapper {
+ position: relative;
+ width: 50%;
+ }
+
+ .tag-select {
+ 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;
+ border: 1px solid transparent;
+ }
+
+ .tag-select-dropdown {
+ position: absolute;
+ top: 100%;
+ left: 160rpx;
+ width: calc(100% - 160rpx);
+ min-width: 260rpx;
+ max-width: none;
+ background-color: #fff;
+ border-radius: 16rpx;
+ box-shadow: 0 12rpx 30rpx rgba(15, 23, 42, 0.1);
+ margin-top: 8rpx;
+ z-index: 11;
+ padding: 12rpx 0;
+ }
+
+ .tag-select-dropdown__item {
+ padding: 20rpx 32rpx;
+ }
+
+ .tag-select-dropdown__item text {
+ font-size: 28rpx;
+ color: #374151;
+ }
+
+ .tag-select-dropdown__item text.active {
+ color: #2A68FF;
+ font-weight: 500;
+ }
+
+ .tag-select-dropdown__item:active {
+ background-color: #f5f7fb;
+ }
+
+ .tag-select-mask {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: transparent;
+ z-index: 10;
+ }
+
.form-actions {
display: flex;
gap: 24rpx;
@@ -866,4 +1407,113 @@ import { getApiUrl } from "@/common/config.js";
.form-btn--save .form-btn__text {
color: #ffffff;
}
+
+ /* 标签管理样式 */
+ .tag-management {
+ height: calc(100vh - 300rpx);
+ background-color: #F5F5F5;
+ }
+
+ .tag-list {
+ height: 100%;
+ padding: 24rpx 32rpx;
+ box-sizing: border-box;
+ }
+
+ .tag-list-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 24rpx;
+ padding: 0;
+ }
+
+ .tag-list-total {
+ font-size: 26rpx;
+ color: #666;
+ }
+
+ .tag-list-btn {
+ display: flex;
+ align-items: center;
+ gap: 8rpx;
+ padding: 12rpx 24rpx;
+ background-color: #E3F2FD;
+ border-radius: 12rpx;
+ color: #2A68FF;
+ font-size: 28rpx;
+ }
+
+ .tag-card {
+ background-color: #FFFFFF;
+ border-radius: 16rpx;
+ padding: 32rpx;
+ margin-bottom: 24rpx;
+ margin-left: 0;
+ margin-right: 0;
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
+ box-sizing: border-box;
+ }
+
+ .tag-card-header {
+ margin-bottom: 20rpx;
+ }
+
+ .tag-card-name {
+ font-size: 32rpx;
+ font-weight: 500;
+ color: #333;
+ }
+
+ .tag-card-content {
+ display: flex;
+ flex-direction: column;
+ gap: 12rpx;
+ }
+
+ .tag-card-item {
+ display: flex;
+ flex-direction: column;
+ gap: 8rpx;
+ }
+
+ .tag-card-label {
+ font-size: 26rpx;
+ color: #666;
+ }
+
+ .tag-card-value {
+ font-size: 28rpx;
+ color: #333;
+ line-height: 1.6;
+ width: 40%;
+ max-width: 40%;
+ word-wrap: break-word;
+ word-break: break-all;
+ }
+
+ .tag-list-empty {
+ padding: 48rpx 0;
+ text-align: center;
+ color: #999;
+ font-size: 28rpx;
+ }
+
+ .tag-form {
+ height: 100%;
+ padding: 24rpx 32rpx;
+ box-sizing: border-box;
+ }
+
+ .form-item__textarea {
+ flex: 1;
+ min-height: 200rpx;
+ background-color: #f9fafb;
+ border-radius: 12rpx;
+ padding: 24rpx;
+ font-size: 28rpx;
+ color: #333;
+ box-sizing: border-box;
+ line-height: 1.6;
+ }