1429 lines
36 KiB
Vue
1429 lines
36 KiB
Vue
<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>
|