2026-05-06 15:56:56 +08:00

516 lines
10 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="profile-page">
<header class="profile-header">
<button class="back-btn" type="button" aria-label="返回" @click="router.back()">
<svg class="back-icon" viewBox="0 0 28 46" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M21 4L6 23L21 42"
stroke="#111827"
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</button>
<span class="page-title">个人中心</span>
</header>
<PageFrame section-class="profile-frame">
<section class="profile-card">
<aside class="student-panel">
<div class="avatar-wrap">
<div class="avatar-face">
<span class="hair hair-left"></span>
<span class="hair hair-right"></span>
<span class="face"></span>
<span class="bangs"></span>
<span class="eye eye-left"></span>
<span class="eye eye-right"></span>
<span class="mouth"></span>
<span class="body"></span>
</div>
</div>
<div class="student-name" :class="{ editing: isEditing }">爱学习的张小小</div>
<div class="student-info">
<label v-for="item in profileItems" :key="item.label" class="info-row" :class="{ editing: isEditing }">
<span class="info-label">{{ item.label }}</span>
<textarea
v-if="isEditing"
v-model="item.value"
class="info-input"
:aria-label="item.label"
rows="1"
@input="resizeTextarea"
></textarea>
<span v-else class="info-value">{{ item.value }}</span>
</label>
</div>
<button
class="edit-btn lx-btn lx-btn--md"
:class="isEditing ? 'lx-btn--short saveBtn' : 'lx-btn--primary'"
type="button"
@click="toggleEditing"
>
<img v-if="!isEditing" class="edit-icon" :src="editIcon" alt="" />
{{ isEditing ? '保存' : '修改资料' }}
</button>
</aside>
<main class="profile-main">
<div class="stats-grid">
<article v-for="stat in stats" :key="stat.label" class="stat-card">
<div class="stat-title">
<span>{{ stat.label }}</span>
<template v-if="stat.level">
<span class="level-badge">Lv1</span>
<span class="level-tip">再32点可升级</span>
</template>
</div>
<strong class="stat-value">{{ stat.value }}</strong>
</article>
</div>
<h2 class="section-title">我的成就</h2>
<div class="achievement-grid">
<article v-for="achievement in achievements" :key="achievement.id" class="achievement-card">
<div class="medal-placeholder"></div>
<h3>{{ achievement.title }}</h3>
<p>
获得途径单词闯关环节中拼写的单词总数达<span>{{ achievement.count }}</span>
</p>
</article>
</div>
</main>
</section>
</PageFrame>
</div>
</template>
<script setup lang="ts">
import { nextTick, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import PageFrame from '@/components/layout/PageFrame.vue'
import editIcon from '@/assets/icon/edit.png'
const router = useRouter()
const isEditing = ref(false)
const profileItems = reactive([
{ label: '姓名', value: '张小小' },
{ label: '性别', value: '女' },
{ label: '学校', value: '关岭自治县花江领航学校' },
{ label: '班级', value: '五年级2班' },
])
const stats = [
{ label: '成长值', value: 2356, level: true },
{ label: '钻石', value: 56 },
{ label: '乐贝', value: 56 },
]
const achievements = Array.from({ length: 6 }, (_, index) => ({
id: index + 1,
title: '百词入门',
count: 100,
}))
const resizeTextarea = (event: Event) => {
const textarea = event.target as HTMLTextAreaElement
textarea.style.height = 'auto'
textarea.style.height = `${textarea.scrollHeight}px`
}
const toggleEditing = async () => {
isEditing.value = !isEditing.value
if (isEditing.value) {
await nextTick()
document.querySelectorAll<HTMLTextAreaElement>('.info-input').forEach((textarea) => {
textarea.style.height = 'auto'
textarea.style.height = `${textarea.scrollHeight}px`
})
}
}
</script>
<style scoped lang="scss">
.profile-page {
display: flex;
min-height: 100%;
flex: 1;
flex-direction: column;
background: #d9e3f8;
}
.profile-header {
display: flex;
align-items: center;
gap: 14px;
min-height: 82px;
padding: 18px 64px 6px;
}
.back-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 44px;
border: none;
background: transparent;
cursor: pointer;
padding: 0;
}
.back-icon {
width: 22px;
height: 36px;
}
.page-title {
color: #15171c;
font-size: 24px;
font-weight: 500;
}
:deep(.profile-frame) {
height: calc(100vh - 124px);
overflow: hidden;
border-color: rgba(255, 255, 255, 0.76);
border-radius: 28px;
background: rgba(255, 255, 255, 0.62);
padding: 28px 34px 32px;
}
.profile-card {
display: grid;
grid-template-columns: 456px 1fr;
gap: 28px;
height: 100%;
min-height: 0;
background: #fff;
border-radius: 14px;
}
.student-panel {
width: 456px;
display: flex;
min-height: 0;
flex-direction: column;
align-items: center;
border-radius: 14px;
background: linear-gradient(180deg, #4387f7 0%, #2d76ee 100%);
border: 2px solid #fff;
padding: 56px 32px;
color: #fff;
}
.avatar-wrap {
display: flex;
align-items: center;
justify-content: center;
width: 180px;
height: 180px;
flex: 0 0 auto;
border-radius: 999px;
background: #f3f8ff;
box-shadow: inset 0 0 0 8px rgba(209, 224, 247, 0.92);
}
.avatar-face {
position: relative;
width: 86px;
height: 86px;
overflow: hidden;
border-radius: 50%;
background: #99dff0;
}
.face {
position: absolute;
left: 18px;
top: 23px;
width: 50px;
height: 46px;
border-radius: 44% 44% 50% 50%;
background: #ffd6cf;
}
.hair {
position: absolute;
z-index: 2;
top: 13px;
width: 35px;
height: 48px;
background: #151113;
}
.hair-left {
left: 13px;
border-radius: 24px 10px 18px 20px;
}
.hair-right {
right: 13px;
border-radius: 10px 24px 20px 18px;
}
.bangs {
position: absolute;
z-index: 3;
left: 18px;
top: 17px;
width: 50px;
height: 22px;
border-radius: 18px 18px 8px 8px;
background: #151113;
}
.eye,
.mouth {
position: absolute;
z-index: 4;
}
.eye {
top: 45px;
width: 5px;
height: 5px;
border-radius: 50%;
background: #201819;
}
.eye-left {
left: 33px;
}
.eye-right {
right: 33px;
}
.mouth {
left: 39px;
top: 57px;
width: 8px;
height: 4px;
border-bottom: 2px solid #d45d67;
border-radius: 0 0 8px 8px;
}
.body {
position: absolute;
z-index: 3;
left: 25px;
bottom: -2px;
width: 36px;
height: 24px;
border-radius: 18px 18px 0 0;
background: #21a8bd;
}
.student-name {
width: 100%;
margin-top: 28px;
text-align: center;
font-size: 36px;
font-weight: 700;
line-height: 54px;
padding: 16px;
border-radius: 12px;
&.editing {
// border-radius: 4px;
background: rgba(255, 255, 255, 0.08);
}
}
.student-info {
display: flex;
width: 100%;
flex-direction: column;
gap: 16px;
margin-top: 64px;
}
.info-row {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
align-items: flex-start;
min-height: 40px;
border-radius: 8px;
color: rgba(255, 255, 255, 0.92);
font-size: 28px;
line-height: 1.35;
padding: 16px;
&.editing {
min-height: 52px;
border-radius: 12px;
background: rgba(255, 255, 255, 0.06);
}
}
.info-label {
white-space: nowrap;
}
.info-input {
min-width: 0;
width: 100%;
min-height: 38px;
border: none;
outline: none;
background: transparent;
color: #fff;
font: inherit;
line-height: 1.35;
resize: none;
overflow: hidden;
white-space: normal;
overflow-wrap: anywhere;
word-break: break-all;
}
.info-value {
min-width: 0;
white-space: normal;
overflow-wrap: anywhere;
word-break: break-all;
}
.edit-btn {
margin-top: auto;
gap: 5.5px;
border-color: #fff;
}
.saveBtn {
background: #fff;
color: #2d76ee;
border-color: #fff;
}
.edit-icon {
width: 21px;
height: 21px;
object-fit: contain;
}
.profile-main {
display: flex;
height: 100%;
min-height: 0;
flex-direction: column;
overflow: hidden;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 18px;
padding-top: 32px;
}
.stat-card {
height: 218px;
border: 1px solid #ebf2ff;
border-radius: 16px;
background: #fff;
padding: 22px 24px;
}
.stat-title {
display: flex;
align-items: center;
gap: 16px;
color: #222;
font-size: 32px;
font-weight: 700;
}
.level-badge {
border-radius: 3px;
background: #2f7df3;
padding: 4px 6px;
color: #fff;
font-size: 18px;
// line-height: 1.2;
}
.level-tip {
color: #6f7785;
font-size: 20px;
font-weight: 400;
}
.stat-value {
display: block;
margin-top: 36px;
text-align: center;
color: #050505;
font-size: 44px;
font-weight: 700;
}
.section-title {
margin: 36px 0 32px;
color: #050505;
font-size: 32px;
font-weight: 700;
}
.achievement-grid {
display: grid;
flex: 1;
min-height: 0;
grid-template-columns: repeat(3, minmax(0, 1fr));
grid-auto-rows: max-content;
gap: 20px;
overflow-y: auto;
padding-right: 2px;
}
.achievement-card {
width: 381px;
height: 392px;
border: 1px solid rgba(229, 231, 235, 0.92);
border-radius: 12px;
background: #fff;
padding: 48px 24px;
text-align: center;
}
.medal-placeholder {
width: 120px;
height: 120px;
margin: 0 auto 48px;
border: 3px solid #dedede;
background:
linear-gradient(135deg, transparent 0 46%, rgba(235, 235, 235, 0.75) 47% 53%, transparent 54%),
#f9f9f9;
}
.achievement-card h3 {
color: #050505;
font-size: 28px;
font-weight: 700;
}
.achievement-card p {
margin-top: 10px;
color: #333b45;
font-size: 24px;
line-height: 1.55;
text-align: left;
}
.achievement-card span {
color: #2f7df3;
}
</style>