智慧教务系统
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.
 
 
 
 
 
 

597 lines
17 KiB

<template>
<el-dialog
v-model="visible"
title="学员详情"
width="800px"
class="student-detail-dialog"
:destroy-on-close="true"
>
<div v-loading="loading" class="student-detail-container">
<!-- 学员基本信息 -->
<el-card class="info-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">基本信息</span>
<el-button
v-if="!editMode"
type="primary"
size="small"
@click="enterEditMode"
>
编辑
</el-button>
<div v-else class="edit-actions">
<el-button size="small" @click="cancelEdit">取消</el-button>
<el-button
type="primary"
size="small"
:loading="saving"
@click="saveChanges"
>
保存
</el-button>
</div>
</div>
</template>
<el-form
ref="studentFormRef"
:model="formData"
:rules="formRules"
label-width="120px"
class="student-form"
>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="姓名" prop="name">
<el-input
v-model="formData.name"
:disabled="!editMode"
placeholder="请输入学员姓名"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="性别" prop="gender">
<el-select
v-model="formData.gender"
:disabled="!editMode"
placeholder="请选择性别"
style="width: 100%"
>
<el-option label="男" :value="1" />
<el-option label="女" :value="2" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="年龄" prop="age">
<el-input-number
v-model="formData.age"
:disabled="!editMode"
:min="0"
:max="150"
:precision="0"
placeholder="年龄"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="生日" prop="birthday">
<el-date-picker
v-model="formData.birthday"
:disabled="!editMode"
type="date"
placeholder="请选择生日"
style="width: 100%"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="联系电话" prop="contact_phone">
<el-input
v-model="formData.contact_phone"
:disabled="!editMode"
placeholder="请输入联系电话"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="紧急联系人" prop="emergency_contact">
<el-input
v-model="formData.emergency_contact"
:disabled="!editMode"
placeholder="请输入紧急联系人"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="校区" prop="campus_id">
<el-select
v-model="formData.campus_id"
:disabled="!editMode"
placeholder="请选择校区"
style="width: 100%"
@change="handleCampusChange"
>
<el-option
v-for="campus in campusList"
:key="campus.id"
:label="campus.name"
:value="campus.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="班级" prop="class_id">
<el-select
v-model="formData.class_id"
:disabled="!editMode"
placeholder="请选择班级"
style="width: 100%"
>
<el-option
v-for="classItem in classList"
:key="classItem.id"
:label="classItem.name"
:value="classItem.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="顾问" prop="consultant_id">
<el-select
v-model="formData.consultant_id"
:disabled="!editMode"
placeholder="请选择顾问"
style="width: 100%"
>
<el-option
v-for="consultant in consultantList"
:key="consultant.id"
:label="consultant.name"
:value="consultant.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="教练" prop="coach_id">
<el-select
v-model="formData.coach_id"
:disabled="!editMode"
placeholder="请选择教练"
style="width: 100%"
>
<el-option
v-for="coach in coachList"
:key="coach.id"
:label="coach.name"
:value="coach.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="会员标签" prop="member_label">
<el-input
v-model="formData.member_label"
:disabled="!editMode"
placeholder="请输入会员标签"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="试听次数" prop="trial_class_count">
<el-input-number
v-model="formData.trial_class_count"
:disabled="!editMode"
:min="0"
placeholder="试听次数"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-select
v-model="formData.status"
:disabled="!editMode"
placeholder="请选择状态"
style="width: 100%"
>
<el-option label="正常" :value="1" />
<el-option label="暂停" :value="0" />
<el-option label="其他" :value="2" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="创建时间">
<el-input
:value="formatDateTime(formData.created_at)"
disabled
placeholder="创建时间"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="备注" prop="note">
<el-input
v-model="formData.note"
:disabled="!editMode"
type="textarea"
:rows="3"
placeholder="请输入备注信息"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- 学员记录信息 -->
<el-card class="info-card" shadow="never">
<template #header>
<span class="card-title">记录信息</span>
</template>
<el-descriptions :column="2" border>
<el-descriptions-item label="用户ID">
{{ formData.user_id || '-' }}
</el-descriptions-item>
<el-descriptions-item label="学员ID">
{{ formData.id || '-' }}
</el-descriptions-item>
<el-descriptions-item label="首次到店">
{{ formatDateTime(formData.first_come) || '-' }}
</el-descriptions-item>
<el-descriptions-item label="二次到店">
{{ formatDateTime(formData.second_come) || '-' }}
</el-descriptions-item>
<el-descriptions-item label="创建人ID">
{{ formData.created_person_id || '-' }}
</el-descriptions-item>
<el-descriptions-item label="最后更新">
{{ formatDateTime(formData.updated_at) || '-' }}
</el-descriptions-item>
</el-descriptions>
</el-card>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="visible = false">关闭</el-button>
<el-button
v-if="!editMode"
type="primary"
@click="enterEditMode"
>
编辑学员信息
</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, reactive, nextTick } from 'vue'
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
import { getStudentInfo, editStudent } from '@/app/api/student'
import { getWithCampusList, getWithClassGradeList } from '@/app/api/student'
// 学员数据接口
interface StudentDetailInfo {
id: number
name: string
gender: number
age: number
birthday: string
user_id: number
campus_id: number
class_id: number
note: string
status: number
emergency_contact: string
contact_phone: string
member_label: string
consultant_id: string
coach_id: string
trial_class_count: number
headimg: string
first_come: string
second_come: string
created_person_id: number
created_at: string
updated_at: string
}
// 选项数据接口
interface OptionItem {
id: number
name: string
}
// 响应式数据
const visible = ref(false)
const loading = ref(false)
const saving = ref(false)
const editMode = ref(false)
const studentFormRef = ref<FormInstance>()
// 表单数据
const formData = reactive<Partial<StudentDetailInfo>>({})
const originalData = reactive<Partial<StudentDetailInfo>>({})
// 选项数据
const campusList = ref<OptionItem[]>([])
const classList = ref<OptionItem[]>([])
const consultantList = ref<OptionItem[]>([])
const coachList = ref<OptionItem[]>([])
// 表单验证规则
const formRules: FormRules = {
name: [
{ required: true, message: '请输入学员姓名', trigger: 'blur' },
{ min: 2, max: 50, message: '姓名长度在 2 到 50 个字符', trigger: 'blur' }
],
gender: [
{ required: true, message: '请选择性别', trigger: 'change' }
],
contact_phone: [
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
]
}
// 打开弹窗
const open = async (studentId: number) => {
if (!studentId) {
ElMessage.error('学员ID不能为空')
return
}
visible.value = true
editMode.value = false
await loadStudentDetail(studentId)
}
// 加载学员详情
const loadStudentDetail = async (studentId: number) => {
try {
loading.value = true
// 并行加载学员详情和选项数据
const [studentResponse, campusResponse, classResponse] = await Promise.all([
getStudentInfo(studentId),
getWithCampusList({}),
getWithClassGradeList({})
])
if (studentResponse.code === 1 && studentResponse.data) {
// 更新表单数据
Object.assign(formData, studentResponse.data)
Object.assign(originalData, studentResponse.data)
console.log('学员详情加载成功:', studentResponse.data)
} else {
ElMessage.error(studentResponse.msg || '获取学员详情失败')
return
}
// 更新选项数据
if (campusResponse.code === 1) {
campusList.value = campusResponse.data || []
}
if (classResponse.code === 1) {
classList.value = classResponse.data || []
}
// 加载顾问和教练数据
await loadPersonnelData()
} catch (error) {
console.error('加载学员详情失败:', error)
ElMessage.error('加载学员详情失败')
} finally {
loading.value = false
}
}
// 加载人员数据(顾问、教练)
const loadPersonnelData = async () => {
try {
// 根据当前学员的校区加载对应的顾问和教练
const campusId = formData.campus_id
// 模拟API调用 - 实际项目中需要调用真实的API
// 可以根据实际的API接口进行调整
consultantList.value = [
{ id: 1, name: '张顾问' },
{ id: 2, name: '李顾问' },
{ id: 3, name: '王顾问' }
]
coachList.value = [
{ id: 1, name: '赵教练' },
{ id: 2, name: '钱教练' },
{ id: 3, name: '孙教练' }
]
// 如果有获取人员的API,可以这样调用:
// const personnelResponse = await getPersonnelByDept(campusId, [1, 24]) // 1:市场部门(顾问), 24:教练部门
// if (personnelResponse.code === 1 && personnelResponse.data) {
// const personnel = personnelResponse.data
// consultantList.value = personnel.filter(p => p.dept_id === 1) // 市场部门为顾问
// coachList.value = personnel.filter(p => p.dept_id === 24) // 教练部门为教练
// }
} catch (error) {
console.error('加载人员数据失败:', error)
}
}
// 处理校区变化
const handleCampusChange = (campusId: number) => {
// 校区变化时清空班级选择
formData.class_id = undefined
// 根据校区筛选班级
// 这里可以根据实际需求调用相关API
console.log('校区变化:', campusId)
}
// 进入编辑模式
const enterEditMode = () => {
editMode.value = true
// 备份原始数据
Object.assign(originalData, formData)
}
// 取消编辑
const cancelEdit = () => {
editMode.value = false
// 恢复原始数据
Object.assign(formData, originalData)
}
// 保存修改
const saveChanges = async () => {
if (!studentFormRef.value) return
try {
// 表单验证
const valid = await studentFormRef.value.validate()
if (!valid) return
saving.value = true
const response = await editStudent({
id: formData.id,
...formData
})
if (response.code === 1) {
ElMessage.success('学员信息更新成功')
editMode.value = false
// 更新原始数据
Object.assign(originalData, formData)
} else {
ElMessage.error(response.msg || '更新失败')
}
} catch (error) {
console.error('保存学员信息失败:', error)
ElMessage.error('保存失败')
} finally {
saving.value = false
}
}
// 格式化日期时间
const formatDateTime = (dateTime: string) => {
if (!dateTime) return ''
try {
return new Date(dateTime).toLocaleString('zh-CN')
} catch {
return dateTime
}
}
// 暴露方法给父组件
defineExpose({
open
})
</script>
<style lang="scss" scoped>
.student-detail-dialog {
.student-detail-container {
max-height: 70vh;
overflow-y: auto;
}
.info-card {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
.card-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.edit-actions {
display: flex;
gap: 8px;
}
}
}
.student-form {
.el-form-item {
margin-bottom: 20px;
}
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
}
:deep(.el-dialog__body) {
padding: 20px;
}
:deep(.el-card__header) {
padding: 16px 20px;
border-bottom: 1px solid #f0f0f0;
}
:deep(.el-card__body) {
padding: 20px;
}
:deep(.el-descriptions__label) {
font-weight: 600;
color: #606266;
}
:deep(.el-descriptions__content) {
color: #303133;
}
</style>