704 lines
25 KiB
Vue
704 lines
25 KiB
Vue
<script setup lang="ts">
|
|
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
|
import { Calendar, Document, Download, Key, Plus, Refresh, Search, Upload } from '@element-plus/icons-vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import http, { apiErrorMessage } from '../api/http'
|
|
import { downloadApiFile, importExcel } from '../api/excel'
|
|
import { useAuthStore } from '../stores/auth'
|
|
|
|
type Registry = 'teachers' | 'students'
|
|
interface PageResult {
|
|
items: any[]
|
|
total: number
|
|
page: number
|
|
pageSize: number
|
|
}
|
|
|
|
const auth = useAuthStore()
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const active = computed<Registry>(() =>
|
|
route.meta.registry === 'students' ? 'students' : 'teachers',
|
|
)
|
|
const loading = ref(false)
|
|
const importing = ref(false)
|
|
const fileInput = ref<HTMLInputElement>()
|
|
const rows = ref<any[]>([])
|
|
const total = ref(0)
|
|
const dialogVisible = ref(false)
|
|
const activationDialog = ref(false)
|
|
const activationLoading = ref(false)
|
|
const activatingTeacher = ref<any | null>(null)
|
|
const editingId = ref('')
|
|
const colleges = ref<any[]>([])
|
|
const majors = ref<any[]>([])
|
|
const classes = ref<any[]>([])
|
|
const query = reactive({
|
|
page: 1,
|
|
pageSize: 20,
|
|
keyword: '',
|
|
collegeId: undefined as string | undefined,
|
|
majorId: undefined as string | undefined,
|
|
classId: undefined as string | undefined,
|
|
enrollmentYear: undefined as number | undefined,
|
|
status: undefined as string | undefined,
|
|
})
|
|
const form = reactive<Record<string, any>>({})
|
|
const activationForm = reactive({
|
|
password: '',
|
|
confirmPassword: '',
|
|
})
|
|
|
|
const canManage = computed(() =>
|
|
auth.user?.roles.some((role) =>
|
|
['SuperAdmin', 'AcademicAdmin', 'CollegeAdmin'].includes(role),
|
|
) ?? false,
|
|
)
|
|
const scopedCollegeId = computed(() =>
|
|
auth.user?.roles.some((role) => ['CollegeAdmin', 'Counselor'].includes(role))
|
|
? auth.user.collegeId
|
|
: undefined,
|
|
)
|
|
const availableColleges = computed(() =>
|
|
scopedCollegeId.value
|
|
? colleges.value.filter((item) => item.id === scopedCollegeId.value)
|
|
: colleges.value,
|
|
)
|
|
const availableMajors = computed(() =>
|
|
scopedCollegeId.value
|
|
? majors.value.filter((item) => item.collegeId === scopedCollegeId.value)
|
|
: majors.value,
|
|
)
|
|
const availableClasses = computed(() => {
|
|
const majorIds = new Set(availableMajors.value.map((item) => item.id))
|
|
return classes.value.filter((item) => majorIds.has(item.majorId))
|
|
})
|
|
const pageTitle = computed(() => active.value === 'teachers' ? '教师档案' : '学生档案')
|
|
const numberLabel = computed(() => active.value === 'teachers' ? '工号' : '学号')
|
|
const filteredMajors = computed(() =>
|
|
query.collegeId
|
|
? availableMajors.value.filter((item) => item.collegeId === query.collegeId)
|
|
: availableMajors.value,
|
|
)
|
|
const filteredClasses = computed(() => {
|
|
if (query.majorId) {
|
|
return availableClasses.value.filter((item) => item.majorId === query.majorId)
|
|
}
|
|
if (query.collegeId) {
|
|
const majorIds = new Set(
|
|
availableMajors.value
|
|
.filter((item) => item.collegeId === query.collegeId)
|
|
.map((item) => item.id),
|
|
)
|
|
return availableClasses.value.filter((item) => majorIds.has(item.majorId))
|
|
}
|
|
return availableClasses.value
|
|
})
|
|
const formClasses = computed(() => availableClasses.value)
|
|
|
|
const genderLabels: Record<string, string> = {
|
|
Unknown: '未设置', Male: '男', Female: '女',
|
|
}
|
|
const teacherStatusLabels: Record<string, string> = {
|
|
Active: '在职', OnLeave: '离岗', Retired: '退休', Departed: '离职',
|
|
}
|
|
const studentStatusLabels: Record<string, string> = {
|
|
Active: '在籍', Suspended: '休学', Graduated: '毕业', Withdrawn: '退学',
|
|
}
|
|
|
|
function resetForm(row?: any) {
|
|
Object.keys(form).forEach((key) => delete form[key])
|
|
if (active.value === 'teachers') {
|
|
Object.assign(form, {
|
|
teacherNumber: '',
|
|
name: '',
|
|
gender: 'Unknown',
|
|
collegeId: scopedCollegeId.value,
|
|
title: '',
|
|
status: 'Active',
|
|
hireDate: '',
|
|
isExternal: false,
|
|
phone: '',
|
|
email: '',
|
|
notes: '',
|
|
}, row ?? {})
|
|
} else {
|
|
const currentYear = new Date().getFullYear()
|
|
Object.assign(form, {
|
|
studentNumber: '',
|
|
name: '',
|
|
gender: 'Unknown',
|
|
administrativeClassId: undefined,
|
|
enrollmentYear: currentYear,
|
|
enrollmentDate: `${currentYear}-09-01`,
|
|
status: 'Active',
|
|
dateOfBirth: '',
|
|
englishName: '',
|
|
idCardNumber: '',
|
|
nationality: '',
|
|
ethnicity: '',
|
|
politicalStatus: '',
|
|
nativePlace: '',
|
|
householdAddress: '',
|
|
currentAddress: '',
|
|
postalCode: '',
|
|
phone: '',
|
|
email: '',
|
|
qq: '',
|
|
weChat: '',
|
|
emergencyContactName: '',
|
|
emergencyContactRelationship: '',
|
|
emergencyContactPhone: '',
|
|
specialTags: '',
|
|
specialNeeds: '',
|
|
biography: '',
|
|
notes: '',
|
|
}, row ?? {})
|
|
}
|
|
}
|
|
|
|
async function load() {
|
|
loading.value = true
|
|
try {
|
|
const params: Record<string, any> = {
|
|
page: query.page,
|
|
pageSize: query.pageSize,
|
|
keyword: query.keyword || undefined,
|
|
collegeId: query.collegeId,
|
|
}
|
|
if (active.value === 'teachers') {
|
|
params.teacherStatus = query.status
|
|
} else {
|
|
Object.assign(params, {
|
|
majorId: query.majorId,
|
|
classId: query.classId,
|
|
enrollmentYear: query.enrollmentYear,
|
|
studentStatus: query.status,
|
|
})
|
|
}
|
|
const { data } = await http.get<PageResult>(`/personnel/${active.value}`, { params })
|
|
rows.value = data.items
|
|
total.value = data.total
|
|
} catch (error) {
|
|
ElMessage.error(apiErrorMessage(error))
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
async function loadReferences() {
|
|
const [collegeRes, majorRes, classRes] = await Promise.all([
|
|
http.get('/base-data/colleges'),
|
|
http.get('/base-data/majors'),
|
|
http.get('/base-data/classes'),
|
|
])
|
|
colleges.value = collegeRes.data
|
|
majors.value = majorRes.data
|
|
classes.value = classRes.data
|
|
if (scopedCollegeId.value) query.collegeId = scopedCollegeId.value
|
|
}
|
|
|
|
function resetFilters() {
|
|
Object.assign(query, {
|
|
page: 1,
|
|
keyword: '',
|
|
collegeId: scopedCollegeId.value,
|
|
majorId: undefined,
|
|
classId: undefined,
|
|
enrollmentYear: undefined,
|
|
status: undefined,
|
|
})
|
|
load()
|
|
}
|
|
|
|
function exportParams() {
|
|
const params: Record<string, any> = {
|
|
keyword: query.keyword || undefined,
|
|
collegeId: query.collegeId,
|
|
}
|
|
if (active.value === 'teachers') params.teacherStatus = query.status
|
|
else {
|
|
Object.assign(params, {
|
|
majorId: query.majorId,
|
|
classId: query.classId,
|
|
enrollmentYear: query.enrollmentYear,
|
|
studentStatus: query.status,
|
|
})
|
|
}
|
|
return params
|
|
}
|
|
|
|
async function downloadTemplate() {
|
|
try {
|
|
await downloadApiFile(
|
|
`/personnel/${active.value}/template`,
|
|
`${pageTitle.value}导入模板.xlsx`,
|
|
)
|
|
} catch (error) {
|
|
ElMessage.error(apiErrorMessage(error))
|
|
}
|
|
}
|
|
|
|
async function exportRows() {
|
|
try {
|
|
await downloadApiFile(
|
|
`/personnel/${active.value}/export`,
|
|
`${pageTitle.value}.xlsx`,
|
|
{ params: exportParams() },
|
|
)
|
|
ElMessage.success(`${pageTitle.value}已导出`)
|
|
} catch (error) {
|
|
ElMessage.error(apiErrorMessage(error))
|
|
}
|
|
}
|
|
|
|
function chooseImportFile() {
|
|
fileInput.value?.click()
|
|
}
|
|
|
|
async function handleImport(event: Event) {
|
|
const input = event.target as HTMLInputElement
|
|
const file = input.files?.[0]
|
|
input.value = ''
|
|
if (!file) return
|
|
importing.value = true
|
|
try {
|
|
const { data } = await importExcel(`/personnel/${active.value}/import`, file)
|
|
ElMessage.success(`导入完成:新增 ${data.created} 条,更新 ${data.updated} 条`)
|
|
await load()
|
|
} catch (error) {
|
|
ElMessage.error(apiErrorMessage(error))
|
|
} finally {
|
|
importing.value = false
|
|
}
|
|
}
|
|
|
|
function openCreate() {
|
|
editingId.value = ''
|
|
resetForm()
|
|
dialogVisible.value = true
|
|
}
|
|
|
|
function openEdit(row: any) {
|
|
editingId.value = row.id
|
|
resetForm(row)
|
|
dialogVisible.value = true
|
|
}
|
|
|
|
async function save() {
|
|
const number = active.value === 'teachers' ? form.teacherNumber : form.studentNumber
|
|
if (!number?.trim() || !form.name?.trim()) {
|
|
ElMessage.warning(`请填写${numberLabel.value}和姓名。`)
|
|
return
|
|
}
|
|
try {
|
|
const path = `/personnel/${active.value}`
|
|
if (editingId.value) await http.put(`${path}/${editingId.value}`, form)
|
|
else await http.post(path, form)
|
|
ElMessage.success(editingId.value ? '档案已更新' : '档案已建立')
|
|
dialogVisible.value = false
|
|
await load()
|
|
} catch (error) {
|
|
ElMessage.error(apiErrorMessage(error))
|
|
}
|
|
}
|
|
|
|
async function remove(row: any) {
|
|
try {
|
|
await ElMessageBox.confirm(
|
|
`确定删除“${row.name}”的${pageTitle.value}吗?`,
|
|
'删除档案',
|
|
{ type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消' },
|
|
)
|
|
await http.delete(`/personnel/${active.value}/${row.id}`)
|
|
ElMessage.success('档案已删除')
|
|
await load()
|
|
} catch (error: any) {
|
|
if (error !== 'cancel' && error !== 'close') ElMessage.error(apiErrorMessage(error))
|
|
}
|
|
}
|
|
|
|
function openActivation(row: any) {
|
|
activatingTeacher.value = row
|
|
activationForm.password = ''
|
|
activationForm.confirmPassword = ''
|
|
activationDialog.value = true
|
|
}
|
|
|
|
async function activateTeacherAccount() {
|
|
if (!activationForm.password) {
|
|
ElMessage.warning('请设置教师的初始密码。')
|
|
return
|
|
}
|
|
if (activationForm.password.length < 8 ||
|
|
!/[a-z]/.test(activationForm.password) ||
|
|
!/[A-Z]/.test(activationForm.password) ||
|
|
!/\d/.test(activationForm.password) ||
|
|
!/[^A-Za-z0-9]/.test(activationForm.password)) {
|
|
ElMessage.warning('密码至少 8 位,并包含大写字母、小写字母、数字和特殊字符。')
|
|
return
|
|
}
|
|
if (activationForm.password !== activationForm.confirmPassword) {
|
|
ElMessage.warning('两次输入的密码不一致。')
|
|
return
|
|
}
|
|
|
|
activationLoading.value = true
|
|
try {
|
|
const { data } = await http.post(
|
|
`/personnel/teachers/${activatingTeacher.value.id}/activate-account`,
|
|
{ password: activationForm.password },
|
|
)
|
|
activationDialog.value = false
|
|
ElMessage.success(`教师账号 ${data.userName} 已激活`)
|
|
await load()
|
|
} catch (error) {
|
|
ElMessage.error(apiErrorMessage(error))
|
|
} finally {
|
|
activationLoading.value = false
|
|
}
|
|
}
|
|
|
|
function viewTimetable(teacher: any) {
|
|
router.push({ name: 'teacher-timetable', params: { teacherId: teacher.id } })
|
|
}
|
|
|
|
onMounted(async () => {
|
|
await Promise.all([loadReferences(), load()])
|
|
})
|
|
|
|
watch(active, async () => {
|
|
resetFilters()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="page-stack">
|
|
<section class="page-intro registry-intro">
|
|
<div>
|
|
<span class="section-kicker">
|
|
{{ active === 'teachers' ? 'TEACHING STAFF' : 'STUDENT ROLL' }}
|
|
</span>
|
|
<h2>{{ pageTitle }}</h2>
|
|
<p>
|
|
{{ active === 'teachers'
|
|
? '以工号维护教师归属、职称、任职状态与联系信息。'
|
|
: '以学号维护学生班级归属、入学信息与学籍状态。' }}
|
|
</p>
|
|
</div>
|
|
<el-button v-if="canManage" type="primary" :icon="Plus" @click="openCreate">
|
|
新增{{ pageTitle }}
|
|
</el-button>
|
|
</section>
|
|
|
|
<section class="registry-banner" :class="{ student: active === 'students' }">
|
|
<div>
|
|
<span>{{ active === 'teachers' ? 'STAFF DIRECTORY' : 'STUDENT DIRECTORY' }}</span>
|
|
<b>{{ active === 'teachers' ? '工号是教师身份与教学任务的唯一索引' : '学号贯穿学籍、选课、成绩与毕业业务' }}</b>
|
|
<small>当前筛选结果会原样用于 Excel 导出</small>
|
|
</div>
|
|
<div class="registry-total">
|
|
<span>当前结果</span>
|
|
<strong>{{ total }}</strong>
|
|
<small>条档案</small>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="data-card">
|
|
<div class="filter-bar">
|
|
<el-input
|
|
v-model="query.keyword"
|
|
:prefix-icon="Search"
|
|
clearable
|
|
:placeholder="`搜索${numberLabel}、姓名${active === 'teachers' ? '或职称' : ''}`"
|
|
@keyup.enter="query.page = 1; load()"
|
|
/>
|
|
<el-select v-model="query.collegeId" clearable placeholder="全部学院">
|
|
<el-option v-for="item in availableColleges" :key="item.id" :label="item.name" :value="item.id" />
|
|
</el-select>
|
|
<template v-if="active === 'students'">
|
|
<el-select v-model="query.majorId" clearable filterable placeholder="全部专业">
|
|
<el-option v-for="item in filteredMajors" :key="item.id" :label="item.name" :value="item.id" />
|
|
</el-select>
|
|
<el-select v-model="query.classId" clearable filterable placeholder="全部班级">
|
|
<el-option v-for="item in filteredClasses" :key="item.id" :label="item.name" :value="item.id" />
|
|
</el-select>
|
|
</template>
|
|
<el-select v-model="query.status" clearable placeholder="全部状态">
|
|
<el-option
|
|
v-for="(label, value) in active === 'teachers' ? teacherStatusLabels : studentStatusLabels"
|
|
:key="value"
|
|
:label="label"
|
|
:value="value"
|
|
/>
|
|
</el-select>
|
|
<el-button type="primary" @click="query.page = 1; load()">查询</el-button>
|
|
<el-button :icon="Refresh" @click="resetFilters">重置</el-button>
|
|
<template v-if="canManage">
|
|
<el-button :icon="Document" @click="downloadTemplate">下载模板</el-button>
|
|
<el-button :icon="Upload" :loading="importing" @click="chooseImportFile">Excel 导入</el-button>
|
|
<input
|
|
ref="fileInput"
|
|
class="visually-hidden"
|
|
type="file"
|
|
accept=".xlsx"
|
|
@change="handleImport"
|
|
/>
|
|
</template>
|
|
<el-button :icon="Download" @click="exportRows">导出结果</el-button>
|
|
</div>
|
|
|
|
<el-table v-loading="loading" :data="rows" class="data-table registry-table">
|
|
<el-table-column :label="numberLabel" width="135">
|
|
<template #default="{ row }"><span class="registry-number">{{ active === 'teachers' ? row.teacherNumber : row.studentNumber }}</span></template>
|
|
</el-table-column>
|
|
<el-table-column label="姓名" min-width="120">
|
|
<template #default="{ row }">
|
|
<div class="record-name">
|
|
<b>{{ row.name }}</b>
|
|
<span>{{ genderLabels[row.gender] }}</span>
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column v-if="active === 'teachers'" prop="collegeName" label="所属学院" min-width="150" />
|
|
<el-table-column v-if="active === 'teachers'" prop="title" label="职称" width="110">
|
|
<template #default="{ row }">{{ row.title || '—' }}</template>
|
|
</el-table-column>
|
|
<el-table-column v-if="active === 'teachers'" label="教师类别" width="105">
|
|
<template #default="{ row }">{{ row.isExternal ? '外聘' : '校内' }}</template>
|
|
</el-table-column>
|
|
<el-table-column v-if="active === 'students'" prop="className" label="行政班" min-width="155" />
|
|
<el-table-column v-if="active === 'students'" prop="majorName" label="专业" min-width="170" />
|
|
<el-table-column v-if="active === 'students'" prop="enrollmentYear" label="入学年级" width="105" />
|
|
<el-table-column label="状态" width="95">
|
|
<template #default="{ row }">
|
|
<span class="table-status" :class="{ off: row.status !== 'Active' }">
|
|
{{ (active === 'teachers' ? teacherStatusLabels : studentStatusLabels)[row.status] }}
|
|
</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="登录账号" width="105">
|
|
<template #default="{ row }">
|
|
<span class="table-status" :class="{ off: !row.userId }">
|
|
{{ row.userId ? '已激活' : '未激活' }}
|
|
</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="联系方式" min-width="170">
|
|
<template #default="{ row }">{{ row.phone || row.email || '—' }}</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
v-if="canManage"
|
|
label="操作"
|
|
:width="active === 'teachers' ? 280 : 145"
|
|
fixed="right"
|
|
>
|
|
<template #default="{ row }">
|
|
<el-button
|
|
v-if="active === 'teachers' && !row.userId"
|
|
link
|
|
type="success"
|
|
:icon="Key"
|
|
:disabled="row.status !== 'Active'"
|
|
@click="openActivation(row)"
|
|
>
|
|
激活账号
|
|
</el-button>
|
|
<el-button
|
|
v-if="active === 'teachers'"
|
|
link
|
|
type="warning"
|
|
:icon="Calendar"
|
|
:disabled="row.status !== 'Active'"
|
|
@click="viewTimetable(row)"
|
|
>
|
|
课表
|
|
</el-button>
|
|
<el-button link type="primary" @click="openEdit(row)">编辑</el-button>
|
|
<el-button link type="danger" @click="remove(row)">删除</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
<template #empty><el-empty description="没有符合条件的档案" /></template>
|
|
</el-table>
|
|
|
|
<div class="pagination-bar">
|
|
<span>共 {{ total }} 条</span>
|
|
<el-pagination
|
|
v-model:current-page="query.page"
|
|
:page-size="query.pageSize"
|
|
:total="total"
|
|
layout="prev, pager, next"
|
|
@current-change="load"
|
|
/>
|
|
</div>
|
|
</section>
|
|
|
|
<el-dialog v-model="dialogVisible" :title="`${editingId ? '编辑' : '新增'}${pageTitle}`" width="min(860px, 94vw)">
|
|
<el-form label-position="top" class="entity-form">
|
|
<template v-if="active === 'teachers'">
|
|
<div class="form-grid">
|
|
<el-form-item label="工号" required><el-input v-model="form.teacherNumber" /></el-form-item>
|
|
<el-form-item label="姓名" required><el-input v-model="form.name" /></el-form-item>
|
|
</div>
|
|
<div class="form-grid">
|
|
<el-form-item label="所属学院" required>
|
|
<el-select v-model="form.collegeId">
|
|
<el-option v-for="item in availableColleges" :key="item.id" :label="item.name" :value="item.id" />
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="职称"><el-input v-model="form.title" placeholder="教授、副教授、讲师等" /></el-form-item>
|
|
</div>
|
|
<div class="form-grid">
|
|
<el-form-item label="性别">
|
|
<el-select v-model="form.gender">
|
|
<el-option v-for="(label, value) in genderLabels" :key="value" :label="label" :value="value" />
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="任职状态">
|
|
<el-select v-model="form.status">
|
|
<el-option v-for="(label, value) in teacherStatusLabels" :key="value" :label="label" :value="value" />
|
|
</el-select>
|
|
</el-form-item>
|
|
</div>
|
|
<div class="form-grid">
|
|
<el-form-item label="入职日期"><el-date-picker v-model="form.hireDate" value-format="YYYY-MM-DD" /></el-form-item>
|
|
<el-form-item label="教师类别"><el-switch v-model="form.isExternal" active-text="外聘" inactive-text="校内" /></el-form-item>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
<div class="form-grid">
|
|
<el-form-item label="学号" required><el-input v-model="form.studentNumber" /></el-form-item>
|
|
<el-form-item label="姓名" required><el-input v-model="form.name" /></el-form-item>
|
|
</div>
|
|
<el-form-item label="行政班" required>
|
|
<el-select v-model="form.administrativeClassId" filterable>
|
|
<el-option
|
|
v-for="item in formClasses"
|
|
:key="item.id"
|
|
:label="`${item.collegeName} · ${item.majorName} · ${item.name}`"
|
|
:value="item.id"
|
|
/>
|
|
</el-select>
|
|
</el-form-item>
|
|
<div class="form-grid">
|
|
<el-form-item label="入学年级"><el-input-number v-model="form.enrollmentYear" :min="2000" :max="2200" /></el-form-item>
|
|
<el-form-item label="入学日期"><el-date-picker v-model="form.enrollmentDate" value-format="YYYY-MM-DD" /></el-form-item>
|
|
</div>
|
|
<div class="form-grid">
|
|
<el-form-item label="性别">
|
|
<el-select v-model="form.gender">
|
|
<el-option v-for="(label, value) in genderLabels" :key="value" :label="label" :value="value" />
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="学籍状态">
|
|
<el-select v-model="form.status">
|
|
<el-option v-for="(label, value) in studentStatusLabels" :key="value" :label="label" :value="value" />
|
|
</el-select>
|
|
</el-form-item>
|
|
</div>
|
|
<el-form-item label="出生日期"><el-date-picker v-model="form.dateOfBirth" value-format="YYYY-MM-DD" /></el-form-item>
|
|
<el-divider content-position="left">个人资料</el-divider>
|
|
<div class="form-grid">
|
|
<el-form-item label="英文姓名"><el-input v-model="form.englishName" /></el-form-item>
|
|
<el-form-item label="证件号码"><el-input v-model="form.idCardNumber" /></el-form-item>
|
|
</div>
|
|
<div class="form-grid">
|
|
<el-form-item label="国籍"><el-input v-model="form.nationality" /></el-form-item>
|
|
<el-form-item label="民族"><el-input v-model="form.ethnicity" /></el-form-item>
|
|
</div>
|
|
<div class="form-grid">
|
|
<el-form-item label="政治面貌"><el-input v-model="form.politicalStatus" /></el-form-item>
|
|
<el-form-item label="籍贯"><el-input v-model="form.nativePlace" /></el-form-item>
|
|
</div>
|
|
<div class="form-grid">
|
|
<el-form-item label="户籍地址"><el-input v-model="form.householdAddress" /></el-form-item>
|
|
<el-form-item label="现居住地址"><el-input v-model="form.currentAddress" /></el-form-item>
|
|
</div>
|
|
<div class="form-grid">
|
|
<el-form-item label="邮政编码"><el-input v-model="form.postalCode" /></el-form-item>
|
|
<el-form-item label="微信"><el-input v-model="form.weChat" /></el-form-item>
|
|
</div>
|
|
<div class="form-grid">
|
|
<el-form-item label="QQ"><el-input v-model="form.qq" /></el-form-item>
|
|
<el-form-item label="紧急联系人"><el-input v-model="form.emergencyContactName" /></el-form-item>
|
|
</div>
|
|
<div class="form-grid">
|
|
<el-form-item label="与本人关系"><el-input v-model="form.emergencyContactRelationship" /></el-form-item>
|
|
<el-form-item label="紧急联系电话"><el-input v-model="form.emergencyContactPhone" /></el-form-item>
|
|
</div>
|
|
<el-form-item label="特殊标记"><el-input v-model="form.specialTags" placeholder="多个标记可用逗号分隔" /></el-form-item>
|
|
<el-form-item label="特殊情况说明"><el-input v-model="form.specialNeeds" type="textarea" :rows="3" /></el-form-item>
|
|
<el-form-item label="个人简介"><el-input v-model="form.biography" type="textarea" :rows="3" /></el-form-item>
|
|
</template>
|
|
<div class="form-grid">
|
|
<el-form-item label="联系电话"><el-input v-model="form.phone" /></el-form-item>
|
|
<el-form-item label="电子邮箱"><el-input v-model="form.email" /></el-form-item>
|
|
</div>
|
|
<el-form-item label="备注"><el-input v-model="form.notes" type="textarea" :rows="3" /></el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<el-button @click="dialogVisible = false">取消</el-button>
|
|
<el-button type="primary" @click="save">保存档案</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<el-dialog
|
|
v-model="activationDialog"
|
|
title="激活教师账号"
|
|
width="520px"
|
|
@closed="activatingTeacher = null"
|
|
>
|
|
<el-alert
|
|
title="账号激活后,教师可使用工号和初始密码登录。请通过安全方式将密码交给教师。"
|
|
type="info"
|
|
:closable="false"
|
|
show-icon
|
|
/>
|
|
<el-form v-if="activatingTeacher" label-position="top" class="entity-form activation-account-form">
|
|
<div class="form-grid">
|
|
<el-form-item label="教师">
|
|
<el-input :model-value="activatingTeacher.name" disabled />
|
|
</el-form-item>
|
|
<el-form-item label="登录账号">
|
|
<el-input :model-value="activatingTeacher.teacherNumber" disabled />
|
|
</el-form-item>
|
|
</div>
|
|
<el-form-item label="初始密码" required>
|
|
<el-input
|
|
v-model="activationForm.password"
|
|
type="password"
|
|
show-password
|
|
autocomplete="new-password"
|
|
placeholder="至少 8 位,含大小写字母、数字和特殊字符"
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item label="确认初始密码" required>
|
|
<el-input
|
|
v-model="activationForm.confirmPassword"
|
|
type="password"
|
|
show-password
|
|
autocomplete="new-password"
|
|
@keyup.enter="activateTeacherAccount"
|
|
/>
|
|
</el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<el-button @click="activationDialog = false">取消</el-button>
|
|
<el-button
|
|
type="primary"
|
|
:loading="activationLoading"
|
|
@click="activateTeacherAccount"
|
|
>
|
|
确认激活
|
|
</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.activation-account-form {
|
|
margin-top: 20px;
|
|
}
|
|
</style>
|