516 lines
10 KiB
Vue
516 lines
10 KiB
Vue
<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>
|