Files
smartDriveEEUniApp/pages/champion/components/practice-tab.vue
2026-01-03 20:58:40 +08:00

1429 lines
36 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>
<scroll-view
class="tab-content practice-content"
scroll-y
@scrolltolower="onPracticeReachBottom">
<view class="service-status-toolbar">
<text class="toolbar-total">{{ practiceTotal }}</text>
<input
class="toolbar-input"
v-model="practiceQuery.title"
placeholder="知识标题"
placeholder-style="color: #b0b0b0"
confirm-type="search"
@confirm="onPracticeSearch"
/>
<input
class="toolbar-input"
v-model="practiceQuery.industry"
placeholder="应用行业"
placeholder-style="color: #b0b0b0"
confirm-type="search"
@confirm="onPracticeSearch"
/>
<view class="toolbar-actions">
<view class="toolbar-btn toolbar-btn--refresh" @click="onPracticeRefresh">
<uni-icons type="refresh" size="18" color="#2A68FF"></uni-icons>
<text>刷新</text>
</view>
<view class="toolbar-btn toolbar-btn--add" @click="showAddPractice">
<uni-icons type="plus" size="18" color="#2A68FF"></uni-icons>
</view>
</view>
</view>
<view
class="service-card practice-card-item"
v-for="(item, index) in practiceList"
:key="item.id || index">
<view class="card-header">
<view class="staff-info">
<view class="staff-avatar">
<text class="avatar-text">{{ (item.title || '知').charAt(0) }}</text>
</view>
<text class="staff-name">{{ item.title || '未命名知识' }}</text>
</view>
<view class="card-menu-trigger" @tap.stop="onPracticeActionBtnClick(item)">
<uni-icons type="more-filled" size="20" color="#999"></uni-icons>
</view>
</view>
<view class="customer-info">
<text class="customer-name" v-if="item.industry">所属行业: {{ item.industry }}</text>
</view>
<view class="customer-tags">
<view class="tag-item tag-blue" v-if="item.content || item.detail">
<text @tap.stop="showFullText('详情', item.content || item.detail)">详情: {{ getContentPreview(item.content || item.detail, 30) }}</text>
</view>
<view class="tag-item tag-orange" v-if="item.remark">
<text @tap.stop="showFullText('', item.remark)">提示词: {{ getContentPreview(item.remark, 30) }}</text>
</view>
</view>
<!-- 操作菜单 -->
<view
class="practice-action-menu"
v-if="selectedPracticeItem && selectedPracticeItem.id === item.id && showPracticeActionMenu"
@tap.stop>
<view class="practice-action-menu-item" @tap.stop="handleStartPractice(item)">
<text>陪练</text>
</view>
<view class="practice-action-menu-divider"></view>
<view class="practice-action-menu-item" @tap.stop="handleEditPractice(item)">
<text>编辑</text>
</view>
<view class="practice-action-menu-divider"></view>
<view class="practice-action-menu-item practice-action-menu-item--danger" @tap.stop="handleDeletePractice(item)">
<text>删除</text>
</view>
</view>
</view>
<view class="service-status-empty" v-if="!practiceLoading && !practiceList.length">
<text>暂无陪练</text>
</view>
<view class="service-status-loading-more" v-if="practiceLoading && practiceList.length">
<text>正在加载更多...</text>
</view>
</scroll-view>
<!-- 添加陪练弹出框 -->
<view class="material-popup-mask" v-if="showPracticePopup" @click="closePracticePopup">
</view>
<view class="material-popup" v-if="showPracticePopup" @click.stop>
<view class="popup-header">
<text class="popup-title">添加陪练</text>
<view class="popup-close" @click="closePracticePopup">
<uni-icons type="close" size="20" color="#999"></uni-icons>
</view>
</view>
<scroll-view class="popup-content" scroll-y>
<view class="form-card">
<view class="form-item">
<text class="form-item__label">知识标题</text>
<input
class="form-item__input"
v-model="practiceForm.title"
placeholder="请输入知识标题"
placeholder-style="color: #9ca3af"
maxlength="512"
/>
</view>
<view class="form-item">
<text class="form-item__label">知识分类</text>
<input
class="form-item__input"
v-model="practiceForm.category"
placeholder="请输入知识分类"
placeholder-style="color: #9ca3af"
maxlength="100"
:disabled="true"
readonly
/>
</view>
<view class="form-item">
<text class="form-item__label">应用行业</text>
<input
class="form-item__input"
v-model="practiceForm.industry"
placeholder="请输入应用行业"
placeholder-style="color: #9ca3af"
maxlength="100"
/>
</view>
<view class="form-item">
<text class="form-item__label">详情</text>
<textarea
class="form-item__textarea"
v-model="practiceForm.content"
placeholder="请输入详情内容"
placeholder-style="color: #9ca3af"
maxlength="2000"
/>
</view>
<view class="form-item">
<text class="form-item__label">提示词</text>
<textarea
class="form-item__textarea"
v-model="practiceForm.remark"
placeholder="请输入提示词"
placeholder-style="color: #9ca3af"
maxlength="500"
/>
</view>
</view>
</scroll-view>
<view class="popup-actions">
<view class="popup-btn popup-btn--cancel" @click="closePracticePopup">
<uni-icons class="popup-btn__icon" type="close" size="20" color="#2A68FF"></uni-icons>
<text class="popup-btn__text">取消</text>
</view>
<view class="popup-btn popup-btn--submit" @click="submitPractice">
<uni-icons class="popup-btn__icon" type="checkmarkempty" size="20" color="#2A68FF"></uni-icons>
<text class="popup-btn__text">提交</text>
</view>
</view>
</view>
<!-- 编辑陪练弹出框 -->
<view class="material-popup-mask" v-if="showEditPracticePopup" @click="closeEditPracticePopup">
</view>
<view class="material-popup" v-if="showEditPracticePopup" @click.stop>
<view class="popup-header">
<text class="popup-title">编辑陪练</text>
<view class="popup-close" @click="closeEditPracticePopup">
<uni-icons type="close" size="20" color="#999"></uni-icons>
</view>
</view>
<scroll-view class="popup-content" scroll-y>
<view class="form-card">
<view class="form-item">
<text class="form-item__label">知识标题</text>
<input
class="form-item__input"
v-model="editPracticeForm.title"
placeholder="请输入知识标题"
placeholder-style="color: #9ca3af"
maxlength="512"
/>
</view>
<view class="form-item">
<text class="form-item__label">知识分类</text>
<input
class="form-item__input"
v-model="editPracticeForm.category"
placeholder="请输入知识分类"
placeholder-style="color: #9ca3af"
maxlength="100"
:disabled="true"
readonly
/>
</view>
<view class="form-item">
<text class="form-item__label">应用行业</text>
<input
class="form-item__input"
v-model="editPracticeForm.industry"
placeholder="请输入应用行业"
placeholder-style="color: #9ca3af"
maxlength="100"
/>
</view>
<view class="form-item">
<text class="form-item__label">详情</text>
<textarea
class="form-item__textarea"
v-model="editPracticeForm.content"
placeholder="请输入详情内容"
placeholder-style="color: #9ca3af"
maxlength="2000"
/>
</view>
<view class="form-item">
<text class="form-item__label">提示词</text>
<textarea
class="form-item__textarea"
v-model="editPracticeForm.remark"
placeholder="请输入提示词"
placeholder-style="color: #9ca3af"
maxlength="500"
/>
</view>
</view>
</scroll-view>
<view class="popup-actions">
<view class="popup-btn popup-btn--cancel" @click="closeEditPracticePopup">
<uni-icons class="popup-btn__icon" type="close" size="20" color="#2A68FF"></uni-icons>
<text class="popup-btn__text">取消</text>
</view>
<view class="popup-btn popup-btn--submit" @click="submitEditPractice">
<uni-icons class="popup-btn__icon" type="checkmarkempty" size="20" color="#2A68FF"></uni-icons>
<text class="popup-btn__text">保存</text>
</view>
</view>
</view>
<!-- 陪练弹出框 -->
<view class="material-popup-mask" v-if="showPracticeTrainingPopup" @click="closePracticeTrainingPopup">
</view>
<view class="material-popup practice-training-popup" v-if="showPracticeTrainingPopup" @click.stop>
<view class="popup-header">
<text class="popup-title">生成陪练</text>
<view class="popup-close" @click="closePracticeTrainingPopup">
<uni-icons type="close" size="20" color="#999"></uni-icons>
</view>
</view>
<scroll-view class="popup-content" scroll-y>
<view class="form-card">
<view class="form-item">
<text class="form-item__label">陪练标题</text>
<input
class="form-item__input"
v-model="practiceTrainingForm.title"
placeholder="请输入陪练标题"
placeholder-style="color: #9ca3af"
maxlength="512"
/>
</view>
<view class="form-item">
<text class="form-item__label">陪练场景</text>
<input
class="form-item__input"
v-model="practiceTrainingForm.scenario"
placeholder="请输入陪练场景"
placeholder-style="color: #9ca3af"
maxlength="500"
:disabled="true"
readonly
/>
</view>
<view class="form-item">
<text class="form-item__label">参与人姓名</text>
<input
class="form-item__input"
v-model="practiceTrainingForm.participantName"
placeholder="请输入参与人姓名"
placeholder-style="color: #9ca3af"
maxlength="100"
/>
</view>
<view class="form-item">
<text class="form-item__label">参与人电话</text>
<input
class="form-item__input"
v-model="practiceTrainingForm.participantPhone"
placeholder="请输入参与人电话"
placeholder-style="color: #9ca3af"
type="number"
maxlength="20"
/>
</view>
</view>
</scroll-view>
<view class="popup-actions">
<view class="popup-btn popup-btn--cancel" @click="closePracticeTrainingPopup">
<uni-icons class="popup-btn__icon" type="close" size="20" color="#2A68FF"></uni-icons>
<text class="popup-btn__text">取消</text>
</view>
<view class="popup-btn popup-btn--submit" @click="submitPracticeTraining">
<uni-icons class="popup-btn__icon" type="checkmarkempty" size="20" color="#2A68FF"></uni-icons>
<text class="popup-btn__text">生成</text>
</view>
</view>
</view>
<!-- 内容详情弹出框 -->
<view class="content-popup-mask" v-if="showContentPopup" @click="closeContentPopup">
</view>
<view class="content-popup" v-if="showContentPopup" @click.stop>
<view class="content-popup-header">
<text class="content-popup-title">{{ contentPopupTitle }}</text>
<view class="content-popup-close" @click="closeContentPopup">
<uni-icons type="close" size="20" color="#999"></uni-icons>
</view>
</view>
<scroll-view class="content-popup-body" scroll-y>
<text class="content-popup-text">{{ contentPopupText }}</text>
</scroll-view>
</view>
<!-- 陪练操作菜单遮罩层 -->
<view
class="practice-action-menu-mask"
v-if="showPracticeActionMenu"
@tap="closePracticeActionMenu">
</view>
</view>
</template>
<script>
import { getApiUrl } from "@/common/config.js";
// 获取租户ID
function getTenantId() {
try {
return uni.getStorageSync('backend-tenant-id') || '';
} catch (e) {
console.error('获取 tenantId 失败:', e);
return '';
}
}
export default {
name: 'PracticeTab',
data() {
return {
// 陪练相关数据
practiceList: [],
practiceLoading: false,
practiceTotal: 0,
practicePage: {
current: 1,
size: 10
},
practiceQuery: {
title: '',
industry: '',
creator: '',
status: ''
},
// 添加陪练弹出框
showPracticePopup: false,
practiceForm: {
title: '',
category: '',
industry: '',
content: '',
remark: ''
},
// 编辑陪练弹出框
showEditPracticePopup: false,
editPracticeForm: {
id: '',
title: '',
category: '',
industry: '',
content: '',
remark: ''
},
// 陪练操作菜单
selectedPracticeItem: null,
showPracticeActionMenu: false,
// 陪练弹出框
showPracticeTrainingPopup: false,
practiceTrainingForm: {
scenarioId: '',
title: '',
scenario: '',
participantName: '',
participantPhone: ''
},
// 内容详情弹出框
showContentPopup: false,
contentPopupTitle: '',
contentPopupText: ''
}
},
mounted() {
this.fetchPracticeList();
},
methods: {
// 陪练相关方法
onPracticeSearch() {
this.practicePage.current = 1;
this.fetchPracticeList({ force: true });
},
onPracticeRefresh() {
this.practicePage.current = 1;
this.fetchPracticeList({ force: true });
},
onPracticeReachBottom() {
// 已在加载中或全部加载完成则不再触发
if (this.practiceLoading) return;
if (this.practiceList.length >= this.practiceTotal) return;
this.practicePage.current += 1;
this.fetchPracticeList();
},
async fetchPracticeList({ force = false } = {}) {
if (this.practiceLoading && !force) {
return;
}
this.practiceLoading = true;
try {
const queryParams = {
current: this.practicePage.current,
size: this.practicePage.size,
category: 'AI陪练虚拟人' // 默认添加知识分类参数
};
const trimmedTitle = this.practiceQuery?.title?.trim();
const trimmedIndustry = this.practiceQuery?.industry?.trim();
const trimmedCreator = this.practiceQuery?.creator?.trim();
const trimmedStatus = this.practiceQuery?.status?.trim();
if (trimmedTitle) {
queryParams.title = trimmedTitle;
}
if (trimmedIndustry) {
queryParams.industry = trimmedIndustry;
}
if (trimmedCreator) {
queryParams.creator = trimmedCreator;
}
if (trimmedStatus) {
queryParams.status = trimmedStatus;
}
const queryString = Object.keys(queryParams)
.map(key => `${key}=${encodeURIComponent(queryParams[key])}`)
.join('&');
const url = `${getApiUrl('/api/knowledgeBase/list')}?${queryString}`;
// 获取租户ID
const tenantId = getTenantId();
const headers = {};
if (tenantId) {
headers['X-Tenant-Id'] = tenantId;
}
const res = await uni.request({
url,
method: 'POST',
header: headers,
timeout: 10000
});
if (res.statusCode === 200 && res.data && res.data.success) {
const records = Array.isArray(res.data.data) ? res.data.data : [];
// 第一页或强制刷新时重置列表,否则追加
if (this.practicePage.current === 1 || force) {
this.practiceList = records;
} else {
this.practiceList = this.practiceList.concat(records);
}
this.practiceTotal = Number(res.data.total) || 0;
} else {
this.practiceList = [];
this.practiceTotal = 0;
uni.showToast({
title: res.data?.message || '获取陪练列表失败',
icon: 'none'
});
}
} catch (error) {
console.error('获取陪练列表失败:', error);
uni.showToast({
title: '获取陪练列表失败,请稍后重试',
icon: 'none'
});
} finally {
this.practiceLoading = false;
}
},
onPracticeActionBtnClick(item) {
// 如果已经选中当前项,则关闭菜单
if (this.selectedPracticeItem && this.selectedPracticeItem.id === item.id && this.showPracticeActionMenu) {
this.closePracticeActionMenu();
return;
}
// 显示菜单
this.selectedPracticeItem = item;
this.showPracticeActionMenu = true;
},
closePracticeActionMenu() {
this.showPracticeActionMenu = false;
this.selectedPracticeItem = null;
},
handleStartPractice(item) {
this.closePracticeActionMenu();
// 获取当前登录用户信息
let participantName = '';
let participantPhone = '';
try {
const loginResponse = uni.getStorageSync('backend-login-response') || {};
const phone = loginResponse.phone || '';
const userName = loginResponse.userName || '';
// 参与人电话默认为当前登录人的电话
participantPhone = phone;
// 参与人姓名默认为当前登录人的登录手机号或者登录账号(优先手机号)
participantName = phone || userName;
} catch (e) {
console.error('获取登录用户信息失败:', e);
}
// 填充陪练表单
this.practiceTrainingForm = {
scenarioId: item.id || '',
title: item.title || '',
scenario: item.content || item.detail || '',
participantName: participantName,
participantPhone: participantPhone
};
this.showPracticeTrainingPopup = true;
},
closePracticeTrainingPopup() {
this.showPracticeTrainingPopup = false;
this.practiceTrainingForm = {
scenarioId: '',
title: '',
scenario: '',
participantName: '',
participantPhone: ''
};
},
async submitPracticeTraining() {
// 表单验证
if (!this.practiceTrainingForm.title || !this.practiceTrainingForm.title.trim()) {
uni.showToast({
title: "请输入陪练标题",
icon: "none"
});
return;
}
if (!this.practiceTrainingForm.participantName || !this.practiceTrainingForm.participantName.trim()) {
uni.showToast({
title: "请输入参与人姓名",
icon: "none"
});
return;
}
if (!this.practiceTrainingForm.participantPhone || !this.practiceTrainingForm.participantPhone.trim()) {
uni.showToast({
title: "请输入参与人电话",
icon: "none"
});
return;
}
try {
uni.showLoading({
title: "创建中..."
});
// 获取租户ID
const tenantId = getTenantId();
const payload = {
scenarioId: this.practiceTrainingForm.scenarioId || '',
title: this.practiceTrainingForm.title?.trim() || '',
scenario: this.practiceTrainingForm.scenario?.trim() || '',
participantName: this.practiceTrainingForm.participantName.trim(),
participantPhone: this.practiceTrainingForm.participantPhone.trim()
};
// 添加租户ID到payload
if (tenantId) {
payload.tenantId = tenantId;
}
const headers = {
"Content-Type": "application/json"
};
// 添加租户ID到header
if (tenantId) {
headers['X-Tenant-Id'] = tenantId;
}
const res = await uni.request({
url: getApiUrl('/api/trainingMain/add'),
method: 'POST',
data: payload,
header: headers,
timeout: 15000
});
uni.hideLoading();
if (res.statusCode === 200 && res.data && (res.data.success || res.data.code === 200)) {
uni.showToast({
title: res.data?.message || "创建成功",
icon: "success"
});
// 关闭弹出框
this.closePracticeTrainingPopup();
// 通知父组件切换到陪练tab
this.$emit('switch-to-training');
} else {
uni.showToast({
title: res.data?.message || "创建失败",
icon: "none"
});
}
} catch (error) {
uni.hideLoading();
console.error("创建陪练失败:", error);
uni.showToast({
title: error?.message || "创建失败,请重试",
icon: "none"
});
}
},
handleEditPractice(item) {
this.closePracticeActionMenu();
// 填充编辑表单
this.editPracticeForm = {
id: item.id,
title: item.title || '',
category: item.category || '',
industry: item.industry || '',
content: item.content || '',
remark: item.remark || ''
};
this.showEditPracticePopup = true;
},
closeEditPracticePopup() {
this.showEditPracticePopup = false;
this.editPracticeForm = {
id: '',
title: '',
category: '',
industry: '',
content: '',
remark: ''
};
},
async submitEditPractice() {
// 表单验证
if (!this.editPracticeForm.title || !this.editPracticeForm.title.trim()) {
uni.showToast({
title: "请输入知识标题",
icon: "none"
});
return;
}
try {
uni.showLoading({
title: "保存中..."
});
// 获取租户ID
const tenantId = getTenantId();
const headers = {
"Content-Type": "application/json"
};
if (tenantId) {
headers['X-Tenant-Id'] = tenantId;
// 同时在数据中也添加租户ID如果后端需要
if (!this.editPracticeForm.tenantId) {
this.editPracticeForm.tenantId = tenantId;
}
}
const res = await uni.request({
url: getApiUrl('/api/knowledgeBase/update'),
method: 'PUT',
data: this.editPracticeForm,
header: headers,
timeout: 15000
});
uni.hideLoading();
if (res.statusCode === 200 && res.data && (res.data.success || res.data.code === 200)) {
uni.showToast({
title: res.data?.message || "保存成功",
icon: "success"
});
// 关闭弹出框并刷新列表
this.closeEditPracticePopup();
this.practicePage.current = 1;
this.fetchPracticeList({ force: true });
} else {
uni.showToast({
title: res.data?.message || "保存失败",
icon: "none"
});
}
} catch (error) {
uni.hideLoading();
console.error("保存陪练失败:", error);
uni.showToast({
title: error?.message || "保存失败,请重试",
icon: "none"
});
}
},
handleDeletePractice(item) {
this.closePracticeActionMenu();
uni.showModal({
title: '提示',
content: '确认删除该陪练吗?',
success: async (res) => {
if (res.confirm) {
try {
uni.showLoading({
title: "删除中..."
});
// 获取租户ID
const tenantId = getTenantId();
const headers = {};
if (tenantId) {
headers['X-Tenant-Id'] = tenantId;
}
const deleteRes = await uni.request({
url: getApiUrl(`/api/knowledgeBase/delete/${item.id}`),
method: 'DELETE',
header: headers,
timeout: 15000
});
uni.hideLoading();
if (deleteRes.statusCode === 200 && deleteRes.data && (deleteRes.data.success || deleteRes.data.code === 200)) {
uni.showToast({
title: deleteRes.data?.message || "删除成功",
icon: "success"
});
// 刷新列表
this.practicePage.current = 1;
this.fetchPracticeList({ force: true });
} else {
uni.showToast({
title: deleteRes.data?.message || "删除失败",
icon: "none"
});
}
} catch (error) {
uni.hideLoading();
console.error("删除陪练失败:", error);
uni.showToast({
title: error?.message || "删除失败,请重试",
icon: "none"
});
}
}
}
});
},
// 添加陪练相关方法
showAddPractice() {
this.showPracticePopup = true;
this.practiceForm = {
title: '',
category: 'AI陪练虚拟人', // 固定值,不可修改
industry: '汽车销售', // 默认值
content: '',
remark: ''
};
},
closePracticePopup() {
this.showPracticePopup = false;
},
async submitPractice() {
// 表单验证
if (!this.practiceForm.title || !this.practiceForm.title.trim()) {
uni.showToast({
title: "请输入知识标题",
icon: "none"
});
return;
}
try {
uni.showLoading({
title: "提交中..."
});
// 获取租户ID
const tenantId = getTenantId();
const payload = {
title: this.practiceForm.title.trim(),
category: this.practiceForm.category?.trim() || '',
industry: this.practiceForm.industry?.trim() || '',
content: this.practiceForm.content?.trim() || '',
remark: this.practiceForm.remark?.trim() || ''
};
// 添加租户ID到payload
if (tenantId) {
payload.tenantId = tenantId;
}
const headers = {
"Content-Type": "application/json"
};
// 添加租户ID到header
if (tenantId) {
headers['X-Tenant-Id'] = tenantId;
}
const res = await uni.request({
url: getApiUrl('/api/knowledgeBase/add'),
method: 'POST',
data: payload,
header: headers,
timeout: 15000
});
uni.hideLoading();
if (res.statusCode === 200 && res.data && (res.data.success || res.data.code === 200)) {
uni.showToast({
title: res.data?.message || "提交成功",
icon: "success"
});
// 关闭弹出框并刷新列表
this.closePracticePopup();
this.practicePage.current = 1;
this.fetchPracticeList({ force: true });
} else {
uni.showToast({
title: res.data?.message || "提交失败",
icon: "none"
});
}
} catch (error) {
uni.hideLoading();
console.error("提交陪练失败:", error);
uni.showToast({
title: error?.message || "提交失败,请重试",
icon: "none"
});
}
},
getContentPreview(content, limit = 30) {
if (!content) return '';
const text = String(content).replace(/\s+/g, ' ').trim();
if (!text) return '';
return text.length > limit ? `${text.substring(0, limit)}...` : text;
},
showFullText(title, content) {
if (!content) return;
this.contentPopupTitle = title || '详情';
this.contentPopupText = String(content);
this.showContentPopup = true;
},
closeContentPopup() {
this.showContentPopup = false;
this.contentPopupTitle = '';
this.contentPopupText = '';
}
}
}
</script>
<style scoped>
/* 内容区域 - 使用绝对定位从tab页底部开始到底部导航栏结束 */
.tab-content {
position: absolute;
top: 160rpx; /* 导航栏(88rpx) + tab页(72rpx) = 160rpx */
left: 0;
right: 0;
bottom: 96rpx; /* 底部导航栏高度 */
background-color: #F5F5F5;
padding: 24rpx 16rpx 0 16rpx; /* 顶部24rpx间距底部0 */
box-sizing: border-box;
overflow-y: auto;
}
/* 素材内容区域和陪练内容区域使用相同定位 */
.practice-content {
position: absolute;
top: 160rpx; /* 导航栏(88rpx) + tab页(72rpx) = 160rpx */
left: 0;
right: 0;
bottom: 96rpx; /* 底部导航栏高度 */
background-color: #F5F5F5;
padding: 24rpx 16rpx 0 16rpx; /* 顶部24rpx间距与列表项间距一致底部0 */
box-sizing: border-box;
overflow-y: auto;
}
.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;
}
.toolbar-btn text {
color: #2A68FF;
}
.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;
}
.practice-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;
position: relative;
}
.card-menu-trigger {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
flex-shrink: 0;
margin-left: auto;
margin-right: 0;
margin-top: 0;
margin-bottom: 0;
flex-grow: 0;
position: relative;
padding: 0;
}
.card-menu-trigger:active {
opacity: 0.7;
}
/* 陪练操作菜单样式 */
.practice-card-item {
position: relative;
}
.practice-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;
}
.practice-action-menu-item {
padding: 24rpx 32rpx;
font-size: 28rpx;
color: #333;
text-align: center;
background-color: #FFFFFF;
}
.practice-action-menu-item:active {
background-color: #F5F5F5;
}
.practice-action-menu-item--danger {
color: #FF5722;
}
.practice-action-menu-divider {
height: 1rpx;
background-color: #E0E0E0;
margin: 0 16rpx;
}
.practice-action-menu-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: transparent;
z-index: 99;
}
.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;
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;
width: 100%;
max-width: 100%;
}
.customer-name {
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;
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;
}
.service-status-empty {
padding: 48rpx 0;
text-align: center;
color: #999;
font-size: 28rpx;
}
.service-status-loading-more {
padding: 24rpx 0;
text-align: center;
color: #999;
font-size: 26rpx;
}
/* 添加素材弹出框样式 */
.material-popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
}
.material-popup {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: calc(100vw - 80rpx);
max-width: 90%;
box-sizing: border-box;
height: 80vh;
max-height: 80vh;
background-color: #FFFFFF;
border-radius: 16rpx;
z-index: 1000;
display: flex;
overflow-y: auto;
flex-direction: column;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.2);
overflow: hidden;
}
.popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx;
border-bottom: 1px solid #E0E0E0;
flex-shrink: 0;
}
.popup-title {
font-size: 36rpx;
font-weight: 500;
color: #333;
}
.popup-close {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
}
.popup-content {
flex: 1;
min-height: 0;
padding: 20rpx 20rpx 28rpx;
box-sizing: border-box;
overflow-y: auto;
}
.form-card {
width: 100%;
box-sizing: border-box;
}
.form-item {
display: flex;
align-items: center;
margin-bottom: 20rpx;
gap: 16rpx;
}
.form-item__label {
font-size: 32rpx;
color: #333;
font-weight: 500;
width: 160rpx;
flex-shrink: 0;
text-align: left;
}
.form-item__input {
flex: 1;
min-width: 0;
height: 88rpx;
background-color: #f9fafb;
border-radius: 12rpx;
padding: 0 24rpx;
font-size: 32rpx;
color: #333;
box-sizing: border-box;
}
/* 只读输入框样式 */
.form-item__input[disabled],
.form-item__input[readonly] {
background-color: #f3f4f6;
color: #6b7280;
cursor: not-allowed;
}
.form-item__textarea {
flex: 1;
min-width: 0;
min-height: 200rpx;
background-color: #f9fafb;
border-radius: 12rpx;
padding: 24rpx;
font-size: 32rpx;
color: #333;
box-sizing: border-box;
}
.popup-actions {
display: flex;
gap: 24rpx;
padding: 24rpx;
border-top: 1px solid #E0E0E0;
flex-shrink: 0;
}
.popup-btn {
flex: 1;
height: 88rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
}
.popup-btn--cancel {
background-color: #f3f4f6;
}
.popup-btn--submit {
/* 对齐取消按钮的风格:浅色背景与边框 */
background-color: #f3f4f6;
border: 1px solid #d1d5db;
}
.popup-btn__text {
font-size: 32rpx;
font-weight: 500;
}
.popup-btn__icon {
margin-right: 8rpx;
}
.popup-btn--cancel .popup-btn__text {
color: #2A68FF;
}
.popup-btn--submit .popup-btn__text {
color: #2A68FF;
}
/* 内容详情弹出框样式 */
.content-popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
}
.content-popup {
position: fixed;
left: 10%;
right: 10%;
top: 50%;
transform: translateY(-50%);
box-sizing: border-box;
max-height: calc(100vh - 320rpx);
height: auto;
background-color: #FFFFFF;
border-radius: 16rpx;
z-index: 1000;
display: flex;
flex-direction: column;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.2);
overflow: hidden;
}
.content-popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx;
border-bottom: 1px solid #E0E0E0;
flex-shrink: 0;
}
.content-popup-title {
font-size: 36rpx;
font-weight: 500;
color: #333;
}
.content-popup-close {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
}
.content-popup-body {
flex: 1;
min-height: 0;
padding: 32rpx;
box-sizing: border-box;
overflow-y: auto;
}
.content-popup-text {
font-size: 32rpx;
color: #333;
line-height: 1.8;
white-space: pre-wrap;
word-wrap: break-word;
}
.practice-training-popup {
max-height: 80vh;
}
</style>