1243 lines
31 KiB
Vue
1243 lines
31 KiB
Vue
<template>
|
||
<view>
|
||
<scroll-view
|
||
class="tab-content training-content"
|
||
scroll-y
|
||
@scrolltolower="onTrainingReachBottom">
|
||
<view class="service-status-toolbar">
|
||
<text class="toolbar-total">共{{ trainingTotal }}条</text>
|
||
<picker
|
||
class="toolbar-picker"
|
||
mode="selector"
|
||
:range="finishStatusOptions"
|
||
range-key="label"
|
||
:value="finishStatusIndex >= 0 ? finishStatusIndex : 0"
|
||
@change="onFinishStatusChange">
|
||
<view class="toolbar-picker-view">
|
||
<text :class="finishStatusIndex === -1 ? 'toolbar-picker-placeholder' : 'toolbar-picker-text'">
|
||
{{ finishStatusIndex === -1 ? '状态' : finishStatusOptions[finishStatusIndex].label }}
|
||
</text>
|
||
<uni-icons type="bottom" size="14" color="#999"></uni-icons>
|
||
</view>
|
||
</picker>
|
||
<input
|
||
class="toolbar-input"
|
||
v-model="trainingQuery.participantName"
|
||
placeholder="参与人姓名"
|
||
placeholder-style="color: #b0b0b0"
|
||
confirm-type="search"
|
||
@confirm="onTrainingSearch"
|
||
/>
|
||
<input
|
||
class="toolbar-input"
|
||
v-model="trainingQuery.participantPhone"
|
||
placeholder="参与人电话"
|
||
placeholder-style="color: #b0b0b0"
|
||
confirm-type="search"
|
||
@confirm="onTrainingSearch"
|
||
/>
|
||
<view class="toolbar-actions">
|
||
<view class="toolbar-btn toolbar-btn--refresh" @click="onTrainingRefresh">
|
||
<uni-icons type="refresh" size="18" color="#2A68FF"></uni-icons>
|
||
<text>刷新</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view
|
||
class="service-card training-card-item"
|
||
v-for="(item, index) in trainingList"
|
||
:key="item.id || index"
|
||
:class="{ 'training-card-item--active': selectedTrainingItem && selectedTrainingItem.id === item.id }">
|
||
<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="training-card-action-btn" @click.stop="onTrainingActionBtnClick(item)">
|
||
<uni-icons type="more-filled" size="20" color="#999"></uni-icons>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 操作菜单 -->
|
||
<view
|
||
class="training-action-menu"
|
||
v-if="selectedTrainingItem && selectedTrainingItem.id === item.id && showTrainingActionMenu"
|
||
@click.stop>
|
||
<view class="training-action-menu-item" @click="startTraining(item)">
|
||
<text>开始</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="customer-info">
|
||
<text class="customer-name" v-if="item.scenario">陪练场景: {{ item.scenario }}</text>
|
||
</view>
|
||
|
||
<view class="customer-tags">
|
||
<view class="tag-item tag-blue" v-if="item.participantName">
|
||
<text>参与人: {{ item.participantName }}</text>
|
||
</view>
|
||
<view class="tag-item tag-orange" v-if="item.participantPhone">
|
||
<text>电话: {{ item.participantPhone }}</text>
|
||
</view>
|
||
<view class="tag-item tag-status" v-if="item.finishStatus">
|
||
<text>状态: {{ getFinishStatusLabel(item.finishStatus) }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="service-duration" v-if="item.endTime">
|
||
<text>结束时间: {{ formatDateTime(item.endTime) }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="service-status-empty" v-if="!trainingLoading && !trainingList.length">
|
||
<text>暂无陪练记录</text>
|
||
</view>
|
||
<view class="service-status-loading-more" v-if="trainingLoading && trainingList.length">
|
||
<text>正在加载更多...</text>
|
||
</view>
|
||
<!-- 遮罩层,点击关闭菜单 -->
|
||
<view
|
||
class="training-action-menu-mask"
|
||
v-if="showTrainingActionMenu"
|
||
@click="closeTrainingActionMenu">
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 陪练中列表弹出框 -->
|
||
<view class="material-popup-mask" v-if="showTrainingItemListPopup" @click="closeTrainingItemListPopup">
|
||
</view>
|
||
<view class="material-popup training-item-list-popup" v-if="showTrainingItemListPopup" @click.stop>
|
||
<view class="popup-header">
|
||
<text class="popup-title">陪练中</text>
|
||
<view class="popup-close" @click="closeTrainingItemListPopup">
|
||
<uni-icons type="close" size="20" color="#999"></uni-icons>
|
||
</view>
|
||
</view>
|
||
<scroll-view class="popup-content" scroll-y @scrolltolower="onTrainingItemListReachBottom">
|
||
<view
|
||
class="service-card training-item-card"
|
||
v-for="(item, index) in trainingItemList"
|
||
:key="item.id || index">
|
||
<view class="card-header">
|
||
<view class="staff-info">
|
||
<view class="staff-avatar">
|
||
<text class="avatar-text">{{ (item.question || '题').charAt(0) }}</text>
|
||
</view>
|
||
<text class="staff-name">{{ item.question || '未命名题目' }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="customer-info" v-if="item.answer">
|
||
<text class="customer-name">答案: {{ item.answer }}</text>
|
||
</view>
|
||
|
||
<!-- 回答输入框 -->
|
||
<view class="form-item">
|
||
|
||
<textarea
|
||
class="form-item__textarea"
|
||
v-model="trainingItemAnswers[item.id]"
|
||
placeholder="请输入回答"
|
||
placeholder-style="color: #9ca3af"
|
||
maxlength="2000"
|
||
/>
|
||
</view>
|
||
|
||
<!-- 操作按钮 -->
|
||
<view class="training-item-actions">
|
||
<view class="action-btn action-btn--continue" @click="handleContinue(item)">
|
||
<text>继续</text>
|
||
</view>
|
||
<view class="action-btn action-btn--end" @click="handleEnd(item)">
|
||
<text>结束</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="service-status-empty" v-if="!trainingItemLoading && !trainingItemList.length">
|
||
<text>暂无记录</text>
|
||
</view>
|
||
<view class="service-status-loading-more" v-if="trainingItemLoading && trainingItemList.length">
|
||
<text>正在加载更多...</text>
|
||
</view>
|
||
</scroll-view>
|
||
</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: 'TrainingTab',
|
||
data() {
|
||
return {
|
||
// 陪练列表相关数据
|
||
trainingList: [],
|
||
trainingLoading: false,
|
||
trainingTotal: 0,
|
||
trainingPage: {
|
||
current: 1,
|
||
size: 10
|
||
},
|
||
trainingQuery: {
|
||
participantName: '',
|
||
participantPhone: '',
|
||
finishStatus: ''
|
||
},
|
||
// 状态选项
|
||
finishStatusOptions: [
|
||
{ value: 'init', label: '初始状态' },
|
||
{ value: 'finished', label: '完成状态' }
|
||
],
|
||
finishStatusIndex: -1, // -1表示未选择,不发送finishStatus参数
|
||
// 陪练操作菜单
|
||
selectedTrainingItem: null,
|
||
showTrainingActionMenu: false,
|
||
// 陪练中列表弹出框
|
||
showTrainingItemListPopup: false,
|
||
trainingItemList: [],
|
||
trainingItemLoading: false,
|
||
trainingItemTotal: 0,
|
||
trainingItemPage: {
|
||
current: 1,
|
||
size: 10
|
||
},
|
||
currentTrainingParentId: '',
|
||
// 存储每个陪练项的答案输入
|
||
trainingItemAnswers: {}
|
||
}
|
||
},
|
||
mounted() {
|
||
this.fetchTrainingList();
|
||
},
|
||
methods: {
|
||
// 状态选择改变事件
|
||
onFinishStatusChange(e) {
|
||
const index = parseInt(e.detail.value);
|
||
this.finishStatusIndex = index;
|
||
if (index >= 0 && index < this.finishStatusOptions.length) {
|
||
this.trainingQuery.finishStatus = this.finishStatusOptions[index].value;
|
||
} else {
|
||
this.trainingQuery.finishStatus = '';
|
||
this.finishStatusIndex = 0;
|
||
}
|
||
this.onTrainingSearch();
|
||
},
|
||
// 获取状态标签
|
||
getFinishStatusLabel(status) {
|
||
if (status === 'init') {
|
||
return '初始状态';
|
||
} else if (status === 'finished') {
|
||
return '完成状态';
|
||
}
|
||
return status || '';
|
||
},
|
||
// 陪练列表相关方法
|
||
onTrainingSearch() {
|
||
this.trainingPage.current = 1;
|
||
this.fetchTrainingList({ force: true });
|
||
},
|
||
onTrainingRefresh() {
|
||
this.trainingPage.current = 1;
|
||
this.fetchTrainingList({ force: true });
|
||
},
|
||
onTrainingReachBottom() {
|
||
// 已在加载中或全部加载完成则不再触发
|
||
if (this.trainingLoading) return;
|
||
if (this.trainingList.length >= this.trainingTotal) return;
|
||
this.trainingPage.current += 1;
|
||
this.fetchTrainingList();
|
||
},
|
||
async fetchTrainingList({ force = false } = {}) {
|
||
if (this.trainingLoading && !force) {
|
||
return;
|
||
}
|
||
this.trainingLoading = true;
|
||
try {
|
||
// 构建查询参数
|
||
const queryParams = {
|
||
current: this.trainingPage.current,
|
||
size: this.trainingPage.size
|
||
};
|
||
|
||
const trimmedParticipantName = this.trainingQuery?.participantName?.trim();
|
||
const trimmedParticipantPhone = this.trainingQuery?.participantPhone?.trim();
|
||
const finishStatus = this.trainingQuery?.finishStatus;
|
||
|
||
if (trimmedParticipantName) {
|
||
queryParams.participantName = trimmedParticipantName;
|
||
}
|
||
if (trimmedParticipantPhone) {
|
||
queryParams.participantPhone = trimmedParticipantPhone;
|
||
}
|
||
if (finishStatus) {
|
||
queryParams.finishStatus = finishStatus;
|
||
}
|
||
|
||
// 将参数转换为 URL 查询字符串
|
||
const queryString = Object.keys(queryParams)
|
||
.map(key => `${key}=${encodeURIComponent(queryParams[key])}`)
|
||
.join('&');
|
||
const url = `${getApiUrl('/api/trainingMain/list')}?${queryString}`;
|
||
|
||
// 获取租户ID
|
||
const tenantId = getTenantId();
|
||
const headers = {};
|
||
if (tenantId) {
|
||
headers['X-Tenant-Id'] = tenantId;
|
||
// 同时添加到查询参数中
|
||
queryParams.tenantId = tenantId;
|
||
}
|
||
|
||
// 重新构建查询字符串(包含tenantId)
|
||
const finalQueryString = Object.keys(queryParams)
|
||
.map(key => `${key}=${encodeURIComponent(queryParams[key])}`)
|
||
.join('&');
|
||
const finalUrl = `${getApiUrl('/api/trainingMain/list')}?${finalQueryString}`;
|
||
|
||
const res = await uni.request({
|
||
url: finalUrl,
|
||
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.trainingPage.current === 1 || force) {
|
||
this.trainingList = records;
|
||
} else {
|
||
this.trainingList = this.trainingList.concat(records);
|
||
}
|
||
this.trainingTotal = Number(res.data.total) || 0;
|
||
} else {
|
||
this.trainingList = [];
|
||
this.trainingTotal = 0;
|
||
uni.showToast({
|
||
title: res.data?.message || '获取陪练列表失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('获取陪练列表失败:', error);
|
||
uni.showToast({
|
||
title: '获取陪练列表失败,请稍后重试',
|
||
icon: 'none'
|
||
});
|
||
} finally {
|
||
this.trainingLoading = false;
|
||
}
|
||
},
|
||
// 陪练操作菜单相关方法
|
||
onTrainingActionBtnClick(item) {
|
||
// 如果已经选中当前项,则关闭菜单
|
||
if (this.selectedTrainingItem && this.selectedTrainingItem.id === item.id && this.showTrainingActionMenu) {
|
||
this.closeTrainingActionMenu();
|
||
return;
|
||
}
|
||
|
||
// 显示菜单
|
||
this.selectedTrainingItem = item;
|
||
this.showTrainingActionMenu = true;
|
||
},
|
||
closeTrainingActionMenu() {
|
||
this.showTrainingActionMenu = false;
|
||
this.selectedTrainingItem = null;
|
||
},
|
||
async startTraining(item) {
|
||
this.closeTrainingActionMenu();
|
||
// 打开详情列表弹窗
|
||
this.currentTrainingParentId = item.id || '';
|
||
this.trainingItemList = [];
|
||
this.showTrainingItemListPopup = true;
|
||
// 加载详情列表(调用listAll接口)
|
||
await this.fetchTrainingItemListAll(item.id || '');
|
||
},
|
||
closeTrainingItemListPopup() {
|
||
this.showTrainingItemListPopup = false;
|
||
this.currentTrainingParentId = '';
|
||
this.trainingItemPage.current = 1;
|
||
this.trainingItemList = [];
|
||
// 清理答案输入
|
||
this.trainingItemAnswers = {};
|
||
},
|
||
onTrainingItemListReachBottom() {
|
||
// 已在加载中或全部加载完成则不再触发
|
||
if (this.trainingItemLoading) return;
|
||
if (this.trainingItemList.length >= this.trainingItemTotal) return;
|
||
this.trainingItemPage.current += 1;
|
||
this.fetchTrainingItemList();
|
||
},
|
||
async fetchTrainingItemListAll(parentId) {
|
||
if (!parentId) {
|
||
uni.showToast({
|
||
title: '缺少父ID参数',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
this.trainingItemLoading = true;
|
||
try {
|
||
// 获取租户ID
|
||
const tenantId = getTenantId();
|
||
|
||
// 构建查询参数
|
||
const queryParams = {
|
||
parentId: parentId
|
||
};
|
||
|
||
if (tenantId) {
|
||
queryParams.tenantId = tenantId;
|
||
}
|
||
|
||
// 将参数转换为 URL 查询字符串
|
||
const queryString = Object.keys(queryParams)
|
||
.filter(key => queryParams[key] !== null && queryParams[key] !== undefined && queryParams[key] !== '')
|
||
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`)
|
||
.join('&');
|
||
const url = `${getApiUrl('/api/trainingItem/listAll')}?${queryString}`;
|
||
|
||
// 构建请求头
|
||
const headers = {
|
||
"Content-Type": "application/json"
|
||
};
|
||
if (tenantId) {
|
||
headers['X-Tenant-Id'] = tenantId;
|
||
}
|
||
|
||
const res = await uni.request({
|
||
url: url,
|
||
method: 'POST',
|
||
header: headers,
|
||
timeout: 30000
|
||
});
|
||
|
||
if (res.statusCode === 200 && res.data && res.data.success) {
|
||
const records = Array.isArray(res.data.data) ? res.data.data : [];
|
||
this.trainingItemList = records;
|
||
this.trainingItemTotal = Number(res.data.count) || records.length;
|
||
// 初始化答案输入
|
||
this.initTrainingItemAnswers(records);
|
||
} else {
|
||
this.trainingItemList = [];
|
||
this.trainingItemTotal = 0;
|
||
uni.showToast({
|
||
title: res.data?.message || '获取陪练中列表失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('获取陪练中列表失败:', error);
|
||
uni.showToast({
|
||
title: '获取陪练中列表失败,请稍后重试',
|
||
icon: 'none'
|
||
});
|
||
} finally {
|
||
this.trainingItemLoading = false;
|
||
}
|
||
},
|
||
async fetchTrainingItemList({ force = false } = {}) {
|
||
if (this.trainingItemLoading && !force) {
|
||
return;
|
||
}
|
||
if (!this.currentTrainingParentId) {
|
||
return;
|
||
}
|
||
this.trainingItemLoading = true;
|
||
try {
|
||
// 构建查询参数
|
||
const queryParams = {
|
||
current: this.trainingItemPage.current,
|
||
size: this.trainingItemPage.size,
|
||
parentId: this.currentTrainingParentId
|
||
};
|
||
|
||
// 获取租户ID
|
||
const tenantId = getTenantId();
|
||
if (tenantId) {
|
||
queryParams.tenantId = tenantId;
|
||
}
|
||
|
||
// 将参数转换为 URL 查询字符串
|
||
const queryString = Object.keys(queryParams)
|
||
.filter(key => queryParams[key] !== null && queryParams[key] !== undefined && queryParams[key] !== '')
|
||
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`)
|
||
.join('&');
|
||
const url = `${getApiUrl('/api/trainingItem/list')}?${queryString}`;
|
||
|
||
// 构建请求头
|
||
const headers = {};
|
||
if (tenantId) {
|
||
headers['X-Tenant-Id'] = tenantId;
|
||
}
|
||
|
||
const res = await uni.request({
|
||
url: url,
|
||
method: 'GET',
|
||
header: headers,
|
||
timeout: 30000
|
||
});
|
||
|
||
if (res.statusCode === 200 && res.data && res.data.success) {
|
||
const records = Array.isArray(res.data.data) ? res.data.data : [];
|
||
// 第一页或强制刷新时重置列表,否则追加
|
||
if (this.trainingItemPage.current === 1 || force) {
|
||
this.trainingItemList = records;
|
||
// 初始化答案输入
|
||
this.initTrainingItemAnswers(records);
|
||
} else {
|
||
this.trainingItemList = this.trainingItemList.concat(records);
|
||
// 追加时也初始化新记录的答案输入
|
||
this.initTrainingItemAnswers(records);
|
||
}
|
||
this.trainingItemTotal = Number(res.data.total) || 0;
|
||
} else {
|
||
this.trainingItemList = [];
|
||
this.trainingItemTotal = 0;
|
||
uni.showToast({
|
||
title: res.data?.message || '获取陪练中列表失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('获取陪练中列表失败:', error);
|
||
uni.showToast({
|
||
title: '获取陪练中列表失败,请稍后重试',
|
||
icon: 'none'
|
||
});
|
||
} finally {
|
||
this.trainingItemLoading = false;
|
||
}
|
||
},
|
||
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}`;
|
||
},
|
||
// 初始化陪练项答案输入
|
||
initTrainingItemAnswers(records) {
|
||
if (!Array.isArray(records)) return;
|
||
records.forEach(item => {
|
||
if (item.id) {
|
||
// 如果已有答案,则使用已有答案,否则初始化为空
|
||
// 使用$set确保响应式
|
||
if (!this.trainingItemAnswers.hasOwnProperty(item.id)) {
|
||
this.$set(this.trainingItemAnswers, item.id, item.answer || '');
|
||
}
|
||
}
|
||
});
|
||
},
|
||
// 继续按钮处理
|
||
async handleContinue(item) {
|
||
if (!item || !item.id) {
|
||
uni.showToast({
|
||
title: '记录ID不能为空',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (!this.currentTrainingParentId) {
|
||
uni.showToast({
|
||
title: '缺少父ID参数',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
try {
|
||
uni.showLoading({
|
||
title: "提交中..."
|
||
});
|
||
|
||
// 获取租户ID
|
||
const tenantId = getTenantId();
|
||
|
||
// 获取当前输入的答案
|
||
const answer = this.trainingItemAnswers[item.id] || '';
|
||
|
||
// 构建请求数据
|
||
const payload = {
|
||
id: item.id,
|
||
parentId: this.currentTrainingParentId,
|
||
answer: answer
|
||
};
|
||
|
||
// 添加租户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/trainingItem/traningNext'),
|
||
method: 'POST',
|
||
data: payload,
|
||
header: headers,
|
||
timeout: 15000
|
||
});
|
||
|
||
uni.hideLoading();
|
||
|
||
if (res.statusCode === 200 && res.data && (res.data.success || res.data.code === 200)) {
|
||
// 获取返回的数据
|
||
const newItems = Array.isArray(res.data.data) ? res.data.data : [];
|
||
|
||
if (newItems.length > 0) {
|
||
// 更新当前item的question、id和parentId
|
||
const newItem = newItems[0];
|
||
const index = this.trainingItemList.findIndex(i => i.id === item.id);
|
||
if (index !== -1) {
|
||
// 保存旧的id,用于清理答案输入
|
||
const oldId = item.id;
|
||
|
||
// 更新question、id和parentId
|
||
if (newItem.question !== undefined) {
|
||
this.$set(this.trainingItemList[index], 'question', newItem.question);
|
||
}
|
||
if (newItem.id !== undefined) {
|
||
this.$set(this.trainingItemList[index], 'id', newItem.id);
|
||
}
|
||
if (newItem.parentId !== undefined) {
|
||
this.$set(this.trainingItemList[index], 'parentId', newItem.parentId);
|
||
}
|
||
|
||
// 如果id改变了,需要更新答案输入的key
|
||
if (newItem.id && newItem.id !== oldId) {
|
||
// 获取旧答案
|
||
const oldAnswer = this.trainingItemAnswers[oldId] || '';
|
||
// 删除旧的答案输入
|
||
this.$delete(this.trainingItemAnswers, oldId);
|
||
// 设置新的答案输入(如果有旧答案则保留,否则为空)
|
||
this.$set(this.trainingItemAnswers, newItem.id, oldAnswer);
|
||
} else {
|
||
// id没变,清空答案输入框(新问题需要重新输入)
|
||
this.$set(this.trainingItemAnswers, item.id, '');
|
||
}
|
||
}
|
||
}
|
||
|
||
uni.showToast({
|
||
title: res.data?.message || "提交成功",
|
||
icon: "success"
|
||
});
|
||
} else {
|
||
uni.showToast({
|
||
title: res.data?.message || "提交失败",
|
||
icon: "none"
|
||
});
|
||
}
|
||
} catch (error) {
|
||
uni.hideLoading();
|
||
console.error("提交失败:", error);
|
||
uni.showToast({
|
||
title: error?.message || "提交失败,请重试",
|
||
icon: "none"
|
||
});
|
||
}
|
||
},
|
||
// 结束按钮处理
|
||
async handleEnd(item) {
|
||
if (!this.currentTrainingParentId) {
|
||
uni.showToast({
|
||
title: '父ID不能为空',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
try {
|
||
uni.showLoading({
|
||
title: "提交中..."
|
||
});
|
||
|
||
// 获取租户ID
|
||
const tenantId = getTenantId();
|
||
|
||
// 构建请求数据,只需要传递parentId
|
||
const payload = {
|
||
parentId: this.currentTrainingParentId
|
||
};
|
||
|
||
// 添加租户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/trainingItem/traningFinish'),
|
||
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.closeTrainingItemListPopup();
|
||
// 刷新陪练列表
|
||
this.trainingPage.current = 1;
|
||
this.fetchTrainingList({ force: true });
|
||
// 通知父组件切换到陪练tab(确保在陪练tab页面)
|
||
this.$emit('refresh-training-list');
|
||
} else {
|
||
uni.showToast({
|
||
title: res.data?.message || "提交失败",
|
||
icon: "none"
|
||
});
|
||
}
|
||
} catch (error) {
|
||
uni.hideLoading();
|
||
console.error("提交失败:", error);
|
||
uni.showToast({
|
||
title: error?.message || "提交失败,请重试",
|
||
icon: "none"
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</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;
|
||
}
|
||
|
||
/* 陪练内容区域使用相同定位 */
|
||
.training-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-picker {
|
||
flex: 1;
|
||
min-width: 120rpx;
|
||
max-width: 200rpx;
|
||
flex-shrink: 1;
|
||
}
|
||
|
||
.toolbar-picker-view {
|
||
height: 56rpx;
|
||
line-height: 56rpx;
|
||
background-color: #F5F6FA;
|
||
border-radius: 8rpx;
|
||
padding: 0 12rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.toolbar-picker-text {
|
||
font-size: 24rpx;
|
||
color: #333;
|
||
flex: 1;
|
||
}
|
||
|
||
.toolbar-picker-placeholder {
|
||
font-size: 24rpx;
|
||
color: #b0b0b0;
|
||
flex: 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;
|
||
}
|
||
|
||
.training-card-item {
|
||
padding-left: 32rpx;
|
||
padding-right: 32rpx;
|
||
padding-top: 32rpx;
|
||
padding-bottom: 32rpx;
|
||
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;
|
||
position: relative;
|
||
}
|
||
|
||
.training-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;
|
||
}
|
||
|
||
.training-card-action-btn uni-icons {
|
||
display: block;
|
||
margin: 0;
|
||
padding: 0;
|
||
line-height: 1;
|
||
}
|
||
|
||
.training-card-action-btn:active {
|
||
opacity: 0.7;
|
||
}
|
||
|
||
.training-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;
|
||
}
|
||
|
||
.training-action-menu-item {
|
||
padding: 24rpx 32rpx;
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
text-align: center;
|
||
background-color: #FFFFFF;
|
||
}
|
||
|
||
.training-action-menu-item:active {
|
||
background-color: #F5F5F5;
|
||
}
|
||
|
||
.training-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;
|
||
}
|
||
|
||
.tag-status {
|
||
background-color: #F3F4F6;
|
||
}
|
||
|
||
.tag-status text {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.service-duration {
|
||
padding-top: 16rpx;
|
||
border-top: 1px solid #F0F0F0;
|
||
}
|
||
|
||
.service-duration text {
|
||
font-size: 26rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.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;
|
||
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;
|
||
}
|
||
|
||
/* 陪练中列表弹窗样式 */
|
||
.training-item-list-popup {
|
||
max-height: 80vh;
|
||
}
|
||
|
||
.training-item-list-popup .popup-content {
|
||
padding: 24rpx;
|
||
}
|
||
|
||
.training-item-card {
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.training-item-card:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
/* 表单样式 */
|
||
.form-item {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
margin-bottom: 20rpx;
|
||
gap: 16rpx;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.form-item__label {
|
||
font-size: 32rpx;
|
||
color: #333;
|
||
font-weight: 500;
|
||
width: 100%;
|
||
text-align: left;
|
||
}
|
||
|
||
.form-item__textarea {
|
||
width: 100%;
|
||
min-height: 200rpx;
|
||
background-color: #f9fafb;
|
||
border-radius: 12rpx;
|
||
padding: 24rpx;
|
||
font-size: 32rpx;
|
||
color: #333;
|
||
box-sizing: border-box;
|
||
border: 1px solid #e5e7eb;
|
||
}
|
||
|
||
/* 操作按钮样式 */
|
||
.training-item-actions {
|
||
display: flex;
|
||
gap: 24rpx;
|
||
margin-top: 24rpx;
|
||
padding-top: 24rpx;
|
||
border-top: 1px solid #F0F0F0;
|
||
}
|
||
|
||
.action-btn {
|
||
flex: 1;
|
||
height: 88rpx;
|
||
border-radius: 12rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 32rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.action-btn--continue {
|
||
background-color: #f3f4f6;
|
||
color: #2A68FF;
|
||
border: 1px solid #d1d5db;
|
||
}
|
||
|
||
.action-btn--continue:active {
|
||
background-color: #e5e7eb;
|
||
}
|
||
|
||
.action-btn--end {
|
||
background-color: #f3f4f6;
|
||
color: #2A68FF;
|
||
border: 1px solid #d1d5db;
|
||
}
|
||
|
||
.action-btn--end:active {
|
||
background-color: #e5e7eb;
|
||
}
|
||
</style>
|