315 lines
8.3 KiB
Vue
315 lines
8.3 KiB
Vue
<template>
|
||
<div class="instructor-manager">
|
||
<div class="header">
|
||
<span class="title">指导老师信息</span>
|
||
<el-button type="primary" size="small" @click="handleAdd">
|
||
<el-icon><Plus /></el-icon>
|
||
添加指导老师
|
||
</el-button>
|
||
</div>
|
||
|
||
<div v-if="instructors.length === 0" class="empty-state">
|
||
<el-empty description="暂无指导老师信息" :image-size="80" />
|
||
</div>
|
||
|
||
<div v-else class="instructor-list">
|
||
<div v-for="(instructor, index) in instructors" :key="index" class="instructor-item">
|
||
<div class="instructor-content">
|
||
<div class="instructor-info">
|
||
<div class="info-row">
|
||
<span class="label">老师姓名:</span>
|
||
<span class="value">{{ instructor.name || '未填写' }}</span>
|
||
</div>
|
||
<div class="info-row" v-if="instructor.dept">
|
||
<span class="label">所属部门:</span>
|
||
<span class="value">{{ instructor.dept }}</span>
|
||
</div>
|
||
<div class="info-row" v-if="instructor.phone">
|
||
<span class="label">联系电话:</span>
|
||
<span class="value">{{ instructor.phone }}</span>
|
||
</div>
|
||
<div class="info-row" v-if="instructor.email">
|
||
<span class="label">邮箱:</span>
|
||
<span class="value">{{ instructor.email }}</span>
|
||
</div>
|
||
<div class="info-row" v-if="instructor.hours">
|
||
<span class="label">指导学时:</span>
|
||
<span class="value">{{ instructor.hours }} 学时</span>
|
||
</div>
|
||
</div>
|
||
<div class="instructor-actions">
|
||
<el-button type="primary" link size="small" @click="handleEdit(index)">
|
||
<el-icon><Edit /></el-icon>
|
||
编辑
|
||
</el-button>
|
||
<el-button type="danger" link size="small" @click="handleDelete(index)">
|
||
<el-icon><Delete /></el-icon>
|
||
删除
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 添加/编辑指导老师对话框 -->
|
||
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px" append-to-body>
|
||
<el-form ref="formRef" :model="currentInstructor" :rules="rules" label-width="100px">
|
||
<el-form-item label="老师姓名" prop="name">
|
||
<el-input v-model="currentInstructor.name" placeholder="请输入老师姓名" maxlength="50" show-word-limit />
|
||
</el-form-item>
|
||
<el-form-item label="所属部门" prop="dept">
|
||
<el-input v-model="currentInstructor.dept" placeholder="请输入所属部门" maxlength="100" />
|
||
</el-form-item>
|
||
<el-form-item label="联系电话" prop="phone">
|
||
<el-input v-model="currentInstructor.phone" placeholder="请输入联系电话" maxlength="20" />
|
||
</el-form-item>
|
||
<el-form-item label="邮箱" prop="email">
|
||
<el-input v-model="currentInstructor.email" placeholder="请输入邮箱地址" maxlength="100" />
|
||
</el-form-item>
|
||
<el-form-item label="指导学时" prop="hours">
|
||
<el-input-number v-model="currentInstructor.hours" :min="0" :max="9999" placeholder="请输入指导学时" style="width: 100%" />
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<el-button @click="handleCancel">取消</el-button>
|
||
<el-button type="primary" @click="handleConfirm">确定</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed, watch } from 'vue';
|
||
import { Plus, Edit, Delete } from '@element-plus/icons-vue';
|
||
import { InstructorInfo } from '@/api/stgl/activity/types';
|
||
import type { FormInstance, FormRules } from 'element-plus';
|
||
|
||
interface Props {
|
||
modelValue: InstructorInfo[];
|
||
}
|
||
|
||
interface Emits {
|
||
(e: 'update:modelValue', value: InstructorInfo[]): void;
|
||
(e: 'change', value: InstructorInfo[]): void;
|
||
}
|
||
|
||
const props = withDefaults(defineProps<Props>(), {
|
||
modelValue: () => []
|
||
});
|
||
|
||
const emit = defineEmits<Emits>();
|
||
|
||
const dialogVisible = ref(false);
|
||
const editIndex = ref(-1);
|
||
const formRef = ref<FormInstance>();
|
||
|
||
const instructors = ref<InstructorInfo[]>([...props.modelValue]);
|
||
|
||
// 当前编辑的指导老师
|
||
const currentInstructor = ref<InstructorInfo>({
|
||
name: '',
|
||
dept: '',
|
||
phone: '',
|
||
email: '',
|
||
hours: undefined
|
||
});
|
||
|
||
// 对话框标题
|
||
const dialogTitle = computed(() => {
|
||
return editIndex.value >= 0 ? '编辑指导老师' : '添加指导老师';
|
||
});
|
||
|
||
// 表单验证规则
|
||
const rules: FormRules = {
|
||
name: [
|
||
{ required: true, message: '请输入老师姓名', trigger: 'blur' },
|
||
{ min: 1, max: 50, message: '老师姓名长度在1到50个字符', trigger: 'blur' }
|
||
],
|
||
dept: [{ max: 100, message: '所属部门长度不能超过100个字符', trigger: 'blur' }],
|
||
phone: [
|
||
{ max: 20, message: '联系电话长度不能超过20个字符', trigger: 'blur' },
|
||
{ pattern: /^[0-9\-\s\+\(\)]*$/, message: '请输入正确的电话号码格式', trigger: 'blur' }
|
||
],
|
||
email: [
|
||
{ max: 100, message: '邮箱长度不能超过100个字符', trigger: 'blur' },
|
||
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
|
||
],
|
||
hours: [{ type: 'number', min: 0, max: 9999, message: '指导学时必须在0到9999之间', trigger: 'blur' }]
|
||
};
|
||
|
||
// 监听props变化
|
||
watch(
|
||
() => props.modelValue,
|
||
(newValue) => {
|
||
instructors.value = [...newValue];
|
||
},
|
||
{ deep: true }
|
||
);
|
||
|
||
// 监听内部数据变化,同步到父组件
|
||
watch(
|
||
instructors,
|
||
(newValue) => {
|
||
emit('update:modelValue', newValue);
|
||
emit('change', newValue);
|
||
},
|
||
{ deep: true }
|
||
);
|
||
|
||
// 添加指导老师
|
||
const handleAdd = () => {
|
||
editIndex.value = -1;
|
||
currentInstructor.value = {
|
||
name: '',
|
||
dept: '',
|
||
phone: '',
|
||
email: '',
|
||
hours: undefined
|
||
};
|
||
dialogVisible.value = true;
|
||
};
|
||
|
||
// 编辑指导老师
|
||
const handleEdit = (index: number) => {
|
||
editIndex.value = index;
|
||
currentInstructor.value = { ...instructors.value[index] };
|
||
dialogVisible.value = true;
|
||
};
|
||
|
||
// 删除指导老师
|
||
const handleDelete = (index: number) => {
|
||
ElMessageBox.confirm('确定要删除这位指导老师吗?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
})
|
||
.then(() => {
|
||
instructors.value.splice(index, 1);
|
||
ElMessage.success('删除成功');
|
||
})
|
||
.catch(() => {
|
||
// 用户取消删除
|
||
});
|
||
};
|
||
|
||
// 确认添加/编辑
|
||
const handleConfirm = async () => {
|
||
if (!formRef.value) return;
|
||
|
||
await formRef.value.validate((valid) => {
|
||
if (valid) {
|
||
if (editIndex.value >= 0) {
|
||
// 编辑模式
|
||
instructors.value[editIndex.value] = { ...currentInstructor.value };
|
||
ElMessage.success('编辑成功');
|
||
} else {
|
||
// 添加模式
|
||
instructors.value.push({ ...currentInstructor.value });
|
||
ElMessage.success('添加成功');
|
||
}
|
||
dialogVisible.value = false;
|
||
}
|
||
});
|
||
};
|
||
|
||
// 取消操作
|
||
const handleCancel = () => {
|
||
dialogVisible.value = false;
|
||
formRef.value?.resetFields();
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.instructor-manager {
|
||
width: 100%;
|
||
}
|
||
|
||
.header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.title {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
|
||
.empty-state {
|
||
text-align: center;
|
||
padding: 40px 20px;
|
||
border: 1px dashed #dcdfe6;
|
||
border-radius: 4px;
|
||
background-color: #fafafa;
|
||
}
|
||
|
||
.instructor-list {
|
||
space-y: 12px;
|
||
}
|
||
|
||
.instructor-item {
|
||
border: 1px solid #e4e7ed;
|
||
border-radius: 6px;
|
||
padding: 16px;
|
||
background-color: #fafafa;
|
||
margin-bottom: 12px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.instructor-item:hover {
|
||
border-color: #409eff;
|
||
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
|
||
}
|
||
|
||
.instructor-content {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.instructor-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.info-row {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.info-row:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.label {
|
||
font-size: 13px;
|
||
color: #606266;
|
||
min-width: 80px;
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.value {
|
||
font-size: 13px;
|
||
color: #303133;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.instructor-actions {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
margin-left: 16px;
|
||
}
|
||
|
||
.dialog-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 10px;
|
||
}
|
||
</style>
|