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

2491 lines
66 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"
:border="false"
title="AI销冠系统"
leftIcon="left"
@clickLeft="goBack"
color="#333"
backgroundColor="#FFFFFF">
</uni-nav-bar>
<view class="content">
<!-- 标签页 -->
<view class="tabs" :style="{ top: computedNavbarTop || navbarTop }">
<view
class="tab-item"
:class="{ active: activeTab === 'status' }"
@click="switchTab('status')">
<text>服务状态</text>
</view>
<view
class="tab-item"
:class="{ active: activeTab === 'reception' }"
@click="switchTab('reception')">
<text>开始接待</text>
</view>
<view
class="tab-item"
:class="{ active: activeTab === 'tag' }"
@click="switchTab('tag')">
<text>标签管理</text>
</view>
</view>
<!-- 服务状态列表 -->
<scroll-view
class="service-status-list"
:style="{ top: computedContentTop || contentTop }"
scroll-y
v-if="activeTab === 'status'"
@scrolltolower="onServiceStatusReachBottom">
<view class="service-status-toolbar">
<text class="toolbar-total">{{ serviceStatusTotal }}</text>
<input
class="toolbar-input"
v-model="serviceStatusQuery.salesName"
placeholder="所属销售姓名"
placeholder-style="color: #b0b0b0"
confirm-type="search"
@confirm="onServiceStatusSearch"
/>
<input
class="toolbar-input"
v-model="serviceStatusQuery.customerName"
placeholder="客户姓名"
placeholder-style="color: #b0b0b0"
confirm-type="search"
@confirm="onServiceStatusSearch"
/>
<view class="toolbar-actions">
<view class="toolbar-btn toolbar-btn--refresh" @click="onServiceStatusRefresh">
<uni-icons type="refresh" size="18" color="#2A68FF"></uni-icons>
<text>刷新</text>
</view>
</view>
</view>
<view
class="service-card"
v-for="(item, index) in serviceStatusList"
:key="item.id || 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">
<view class="service-action-btn service-action-btn--finish" @click.stop="finishService(item)">
<text>结束</text>
</view>
<uni-icons type="bars" size="16" color="#007AFF"></uni-icons>
<text>服务中</text>
</view>
</view>
<view class="customer-info">
<text class="customer-name">客户{{ item.customerName || '未知客户' }}</text>
<text class="customer-phone" v-if="item.customerPhone">电话{{ item.customerPhone }}</text>
</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.durationText }}</text>
</view>
</view>
<view class="service-status-empty" v-if="!serviceStatusLoading && !serviceStatusList.length">
<text>暂无服务中记录</text>
</view>
<view class="service-status-loading-more" v-if="serviceStatusLoading && serviceStatusList.length">
<text>正在加载更多...</text>
</view>
</scroll-view>
<!-- 开始接待表单 -->
<scroll-view class="reception-form" :style="{ top: computedContentTop || contentTop }" 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>
<input
class="form-item__input"
v-model="receptionForm.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="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>
<!-- 标签管理 -->
<scroll-view
class="service-status-list tag-list"
:style="{ top: computedContentTop || contentTop }"
scroll-y
v-if="activeTab === 'tag' && tagViewMode === 'list'"
@scrolltolower="onTagListReachBottom">
<view class="service-status-toolbar">
<text class="toolbar-total">{{ tagTotal }}</text>
<input
class="toolbar-input"
v-model="tagQuery.tagType"
placeholder="标签分类"
placeholder-style="color: #b0b0b0"
confirm-type="search"
@confirm="onTagListSearch"
/>
<input
class="toolbar-input"
v-model="tagQuery.tagName"
placeholder="标签名称"
placeholder-style="color: #b0b0b0"
confirm-type="search"
@confirm="onTagListSearch"
/>
<view class="toolbar-actions">
<view class="toolbar-btn toolbar-btn--refresh" @click="onTagListRefresh">
<uni-icons type="refresh" size="18" color="#2A68FF"></uni-icons>
</view>
<view class="toolbar-btn toolbar-btn--add" @click="showAddTag">
<uni-icons type="plus" size="18" color="#2A68FF"></uni-icons>
</view>
</view>
</view>
<view
class="service-card tag-card-item"
v-for="(item, index) in tagList"
:key="index"
:class="{ 'tag-card-item--active': selectedTagItem && selectedTagItem.id === item.id }">
<view class="card-header">
<view class="staff-info" @click.stop="onTagItemClick(item)">
<view class="staff-avatar">
<text class="avatar-text">{{ (item.tagName || item.name || '标').charAt(0) }}</text>
</view>
<text class="staff-name">{{ item.tagName || item.name || '未命名标签' }}</text>
</view>
<view class="tag-card-action-btn" @click.stop="onTagActionBtnClick(item)">
<uni-icons type="more-filled" size="20" color="#999"></uni-icons>
</view>
</view>
<view class="customer-info" @click.stop="onTagItemClick(item)">
<text class="customer-name" v-if="item.industry">所属行业{{ item.industry }}</text>
</view>
<view class="customer-tags" v-if="item.tagDetail || item.detail || item.remark" @click.stop="onTagItemClick(item)">
<view
class="tag-item tag-blue"
v-if="item.tagDetail || item.detail">
<text>详情{{ item.tagDetail || item.detail }}</text>
</view>
<view
class="tag-item tag-orange"
v-if="item.remark">
<text>备注{{ item.remark }}</text>
</view>
</view>
<!-- 操作菜单 -->
<view
class="tag-action-menu"
v-if="selectedTagItem && selectedTagItem.id === item.id && showTagActionMenu"
@click.stop>
<view class="tag-action-menu-item" @click="editTag(item)">
<text>编辑</text>
</view>
<view class="tag-action-menu-divider"></view>
<view class="tag-action-menu-item tag-action-menu-item--danger" @click="deleteTag(item)">
<text>删除</text>
</view>
</view>
</view>
<!-- 遮罩层点击关闭菜单 -->
<view
class="tag-action-menu-mask"
v-if="showTagActionMenu"
@click="closeTagActionMenu">
</view>
<view class="service-status-empty" v-if="!tagLoading && !tagList.length">
<text>暂无标签</text>
</view>
<view class="service-status-loading-more" v-if="tagLoading && tagList.length">
<text>正在加载更多...</text>
</view>
</scroll-view>
<!-- 标签管理 - 添加/编辑标签表单 -->
<view class="tag-management" :style="{ top: computedContentTop || contentTop }" v-if="activeTab === 'tag' && (tagViewMode === 'add' || tagViewMode === 'edit')">
<!-- 添加标签表单 -->
<scroll-view
class="tag-form"
scroll-y>
<view class="form-card">
<view class="form-item" v-if="tagViewMode === 'add'">
<text class="form-item__label">所属行业</text>
<input
class="form-item__input"
v-model="tagForm.industry"
placeholder="请输入所属行业"
placeholder-style="color: #9ca3af"
/>
</view>
<view class="form-item tag-select-wrapper">
<text class="form-item__label">标签分类</text>
<view
class="tag-select"
@click.stop="toggleTagTypeDropdown"
>
<text
:class="tagForm.tagType ? 'form-item__picker-text' : 'form-item__picker-placeholder'"
>
{{ tagForm.tagType || "请选择标签分类" }}
</text>
<uni-icons type="bottom" size="16" color="#9ca3af"></uni-icons>
</view>
<view
v-if="showTagTypeDropdown"
class="tag-select-dropdown"
@click.stop
>
<view
class="tag-select-dropdown__item"
v-for="option in tagTypeOptions"
:key="option"
@click="selectTagType(option)"
>
<text :class="{'active': option === tagForm.tagType}">
{{ option }}
</text>
</view>
</view>
</view>
<view class="form-item">
<text class="form-item__label">标签名</text>
<input
class="form-item__input"
v-model="tagForm.name"
placeholder="请输入标签名"
placeholder-style="color: #9ca3af"
/>
</view>
<view class="form-item">
<text class="form-item__label">详情</text>
<input
class="form-item__input"
v-model="tagForm.detail"
placeholder="请输入详情"
placeholder-style="color: #9ca3af"
:maxlength="500"
/>
</view>
<view class="form-item">
<text class="form-item__label">备注</text>
<input
class="form-item__input"
v-model="tagForm.remark"
placeholder="请输入备注"
placeholder-style="color: #9ca3af"
:maxlength="500"
/>
</view>
<view class="form-item">
<text class="form-item__label">是否启用</text>
<switch
:checked="tagForm.enabled"
@change="onEnabledChange"
color="#007AFF"
/>
</view>
</view>
<view class="form-actions">
<view class="form-btn form-btn--cancel" @click="cancelAddTag">
<text class="form-btn__text">取消</text>
</view>
<view class="form-btn form-btn--save" @click="saveTag">
<text class="form-btn__text">{{ tagViewMode === 'edit' ? '更新' : '保存' }}</text>
</view>
</view>
</scroll-view>
<view
v-if="showTagTypeDropdown"
class="tag-select-mask"
@click="closeTagTypeDropdown"
></view>
</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() {
// 动态计算导航栏高度
const systemInfo = uni.getSystemInfoSync();
const statusBarHeight = systemInfo.statusBarHeight || 20; // 默认20px
const navbarHeight = 44; // navbar高度44px
// 转换为rpx1px = 2rpx在375px设计稿下
const statusBarHeightRpx = statusBarHeight * 2;
const navbarHeightRpx = navbarHeight * 2;
const totalNavbarHeight = statusBarHeightRpx + navbarHeightRpx;
console.log('[Reception][data] 导航栏高度计算:', {
statusBarHeight: statusBarHeight,
navbarHeight: navbarHeight,
statusBarHeightRpx: statusBarHeightRpx,
navbarHeightRpx: navbarHeightRpx,
totalNavbarHeight: totalNavbarHeight
});
return {
activeTab: 'status',
navbarTop: totalNavbarHeight + 'rpx', // tab栏距离顶部的距离
contentTop: (totalNavbarHeight + 72) + 'rpx', // 内容区域距离顶部的距离tab栏高度72rpx
genderOptions: ["男", "女"],
isFetchingContact: false,
serviceStatusLoading: false,
serviceStatusTotal: 0,
serviceStatusPage: {
current: 1,
size: 10
},
serviceStatusQuery: {
salesName: '',
customerName: ''
},
receptionForm: {
id: "",
customerName: "",
contact: "",
customerSource: "",
gender: "女",
age: "",
dealershipId: "",
dealershipName: "",
salesId: "",
salesName: "",
salesPhone: "",
recordingCount: 0,
intendedModel: "",
infoCard: "",
remark: "",
detailedAddress: "",
contactCount: 0
},
serviceStatusList: [],
// 标签管理
tagViewMode: 'list', // 'list' 或 'add' 或 'edit'
tagList: [],
tagLoading: false,
tagTotal: 0,
tagPage: {
current: 1,
size: 10
},
tagQuery: {
industry: '',
tagType: '',
tagName: ''
},
tagForm: {
id: '',
industry: '家居行业',
tagType: '',
name: '',
detail: '',
remark: '',
enabled: true
},
tagTypeOptions: ["接待客户", "质检SOP", "客户画像"],
showTagTypeDropdown: false,
selectedTagItem: null,
showTagActionMenu: false
}
},
onLoad() {
console.log('[Reception][onLoad] 页面加载');
// 更新导航栏位置
this.updateNavbarPosition();
this.fetchServiceStatusList();
// 加载当前登录用户信息,填充销售姓名和电话
this.loadCurrentUserInfo();
},
onShow() {
console.log('[Reception][onShow] 页面显示');
// 每次页面显示时都重新计算导航栏位置,确保通过导航菜单进入时也能正确显示
// 使用 $nextTick 确保DOM更新后再设置样式
this.$nextTick(() => {
this.updateNavbarPosition();
// 再次延迟更新,确保样式已应用
setTimeout(() => {
this.updateNavbarPosition();
console.log('[Reception][onShow] 延迟更新后的位置:', {
navbarTop: this.navbarTop,
contentTop: this.contentTop,
computedNavbarTop: this.computedNavbarTop,
computedContentTop: this.computedContentTop
});
}, 50);
});
},
onReady() {
// 页面渲染完成后检查tab栏是否显示
console.log('[Reception][onReady] 页面渲染完成');
console.log('[Reception][onReady] activeTab:', this.activeTab);
console.log('[Reception][onReady] navbarTop:', this.navbarTop);
console.log('[Reception][onReady] contentTop:', this.contentTop);
console.log('[Reception][onReady] computedNavbarTop:', this.computedNavbarTop);
console.log('[Reception][onReady] computedContentTop:', this.computedContentTop);
// 再次更新导航栏位置,确保渲染后位置正确
this.updateNavbarPosition();
const systemInfo = uni.getSystemInfoSync();
console.log('[Reception][onReady] 系统信息:', {
statusBarHeight: systemInfo.statusBarHeight,
windowHeight: systemInfo.windowHeight,
screenHeight: systemInfo.screenHeight
});
// 检查tab栏元素是否存在和可见
// #ifdef MP-WEIXIN
setTimeout(() => {
const query = uni.createSelectorQuery().in(this);
query.select('.tabs').boundingClientRect((data) => {
console.log('[Reception][onReady] tab栏元素信息:', data);
if (!data) {
console.error('[Reception][onReady] tab栏元素未找到');
// 如果找不到元素,强制更新并重试
this.updateNavbarPosition();
this.$forceUpdate();
} else {
console.log('[Reception][onReady] tab栏位置:', {
top: data.top,
left: data.left,
width: data.width,
height: data.height,
expectedTop: this.navbarTop,
expectedComputedTop: this.computedNavbarTop,
isVisible: data.width > 0 && data.height > 0,
isOnScreen: data.top >= 0 && data.top < systemInfo.windowHeight
});
// 如果tab栏不可见强制更新位置
if (data.width === 0 || data.height === 0 || data.top < 0) {
console.warn('[Reception][onReady] tab栏不可见强制更新位置');
this.updateNavbarPosition();
this.$forceUpdate();
// 再次检查
setTimeout(() => {
const retryQuery = uni.createSelectorQuery().in(this);
retryQuery.select('.tabs').boundingClientRect((retryData) => {
if (retryData && (retryData.width === 0 || retryData.height === 0)) {
console.error('[Reception][onReady] tab栏仍然不可见可能需要检查样式');
}
}).exec();
}, 100);
}
}
}).exec();
}, 100);
// #endif
},
computed: {
// 计算导航栏和内容区域的位置(使用计算属性确保响应式)
computedNavbarTop() {
const systemInfo = uni.getSystemInfoSync();
const statusBarHeight = systemInfo.statusBarHeight || 20;
const navbarHeight = 44;
const statusBarHeightRpx = statusBarHeight * 2;
const navbarHeightRpx = navbarHeight * 2;
const totalNavbarHeight = statusBarHeightRpx + navbarHeightRpx;
return totalNavbarHeight + 'rpx';
},
computedContentTop() {
const systemInfo = uni.getSystemInfoSync();
const statusBarHeight = systemInfo.statusBarHeight || 20;
const navbarHeight = 44;
const statusBarHeightRpx = statusBarHeight * 2;
const navbarHeightRpx = navbarHeight * 2;
const totalNavbarHeight = statusBarHeightRpx + navbarHeightRpx;
return (totalNavbarHeight + 72) + 'rpx'; // tab栏高度72rpx
}
},
methods: {
// 计算并更新导航栏和内容区域的位置
updateNavbarPosition() {
const systemInfo = uni.getSystemInfoSync();
const statusBarHeight = systemInfo.statusBarHeight || 20; // 默认20px
const navbarHeight = 44; // navbar高度44px
// 转换为rpx1px = 2rpx在375px设计稿下
const statusBarHeightRpx = statusBarHeight * 2;
const navbarHeightRpx = navbarHeight * 2;
const totalNavbarHeight = statusBarHeightRpx + navbarHeightRpx;
const navbarTop = totalNavbarHeight + 'rpx';
const contentTop = (totalNavbarHeight + 72) + 'rpx'; // tab栏高度72rpx
console.log('[Reception][updateNavbarPosition] 更新导航栏位置:', {
statusBarHeight: statusBarHeight,
navbarHeight: navbarHeight,
totalNavbarHeight: totalNavbarHeight,
navbarTop: navbarTop,
contentTop: contentTop,
currentNavbarTop: this.navbarTop,
currentContentTop: this.contentTop
});
// 强制更新,确保响应式数据变化被检测到
this.$set(this, 'navbarTop', navbarTop);
this.$set(this, 'contentTop', contentTop);
// 在微信小程序中,可能需要强制更新视图
// #ifdef MP-WEIXIN
this.$forceUpdate();
// #endif
},
/**
* 加载当前登录用户信息,填充销售姓名和电话
*/
loadCurrentUserInfo() {
try {
// 从本地存储获取登录响应信息
const loginResponse = uni.getStorageSync('backend-login-response') || {};
// 填充销售姓名
if (loginResponse.userName) {
this.receptionForm.salesName = loginResponse.userName;
}
// 填充销售电话:如果电话为空,则使用姓名作为电话
if (loginResponse.phone) {
this.receptionForm.salesPhone = loginResponse.phone;
} else if (loginResponse.userName) {
// 如果电话为空,使用姓名作为电话
this.receptionForm.salesPhone = loginResponse.userName;
}
// 如果有 userId也可以填充到 salesId
if (loginResponse.userId) {
this.receptionForm.salesId = String(loginResponse.userId);
}
} catch (e) {
console.error('加载当前登录用户信息失败:', e);
}
},
goBack() {
// 检查页面栈,如果当前是第一个页面,则不能返回
const pages = getCurrentPages();
if (pages.length <= 1) {
// 如果是第一个页面,不执行返回操作
// 对于 tabBar 页面,通常不需要返回按钮,或者可以隐藏返回按钮
console.log('[Reception][goBack] 当前是第一个页面,无法返回');
return;
}
uni.navigateBack({
fail: (err) => {
console.error('[Reception][goBack] 返回失败:', err);
// 如果返回失败,可能是页面栈问题,忽略错误
}
});
},
switchTab(tab) {
this.activeTab = tab;
if (tab === 'status') {
this.fetchServiceStatusList();
} else if (tab === 'reception') {
// 切换到开始接待标签页时,重新加载当前登录用户信息
this.loadCurrentUserInfo();
} 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,
serviceStatus: '服务中' // 默认搜索条件
};
const trimmedSalesName = this.serviceStatusQuery?.salesName?.trim();
const trimmedCustomerName = this.serviceStatusQuery?.customerName?.trim();
if (trimmedSalesName) {
queryParams.salesName = trimmedSalesName;
}
if (trimmedCustomerName) {
queryParams.customerName = trimmedCustomerName;
}
// 确保 serviceStatus 始终存在
queryParams.serviceStatus = '服务中';
// 将参数转换为 URL 查询字符串(因为后端使用 @RequestParam
const queryString = Object.keys(queryParams)
.filter(key => queryParams[key] !== null && queryParams[key] !== undefined && queryParams[key] !== '')
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`)
.join('&');
// 调试:打印请求参数
console.log('服务状态查询参数:', JSON.stringify(queryParams));
console.log('查询字符串:', queryString);
// 后端使用 @RequestParam参数需要作为 URL 查询参数传递
const baseUrl = getApiUrl('/api/audioManagement/list');
const url = queryString ? `${baseUrl}?${queryString}` : baseUrl;
// 获取认证信息
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,
method: 'POST',
data: {}, // POST 请求体为空,参数都在 URL 查询字符串中
header: headers,
timeout: 30000 // 增加超时时间到30秒
});
if (res.statusCode === 200 && res.data && res.data.success) {
const records = Array.isArray(res.data.data) ? res.data.data : [];
if (records.length === 0) {
this.serviceStatusList = [];
this.serviceStatusTotal = 0;
return;
}
const mapped = records
.map(item => this.buildServiceStatusItem(item))
.filter(item => item !== null); // 过滤掉无效的数据
// 第一页或强制刷新时重置列表,否则追加
if (this.serviceStatusPage.current === 1 || force) {
// 使用 Vue 3 的响应式更新方式
this.serviceStatusList = [...mapped];
} else {
this.serviceStatusList = [...this.serviceStatusList, ...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);
// 根据错误类型提供更详细的错误信息
let errorMessage = '获取服务状态失败,请稍后重试';
if (error.errMsg) {
if (error.errMsg.includes('timeout')) {
errorMessage = '请求超时,请检查网络连接后重试';
} else if (error.errMsg.includes('fail')) {
errorMessage = '网络请求失败,请检查网络连接';
}
}
uni.showToast({
title: errorMessage,
icon: 'none',
duration: 3000
});
} finally {
this.serviceStatusLoading = false;
}
},
buildServiceStatusItem(record = {}) {
if (!record || typeof record !== 'object') {
return null;
}
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) {
// 查看服务详情
console.log('查看服务详情', item);
},
async finishService(item) {
if (!item || !item.id) {
uni.showToast({
title: '无法获取服务记录ID',
icon: 'none'
});
return;
}
uni.showModal({
title: '确认结束',
content: `确定要结束这条服务记录吗?`,
success: async (res) => {
if (res.confirm) {
try {
uni.showLoading({
title: '结束中...'
});
const url = getApiUrl('/api/audioManagement/finishServiceById');
// 获取认证信息
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 requestData = {
id: item.id
};
const finishRes = await uni.request({
url: url,
method: 'POST',
data: requestData,
header: headers,
timeout: 30000
});
uni.hideLoading();
if (finishRes.statusCode === 200 && finishRes.data && finishRes.data.success) {
uni.showToast({
title: finishRes.data?.message || '结束服务成功',
icon: 'success'
});
// 结束成功后刷新列表
this.serviceStatusPage.current = 1;
this.fetchServiceStatusList({ force: true });
} else {
uni.showToast({
title: finishRes.data?.message || '结束服务失败',
icon: 'none'
});
}
} catch (error) {
uni.hideLoading();
console.error('结束服务失败:', error);
// 根据错误类型提供更详细的错误信息
let errorMessage = '结束服务失败,请重试';
if (error.errMsg) {
if (error.errMsg.includes('timeout')) {
errorMessage = '请求超时,请检查网络连接后重试';
} else if (error.errMsg.includes('fail')) {
errorMessage = '网络请求失败,请检查网络连接';
}
}
uni.showToast({
title: errorMessage,
icon: 'none',
duration: 3000
});
}
}
}
});
},
onGenderChange(e) {
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 配置自动切换环境
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.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 ? String(customer.dealershipId) : this.receptionForm.dealershipId,
dealershipName: customer.dealershipName || this.receptionForm.dealershipName,
salesId: customer.salesId ? String(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: "已填充客户信息",
icon: "none"
});
}
// 未获取到数据时,不提示任何信息
} catch (error) {
// 查询失败时,也不提示任何信息
console.error("查询客户信息失败:", error);
} finally {
this.isFetchingContact = false;
}
},
onCancel() {
// 重置表单
this.receptionForm = {
id: "",
customerName: "",
contact: "",
customerSource: "",
gender: "女",
age: "",
dealershipId: "",
dealershipName: "",
salesId: "",
salesName: "",
salesPhone: "",
recordingCount: 0,
intendedModel: "",
infoCard: "",
remark: "",
detailedAddress: "",
contactCount: 0
};
// 重置后重新填充当前登录用户信息
this.loadCurrentUserInfo();
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 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: trimmedContact,
customerSource: this.receptionForm.customerSource?.trim() || "",
dealershipId: String(this.receptionForm.dealershipId || '').trim() || "",
dealershipName: this.receptionForm.dealershipName?.trim() || "",
salesId: String(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: trimmedSalesPhone || "",
contactCount: Number(this.receptionForm.contactCount) || 0,
operationType: action
};
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.onCancel();
} else {
uni.showToast({
title: res.data?.message || "保存失败",
icon: "none"
});
}
} catch (error) {
uni.hideLoading();
console.error("保存失败:", error);
uni.showToast({
title: "保存失败,请重试",
icon: "none"
});
}
},
// 标签管理相关方法
onTagListSearch() {
if (this.activeTab !== 'tag') {
this.activeTab = 'tag';
}
this.tagPage.current = 1;
this.fetchTagList({ force: true });
},
onTagListRefresh() {
this.tagPage.current = 1;
this.fetchTagList({ force: true });
},
onTagListReachBottom() {
// 已在加载中或全部加载完成则不再触发
if (this.tagLoading) return;
if (this.tagList.length >= this.tagTotal) return;
this.tagPage.current += 1;
this.fetchTagList();
},
async fetchTagList({ force = false } = {}) {
if (this.tagLoading && !force) {
return;
}
this.tagLoading = true;
try {
const queryParams = {
current: this.tagPage.current,
size: this.tagPage.size
};
const trimmedTagType = this.tagQuery?.tagType?.trim();
const trimmedTagName = this.tagQuery?.tagName?.trim();
if (trimmedTagType) {
queryParams.tagType = trimmedTagType;
}
if (trimmedTagName) {
queryParams.tagName = trimmedTagName;
}
// 后端接口不支持 GET改为 POST 传参
const url = getApiUrl('/api/industryTags/list');
// 获取认证信息
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,
method: 'POST',
data: queryParams,
header: headers,
timeout: 30000 // 增加超时时间到30秒
});
if (res.statusCode === 200 && res.data && res.data.success) {
const records = Array.isArray(res.data.data) ? res.data.data : [];
// 第一页或强制刷新时重置列表,否则追加
if (this.tagPage.current === 1 || force) {
this.tagList = records;
} else {
this.tagList = this.tagList.concat(records);
}
this.tagTotal = Number(res.data.total) || 0;
} else {
this.tagList = [];
this.tagTotal = 0;
uni.showToast({
title: res.data?.message || '获取标签列表失败',
icon: 'none'
});
}
} catch (error) {
console.error('获取标签列表失败:', error);
// 根据错误类型提供更详细的错误信息
let errorMessage = '获取标签列表失败,请稍后重试';
if (error.errMsg) {
if (error.errMsg.includes('timeout')) {
errorMessage = '请求超时,请检查网络连接后重试';
} else if (error.errMsg.includes('fail')) {
errorMessage = '网络请求失败,请检查网络连接';
}
}
uni.showToast({
title: errorMessage,
icon: 'none',
duration: 3000
});
} finally {
this.tagLoading = false;
}
},
showAddTag() {
this.tagViewMode = 'add';
this.showTagTypeDropdown = false;
this.tagForm = {
id: '',
industry: '家居行业',
tagType: '',
name: '',
detail: '',
remark: '',
enabled: true
};
},
onTagItemClick(item) {
// 点击卡片内容区域,不做任何操作(保留用于其他可能的交互)
},
onTagActionBtnClick(item) {
// 如果已经选中当前项,则关闭菜单
if (this.selectedTagItem && this.selectedTagItem.id === item.id && this.showTagActionMenu) {
this.closeTagActionMenu();
return;
}
// 显示菜单
this.selectedTagItem = item;
this.showTagActionMenu = true;
},
closeTagActionMenu() {
this.showTagActionMenu = false;
this.selectedTagItem = null;
},
editTag(item) {
this.closeTagActionMenu();
this.tagViewMode = 'edit';
this.showTagTypeDropdown = false;
this.tagForm = {
id: item.id || '',
industry: item.industry || '家居行业',
tagType: item.tagType || '',
name: item.tagName || item.name || '',
detail: item.tagDetail || item.detail || '',
remark: item.remark || '',
enabled: item.enabled !== undefined ? item.enabled : (item.isEnabled !== undefined ? item.isEnabled : true)
};
},
async deleteTag(item) {
this.closeTagActionMenu();
const tagId = item.id;
if (!tagId) {
uni.showToast({
title: '无法获取标签ID',
icon: 'none'
});
return;
}
uni.showModal({
title: '确认删除',
content: `确定要删除标签"${item.tagName || item.name || '未命名标签'}"吗?`,
success: async (res) => {
if (res.confirm) {
try {
uni.showLoading({
title: '删除中...'
});
const deleteUrl = getApiUrl(`/api/industryTags/delete/${tagId}`);
// 获取认证信息
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 deleteRes = await uni.request({
url: deleteUrl,
method: 'DELETE',
header: headers,
timeout: 30000 // 增加超时时间到30秒
});
uni.hideLoading();
if (deleteRes.statusCode === 200 && deleteRes.data && deleteRes.data.success) {
uni.showToast({
title: deleteRes.data?.message || '删除成功',
icon: 'success'
});
// 删除成功后刷新列表
this.tagPage.current = 1;
this.fetchTagList({ force: true });
} else {
uni.showToast({
title: deleteRes.data?.message || '删除失败',
icon: 'none'
});
}
} catch (error) {
uni.hideLoading();
console.error('删除标签失败:', error);
// 根据错误类型提供更详细的错误信息
let errorMessage = '删除失败,请重试';
if (error.errMsg) {
if (error.errMsg.includes('timeout')) {
errorMessage = '请求超时,请检查网络连接后重试';
} else if (error.errMsg.includes('fail')) {
errorMessage = '网络请求失败,请检查网络连接';
}
}
uni.showToast({
title: errorMessage,
icon: 'none',
duration: 3000
});
}
}
}
});
},
cancelAddTag() {
this.tagViewMode = 'list';
this.showTagTypeDropdown = false;
this.tagForm = {
id: '',
industry: '家居行业',
tagType: '',
name: '',
detail: '',
remark: '',
enabled: true
};
},
toggleTagTypeDropdown() {
this.showTagTypeDropdown = !this.showTagTypeDropdown;
},
selectTagType(option) {
this.tagForm.tagType = option;
this.showTagTypeDropdown = false;
},
closeTagTypeDropdown() {
this.showTagTypeDropdown = false;
},
onEnabledChange(e) {
this.tagForm.enabled = e.detail.value;
},
async saveTag() {
// 表单验证
if (!this.tagForm.name || !this.tagForm.name.trim()) {
uni.showToast({
title: "请输入标签名",
icon: "none"
});
return;
}
try {
uni.showLoading({
title: this.tagViewMode === 'edit' ? "更新中..." : "保存中..."
});
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() || '',
enabled: this.tagForm.enabled !== undefined ? this.tagForm.enabled : true
};
// 如果是编辑模式添加id
if (this.tagViewMode === 'edit' && this.tagForm.id) {
payload.id = this.tagForm.id;
}
// 根据模式选择不同的接口
const url = this.tagViewMode === 'edit'
? getApiUrl('/api/industryTags/update')
: getApiUrl('/api/industryTags/add');
const method = this.tagViewMode === 'edit' ? 'PUT' : 'POST';
// 获取认证信息
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: url,
method: method,
data: payload,
header: headers,
timeout: 30000 // 增加超时时间到30秒
});
const { statusCode, data } = res;
if (statusCode === 200 && data && (data.success || data.code === 200)) {
uni.showToast({
title: data?.message || (this.tagViewMode === 'edit' ? "更新成功" : "保存成功"),
icon: "success"
});
// 保存成功后返回列表并刷新
this.tagViewMode = 'list';
this.tagPage.current = 1;
this.fetchTagList({ force: true });
} else {
throw new Error(data?.message || (this.tagViewMode === 'edit' ? "更新失败" : "保存失败"));
}
} catch (error) {
console.error("保存标签失败:", error);
// 根据错误类型提供更详细的错误信息
let errorMessage = error?.message || (this.tagViewMode === 'edit' ? "更新失败,请重试" : "保存失败,请重试");
if (error.errMsg) {
if (error.errMsg.includes('timeout')) {
errorMessage = '请求超时,请检查网络连接后重试';
} else if (error.errMsg.includes('fail')) {
errorMessage = '网络请求失败,请检查网络连接';
}
}
uni.showToast({
title: errorMessage,
icon: "none",
duration: 3000
});
} finally {
uni.hideLoading();
}
}
}
}
</script>
<style>
page {
background-color: #F5F5F5;
}
.page {
min-height: 100vh;
background-color: #F5F5F5;
}
.content {
padding-top: 0; /* 移除padding-top因为tab页和scroll-view都使用绝对定位 */
margin-top: 0;
position: relative;
min-height: 100vh; /* 确保content有足够高度 */
}
/* 隐藏导航栏占位区域(红色区域) */
::v-deep .uni-navbar__placeholder {
display: none !important;
height: 0 !important;
}
::v-deep .uni-navbar__placeholder-view {
display: none !important;
height: 0 !important;
}
/* 隐藏导航栏底部边框 */
::v-deep .uni-navbar--border {
border-bottom: none !important;
}
/* 标签页 - 使用固定定位,确保显示在导航栏下方 */
.tabs {
display: flex !important; /* 强制显示 */
visibility: visible !important; /* 强制可见 */
opacity: 1 !important; /* 强制不透明 */
background-color: #FFFFFF;
border-bottom: 1px solid #E0E0E0;
padding: 0 16rpx;
margin-top: 0;
position: fixed; /* 改为fixed定位确保始终可见 */
/* 导航栏高度statusBar + navbar动态计算 */
/* 使用内联样式动态设置top值 */
left: 0;
right: 0;
z-index: 999; /* 提高z-index确保在最上层 */
height: 72rpx; /* 明确设置标签页高度 */
box-sizing: border-box;
align-items: center;
/* 确保tab栏始终可见 */
will-change: top; /* 优化渲染性能 */
}
.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 {
/* 使用绝对定位从tab页底部开始 */
/* 使用内联样式动态设置top值 */
position: absolute;
left: 0;
right: 0;
bottom: 96rpx; /* 底部导航栏高度 */
background-color: #F5F5F5;
padding: 24rpx 16rpx 0 16rpx; /* 顶部24rpx间距与列表项间距一致底部0 */
box-sizing: border-box;
}
/* 标签管理列表区使用绝对定位与service-status-list保持一致 */
.tag-list {
/* 使用绝对定位从tab页底部开始 */
/* 导航栏(128rpx) + tab页(72rpx) = 200rpx */
position: absolute;
top: 200rpx; /* 导航栏(128rpx) + tab页(72rpx) = 200rpx */
left: 0;
right: 0;
bottom: 96rpx; /* 底部导航栏高度 */
}
.service-status-toolbar {
display: flex;
flex-wrap: nowrap;
align-items: center;
gap: 8rpx; /* 增加间距,避免元素被挤压 */
padding: 12rpx 8rpx; /* 增加内边距,给搜索框更多空间 */
margin-bottom: 24rpx; /* 与列表项间距保持一致24rpx */
background-color: #F8F8FA;
border-radius: 8rpx;
border: 1px solid #EFEFF2;
overflow-x: auto;
min-height: 64rpx; /* 设置最小高度,确保搜索区域不被挤压 */
box-sizing: border-box;
}
.toolbar-total {
font-size: 24rpx;
color: #666;
white-space: nowrap;
flex-shrink: 0;
margin-right: 8rpx; /* 增加右边距,与搜索框保持距离 */
line-height: 1.2;
padding: 0 4rpx; /* 增加左右内边距 */
}
.toolbar-input {
flex: 1;
min-width: 120rpx; /* 增加最小宽度,避免被挤压变形 */
max-width: 200rpx; /* 设置最大宽度,保持合理比例 */
height: 56rpx; /* 稍微增加高度,提升可用性 */
line-height: 56rpx;
background-color: #F5F6FA;
border-radius: 8rpx;
padding: 0 12rpx; /* 增加左右内边距 */
font-size: 24rpx;
border: 1px solid transparent;
box-sizing: border-box;
flex-shrink: 1; /* 允许适当收缩,但保持最小宽度 */
}
.toolbar-actions {
display: flex;
align-items: center;
margin-left: auto;
flex-shrink: 0;
gap: 8rpx; /* 增加按钮之间的间距 */
}
.toolbar-btn {
padding: 0 12rpx; /* 增加左右内边距 */
height: 56rpx; /* 与输入框高度保持一致 */
min-width: 56rpx;
color: #2A68FF;
display: flex;
align-items: center;
justify-content: center;
gap: 4rpx; /* 增加图标和文字之间的间距 */
font-size: 24rpx;
font-weight: 400;
line-height: 1;
box-sizing: border-box;
}
.service-card {
width: 100%;
box-sizing: border-box;
background-color: #FFFFFF;
border-radius: 16rpx;
padding: 32rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
overflow: visible;
/* 确保左右padding完全对称所有内容都在padding范围内 */
}
/* 标签卡片特殊处理 */
.service-card.tag-card-item {
padding-left: 32rpx;
padding-right: 32rpx;
padding-top: 32rpx;
padding-bottom: 32rpx;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
width: 100%;
box-sizing: border-box;
padding: 0;
margin: 0;
/* 确保头部内容在卡片padding范围内 */
}
.staff-info {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
margin: 0;
padding: 0;
}
.staff-avatar {
width: 64rpx;
height: 64rpx;
background-color: #2196F3;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16rpx;
margin-left: 0;
margin-top: 0;
margin-bottom: 0;
flex-shrink: 0;
}
.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;
gap: 8rpx;
}
.service-status text {
font-size: 26rpx;
color: #007AFF;
}
.customer-tags {
display: flex;
flex-wrap: wrap;
margin-bottom: 20rpx;
gap: 12rpx;
box-sizing: border-box;
margin-left: 0;
margin-right: 0;
padding: 0;
/* 确保标签容器不会超出卡片padding范围 */
width: 100%;
max-width: 100%;
}
.customer-info {
margin-bottom: 16rpx;
display: flex;
flex-direction: column;
gap: 8rpx;
box-sizing: border-box;
margin-left: 0;
margin-right: 0;
padding: 0;
/* 确保信息容器不会超出卡片padding范围 */
width: 100%;
max-width: 100%;
}
.customer-name,
.customer-phone {
font-size: 28rpx;
color: #555;
}
.tag-item {
padding: 8rpx 16rpx;
border-radius: 8rpx;
box-sizing: border-box;
word-wrap: break-word;
word-break: break-all;
/* 确保标签项不会超出卡片padding范围允许换行 */
display: inline-block;
max-width: 100%;
}
.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;
}
.service-action-btn {
padding: 6rpx 16rpx;
border-radius: 4rpx;
font-size: 26rpx;
font-weight: 400;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12rpx;
cursor: pointer;
}
.service-action-btn--finish {
color: #007AFF;
background-color: transparent;
}
.service-action-btn--finish:active {
opacity: 0.7;
}
.service-status-empty {
padding: 48rpx 0;
text-align: center;
color: #999;
font-size: 28rpx;
}
/* 开始接待表单样式 */
.reception-form {
/* 使用绝对定位从tab页底部开始 */
/* 导航栏(128rpx) + tab页(72rpx) = 200rpx */
position: absolute;
top: 200rpx; /* 导航栏(128rpx) + tab页(72rpx) = 200rpx */
left: 0;
right: 0;
bottom: 96rpx; /* 底部导航栏高度 */
background-color: #F5F5F5;
padding: 24rpx 16rpx 0 16rpx; /* 顶部24rpx间距与列表项间距一致底部0 */
box-sizing: border-box;
}
.form-card {
width: 100%;
box-sizing: border-box;
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;
}
/* 对于包含 textarea 的表单项label 顶部对齐 */
.form-item--textarea {
align-items: flex-start;
}
.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;
/* 原 160rpx缩小 20% 以减小与输入框的间距 */
width: 128rpx;
flex-shrink: 0;
}
.form-item--half {
width: 50%;
}
.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;
}
.tag-select-wrapper {
position: relative;
flex: 1;
}
.tag-select {
width: 100%;
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;
right: 0;
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;
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: #4C8DFF;
}
.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;
}
/* 标签管理样式 - 添加表单 */
.tag-management {
/* 使用绝对定位从tab页底部开始 */
/* 导航栏(128rpx) + tab页(72rpx) = 200rpx */
position: absolute;
top: 200rpx; /* 导航栏(128rpx) + tab页(72rpx) = 200rpx */
left: 0;
right: 0;
bottom: 96rpx; /* 底部导航栏高度 */
background-color: #F5F5F5;
box-sizing: border-box;
}
/* 标签卡片操作菜单 */
.tag-card-item {
position: relative;
}
.tag-card-action-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
margin-left: auto;
margin-right: 0;
margin-top: 0;
margin-bottom: 0;
flex-shrink: 0;
flex-grow: 0;
position: relative;
padding: 0;
/* 确保按钮在卡片padding范围内距离右边框32rpx */
}
.tag-card-action-btn uni-icons {
display: block;
margin: 0;
padding: 0;
line-height: 1;
}
.tag-card-action-btn:active {
opacity: 0.7;
}
.tag-action-menu {
position: absolute;
top: 60rpx;
right: 32rpx;
width: 160rpx;
background-color: #FFFFFF;
border-radius: 12rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
z-index: 100;
overflow: hidden;
margin-right: 0;
}
.tag-action-menu-item {
padding: 24rpx 32rpx;
font-size: 28rpx;
color: #333;
text-align: center;
background-color: #FFFFFF;
}
.tag-action-menu-item:active {
background-color: #F5F5F5;
}
.tag-action-menu-item--danger {
color: #FF5722;
}
.tag-action-menu-divider {
height: 1rpx;
background-color: #E0E0E0;
margin: 0 16rpx;
}
.tag-action-menu-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: transparent;
z-index: 99;
}
.tag-form {
height: 100%;
padding: 24rpx 32rpx;
box-sizing: border-box;
}
/* 标签弹窗整体高度控制在 70% 视窗,内部通过尺寸缩减而非拉伸变形 */
.tag-management {
max-height: 70vh;
}
.tag-management .tag-form {
max-height: 70vh;
}
/* 弹窗内部元素高度约 80%(缩减 padding/字号/高度),保持比例一致 */
.tag-management .form-card {
padding: 24rpx 20rpx;
margin-bottom: 24rpx;
}
.tag-management .form-item {
margin-bottom: 24rpx;
}
.tag-management .form-item__label {
font-size: 26rpx;
}
.tag-management .form-item__input,
.tag-management .form-item__text,
.tag-management .form-item__picker,
.tag-management .tag-select {
height: 70rpx; /* 约为原 88rpx 的 80% */
padding: 0 20rpx;
font-size: 26rpx;
}
.tag-management .form-item__picker-icon {
font-size: 22rpx;
}
.tag-management .tag-select-dropdown__item {
padding: 16rpx 28rpx;
}
.tag-management .tag-select-dropdown__item text {
font-size: 26rpx;
}
.tag-management .form-item__textarea {
padding: 16rpx;
font-size: 26rpx;
line-height: 1.5;
}
/* 详情与备注区域缩到约 50% 高度 */
.tag-management .form-item__textarea--detail {
min-height: 60rpx;
}
.tag-management .form-item__textarea--remark {
min-height: 50rpx;
}
.tag-management .form-actions {
padding-bottom: 24rpx;
gap: 16rpx;
}
.tag-management .form-btn {
height: 72rpx; /* 约为原 88rpx 的 80% */
font-size: 28rpx;
}
.form-item__textarea {
flex: 1;
min-height: 100rpx;
background-color: #f9fafb;
border-radius: 12rpx;
padding: 24rpx;
font-size: 28rpx;
color: #333;
box-sizing: border-box;
line-height: 1.6;
}
.form-item__textarea--detail {
min-height: 100rpx;
}
.form-item__textarea--remark {
min-height: 88rpx;
}
/* 确保包含 textarea 的表单项 label 在顶部对齐 */
.form-item--textarea .form-item__label {
padding-top: 24rpx;
}
/* 开关样式 */
.form-item switch {
margin-left: auto;
}
</style>