170 lines
9.3 KiB
Vue
170 lines
9.3 KiB
Vue
<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>
|