智慧教务系统
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1708 lines
54 KiB

<!--客户详情 - 重构版本-->
<template>
<view class="assemble">
<!-- 主要内容区域 -->
<view class="content">
<!-- 客户信息卡片 -->
<ClientInfoCard
:client-info="clientInfo"
:actions="[]"
@call="handleMakeCall"
@message="handleSendMessage"
/>
<!-- 标签切换器 -->
<view class="tab-switcher-container">
<TabSwitcher
:tabs="tabs"
:active-tab-id="switch_tags_type"
@tab-change="handleTabChange"
/>
</view>
<!-- 学生信息卡片区域 -->
<view class="student-section" v-if="switch_tags_type == 1 || switch_tags_type == 7">
<view class="section-header">
<text class="section-title">学生信息</text>
<view class="add-student-btn" @click.stop="openAddStudentDialog">
<text class="add-icon">+</text>
<text class="add-text">添加学生</text>
</view>
</view>
<!-- 学生信息滑动卡片 -->
<view class="student-cards">
<swiper
class="student-swiper"
:indicator-dots="studentList.length > 1"
:circular="studentList.length > 1"
indicator-color="rgba(255, 255, 255, 0.3)"
indicator-active-color="#29D3B4"
previous-margin="20"
next-margin="20"
@change="onStudentSwiperChange">
<swiper-item v-for="(student, index) in studentList" :key="student.id">
<view class="student-swiper-content">
<StudentInfoCard
:student="student"
:show-details="true"
@action="handleStudentAction"
/>
</view>
</swiper-item>
</swiper>
</view>
<!-- 操作按钮区域 - 移到Swiper外部,独立滑动 -->
<view class="action-buttons-section" v-if="currentStudent">
<view
class="action-item"
v-for="action in actionButtons"
:key="action.key"
@click.stop="handleStudentActionClick(action, currentStudent)"
>
<view class="action-icon">
<text>{{ action.icon }}</text>
</view>
<text class="action-text">{{ action.text }}</text>
</view>
</view>
<!-- 空状态 -->
<view v-if="studentList.length === 0" class="empty-state">
<text class="empty-icon">👤</text>
<text class="empty-text">暂无学生信息</text>
<view class="empty-add-btn" @click.stop="openAddStudentDialog">
<text>添加第一个学生</text>
</view>
</view>
</view>
<!-- 课程信息标签内容 -->
<view class="course-section" v-if="switch_tags_type == 2">
<CourseInfoCard
v-if="courseInfo && courseInfo.length > 0"
:course-list="courseInfo"
@view-detail="viewCourseDetail"
/>
<view v-else class="empty-state">
<text class="empty-icon">📚</text>
<text class="empty-text">暂无课程信息</text>
</view>
</view>
<!-- 通话记录标签内容 -->
<view class="call-section" v-if="switch_tags_type == 3">
<view v-if="listCallUp.length === 0" class="empty-state">
<text class="empty-icon">📞</text>
<text class="empty-text">暂无通话记录</text>
</view>
<CallRecordCard
v-for="record in listCallUp"
:key="record.id"
:record="record"
@remark="openAddRemark"
/>
</view>
<!-- 体测记录标签内容 -->
<view class="fitness-section" v-if="switch_tags_type == 4">
<view class="section-header" v-if="currentStudent">
<text class="context-title">{{ currentStudent.name }}的体测记录</text>
<view class="add-record-btn" @click.stop="openAddFitnessRecord">
<text class="add-icon">+</text>
<text class="add-text">新增记录</text>
</view>
</view>
<view v-if="currentStudentFitnessRecords.length === 0" class="empty-state">
<text class="empty-icon">📊</text>
<text class="empty-text">暂无体测记录</text>
</view>
<FitnessRecordCard
v-for="record in currentStudentFitnessRecords"
:key="record.id"
:record="record"
@edit="openEditFitnessRecord"
/>
</view>
<!-- 学习计划标签内容 -->
<view class="study-plan-section" v-if="switch_tags_type == 5">
<view v-if="!studyPlanList || studyPlanList.length === 0" class="empty-state">
<text class="empty-icon">📚</text>
<text class="empty-text">暂无学习计划</text>
</view>
</view>
<!-- 赠品记录标签内容 -->
<view class="gift-record-section" v-if="switch_tags_type == 8">
<view v-if="giftRecords.length === 0" class="empty-state">
<text class="empty-icon">🎁</text>
<text class="empty-text">暂无赠品记录</text>
</view>
<GiftRecordCard
v-for="record in giftRecords"
:key="record.id"
:record="record"
/>
</view>
</view>
<!-- 底部弹窗组件 -->
<BottomPopup
:visible="currentPopup !== null"
:title="popupTitle"
:has-footer="needsFooter"
@close="closePopup"
@click.stop
>
<!-- 课程信息弹窗 -->
<CourseInfoCard
v-if="currentPopup === 'course_info'"
:course-list="courseInfo"
@view-detail="viewCourseDetail"
/>
<!-- 体测记录弹窗 -->
<view
class="fitness-records-container"
v-if="currentPopup === 'fitness_record'"
>
<!-- 空状态提示 -->
<view v-if="currentStudentFitnessRecords.length === 0" class="empty-state">
<view class="empty-icon">📊</view>
<view class="empty-text">暂无体测记录</view>
<view class="empty-tip">点击下方"新增"按钮添加体测记录</view>
</view>
<!-- 体测记录列表 -->
<FitnessRecordCard
v-for="record in currentStudentFitnessRecords"
:key="record.id"
:record="record"
@edit="openEditFitnessRecord"
/>
</view>
<!-- 学习计划弹窗 -->
<StudyPlanCard
v-if="currentPopup === 'study_plan'"
:plan-list="studyPlanList"
@edit="openEditStudyPlan"
/>
<!-- 订单列表弹窗 -->
<OrderListCard
v-if="currentPopup === 'order_list'"
:order-list="orderList"
@add-order="openAddOrderDialog"
@pay-order="handlePayOrder"
@view-detail="viewOrderDetail"
/>
<!-- 服务列表弹窗 -->
<ServiceListCard
v-if="currentPopup === 'service_list'"
:service-list="serviceList"
/>
<!-- 底部操作按钮 -->
<template #footer>
<view class="popup-footer-btns">
<view class="footer-btn cancel-btn" @click.stop="closePopup">关闭</view>
<view
class="footer-btn confirm-btn"
v-if="showAddButton"
@click.stop="handleAddAction"
>新增</view>
</view>
</template>
</BottomPopup>
<!-- 保留原有的编辑弹窗 -->
<uni-popup ref="remarkPopup" type="dialog">
<view class="remark-dialog">
<textarea
v-model="remark_content"
placeholder="请输入备注内容"
maxlength="200"
></textarea>
<view class="dialog-btns">
<view class="btn cancel" @click="closeRemark">取消</view>
<view class="btn confirm" @click="confirmRemark">确定</view>
</view>
</view>
</uni-popup>
<FitnessRecordPopup ref="fitnessRecordPopup" :resource-id="String(clientInfo.resource_id)" :student-id="currentStudent && currentStudent.id" @confirm="handleFitnessRecordConfirm" />
<StudentEditPopup ref="studentEditPopup" :resource-id="clientInfo.resource_id" @confirm="handleStudentEditConfirm" />
<StudyPlanPopup ref="studyPlanPopup" :student-id="currentStudent && currentStudent.id" @confirm="handleStudyPlanConfirm" />
<!-- 新增订单弹窗 -->
<uni-popup ref="orderFormPopup" type="bottom">
<OrderFormPopup
:visible="showOrderForm"
:student-info="currentStudent"
:resource-id="clientInfo.resource_id"
@cancel="closeOrderForm"
@confirm="handleOrderFormConfirm"
/>
</uni-popup>
<!-- 二维码支付弹窗 -->
<uni-popup ref="qrCodePopup" type="center" @close="closeQRCodeModal">
<view class="qrcode-payment-modal" v-if="qrCodePaymentData">
<!-- 弹窗头部 -->
<view class="modal-header">
<text class="modal-title">扫码支付</text>
<view class="close-btn" @click="closeQRCodeModal">
<text>✕</text>
</view>
</view>
<!-- 订单信息 -->
<view class="order-info">
<view class="info-row">
<text class="label">订单号:</text>
<text class="value">{{ qrCodePaymentData.order.order_no }}</text>
</view>
<view class="info-row">
<text class="label">支付金额:</text>
<text class="amount">¥{{ qrCodePaymentData.order.total_amount }}</text>
</view>
</view>
<!-- 二维码区域 -->
<view class="qrcode-container">
<image
v-if="qrCodePaymentData.qrcodeImage"
:src="qrCodePaymentData.qrcodeImage"
class="qrcode-image"
mode="aspectFit"
/>
<text v-else class="qrcode-placeholder">二维码加载中...</text>
<text class="qrcode-tip">请使用微信扫码完成支付</text>
</view>
<!-- 操作按钮 -->
<view class="modal-buttons">
<view class="btn secondary" @click="closeQRCodeModal">取消支付</view>
<view class="btn primary" @click="confirmQRCodePayment">发送二维码给客户</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js'
// 组件导入
import ClientInfoCard from '@/components/client-info-card/client-info-card.vue'
import StudentInfoCard from '@/components/student-info-card/student-info-card.vue'
import TabSwitcher from '@/components/tab-switcher/tab-switcher.vue'
import CallRecordCard from '@/components/call-record-card/call-record-card.vue'
import FitnessRecordCard from '@/components/fitness-record-card/fitness-record-card.vue'
import GiftRecordCard from '@/components/gift-record-card/gift-record-card.vue'
// 新的底部弹窗组件
import BottomPopup from '@/components/bottom-popup/index.vue'
import StudyPlanCard from '@/components/study-plan-card/index.vue'
import CourseInfoCard from '@/components/course-info-card/index.vue'
import OrderListCard from '@/components/order-list-card/index.vue'
import ServiceListCard from '@/components/service-list-card/index.vue'
import OrderFormPopup from '@/components/order-form-popup/index.vue'
// 编辑弹窗
import StudentEditPopup from '@/components/student-edit-popup/student-edit-popup.vue'
import FitnessRecordPopup from '@/components/fitness-record-popup/fitness-record-popup.vue'
import StudyPlanPopup from '@/components/study-plan-popup/study-plan-popup.vue'
export default {
components: {
ClientInfoCard,
StudentInfoCard,
TabSwitcher,
CallRecordCard,
FitnessRecordCard,
GiftRecordCard,
BottomPopup,
StudyPlanCard,
CourseInfoCard,
OrderListCard,
ServiceListCard,
OrderFormPopup,
StudentEditPopup,
FitnessRecordPopup,
StudyPlanPopup
},
data() {
return {
switch_tags_type: 1,
resource_sharing_id: '',
// 基本数据
clientInfo: { id: '', resource_id: '', customerResource: {} },
userInfo: {},
listCallUp: [],
courseInfo: [],
fitnessRecords: [],
giftRecords: [],
studentList: [],
currentStudentIndex: 0,
// 底部弹窗相关
currentPopup: null,
studyPlanList: [],
orderList: [],
serviceList: [],
// 订单表单弹窗
showOrderForm: false,
// 二维码支付弹窗
showQRCodeModal: false,
qrCodePaymentData: null,
// 编辑相关
remark_content: '',
currentRecord: null,
// 配置数据
tabs: [
{ id: 1, name: '基本资料' },
{ id: 3, name: '通话记录' },
{ id: 7, name: '修改资料' },
{ id: 6, name: '修改记录' },
{ id: 8, name: '赠品记录' }
],
actionButtons: [
{ key: 'course_arrangement', text: '课程安排', icon: '📅' },
{ key: 'order_list', text: '订单列表', icon: '📋' },
{ key: 'service_list', text: '服务列表', icon: '🔧' },
{ key: 'course_info', text: '课程信息', icon: '📚' },
{ key: 'fitness_record', text: '体测记录', icon: '📊' },
{ key: 'study_plan', text: '学习计划', icon: '📝' }
]
}
},
computed: {
currentStudent() {
return this.studentList[this.currentStudentIndex] || null
},
currentStudentFitnessRecords() {
if (!this.currentStudent) return []
// 体测记录使用resource_id字段,与学生记录的resource_id匹配
// 如果没有匹配到,则显示所有记录(用于展示Mock数据)
const filtered = this.fitnessRecords.filter(record =>
record.student_id === this.currentStudent.id ||
record.resource_id === this.currentStudent.id ||
record.resource_id === this.clientInfo.resource_id
)
// 如果没有匹配到任何记录,返回所有记录用于测试
return filtered.length > 0 ? filtered : this.fitnessRecords
},
popupTitle() {
const titles = {
'course_info': '课程信息',
'fitness_record': '体测记录',
'study_plan': '学习计划',
'order_list': '订单列表',
'service_list': '服务列表'
}
return titles[this.currentPopup] || ''
},
needsFooter() {
// 所有弹窗都显示底部按钮区域
return this.currentPopup !== null
},
showAddButton() {
// 只有体测记录和学习计划弹窗显示新增按钮
return ['fitness_record', 'study_plan'].includes(this.currentPopup)
},
},
onLoad(options) {
if (!options?.resource_sharing_id) {
uni.showToast({ title: '缺少必要参数', icon: 'none' })
setTimeout(() => uni.navigateBack(), 1500)
return
}
this.resource_sharing_id = options.resource_sharing_id
},
onShow() {
this.init()
},
methods: {
async init() {
try {
await this.getInfo()
await Promise.all([
this.getUserInfo(),
this.getListCallUp(),
this.getCourseInfo(),
this.getFitnessRecords(),
this.getStudentList()
])
} catch (error) {
console.error('数据加载失败:', error)
}
},
async getUserInfo() {
try {
const res = await apiRoute.getPersonnelInfo({})
if (res.code === 1) {
this.userInfo = res.data
}
} catch (error) {
console.error('获取员工信息失败:', error)
}
},
async getInfo() {
if (!this.resource_sharing_id) return
try {
const res = await apiRoute.xs_resourceSharingInfo({ resource_sharing_id: this.resource_sharing_id })
if (res.code === 1) {
this.clientInfo = res.data
}
} catch (error) {
console.error('获取客户详情失败:', error)
}
},
async getListCallUp() {
if (!this.clientInfo?.resource_id) return
try {
const res = await apiRoute.listCallUp({ resource_id: this.clientInfo.resource_id })
if (res.code === 1) {
this.listCallUp = res.data || []
}
} catch (error) {
console.error('获取通话记录失败:', error)
}
},
handleMakeCall() {
const phone = this.clientInfo?.customerResource?.phone_number
if (phone) {
uni.makePhoneCall({ phoneNumber: phone })
}
},
handleSendMessage() {
uni.showToast({ title: '发送消息功能待实现', icon: 'none' })
},
openAddRemark(record) {
this.remark_content = record.remarks || ''
this.currentRecord = record
this.$refs.remarkPopup.open()
},
async confirmRemark() {
if (!this.remark_content.trim() || !this.currentRecord?.id) {
uni.showToast({ title: '请输入备注内容', icon: 'none' })
return
}
try {
const res = await apiRoute.xs_communicationRecordsEdit({
id: this.currentRecord.id,
remarks: this.remark_content
})
if (res.code === 1) {
uni.showToast({ title: '备注更新成功', icon: 'success' })
await this.getListCallUp()
}
} catch (error) {
uni.showToast({ title: '更新失败', icon: 'none' })
}
this.closeRemark()
},
closeRemark() {
this.$refs.remarkPopup.close()
this.remark_content = ''
this.currentRecord = null
},
async getCourseInfo(studentId = null) {
if (!this.clientInfo?.resource_id) return
try {
const targetStudentId = studentId || this.currentStudent?.id
const res = await apiRoute.getStudentCourseInfo({
resource_id: this.clientInfo.resource_id,
member_id: this.clientInfo.customerResource?.member_id || '',
student_id: targetStudentId
})
if (res.code === 1) {
this.courseInfo = res.data || []
// 为测试数据添加必要的resource_id字段
if (this.courseInfo.length > 0) {
this.courseInfo.forEach(course => {
if (!course.resource_id) {
course.resource_id = this.clientInfo.resource_id || 1; // 添加resource_id
}
if (!course.student_course_id && !course.id) {
course.student_course_id = Math.floor(Math.random() * 1000); // 添加测试用的ID
}
});
}
}
} catch (error) {
console.error('获取课程信息失败:', error)
}
},
// 学生卡片操作按钮点击处理 - 传递具体学生信息
async handleStudentActionClick(action, student) {
// 设置当前操作的学生
this.setCurrentStudent(student)
switch (action.key) {
case 'course_arrangement':
this.$navigateToPage(`/pages-market/clue/class_arrangement`, {
resource_id: this.clientInfo.id,
student_id: student.id
})
break
case 'course_info':
await this.getCourseInfo(student.id)
this.currentPopup = 'course_info'
break
case 'fitness_record':
await this.getFitnessRecords(student.id)
this.currentPopup = 'fitness_record'
break
case 'study_plan':
await this.getStudyPlanList(student.id)
this.currentPopup = 'study_plan'
break
case 'order_list':
await this.getOrderList(student.id)
this.currentPopup = 'order_list'
break
case 'service_list':
await this.getServiceList(student.id)
this.currentPopup = 'service_list'
break
}
},
closePopup() {
console.log('关闭弹窗,当前弹窗类型:', this.currentPopup)
this.currentPopup = null
// 重置相关数据
this.studyPlanList = []
this.orderList = []
this.serviceList = []
this.courseInfo = []
this.fitnessRecords = []
},
handleAddAction() {
if (this.currentPopup === 'fitness_record') {
this.openAddFitnessRecord()
} else if (this.currentPopup === 'study_plan') {
this.openAddStudyPlan()
}
},
viewCourseDetail(course) {
if (!course?.id) {
uni.showToast({ title: '课程信息不完整', icon: 'none' })
return
}
this.$navigateToPage(`/pages-market/course/course_detail`, {
id: course.id,
resource_id: this.clientInfo.resource_id
})
},
async handleTabChange({ tabId }) {
this.switch_tags_type = tabId
if (tabId === 2) await this.getCourseInfo()
if (tabId === 3) await this.getListCallUp()
if (tabId === 4) await this.getFitnessRecords()
if (tabId === 6) {
this.$navigateToPage(`/pages-market/clue/edit_clues_log`, {
resource_id: this.clientInfo.id
})
}
console.log('切换标签页:', this.clientInfo)
if (tabId === 7) this.$navigateToPage(`/pages-market/clue/edit_clues`, {
resource_sharing_id: this.clientInfo.id
})
if (tabId === 8) await this.getGiftRecords()
},
handleStudentAction({ action, student }) {
// this.setCurrentStudent(student)
console.log('学生操作:', action, student)
// 处理不同的学员操作
switch (action) {
case 'edit':
// 编辑学员信息 - 打开编辑弹窗并回显数据
this.openEditStudentDialog(student)
break
case 'view':
// 查看学员详情
this.viewStudentDetail(student)
break
case 'delete':
// 删除学员
this.confirmDeleteStudent(student)
break
default:
console.log('未处理的学员操作:', action)
}
},
setCurrentStudent(student) {
const index = this.studentList.findIndex(s => s.id === student.id)
if (index !== -1) this.currentStudentIndex = index
},
onStudentSwiperChange(e) {
this.currentStudentIndex = e.detail.current
},
async getFitnessRecords(studentId = null) {
if (!this.clientInfo?.resource_id) return
try {
// 如果指定了学生ID,则获取该学生的体测记录
const targetStudentId = studentId || this.currentStudent?.id
// 调用体测记录API
const res = await apiRoute.xy_physicalTest({
resource_id: this.clientInfo.resource_id,
student_id: targetStudentId
})
if (res.code === 1 && res.data) {
// 处理体测记录数据,转换PDF文件格式
this.fitnessRecords = this.processFitnessRecords(res.data.data || [])
} else {
console.warn('获取体测记录失败:', res.msg)
this.fitnessRecords = []
}
} catch (error) {
console.error('获取体测记录异常:', error)
this.fitnessRecords = []
}
},
// 处理体测记录数据,转换PDF文件格式
processFitnessRecords(records) {
if (!records || !Array.isArray(records)) return []
return records.map(record => {
const processedRecord = {
...record,
test_date: record.created_at ? record.created_at.split(' ')[0] : '', // 从created_at提取日期
pdf_files: []
}
// 处理PDF文件
if (record.physical_test_report) {
// 如果physical_test_report是字符串路径
if (typeof record.physical_test_report === 'string') {
const pdfPaths = record.physical_test_report.split(',').filter(path => path.trim())
processedRecord.pdf_files = pdfPaths.map((path, index) => ({
id: `${record.id}_${index}`,
name: this.extractFileName(path) || `体测报告_${index + 1}.pdf`,
size: 0, // 无法从路径获取大小
url: this.getFullPdfUrl(path),
server_path: path.trim(),
upload_time: record.created_at || ''
}))
}
// 如果physical_test_report已经是数组格式
else if (Array.isArray(record.physical_test_report)) {
processedRecord.pdf_files = record.physical_test_report
}
}
return processedRecord
})
},
// 从文件路径提取文件名
extractFileName(filePath) {
if (!filePath) return ''
const parts = filePath.split('/')
return parts[parts.length - 1] || ''
},
// 获取完整的PDF文件URL
getFullPdfUrl(relativePath) {
if (!relativePath) return ''
// 如果已经是完整URL,直接返回
if (relativePath.startsWith('http://') || relativePath.startsWith('https://')) {
return relativePath
}
// 构建完整的URL
const { img_domian } = require('@/common/config.js')
// 处理相对路径
let cleanPath = relativePath
if (cleanPath.startsWith('./')) {
cleanPath = cleanPath.substring(2)
}
if (cleanPath.startsWith('/')) {
cleanPath = cleanPath.substring(1)
}
return `${img_domian}${cleanPath}`
},
openAddFitnessRecord() {
this.$refs.fitnessRecordPopup.openAdd()
},
openEditFitnessRecord(record) {
// 确保记录包含正确格式的PDF文件信息
const processedRecord = {
...record,
pdf_files: record.pdf_files || [] // 使用已处理的pdf_files数据
}
this.$refs.fitnessRecordPopup.openEdit(processedRecord)
},
async handleFitnessRecordConfirm(result) {
try {
const { isEditing, data } = result
if (isEditing) {
// 编辑体测记录
const response = await apiRoute.xy_physicalTestEdit(data)
if (response.code === 1) {
uni.showToast({ title: '编辑成功', icon: 'success' })
// 刷新体测记录列表
await this.getFitnessRecords()
} else {
uni.showToast({ title: response.msg || '编辑失败', icon: 'none' })
}
} else {
// 新增体测记录
const response = await apiRoute.xy_physicalTestAdd(data)
if (response.code === 1) {
uni.showToast({ title: '新增成功', icon: 'success' })
// 刷新体测记录列表
await this.getFitnessRecords()
} else {
uni.showToast({ title: response.msg || '新增失败', icon: 'none' })
}
}
} catch (error) {
console.error('保存体测记录失败:', error)
uni.showToast({ title: '保存失败,请重试', icon: 'none' })
}
},
// 学习计划相关方法
openAddStudyPlan() {
if (!this.currentStudent) {
uni.showToast({ title: '请先选择学生', icon: 'none' })
return
}
this.$refs.studyPlanPopup.openAdd()
},
openEditStudyPlan(plan) {
this.$refs.studyPlanPopup.openEdit(plan)
},
async handleStudyPlanConfirm(result) {
try {
const { isEditing, data } = result
if (isEditing) {
// 编辑学习计划
const response = await apiRoute.editStudyPlan(data)
if (response.code === 1) {
uni.showToast({ title: '编辑成功', icon: 'success' })
// 刷新学习计划列表
await this.getStudyPlanList()
} else {
uni.showToast({ title: response.msg || '编辑失败', icon: 'none' })
}
} else {
// 新增学习计划
const response = await apiRoute.addStudyPlan(data)
if (response.code === 1) {
uni.showToast({ title: '新增成功', icon: 'success' })
// 刷新学习计划列表
await this.getStudyPlanList()
} else {
uni.showToast({ title: response.msg || '新增失败', icon: 'none' })
}
}
} catch (error) {
console.error('保存学习计划失败:', error)
uni.showToast({ title: '保存失败,请重试', icon: 'none' })
}
},
async getStudentList() {
try {
if (this.clientInfo?.resource_id) {
const res = await apiRoute.xs_getStudentList({ parent_resource_id: this.clientInfo.resource_id })
if (res.code === 1) {
// 处理接口返回的学生数据,转换字段
this.studentList = await this.processStudentData(res.data || [])
return
}
}
// 使用模拟数据(匹配真实接口字段结构)
const mockData = [
{
id: 1,
name: '张小明',
gender: 1, // 1=男, 2=女
birthday: '2015-05-10',
remark: '活泼开朗,学习能力强',
member_label: 1, // student_label表的ID,需要转换为标签名称
coach_id: 1, // personnel表的ID,需要转换为教练姓名
consultant_id: 2, // personnel表的ID,需要转换为顾问姓名
trial_class_count: 3,
// 其他原有字段
age: 9
}
]
// 处理模拟数据
this.studentList = await this.processStudentData(mockData)
} catch (error) {
console.error('获取学生列表失败:', error)
}
},
// 处理学生数据,转换字段为显示用的格式
async processStudentData(students) {
if (!students || students.length === 0) return []
const processedStudents = []
for (const student of students) {
const processedStudent = {
...student,
// 转换字段名称和处理逻辑
student_tags: await this.getStudentLabel(student.member_label),
class_teacher: await this.getPersonnelName(student.coach_id),
academic_affairs: await this.getPersonnelName(student.consultant_id),
trial_course_count: student.trial_class_count || 0,
// 使用后端API返回的真实到访状态数据
course_visit_status: this.formatVisitStatus(student.first_visit_status, student.second_visit_status)
}
processedStudents.push(processedStudent)
}
return processedStudents
},
// 获取学员标签名称
async getStudentLabel(labelId) {
if (!labelId) return []
try {
// 调用接口获取school_student_label表中的标签名称
const res = await apiRoute.getStudentLabel({ id: labelId })
if (res.code === 1) {
return res.data ? [res.data.label_name] : []
}
// 如果接口调用失败,使用模拟数据作为后备
console.warn('接口调用失败,使用模拟数据:', res.msg)
const labelMap = {
1: '优秀学员',
2: '积极参与',
3: '需要关注',
4: '进步明显',
5: '需要鼓励'
}
return labelMap[labelId] ? [labelMap[labelId]] : []
} catch (error) {
console.error('获取学生标签失败:', error)
// 出现错误时使用模拟数据
const labelMap = {
1: '优秀学员',
2: '积极参与',
3: '需要关注',
4: '进步明显',
5: '需要鼓励'
}
return labelMap[labelId] ? [labelMap[labelId]] : []
}
},
// 获取人员姓名(教练或顾问)
async getPersonnelName(personnelId) {
if (!personnelId) return ''
// TODO: 调用接口获取personnel表中的人员姓名
// const res = await apiRoute.getPersonnelInfo({ id: personnelId })
// 模拟数据
const personnelMap = {
1: '李老师',
2: '王主任',
3: '张教练'
}
return personnelMap[personnelId] || ''
},
// 格式化到访状态 - 使用后端API返回的真实数据
formatVisitStatus(firstVisitStatus, secondVisitStatus) {
// 如果都未到访
if (firstVisitStatus === '未到访' && secondVisitStatus === '未到访') {
return '未到访'
}
// 如果一访已到,二访未到
else if (firstVisitStatus === '已到访' && secondVisitStatus === '未到访') {
return '一访已到'
}
// 如果一访和二访都已到
else if (firstVisitStatus === '已到访' && secondVisitStatus === '已到访') {
return '二访已到'
}
// 默认情况
else {
return '未到访'
}
},
openAddStudentDialog() {
// 确保组件已经挂载后再调用
this.$nextTick(() => {
if (this.$refs.studentEditPopup) {
// 传递客户信息给学生编辑弹窗,用于自动填充
const clientData = {
contact_phone: this.clientInfo?.customerResource?.phone_number || '',
emergency_contact: this.clientInfo?.customerResource?.name || ''
}
this.$refs.studentEditPopup.openAdd(clientData)
} else {
console.error('studentEditPopup 组件引用不存在')
uni.showToast({
title: '组件加载中,请稍后再试',
icon: 'none'
})
}
})
},
// 打开编辑学员信息弹窗
openEditStudentDialog(student) {
// 确保组件已经挂载后再调用
this.$nextTick(() => {
if (this.$refs.studentEditPopup) {
console.log('打开编辑学员弹窗,学员信息:', student)
// 调用组件的 openEdit 方法并传入学员数据进行回显
this.$refs.studentEditPopup.openEdit(student)
} else {
console.error('studentEditPopup 组件引用不存在')
uni.showToast({
title: '组件加载中,请稍后再试',
icon: 'none'
})
}
})
},
// 查看学员详情
viewStudentDetail(student) {
console.log('查看学员详情:', student)
// 可以显示一个详情弹窗或跳转到详情页面
const detailInfo = `
姓名:${student.name}
性别:${student.gender === 1 ? '男' : student.gender === 2 ? '女' : '未知'}
年龄:${student.age || '未知'}
生日:${student.birthday || '未知'}
联系电话:${student.contact_phone || '未填写'}
紧急联系人:${student.emergency_contact || '未填写'}
备注:${student.note || '无'}
状态:${student.status === 1 ? '有效' : '无效'}
`.trim()
uni.showModal({
title: '学员详情',
content: detailInfo,
showCancel: false,
confirmText: '知道了'
})
},
// 确认删除学员
confirmDeleteStudent(student) {
uni.showModal({
title: '确认删除',
content: `确定要删除学员"${student.name}"吗?此操作无法撤销。`,
success: (res) => {
if (res.confirm) {
this.deleteStudent(student)
}
}
})
},
// 删除学员
async deleteStudent(student) {
try {
// 这里需要调用删除学员的API
// const res = await apiRoute.deleteStudent({ student_id: student.id })
// if (res.code === 1) {
uni.showToast({ title: '删除成功', icon: 'success' })
// 刷新学员列表
await this.getStudentList()
// } else {
// uni.showToast({ title: res.msg || '删除失败', icon: 'none' })
// }
} catch (error) {
console.error('删除学员失败:', error)
uni.showToast({ title: '删除失败', icon: 'none' })
}
},
async handleStudentEditConfirm(result) {
try {
console.log('学员编辑确认结果:', result)
let res
if (result.isEditing) {
// 编辑模式 - 调用学员信息更新API
console.log('编辑学员信息:', result.studentData)
res = await apiRoute.xs_editStudent(result.studentData)
} else {
// 新增模式 - 调用学员添加API
console.log('新增学员信息:', result.studentData)
res = await apiRoute.xs_addStudent(result.studentData)
}
if (res.code === 1) {
this.$refs.studentEditPopup.close()
uni.showToast({
title: result.isEditing ? '编辑成功' : '添加成功',
icon: 'success'
})
// 保存成功后刷新学生列表
await this.getStudentList()
} else {
uni.showToast({ title: res.msg || '保存失败', icon: 'none' })
}
} catch (error) {
console.error('保存学生信息失败:', error)
uni.showToast({ title: '保存失败', icon: 'none' })
}
},
// 获取学习计划列表
async getStudyPlanList(studentId = null) {
if (!this.clientInfo?.resource_id) return
const targetStudentId = studentId || this.currentStudent?.id
if (!targetStudentId) {
console.warn('学习计划列表:缺少学生ID')
this.studyPlanList = []
return
}
try {
const response = await apiRoute.getStudyPlanList({
student_id: targetStudentId
})
if (response.code === 1) {
this.studyPlanList = response.data || []
} else {
console.error('获取学习计划失败:', response.msg)
this.studyPlanList = []
}
} catch (error) {
console.error('获取学习计划异常:', error)
this.studyPlanList = []
}
},
// 获取订单列表
async getOrderList(studentId = null) {
if (!this.clientInfo?.resource_id) return
try {
const targetStudentId = studentId || this.currentStudent?.id
// 构建查询参数,优先使用student_id
const params = {}
if (targetStudentId) {
params.student_id = targetStudentId
} else if (this.clientInfo.resource_id) {
params.resource_id = this.clientInfo.resource_id
} else {
console.warn('缺少查询参数')
this.orderList = []
return
}
const res = await apiRoute.xs_orderTableList(params)
if (res.code === 1) {
// 处理API返回的数据格式
this.orderList = this.processOrderData(res.data?.data || [])
} else {
console.error('获取订单列表失败:', res.msg)
this.orderList = []
}
} catch (error) {
console.error('获取订单列表异常:', error)
this.orderList = []
}
},
// 处理订单数据格式,适配前端组件
processOrderData(orders) {
if (!Array.isArray(orders)) return []
return orders.map(order => ({
id: order.id,
student_id: order.student_id,
order_no: order.payment_id || `ORD${order.id}`, // 使用payment_id作为订单号
product_name: order.course_id_name || '课程',
total_amount: order.order_amount || '0.00',
paid_amount: order.order_status === 'paid' ? order.order_amount : '0.00',
unpaid_amount: order.order_status === 'paid' ? '0.00' : order.order_amount,
payment_method: this.formatPaymentType(order.payment_type),
salesperson_name: order.staff_id_name || '未指定',
course_count: order.total_hours || 0, // 使用课时数
status: this.mapOrderStatus(order.order_status),
create_time: order.created_at,
// 保留原始数据
_raw: order
}))
},
// 格式化支付类型
formatPaymentType(paymentType) {
const typeMap = {
'cash': '现金支付',
'scan_code': '扫码支付',
'subscription': '订阅支付',
'wxpay_online': '微信在线代付'
}
return typeMap[paymentType] || paymentType || '未知'
},
// 映射订单状态
mapOrderStatus(orderStatus) {
const statusMap = {
'pending': 'pending',
'paid': 'paid',
'signed': 'completed',
'completed': 'completed',
'transfer': 'partial'
}
return statusMap[orderStatus] || 'pending'
},
// 获取服务列表
async getServiceList(studentId = null) {
if (!this.clientInfo?.resource_id) return
const targetStudentId = studentId || this.currentStudent?.id
if (!targetStudentId) {
uni.showToast({ title: '请先选择学生', icon: 'none' })
return
}
try {
const response = await apiRoute.getStudentServiceList({
student_id: targetStudentId
})
if (response.code === 1) {
this.serviceList = response.data || []
} else {
console.error('获取服务记录失败:', response.msg)
uni.showToast({
title: response.msg || '获取服务记录失败',
icon: 'none'
})
this.serviceList = []
}
} catch (error) {
console.error('获取服务记录异常:', error)
uni.showToast({
title: '网络请求失败',
icon: 'none'
})
this.serviceList = []
}
},
// 打开新增订单弹窗
openAddOrderDialog() {
if (!this.currentStudent) {
uni.showToast({
title: '请先选择学生',
icon: 'none'
})
return
}
// 先关闭订单列表弹窗,避免遮挡新增订单弹窗
this.closePopup()
this.showOrderForm = true
this.$refs.orderFormPopup.open()
},
// 关闭新增订单弹窗
closeOrderForm() {
this.showOrderForm = false
this.$refs.orderFormPopup.close()
},
// 订单表单确认处理
async handleOrderFormConfirm() {
try {
// 关闽弹窗
this.closeOrderForm()
// 刷新订单列表
await this.getOrderList()
uni.showToast({ title: '订单创建成功', icon: 'success' })
} catch (error) {
console.error('处理订单确认失败:', error)
}
},
// 处理订单支付
handlePayOrder(order) {
// 关闭当前弹窗
this.closePopup()
// 根据支付类型处理不同的支付流程
this.processPayment(order)
},
// 处理支付流程
async processPayment(order) {
const paymentType = order._raw?.payment_type || order.payment_type
console.log('paymentType', paymentType)
try {
switch(paymentType) {
case 'cash':
// 现金支付 - 直接标记为已支付
await this.confirmCashPayment(order)
break
case 'scan_code':
// 扫码支付 - 显示二维码
this.showQRCodePayment(order)
break
case 'subscription':
// 订阅支付 - 分期支付流程
this.showSubscriptionPayment(order)
break
case 'wxpay_online':
// 微信在线代付 - 调用微信支付
this.showWechatPayment(order)
break
default:
uni.showToast({ title: '不支持的支付方式', icon: 'none' })
}
} catch (error) {
console.error('支付处理失败:', error)
uni.showToast({ title: '支付处理失败', icon: 'none' })
}
},
// 现金支付确认
async confirmCashPayment(order) {
uni.showModal({
title: '现金支付确认',
content: `确认已收到现金支付 ¥${order.total_amount}`,
success: async (res) => {
if (res.confirm) {
try {
// 调用API更新订单状态为已支付
const updateData = {
order_id: order._raw?.id || order.id,
order_status: 'paid',
payment_id: `CASH${Date.now()}` // 生成现金支付单号
}
const result = await apiRoute.xs_orderTableUpdatePaymentStatus(updateData)
if (result.code === 1) {
uni.showToast({ title: '支付确认成功', icon: 'success' })
// 刷新订单列表
await this.getOrderList()
} else {
uni.showToast({ title: result.msg || '支付确认失败', icon: 'none' })
}
} catch (error) {
console.error('现金支付确认失败:', error)
uni.showToast({ title: '支付确认失败', icon: 'none' })
}
}
}
})
},
// 扫码支付
async showQRCodePayment(order) {
console.log('扫码支付:', order)
try {
uni.showLoading({ title: '生成支付二维码...' })
// 调用接口获取支付二维码
const res = await apiRoute.getOrderPayQrcode({
order_id: order._raw?.id || order.id
})
uni.hideLoading()
if (res.code === 1 && res.data) {
// 显示二维码支付弹窗
this.openQRCodeModal({
order: order,
qrcode: res.data.code_url,
// 优先使用base64编码的二维码(解决微信小程序无法访问localhost的问题)
qrcodeImage: res.data.qrcode_base64 || res.data.qrcode_url
})
} else {
uni.showToast({
title: res.msg || '获取支付二维码失败',
icon: 'none'
})
}
} catch (error) {
uni.hideLoading()
console.error('获取支付二维码失败:', error)
uni.showToast({
title: '获取支付二维码失败',
icon: 'none'
})
}
},
// 打开二维码支付弹窗
openQRCodeModal(paymentData) {
this.qrCodePaymentData = paymentData
this.showQRCodeModal = true
this.$refs.qrCodePopup.open()
},
// 关闭二维码支付弹窗
closeQRCodeModal() {
this.showQRCodeModal = false
this.qrCodePaymentData = null
this.$refs.qrCodePopup.close()
},
// 确认二维码支付完成
async confirmQRCodePayment() {
if (!this.qrCodePaymentData?.order) return
const order = this.qrCodePaymentData.order
uni.showModal({
title: '支付确认',
content: '请确认是否已完成扫码支付?',
success: async (res) => {
if (res.confirm) {
try {
// 更新订单状态为已支付
await this.updateOrderStatus(order, 'paid', `QR${Date.now()}`)
this.closeQRCodeModal()
} catch (error) {
console.error('支付确认失败:', error)
uni.showToast({ title: '支付确认失败', icon: 'none' })
}
}
}
})
},
// 订阅支付
showSubscriptionPayment(order) {
uni.showActionSheet({
itemList: ['确认分期支付方案', '取消支付'],
success: async (res) => {
if (res.tapIndex === 0) {
uni.showModal({
title: '分期支付确认',
content: `确认使用分期支付方式支付 ¥${order.total_amount}`,
success: async (modalRes) => {
if (modalRes.confirm) {
// 这里可以设置为部分支付状态
await this.updateOrderStatus(order, 'partial', `SUB${Date.now()}`)
}
}
})
}
}
})
},
// 微信支付
showWechatPayment(order) {
uni.showModal({
title: '微信支付',
content: `将调用微信支付 ¥${order.total_amount}`,
confirmText: '确认支付',
cancelText: '取消',
success: async (res) => {
if (res.confirm) {
// 模拟微信支付流程
uni.showLoading({ title: '正在调用微信支付...' })
// 模拟支付延时
setTimeout(async () => {
uni.hideLoading()
// 模拟支付结果(实际应该调用微信支付API)
uni.showModal({
title: '支付结果',
content: '微信支付完成,请确认是否已收到款项?',
success: async (confirmRes) => {
if (confirmRes.confirm) {
await this.updateOrderStatus(order, 'paid', `WX${Date.now()}`)
}
}
})
}, 1500)
}
}
})
},
// 统一的订单状态更新方法
async updateOrderStatus(order, status, paymentId = '') {
try {
const updateData = {
order_id: order._raw?.id || order.id,
order_status: status,
payment_id: paymentId
}
const result = await apiRoute.xs_orderTableUpdatePaymentStatus(updateData)
if (result.code === 1) {
const statusText = {
'paid': '支付成功',
'partial': '分期支付确认成功',
'cancelled': '支付已取消'
}
uni.showToast({
title: statusText[status] || '状态更新成功',
icon: 'success'
})
// 刷新订单列表
await this.getOrderList()
} else {
uni.showToast({ title: result.msg || '状态更新失败', icon: 'none' })
}
} catch (error) {
console.error('更新订单状态失败:', error)
uni.showToast({ title: '状态更新失败', icon: 'none' })
}
},
// 查看订单详情
viewOrderDetail(order) {
const orderInfo = order._raw || order
const detailText = `
订单号:${order.order_no}
课程:${order.product_name}
金额:¥${order.total_amount}
已付:¥${order.paid_amount}
未付:¥${order.unpaid_amount}
支付方式:${order.payment_method}
销售顾问:${order.salesperson_name}
课时数:${order.course_count}
状态:${this.getOrderStatusText(order.status)}
创建时间:${this.formatOrderTime(order.create_time)}
${orderInfo.paid_at ? '支付时间:' + this.formatOrderTime(orderInfo.paid_at) : ''}
`.trim()
// 检查是否为已支付订单,显示不同的按钮
const isOrderPaid = order.status === 'paid'
const buttons = isOrderPaid ? ['知道了', '合同签署'] : ['知道了']
uni.showModal({
title: '订单详情',
content: detailText,
showCancel: isOrderPaid,
cancelText: isOrderPaid ? '知道了' : '',
confirmText: isOrderPaid ? '合同签署' : '知道了',
success: (res) => {
if (res.confirm && isOrderPaid) {
// 点击了合同签署按钮
this.goToContractSign(order)
}
}
})
},
// 跳转到合同签署页面
goToContractSign(order) {
// 检查必要参数
const studentId = order.student_id || this.currentStudent?.id
const contractId = order._raw?.contract_id || order.contract_id
if (!studentId) {
uni.showToast({
title: '缺少学生信息',
icon: 'none'
})
return
}
if (!contractId) {
// 如果订单中没有合同ID,尝试从课程信息或其他地方获取
// 这里可能需要调用API获取合同信息
this.getContractByOrder(order, studentId)
return
}
// 跳转到合同签署页面
uni.navigateTo({
url: `/pages-student/contracts/sign?contract_id=${contractId}&student_id=${studentId}&contract_name=${encodeURIComponent(order.product_name + '合同')}`
})
},
// 根据订单获取合同信息
async getContractByOrder(order, studentId) {
try {
uni.showLoading({ title: '获取合同信息...' })
// 调用API获取订单对应的合同
const res = await apiRoute.getContractByOrder({
order_id: order._raw?.id || order.id,
student_id: studentId
})
uni.hideLoading()
if (res.code === 1 && res.data) {
const contractInfo = res.data
// 跳转到合同签署页面
uni.navigateTo({
url: `/pages-student/contracts/sign?contract_id=${contractInfo.contract_id}&student_id=${studentId}&contract_name=${encodeURIComponent(contractInfo.contract_name || order.product_name + '合同')}`
})
} else {
uni.showToast({
title: res.msg || '未找到相关合同',
icon: 'none'
})
}
} catch (error) {
uni.hideLoading()
console.error('获取合同信息失败:', error)
uni.showToast({
title: '获取合同信息失败',
icon: 'none'
})
}
},
// 获取订单状态文本
getOrderStatusText(status) {
const statusMap = {
'pending': '待支付',
'paid': '已支付',
'partial': '部分支付',
'cancelled': '已取消',
'completed': '已完成',
'refunded': '已退款'
}
return statusMap[status] || '未知状态'
},
// 格式化订单时间
formatOrderTime(timeStr) {
if (!timeStr) return '未知'
try {
const date = new Date(timeStr)
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
} catch (e) {
return timeStr
}
},
// 获取赠品记录列表
async getGiftRecords() {
if (!this.clientInfo?.resource_id) return
try {
const res = await apiRoute.xs_customerResourcesGetGiftRecordList({
resource_id: this.clientInfo.resource_id
})
if (res.code === 1) {
this.giftRecords = res.data || []
} else {
console.error('获取赠品记录失败:', res.msg)
this.giftRecords = []
}
} catch (error) {
console.error('获取赠品记录异常:', error)
this.giftRecords = []
}
}
},
}
</script>
<style lang="less" scoped>
.fitness-records-container {
max-height: 60vh;
overflow-y: auto;
}
/* 滚动条样式 */
.fitness-records-container::-webkit-scrollbar {
width: 6rpx;
}
.fitness-records-container::-webkit-scrollbar-track {
background: transparent;
}
.fitness-records-container::-webkit-scrollbar-thumb {
background: #29D3B4;
border-radius: 3rpx;
}
.fitness-records-container::-webkit-scrollbar-thumb:hover {
background: #24B89E;
}
@import './clue_info.less';
</style>