Files
Academic-Affairs-System/web/src/views/StudentProfileView.vue
T
2026-08-04 10:48:08 +08:00

170 lines
9.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.
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import { Check, EditPen, Refresh } from '@element-plus/icons-vue'
import http, { apiErrorMessage } from '../api/http'
const loading = ref(true)
const saving = ref(false)
const editing = ref(false)
const profile = reactive<Record<string, any>>({})
const form = reactive<Record<string, any>>({})
const genderLabels: Record<string, string> = {
Unknown: '未设置', Male: '男', Female: '女',
}
const statusLabels: Record<string, string> = {
Active: '在籍', Suspended: '休学', Graduated: '毕业', Withdrawn: '退学',
}
const editableFields = [
'gender', 'dateOfBirth', 'englishName', 'idCardNumber', 'nationality',
'ethnicity', 'politicalStatus', 'nativePlace', 'householdAddress',
'currentAddress', 'postalCode', 'phone', 'email', 'qq', 'weChat',
'emergencyContactName', 'emergencyContactRelationship',
'emergencyContactPhone', 'specialTags', 'specialNeeds', 'biography',
]
function copyToForm() {
for (const key of editableFields) form[key] = profile[key] ?? ''
}
async function load() {
loading.value = true
try {
Object.assign(profile, (await http.get('/student/profile')).data)
copyToForm()
} catch (error) {
ElMessage.error(apiErrorMessage(error))
} finally {
loading.value = false
}
}
function startEdit() {
copyToForm()
editing.value = true
}
function cancelEdit() {
copyToForm()
editing.value = false
}
async function save() {
saving.value = true
try {
await http.put('/student/profile', form)
ElMessage.success('个人信息已保存,无需审核。')
editing.value = false
await load()
} catch (error) {
ElMessage.error(apiErrorMessage(error))
} finally {
saving.value = false
}
}
onMounted(load)
</script>
<template>
<div class="profile-page" v-loading="loading">
<section class="profile-hero">
<div>
<span>STUDENT PROFILE</span>
<h2>我的个人信息</h2>
<p>完善个人资料联系方式与需要任课教师关注的特殊情况</p>
</div>
<div class="profile-actions">
<el-button :icon="Refresh" @click="load">刷新</el-button>
<el-button v-if="!editing" type="primary" :icon="EditPen" @click="startEdit">修改信息</el-button>
<template v-else>
<el-button @click="cancelEdit">取消</el-button>
<el-button type="primary" :icon="Check" :loading="saving" @click="save">直接保存</el-button>
</template>
</div>
</section>
<el-alert
title="联系电话、电子邮箱、微信、紧急联系人以及特殊标记/说明会向你的任课教师展示,并写入教学班名单导出文件。"
type="info"
:closable="false"
show-icon
/>
<section class="identity-strip">
<div><span>学号</span><b>{{ profile.studentNumber }}</b><small>不可自行修改</small></div>
<div><span>姓名</span><b>{{ profile.name }}</b><small>不可自行修改</small></div>
<div><span>学院 / 专业</span><b>{{ profile.collegeName }}</b><small>{{ profile.majorName }}</small></div>
<div><span>行政班 / 学籍</span><b>{{ profile.className }}</b><small>{{ statusLabels[profile.status] }} · {{ profile.enrollmentYear }} </small></div>
</section>
<el-form :model="form" label-position="top" class="profile-form" :disabled="!editing">
<article class="profile-card">
<header><span>01</span><div><h3>身份与背景</h3><p>除学号和姓名外可直接维护常用身份信息</p></div></header>
<div class="form-grid">
<el-form-item label="英文姓名"><el-input v-model="form.englishName" maxlength="100" /></el-form-item>
<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-date-picker v-model="form.dateOfBirth" value-format="YYYY-MM-DD" /></el-form-item>
<el-form-item label="证件号码"><el-input v-model="form.idCardNumber" maxlength="30" /></el-form-item>
<el-form-item label="国籍"><el-input v-model="form.nationality" maxlength="50" /></el-form-item>
<el-form-item label="民族"><el-input v-model="form.ethnicity" maxlength="50" /></el-form-item>
<el-form-item label="政治面貌"><el-input v-model="form.politicalStatus" maxlength="50" /></el-form-item>
<el-form-item label="籍贯"><el-input v-model="form.nativePlace" maxlength="100" /></el-form-item>
</div>
</article>
<article class="profile-card">
<header><span>02</span><div><h3>联系方式与地址</h3><p>任课教师仅能看到本区的电话邮箱和微信</p></div></header>
<div class="form-grid">
<el-form-item label="联系电话"><el-input v-model="form.phone" maxlength="30" /></el-form-item>
<el-form-item label="电子邮箱"><el-input v-model="form.email" maxlength="100" /></el-form-item>
<el-form-item label="微信"><el-input v-model="form.weChat" maxlength="60" /></el-form-item>
<el-form-item label="QQ"><el-input v-model="form.qq" maxlength="30" /></el-form-item>
<el-form-item label="邮政编码"><el-input v-model="form.postalCode" maxlength="20" /></el-form-item>
<el-form-item label="户籍地址" class="span-2"><el-input v-model="form.householdAddress" maxlength="300" /></el-form-item>
<el-form-item label="现居住地址" class="span-2"><el-input v-model="form.currentAddress" maxlength="300" /></el-form-item>
</div>
</article>
<article class="profile-card attention-card">
<header><span>03</span><div><h3>紧急联系与特殊标记</h3><p>本区内容会向任课教师展示请填写真实且必要的信息</p></div></header>
<div class="form-grid">
<el-form-item label="紧急联系人"><el-input v-model="form.emergencyContactName" maxlength="50" /></el-form-item>
<el-form-item label="与本人关系"><el-input v-model="form.emergencyContactRelationship" maxlength="30" /></el-form-item>
<el-form-item label="紧急联系电话"><el-input v-model="form.emergencyContactPhone" maxlength="30" /></el-form-item>
<el-form-item label="特殊标记"><el-input v-model="form.specialTags" maxlength="300" placeholder="多个标记可用逗号分隔" /></el-form-item>
<el-form-item label="特殊情况说明" class="span-2"><el-input v-model="form.specialNeeds" type="textarea" :rows="4" maxlength="1000" show-word-limit placeholder="例如学习支持、健康与安全方面需教师留意的事项" /></el-form-item>
<el-form-item label="个人简介" class="span-2"><el-input v-model="form.biography" type="textarea" :rows="4" maxlength="1000" show-word-limit /></el-form-item>
</div>
</article>
</el-form>
</div>
</template>
<style scoped>
.profile-page { display: grid; gap: 18px; }
.profile-hero { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 26px 30px; border-radius: 18px; color: white; background: linear-gradient(125deg, #173a50, #176b87 62%, #2e9b91); box-shadow: 0 18px 45px rgba(23, 58, 80, .16); }
.profile-hero span { color: #8ee4d8; font-size: 11px; font-weight: 800; letter-spacing: .12em; }
.profile-hero h2 { margin: 7px 0 5px; font-size: 30px; font-family: "STZhongsong", "Songti SC", serif; }
.profile-hero p { margin: 0; color: rgba(255,255,255,.75); }
.profile-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; }
.identity-strip { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); border: 1px solid var(--line); background: white; }
.identity-strip > div { display: grid; gap: 5px; padding: 18px 20px; border-right: 1px solid var(--line); }
.identity-strip > div:last-child { border-right: 0; }
.identity-strip span, .identity-strip small { color: var(--muted); font-size: 12px; }
.identity-strip b { color: #263445; }
.profile-form { display: grid; gap: 18px; }
.profile-card { padding: 24px; border: 1px solid var(--line); border-radius: 14px; background: white; box-shadow: 0 10px 28px rgba(35,57,78,.05); }
.profile-card > header { display: flex; gap: 14px; margin-bottom: 22px; padding-bottom: 16px; border-bottom: 1px solid #edf0f3; }
.profile-card > header > span { color: var(--teal); font: 800 12px/1 Consolas, monospace; }
.profile-card h3 { margin: 0 0 5px; color: #263445; }
.profile-card p { margin: 0; color: var(--muted); font-size: 12px; }
.attention-card { border-top: 3px solid #d89b36; }
.form-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 18px; }
.span-2 { grid-column: span 2; }
:deep(.el-form.is-disabled .el-input__wrapper), :deep(.el-form.is-disabled .el-textarea__inner) { background: #f8fafb; color: #364655; }
@media (max-width: 900px) { .identity-strip { grid-template-columns: repeat(2, 1fr); } .form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px) { .profile-hero { align-items: flex-start; flex-direction: column; padding: 22px; } .profile-actions { justify-content: flex-start; } .identity-strip, .form-grid { grid-template-columns: 1fr; } .identity-strip > div { border-right: 0; border-bottom: 1px solid var(--line); } .span-2 { grid-column: auto; } .profile-card { padding: 18px; } }
</style>