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
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>
|