Files
smartDriveEEUniApp/pages-subpackage/furniture_reception/furniture_reception-impl.vue

1839 lines
49 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 === 'reception' }"
@click="switchTab('reception')">
<text>开始接待</text>
</view>
<view
class="tab-item"
:class="{ active: activeTab === 'status' }"
@click="switchTab('status')">
<text>服务中</text>
</view>
<view
class="tab-item"
:class="{ active: activeTab === 'tag' }"
@click="switchTab('tag')">
<text>标签管理</text>
</view>
</view>
<!-- 服务状态列表 -->
<service-list-furniture
v-if="activeTab === 'status'"
ref="serviceListRef"
class="service-status-container"
:style="{ top: computedContentTop || contentTop }"
></service-list-furniture>
<!-- 开始接待表单 -->
<common-begin-reception
v-if="activeTab === 'reception'"
:initial-data="receptionForm"
@cancel="onReceptionCancel"
@save-success="onReceptionSaveSuccess"
@save-error="onReceptionSaveError"
/>
<!-- 标签管理 -->
<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
ref="tagTypeSelect"
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
ref="tagTypeDropdown"
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 form-item--textarea">
<text class="form-item__label">详情</text>
<textarea
class="form-item__textarea"
v-model="tagForm.detail"
placeholder="请输入详情"
placeholder-style="color: #9ca3af"
:maxlength="500"
auto-height
/>
</view>
<view class="form-item form-item--textarea">
<text class="form-item__label">备注</text>
<textarea
class="form-item__textarea"
v-model="tagForm.remark"
placeholder="请输入备注"
placeholder-style="color: #9ca3af"
:maxlength="500"
auto-height
/>
</view>
<view class="form-item form-item--switch">
<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";
import CommonBeginReception from "./common_begin_reception.vue";
import ServiceListFurniture from "./serviceListFurniture.vue";
export default {
props: {
incomingTab: {
type: String,
default: ''
}
},
// #ifdef APP
components: {
statusBar,
CommonBeginReception,
ServiceListFurniture
},
// #endif
// #ifndef APP
components: {
CommonBeginReception,
ServiceListFurniture
},
// #endif
data() {
return {
activeTab: 'status',
// 导航栏位置相关
navbarTop: '88rpx', // 默认值会在onLoad/onShow时动态计算
contentTop: '160rpx', // 默认值会在onLoad/onShow时动态计算
receptionForm: {
id: "",
customerName: "",
contact: "",
customerSource: "",
gender: "女",
age: "",
dealershipId: "",
dealershipName: "",
salesId: "",
salesName: "",
salesPhone: "",
recordingCount: 0,
intendedModel: "",
infoCard: "",
remark: "",
detailedAddress: "",
contactCount: 0
},
// 标签管理
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
}
},
watch: {
incomingTab: {
immediate: true,
handler(tab) {
this.applyIncomingTab(tab);
}
}
},
computed: {
// 计算导航栏和内容区域的位置(使用计算属性确保响应式)
computedNavbarTop() {
console.log('[FurnitureReception][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;
const result = totalNavbarHeight + 'rpx';
console.log('[FurnitureReception][computedNavbarTop] 计算结果:', {
statusBarHeight,
navbarHeight,
totalNavbarHeight,
result
});
return result;
},
computedContentTop() {
console.log('[FurnitureReception][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;
const result = (totalNavbarHeight + 72) + 'rpx'; // tab栏高度72rpx
console.log('[FurnitureReception][computedContentTop] 计算结果:', {
statusBarHeight,
navbarHeight,
totalNavbarHeight,
result
});
return result;
}
},
onLoad() {
console.log('[FurnitureReception][onLoad] 页面加载');
// 更新导航栏位置
this.updateNavbarPosition();
// 加载当前登录用户信息,填充销售姓名和电话
this.loadCurrentUserInfo();
},
onShow() {
console.log('[FurnitureReception][onShow] 页面显示');
// 每次页面显示时都重新计算导航栏位置,确保通过导航菜单进入时也能正确显示
// 使用 $nextTick 确保DOM更新后再设置样式
this.$nextTick(() => {
this.updateNavbarPosition();
// 再次延迟更新,确保样式已应用
setTimeout(() => {
this.updateNavbarPosition();
console.log('[FurnitureReception][onShow] 延迟更新后的位置:', {
navbarTop: this.navbarTop,
contentTop: this.contentTop,
computedNavbarTop: this.computedNavbarTop,
computedContentTop: this.computedContentTop
});
}, 50);
});
},
onReady() {
// 页面渲染完成后检查tab栏是否显示
console.log('[FurnitureReception][onReady] 页面渲染完成');
console.log('[FurnitureReception][onReady] activeTab:', this.activeTab);
console.log('[FurnitureReception][onReady] navbarTop:', this.navbarTop);
console.log('[FurnitureReception][onReady] contentTop:', this.contentTop);
console.log('[FurnitureReception][onReady] computedNavbarTop:', this.computedNavbarTop);
console.log('[FurnitureReception][onReady] computedContentTop:', this.computedContentTop);
// 再次更新导航栏位置,确保渲染后位置正确
this.updateNavbarPosition();
// 延迟初始化URL隐藏避免干扰页面刷新加载
this.initUrlHidingAfterPageLoad();
const systemInfo = uni.getSystemInfoSync();
console.log('[FurnitureReception][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('[FurnitureReception][onReady] tab栏元素信息:', data);
if (!data) {
console.error('[FurnitureReception][onReady] tab栏元素未找到');
} else {
console.log('[FurnitureReception][onReady] tab栏位置:', {
top: data.top,
left: data.left,
width: data.width,
height: data.height,
expectedTop: this.navbarTop,
computedNavbarTop: 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('[FurnitureReception][onReady] tab栏不可见强制更新位置');
this.updateNavbarPosition();
this.$forceUpdate();
}
}
}).exec();
}, 100);
// #endif
// 监听窗口大小变化,重新定位下拉框
uni.onWindowResize(() => {
if (this.showTagTypeDropdown) {
this.updateTagTypeDropdownPosition();
}
});
},
methods: {
/**
* 由宿主页 onShow / 分包就绪后调用。tab 页缓存时子列表的 mounted 不会重复执行,需在此主动拉取后端数据。
*/
refreshPageData() {
this.$nextTick(() => {
if (this.activeTab === 'status') {
const list = this.$refs.serviceListRef;
if (list && typeof list.onServiceStatusRefresh === 'function') {
list.onServiceStatusRefresh();
}
} else if (this.activeTab === 'tag' && this.tagViewMode === 'list') {
this.fetchTagList({ force: true });
}
});
},
applyIncomingTab(tab) {
const allowed = ['status', 'reception', 'tag'];
if (!allowed.includes(tab)) return;
// 切tab并执行必要初始化开始接待需要重置表单
if (this.activeTab !== tab) {
this.activeTab = tab;
}
if (tab === 'reception') {
this.resetReceptionForm();
} else if (tab === 'tag') {
this.tagViewMode = 'list';
this.fetchTagList({ force: true });
}
},
// 计算并更新导航栏和内容区域的位置
updateNavbarPosition() {
console.log('[FurnitureReception][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('[FurnitureReception][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 页从其他入口跳转进来时,兜底切回 AI 分析页
uni.switchTab({
url: '/pages/ai_analysis/ai_analysis',
fail: (err) => {
console.error('[FurnitureReception][goBack] 切回AI分析页失败:', err);
}
});
return;
}
uni.navigateBack({
fail: (err) => {
console.error('[FurnitureReception][goBack] 返回失败:', err);
// 如果返回失败,可能是页面栈问题,忽略错误
}
});
},
switchTab(tab) {
this.activeTab = tab;
if (tab === 'reception') {
// 切换到开始接待标签页时,重置表单数据
this.resetReceptionForm();
} else if (tab === 'tag') {
this.tagViewMode = 'list';
// 当进入标签管理页面时,请求接口填充数据
this.fetchTagList();
}
},
resetReceptionForm() {
// 重置表单数据
this.receptionForm = {
id: "",
customerName: "",
contact: "",
customerSource: "",
gender: "女",
age: "",
dealershipId: "",
dealershipName: "",
salesId: "",
salesName: "",
salesPhone: "",
recordingCount: 0,
intendedModel: "",
infoCard: "",
remark: "",
detailedAddress: "",
contactCount: 0
};
// 重新加载当前登录用户信息
this.loadCurrentUserInfo();
},
onReceptionCancel() {
// 组件内部已处理取消逻辑,这里只需要重置表单数据
this.resetReceptionForm();
},
onReceptionSaveSuccess({ action, data }) {
// 保存成功后,更新本地表单数据
if (data) {
this.receptionForm = {
...this.receptionForm,
...data
};
}
// 重置表单
this.resetReceptionForm();
// 如果是开始接待操作,可以在这里处理后续逻辑
if (action === 'start') {
// 可以在这里添加开始接待后的逻辑,比如跳转到接待页面等
console.log('开始接待成功', data);
}
},
onReceptionSaveError({ action, error }) {
// 保存失败时的处理
console.error('保存失败:', error);
},
// 标签管理相关方法
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;
if (this.showTagTypeDropdown) {
this.$nextTick(() => {
this.updateTagTypeDropdownPosition();
});
}
},
updateTagTypeDropdownPosition() {
const query = uni.createSelectorQuery().in(this);
query.select('.tag-select').boundingClientRect((rect) => {
if (rect) {
const dropdownEl = this.$refs.tagTypeDropdown;
if (dropdownEl) {
// 在 uni-app 中,直接操作 DOM 需要使用 nextTick
this.$nextTick(() => {
const el = dropdownEl.$el || dropdownEl;
if (el && el.style) {
el.style.top = (rect.bottom + 8) + 'px';
el.style.left = rect.left + 'px';
el.style.width = rect.width + 'px';
}
});
}
}
}).exec();
},
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();
}
},
// 页面加载后的URL隐藏初始化只在页面完全稳定后执行
initUrlHidingAfterPageLoad() {
// #ifdef H5
console.log('[FurnitureReception][initUrlHidingAfterPageLoad] 开始页面加载后URL隐藏初始化');
// 路径映射
this.urlPathMap = {
'pages-subpackage/furniture_customer/furniture_customer': '/customer',
'pages/furniture_reception/furniture_reception': '/reception',
'pages/ai_qa/ai_qa': '/ai-qa',
'pages/ai_analysis/ai_analysis': '/ai-analysis',
'pages/xixi/xixi': '/xixi',
'pages/ucenter/ucenter': '/profile'
};
// 策略只在用户明确进行页面导航时才隐藏URL
// 避免在页面刷新或直接访问时执行,以防止空白页面
// 监听tabBar切换事件用户主动导航
this.setupTabNavigationHiding();
// 监听浏览器前进后退(用户主动导航)
this.setupBrowserNavigationHiding();
console.log('[FurnitureReception][initUrlHidingAfterPageLoad] URL隐藏系统已准备只在用户导航时激活');
// #endif
},
// 为当前页面尝试URL隐藏
attemptUrlHidingForCurrentPage() {
// #ifdef H5
try {
const pages = getCurrentPages();
if (pages.length === 0) {
console.log('[FurnitureReception][attemptUrlHidingForCurrentPage] 页面栈为空,跳过');
return;
}
const currentPage = pages[pages.length - 1];
const currentPath = currentPage.route;
if (!currentPath) {
console.log('[FurnitureReception][attemptUrlHidingForCurrentPage] 页面路径为空,跳过');
return;
}
// 只为当前页面的路径执行URL隐藏
if (currentPath === 'pages/furniture_reception/furniture_reception' && this.urlPathMap[currentPath]) {
const shortPath = this.urlPathMap[currentPath];
const currentPathname = window.location.pathname;
if (currentPathname !== shortPath) {
console.log(`[FurnitureReception][attemptUrlHidingForCurrentPage] 执行URL隐藏: ${currentPathname} -> ${shortPath}`);
// 使用try-catch包装history操作
try {
const newUrl = `${window.location.origin}${shortPath}`;
window.history.replaceState(null, '', newUrl);
console.log('[FurnitureReception][attemptUrlHidingForCurrentPage] URL隐藏成功');
} catch (historyError) {
console.warn('[FurnitureReception][attemptUrlHidingForCurrentPage] history操作失败:', historyError);
}
} else {
console.log('[FurnitureReception][attemptUrlHidingForCurrentPage] URL已经是隐藏状态');
}
}
} catch (error) {
console.warn('[FurnitureReception][attemptUrlHidingForCurrentPage] URL隐藏尝试失败:', error);
}
// #endif
},
// 设置tab导航时的URL隐藏
setupTabNavigationHiding() {
// #ifdef H5
console.log('[FurnitureReception][setupTabNavigationHiding] 设置tab导航监听');
// 监听uni-app的页面显示事件当通过tabBar切换到此页面时
if (typeof uni !== 'undefined' && uni.on) {
uni.on('onShow', () => {
console.log('[FurnitureReception][setupTabNavigationHiding] 页面显示事件触发可能是tab切换');
// 延迟检查确保是tab切换而不是页面刷新
setTimeout(() => {
this.attemptUrlHidingForCurrentPage();
}, 300);
});
}
console.log('[FurnitureReception][setupTabNavigationHiding] tab导航监听已设置');
// #endif
},
// 设置浏览器导航时的URL隐藏
setupBrowserNavigationHiding() {
// #ifdef H5
console.log('[FurnitureReception][setupBrowserNavigationHiding] 设置浏览器导航监听');
// 监听前进后退按钮
window.addEventListener('popstate', () => {
console.log('[FurnitureReception][setupBrowserNavigationHiding] 浏览器前进后退事件');
setTimeout(() => {
this.attemptUrlHidingForCurrentPage();
}, 200);
});
console.log('[FurnitureReception][setupBrowserNavigationHiding] 浏览器导航监听已设置');
// #endif
},
}
}
</script>
<style>
page {
background-color: #F5F5F5;
}
.page {
/* #ifndef MP-WEIXIN */
min-height: 100vh;
/* #endif */
/* #ifdef MP-WEIXIN */
/* 微信小程序:确保页面容器铺满整个视口 */
height: 100vh !important;
min-height: 100vh !important;
/* #endif */
background-color: #F5F5F5;
}
.content {
padding-top: 0; /* 移除padding-top因为tab页和scroll-view都使用绝对定位 */
margin-top: 0;
position: relative;
/* #ifndef MP-WEIXIN */
min-height: 100vh; /* 确保content有足够高度 */
/* #endif */
/* #ifdef MP-WEIXIN */
/* 微信小程序:确保内容容器铺满整个视口,底部导航栏是 fixed 定位,不占用文档流 */
height: 100vh !important;
min-height: 100vh !important;
max-height: 100vh !important;
/* #endif */
}
/* 隐藏导航栏占位区域(红色区域) */
::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-container {
/* 使用绝对定位从tab页底部开始到底部导航栏结束 */
position: absolute;
/* top值通过内联样式动态设置 */
left: 0;
right: 0;
/* #ifndef MP-WEIXIN */
bottom: 96rpx; /* H5环境底部导航栏高度 */
/* #endif */
/* #ifdef MP-WEIXIN */
/* 微信小程序:底部导航栏是 fixed 定位,不占用文档流,容器可以延伸到视口底部 */
bottom: 0 !important;
/* #endif */
}
/* 标签管理列表区:使用绝对定位,与服务状态列表保持一致 */
.tag-list {
/* 使用绝对定位从tab页底部开始到底部导航栏结束 */
position: absolute;
/* top值通过内联样式动态设置 */
left: 0;
right: 0;
/* #ifndef MP-WEIXIN */
bottom: 96rpx; /* H5环境底部导航栏高度 */
/* #endif */
/* #ifdef MP-WEIXIN */
/* 微信小程序:底部导航栏是 fixed 定位,不占用文档流,容器可以延伸到视口底部 */
bottom: 0 !important;
/* #endif */
background-color: #F5F5F5;
padding: 24rpx 16rpx 0 16rpx;
box-sizing: border-box;
}
/* 标签管理工具栏样式 */
.service-status-toolbar {
display: flex;
flex-wrap: nowrap;
align-items: center;
gap: 8rpx;
padding: 12rpx 8rpx;
margin-bottom: 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.tag-card-item {
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;
position: relative;
}
.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;
}
.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;
}
/* 标签管理样式 - 添加表单 */
.tag-management {
/* 使用绝对定位从tab页底部开始到底部导航栏结束 */
position: absolute;
/* top值通过内联样式动态设置 */
left: 0;
right: 0;
/* #ifndef MP-WEIXIN */
bottom: 96rpx; /* H5环境底部导航栏高度 */
/* #endif */
/* #ifdef MP-WEIXIN */
/* 微信小程序:底部导航栏是 fixed 定位,不占用文档流,容器可以延伸到视口底部 */
bottom: 0 !important;
/* #endif */
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: 32rpx;
box-sizing: border-box;
}
/* 表单卡片样式 */
.form-card {
background-color: #FFFFFF;
border-radius: 24rpx;
padding: 40rpx 24rpx 32rpx 24rpx;
margin-bottom: 32rpx;
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.06);
}
/* 第一个表单项特殊处理,确保顶部空间 */
.form-card .form-item:first-child {
padding-top: 24rpx;
margin-top: 16rpx;
}
/* 表单项样式 */
.form-item {
display: flex !important;
flex-direction: row !important;
flex-wrap: nowrap !important;
align-items: flex-start;
margin-bottom: 32rpx;
min-height: 88rpx;
padding-top: 8rpx;
box-sizing: border-box;
width: 100%;
overflow: hidden;
justify-content: center;
}
.form-item:last-child {
margin-bottom: 0;
}
/* 表单项标签样式 */
.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__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: 2rpx solid #E5E7EB;
border-radius: 16rpx;
padding: 0 24rpx;
font-size: 28rpx;
color: #333333;
box-sizing: border-box;
transition: all 0.3s 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__textarea {
flex: 1 !important;
flex-shrink: 1 !important;
flex-grow: 1 !important;
min-width: 0 !important;
max-width: none !important;
width: auto !important;
min-height: 160rpx;
background-color: #F9FAFB;
border: 2rpx solid #E5E7EB;
border-radius: 16rpx;
padding: 20rpx 24rpx;
font-size: 28rpx;
color: #333333;
box-sizing: border-box;
line-height: 1.6;
transition: all 0.3s ease;
}
.form-item__textarea:focus {
border-color: #007AFF;
background-color: #FFFFFF;
box-shadow: 0 0 0 4rpx rgba(0, 122, 255, 0.1);
}
/* 标签选择器样式 */
.tag-select-wrapper {
position: relative;
flex: 1;
flex-shrink: 1;
min-width: 0;
display: flex;
align-items: center;
overflow: hidden;
height: 88rpx;
}
.tag-select {
width: 100%;
height: 88rpx;
background-color: #F9FAFB;
border: 2rpx solid #E5E7EB;
border-radius: 16rpx;
padding: 0 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
cursor: pointer;
transition: all 0.3s ease;
}
.tag-select:active {
border-color: #007AFF;
background-color: #FFFFFF;
box-shadow: 0 0 0 4rpx rgba(0, 122, 255, 0.1);
}
.form-item__picker-text {
font-size: 28rpx;
color: #333333;
flex: 1;
}
.form-item__picker-placeholder {
font-size: 28rpx;
color: #9CA3AF;
flex: 1;
}
/* 下拉菜单样式 */
.tag-select-dropdown {
position: fixed;
background-color: #FFFFFF;
border: 2rpx solid #E5E7EB;
border-radius: 16rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.12);
z-index: 9999;
overflow: hidden;
max-height: 400rpx;
overflow-y: auto;
min-width: 200rpx;
}
.tag-select-dropdown__item {
padding: 24rpx;
border-bottom: 1rpx solid #F3F4F6;
transition: background-color 0.2s ease;
}
.tag-select-dropdown__item:last-child {
border-bottom: none;
}
.tag-select-dropdown__item:active {
background-color: #F9FAFB;
}
.tag-select-dropdown__item text {
font-size: 28rpx;
color: #333333;
}
.tag-select-dropdown__item text.active {
color: #007AFF;
font-weight: 500;
}
/* 下拉菜单遮罩 */
.tag-select-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: transparent;
z-index: 9998;
}
/* textarea类型表单项 - 标签和textarea在同一行但textarea可以换行 */
.form-item--textarea {
align-items: flex-start;
}
/* 开关类型表单项 - 横向布局 */
.form-item--switch {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.form-item--switch .form-item__label {
margin-bottom: 0;
}
/* 开关样式 */
.form-item switch {
margin-left: auto;
transform: scale(0.9);
}
/* 表单操作按钮区域 */
.form-actions {
display: flex;
gap: 24rpx;
padding-top: 16rpx;
}
.form-btn {
flex: 1;
height: 88rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 30rpx;
font-weight: 500;
transition: all 0.3s ease;
}
.form-btn--cancel {
background-color: #F3F4F6;
color: #6B7280;
}
.form-btn--cancel:active {
background-color: #E5E7EB;
opacity: 0.8;
}
.form-btn--save {
background-color: #007AFF;
color: #FFFFFF;
}
.form-btn--save:active {
background-color: #0056CC;
opacity: 0.9;
}
.form-btn__text {
font-size: 30rpx;
font-weight: 500;
}
</style>