Files
hzdyjs/web/src/components/InstructorManager/index.vue
T
2025-08-25 15:53:02 +08:00

315 lines
8.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>