Files
happy-life-star/mini-program/src/pages/onboarding/index.vue
T

1019 lines
25 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>
<view class="profile-page">
<view class="space-bg"></view>
<view class="status-space" :style="{ height: capsuleTopReservePx + 'px' }"></view>
<view class="topbar">
<text class="back" @click="goBack"></text>
<text class="title">编辑资料 <text class="gold"></text></text>
<text class="save" :class="{ disabled: saving }" @click="saveProfile">{{ saving ? '保存中' : '保存' }}</text>
</view>
<scroll-view class="scroll" scroll-y :show-scrollbar="false">
<view class="hero-card glass-card">
<view class="avatar-wrap">
<image class="avatar-img" :src="avatarUrl" mode="aspectFill" />
<view class="avatar-edit" @click="chooseAvatar">
<view class="pen-icon"></view>
</view>
</view>
<view class="hero-info">
<view class="hero-name-row">
<text class="hero-name">{{ form.nickname || '未设置昵称' }}</text>
<text class="gold hero-star"></text>
</view>
<text class="hero-sub">正在成为更清晰的自己</text>
<view class="hero-line"></view>
<text class="hero-quote">生活是自己的选择也是</text>
</view>
<view class="hero-planet">
<view class="planet-core"></view>
<view class="planet-ring"></view>
</view>
</view>
<view class="panel glass-card">
<view class="section-head">
<view class="section-icon basic-icon"></view>
<text class="section-title">基础信息</text>
</view>
<view class="profile-row">
<text class="row-label">昵称</text>
<input class="row-input" v-model="form.nickname" placeholder="输入昵称" placeholder-class="placeholder" />
<text class="chevron"></text>
</view>
<view class="profile-row">
<text class="row-label">性别</text>
<view class="gender-row">
<text v-for="item in genderOptions" :key="item" class="choice" :class="{ active: form.gender === item }" @click="form.gender = item">{{ item }}</text>
</view>
</view>
<view class="profile-row">
<text class="row-label">生日</text>
<picker class="row-picker" mode="date" :value="birthday" @change="onBirthday">
<view class="row-value">{{ birthdayDisplay }}</view>
</picker>
<text class="chevron"></text>
</view>
<view class="profile-row">
<text class="row-label">所在城市</text>
<input class="row-input" v-model="form.city" placeholder="填写城市" placeholder-class="placeholder" />
<text class="chevron"></text>
</view>
<view class="astro-panel">
<view class="astro-col">
<view class="astro-title-row">
<view class="zodiac-head-icon"></view>
<text class="astro-title">星座</text>
<text class="astro-current">{{ form.zodiac || '巨蟹座' }}</text>
<text class="chevron"></text>
</view>
<text class="select-title">选择星座</text>
<view class="zodiac-grid">
<view
v-for="item in zodiacOptions"
:key="item.name"
class="zodiac-item"
:class="{ active: form.zodiac === item.name }"
@click="form.zodiac = item.name"
>
<view class="zodiac-bubble">{{ item.symbol }}</view>
<text>{{ item.name }}</text>
</view>
</view>
</view>
<view class="astro-col mbti-col">
<view class="astro-title-row">
<view class="mbti-head-icon"></view>
<text class="astro-title">MBTI</text>
<text class="astro-current">{{ form.mbti || 'ENTJ' }}</text>
<text class="chevron"></text>
</view>
<text class="select-title">选择MBTI</text>
<view class="mbti-grid">
<text
v-for="item in mbtiOptions"
:key="item"
class="mbti-chip"
:class="{ active: form.mbti === item }"
@click="form.mbti = item"
>{{ item }}</text>
</view>
</view>
</view>
</view>
<view class="panel glass-card">
<view class="section-head">
<view class="section-icon job-title-icon"></view>
<text class="section-title">职业信息</text>
</view>
<view class="profile-row">
<text class="row-label">职业</text>
<input class="row-input" v-model="form.profession" placeholder="产品经理" placeholder-class="placeholder" />
<text class="chevron"></text>
</view>
<view class="profile-row">
<text class="row-label">行业</text>
<input class="row-input" v-model="form.industry" placeholder="互联网" placeholder-class="placeholder" />
<text class="chevron"></text>
</view>
<view class="profile-row">
<text class="row-label">公司可选</text>
<input class="row-input muted" v-model="form.company" placeholder="填写公司名称" placeholder-class="placeholder" />
<text class="chevron"></text>
</view>
</view>
<view class="panel glass-card">
<view class="section-head with-action">
<view class="section-title-wrap">
<view class="section-icon smile-title-icon"></view>
<text class="section-title">性格标签</text>
<text class="section-hint">最多选择5个</text>
</view>
<text class="manage-link" @click="goTagManage('personality')">管理</text>
</view>
<view class="tag-grid">
<text
v-for="tag in displayPersonalityTags"
:key="tag"
class="tag-choice"
:class="{ active: form.personalityTags.includes(tag) }"
@click="toggleList(form.personalityTags, tag, 5)"
>{{ tag }}</text>
<text class="tag-choice dashed" @click="addCustomTag('personality')"> 添加标签</text>
</view>
</view>
<view class="panel glass-card">
<view class="section-head with-action">
<view class="section-title-wrap">
<view class="section-icon heart-title-icon"></view>
<text class="section-title">兴趣爱好</text>
<text class="section-hint">最多选择5个</text>
</view>
<text class="manage-link" @click="goTagManage('hobby')">管理</text>
</view>
<view class="tag-grid">
<text
v-for="tag in displayHobbyTags"
:key="tag"
class="tag-choice"
:class="{ active: form.hobbies.includes(tag) }"
@click="toggleList(form.hobbies, tag, 5)"
>{{ tag }}</text>
<text class="tag-choice dashed" @click="addCustomTag('hobby')"> 添加兴趣</text>
</view>
</view>
<view class="panel glass-card bio-panel">
<view class="section-head">
<view class="section-icon bio-title-icon"></view>
<text class="section-title">个人简介</text>
</view>
<textarea
class="bio"
v-model="form.future.ideal"
maxlength="200"
:placeholder="bioPlaceholder"
placeholder-class="placeholder"
/>
<text class="bio-count">{{ (form.future.ideal || '').length }}/200</text>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { computed, onMounted, reactive, ref } from 'vue'
import { useAppStore } from '../../stores/app.js'
import { useMenuButtonSafeArea } from '../../composables/useMenuButtonSafeArea.js'
const store = useAppStore()
const { capsuleTopReservePx } = useMenuButtonSafeArea({ extraTopPx: 2 })
const isEdit = ref(false)
const saving = ref(false)
const birthday = ref('')
const avatarLocal = ref('')
const bioPlaceholder = '热爱阅读和旅行,喜欢用文字和镜头记录生活。\n相信真诚和努力能让世界变得更美好。'
const genderOptions = ['女', '男', '不透露']
const zodiacOptions = [
{ name: '白羊座', symbol: '♈' },
{ name: '金牛座', symbol: '♉' },
{ name: '双子座', symbol: '♊' },
{ name: '巨蟹座', symbol: '♋' },
{ name: '狮子座', symbol: '♌' },
{ name: '处女座', symbol: '♍' },
{ name: '天秤座', symbol: '♎' },
{ name: '天蝎座', symbol: '♏' },
{ name: '射手座', symbol: '♐' },
{ name: '摩羯座', symbol: '♑' },
{ name: '水瓶座', symbol: '♒' },
{ name: '双鱼座', symbol: '♓' }
]
const mbtiOptions = ['INTJ', 'INTP', 'ENTJ', 'ENTP', 'INFJ', 'INFP', 'ENFJ', 'ENFP', 'ISTJ', 'ISFJ', 'ESTJ', 'ESFJ', 'ISTP', 'ISFP', 'ESTP', 'ESFP']
const personalityTags = ['理性', '感性', '乐观', '独立', '有创造力', '坚韧', '细腻', '好奇', '内敛', '冒险', '自由']
const hobbyOptions = ['阅读', '旅行', '音乐', '写作', '摄影', '电影', '运动', '绘画', '咖啡', '游戏']
const form = reactive({
nickname: '',
gender: '',
zodiac: '',
mbti: '',
profession: '',
city: '',
industry: '',
company: '',
personalityTags: [],
hobbies: [],
personalityTagLibrary: [], // 新增
hobbyLibrary: [], // 新增
childhood: { date: '', text: '' },
joy: { date: '', text: '' },
low: { date: '', text: '' },
future: { vision: '', ideal: '' }
})
const avatarUrl = computed(() => {
if (avatarLocal.value) return avatarLocal.value
const nickname = form.nickname || 'User'
return `https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(nickname)}&backgroundColor=b982ff`
})
const birthdayDisplay = computed(() => {
if (!birthday.value) return '请选择生日'
const [year, month, day] = birthday.value.split('-')
return `${year}${month}${day}`
})
/**
* 合并预设数组与已选数组,去重返回完整库
*/
const buildInitialLibrary = (preset, selected) => {
const library = [...preset]
if (Array.isArray(selected)) {
selected.forEach((tag) => {
if (!library.includes(tag)) library.push(tag)
})
}
return library
}
const syncFromStore = () => {
const source = store.userProfile || store.registrationData || {}
// 性格标签库:后端有则用后端,否则用预设 + 已选合并(老用户兼容)
const personalityTagLibrary = Array.isArray(source.personalityTagLibrary) && source.personalityTagLibrary.length > 0
? [...source.personalityTagLibrary]
: buildInitialLibrary(personalityTags, source.personalityTags)
// 兴趣爱好库:后端有则用后端,否则用预设 + 已选合并(老用户兼容)
const hobbyLibrary = Array.isArray(source.hobbyLibrary) && source.hobbyLibrary.length > 0
? [...source.hobbyLibrary]
: buildInitialLibrary(hobbyOptions, source.hobbies)
Object.assign(form, {
nickname: source.nickname || '',
gender: source.gender || '',
zodiac: source.zodiac || '',
mbti: source.mbti || '',
profession: source.profession || '',
city: source.city || '',
industry: source.industry || '',
company: source.company || '',
personalityTags: Array.isArray(source.personalityTags) ? [...source.personalityTags] : [],
hobbies: Array.isArray(source.hobbies) ? [...source.hobbies] : [],
personalityTagLibrary,
hobbyLibrary,
childhood: { date: source.childhood?.date || '', text: source.childhood?.text || '' },
joy: { date: source.joy?.date || '', text: source.joy?.text || '' },
low: { date: source.low?.date || '', text: source.low?.text || '' },
future: { vision: source.future?.vision || '', ideal: source.future?.ideal || '' }
})
birthday.value = source.birthday || ''
}
/**
* 性格标签有序展示列表:
* 1. 新增的自定义标签(不在预设中)按库顺序排在最前
* 2. 已选中的其他标签紧随其后
* 3. 其余未选中的预设标签排在最后
*/
const displayPersonalityTags = computed(() => {
const selectedSet = new Set(form.personalityTags)
const presetSet = new Set(personalityTags)
const customTags = form.personalityTagLibrary.filter((tag) => !presetSet.has(tag))
const selectedPreset = form.personalityTagLibrary.filter(
(tag) => presetSet.has(tag) && selectedSet.has(tag)
)
const unselectedPreset = form.personalityTagLibrary.filter(
(tag) => presetSet.has(tag) && !selectedSet.has(tag)
)
return [...customTags, ...selectedPreset, ...unselectedPreset]
})
/**
* 兴趣爱好有序展示列表(逻辑同上)
*/
const displayHobbyTags = computed(() => {
const selectedSet = new Set(form.hobbies)
const presetSet = new Set(hobbyOptions)
const customTags = form.hobbyLibrary.filter((tag) => !presetSet.has(tag))
const selectedPreset = form.hobbyLibrary.filter(
(tag) => presetSet.has(tag) && selectedSet.has(tag)
)
const unselectedPreset = form.hobbyLibrary.filter(
(tag) => presetSet.has(tag) && !selectedSet.has(tag)
)
return [...customTags, ...selectedPreset, ...unselectedPreset]
})
const onBirthday = (event) => {
birthday.value = event.detail.value
}
const toggleList = (list, tag, max) => {
const index = list.indexOf(tag)
if (index >= 0) {
list.splice(index, 1)
return
}
if (list.length >= max) {
uni.showToast({ title: `最多选择${max}`, icon: 'none' })
return
}
list.push(tag)
}
const chooseAvatar = () => {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
avatarLocal.value = res.tempFilePaths?.[0] || ''
uni.showToast({ title: '头像已更新到本地预览', icon: 'none' })
}
})
}
/**
* 跳转到管理标签页面
*/
const goTagManage = (type) => {
uni.navigateTo({
url: `/pages/onboarding/tag-manage?type=${type}`
})
}
/**
* 新增自定义标签(性格标签 / 兴趣爱好通用)
* @param {string} type 'personality' 或 'hobby'
*/
const addCustomTag = (type) => {
const isPersonality = type === 'personality'
const library = isPersonality ? form.personalityTagLibrary : form.hobbyLibrary
const selected = isPersonality ? form.personalityTags : form.hobbies
const title = isPersonality ? '添加性格标签' : '添加兴趣爱好'
uni.showModal({
title,
editable: true,
placeholderText: '请输入标签(最多8个字符)',
success: (res) => {
const value = String(res.content || '').trim()
if (!res.confirm) return
if (!value) {
uni.showToast({ title: '请输入标签内容', icon: 'none' })
return
}
if (value.length > 8) {
uni.showToast({ title: '标签最多8个字符', icon: 'none' })
return
}
if (library.includes(value)) {
uni.showToast({ title: '标签已存在', icon: 'none' })
return
}
// 新标签插入到库数组最前面
library.unshift(value)
// 若已选未满 5 个,自动加入已选;否则仅提示
if (selected.length >= 5) {
uni.showToast({ title: '已达上限,仅加入标签库', icon: 'none' })
return
}
selected.push(value)
}
})
}
const saveProfile = async () => {
if (saving.value) return
if (!form.nickname.trim()) {
uni.showToast({ title: '请填写昵称', icon: 'none' })
return
}
saving.value = true
store.updateRegistration(JSON.parse(JSON.stringify({ ...form, birthday: birthday.value })))
const res = await store.saveUserProfile()
saving.value = false
if (!res.success) {
uni.showToast({ title: res.error || '保存失败', icon: 'none' })
return
}
syncFromStore()
uni.showToast({ title: '已保存', icon: 'success' })
setTimeout(() => {
if (isEdit.value) uni.navigateBack()
else uni.reLaunch({ url: '/pages/main/index?tab=script' })
}, 350)
}
const goBack = () => {
if (isEdit.value) uni.navigateBack()
else uni.reLaunch({ url: '/pages/main/index?tab=script' })
}
onMounted(async () => {
const pages = getCurrentPages()
isEdit.value = pages[pages.length - 1]?.options?.edit === '1'
if (isEdit.value) {
await store.fetchUserProfile()
}
syncFromStore()
})
</script>
<style scoped>
.profile-page {
position: relative;
height: 100vh;
min-height: 0;
overflow: hidden;
display: flex;
flex-direction: column;
color: #fff;
background: #050615;
}
.space-bg {
position: absolute;
inset: 0;
background:
radial-gradient(circle at 84% 3%, rgba(87, 122, 255, 0.12), transparent 28%),
radial-gradient(circle at 14% 26%, rgba(130, 71, 255, 0.16), transparent 30%),
linear-gradient(180deg, #05081b 0%, #07031a 52%, #03020d 100%);
}
.status-space,
.topbar,
.scroll {
position: relative;
z-index: 1;
}
.status-space,
.topbar {
flex-shrink: 0;
}
.topbar {
height: 72rpx;
display: grid;
grid-template-columns: 76rpx 1fr 112rpx;
align-items: center;
padding: 0 28rpx;
}
.back {
color: #fff;
font-size: 56rpx;
line-height: 1;
}
.title {
text-align: center;
color: #fff;
font-size: 32rpx;
font-weight: 900;
}
.gold {
color: #ffd58c;
text-shadow: 0 0 20rpx rgba(255, 202, 125, 0.45);
}
.save {
color: #b94cff;
font-size: 26rpx;
text-align: right;
justify-self: end;
min-width: 96rpx;
}
.save.disabled {
opacity: 0.58;
}
.scroll {
flex: 1;
height: 0;
min-height: 0;
box-sizing: border-box;
padding: 0 24rpx 28rpx;
margin-top: -6rpx;
}
.glass-card {
position: relative;
overflow: hidden;
border: 1rpx solid rgba(155, 110, 255, 0.14);
background:
radial-gradient(circle at 92% 12%, rgba(104, 66, 255, 0.1), transparent 34%),
rgba(10, 13, 43, 0.54);
box-shadow: inset 0 0 30rpx rgba(123, 60, 255, 0.05), 0 10rpx 36rpx rgba(0, 0, 0, 0.16);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
}
.hero-card {
min-height: 156rpx;
border-radius: 22rpx;
margin-bottom: 18rpx;
padding: 18rpx 24rpx;
display: flex;
align-items: center;
gap: 22rpx;
}
.avatar-wrap {
position: relative;
width: 118rpx;
height: 118rpx;
flex-shrink: 0;
padding: 5rpx;
border-radius: 50%;
background: linear-gradient(135deg, #fff, #9b54ff 46%, #4a67ff);
box-shadow: 0 0 34rpx rgba(162, 91, 255, 0.52);
}
.avatar-img {
width: 100%;
height: 100%;
border-radius: 50%;
background: #27124a;
}
.avatar-edit {
position: absolute;
right: -4rpx;
bottom: -2rpx;
width: 38rpx;
height: 38rpx;
border-radius: 50%;
background: linear-gradient(135deg, #8f4dff, #582cff);
box-shadow: 0 0 18rpx rgba(158, 91, 255, 0.62);
}
.pen-icon {
width: 17rpx;
height: 6rpx;
margin: 16rpx auto;
border-radius: 999rpx;
background: #fff;
transform: rotate(-45deg);
}
.hero-info {
position: relative;
z-index: 2;
flex: 1;
min-width: 0;
}
.hero-name-row {
display: flex;
align-items: center;
gap: 12rpx;
}
.hero-name {
color: #fff;
font-size: 34rpx;
font-weight: 900;
line-height: 1.1;
}
.hero-star {
font-size: 22rpx;
}
.hero-sub {
display: block;
margin-top: 8rpx;
color: rgba(239, 232, 255, 0.84);
font-size: 23rpx;
}
.hero-line {
width: 280rpx;
height: 1rpx;
margin-top: 14rpx;
background: rgba(180, 139, 255, 0.22);
}
.hero-quote {
display: block;
margin-top: 12rpx;
color: #b94cff;
font-size: 23rpx;
font-weight: 700;
}
.hero-planet {
position: absolute;
right: 34rpx;
top: 28rpx;
width: 210rpx;
height: 150rpx;
opacity: 0.42;
}
.planet-core {
position: absolute;
right: 30rpx;
top: 24rpx;
width: 86rpx;
height: 86rpx;
border-radius: 50%;
background: radial-gradient(circle at 34% 26%, #7d63ff, #4b1da8 62%, #19083b);
box-shadow: 0 0 48rpx rgba(141, 78, 255, 0.58);
}
.planet-ring {
position: absolute;
right: 4rpx;
top: 58rpx;
width: 158rpx;
height: 34rpx;
border: 5rpx solid rgba(161, 92, 255, 0.55);
border-top-color: transparent;
border-radius: 50%;
transform: rotate(-18deg);
}
.panel {
border-radius: 22rpx;
margin-bottom: 18rpx;
padding: 22rpx 24rpx;
}
.section-head,
.section-title-wrap {
display: flex;
align-items: center;
}
.section-head {
min-height: 44rpx;
gap: 10rpx;
}
.section-head.with-action {
justify-content: space-between;
}
.section-title {
color: rgba(239, 232, 255, 0.9);
font-size: 24rpx;
font-weight: 800;
}
.section-hint {
color: rgba(222, 211, 240, 0.54);
font-size: 22rpx;
}
.section-icon {
position: relative;
width: 28rpx;
height: 28rpx;
color: #a855ff;
flex-shrink: 0;
}
.basic-icon::before {
content: '♜';
color: currentColor;
font-size: 28rpx;
line-height: 1;
}
.job-title-icon {
border: 4rpx solid currentColor;
border-radius: 6rpx;
box-sizing: border-box;
}
.job-title-icon::before {
content: '';
position: absolute;
left: 7rpx;
top: -8rpx;
width: 10rpx;
height: 7rpx;
border: 3rpx solid currentColor;
border-bottom: 0;
border-radius: 6rpx 6rpx 0 0;
}
.smile-title-icon {
border-radius: 50%;
border: 3rpx solid currentColor;
}
.smile-title-icon::before,
.smile-title-icon::after {
content: '';
position: absolute;
top: 8rpx;
width: 4rpx;
height: 4rpx;
border-radius: 50%;
background: currentColor;
}
.smile-title-icon::before { left: 7rpx; }
.smile-title-icon::after { right: 7rpx; }
.heart-title-icon::before {
content: '♡';
font-size: 30rpx;
line-height: 1;
}
.bio-title-icon {
border: 3rpx solid currentColor;
border-radius: 5rpx;
box-sizing: border-box;
}
.bio-title-icon::before,
.bio-title-icon::after {
content: '';
position: absolute;
left: 6rpx;
right: 6rpx;
height: 3rpx;
background: currentColor;
}
.bio-title-icon::before { top: 8rpx; }
.bio-title-icon::after { top: 15rpx; }
.profile-row {
min-height: 60rpx;
display: grid;
grid-template-columns: 142rpx 1fr 24rpx;
align-items: center;
border-top: 1rpx solid rgba(180, 139, 255, 0.16);
}
.section-head + .profile-row {
margin-top: 8rpx;
}
.row-label {
color: rgba(205, 191, 238, 0.82);
font-size: 23rpx;
}
.row-input,
.row-value {
min-width: 0;
color: rgba(255, 255, 255, 0.9);
font-size: 23rpx;
text-align: left;
}
.row-picker {
min-width: 0;
}
.muted {
color: rgba(217, 205, 238, 0.42);
}
.chevron {
color: rgba(218, 204, 243, 0.7);
font-size: 38rpx;
line-height: 1;
text-align: right;
}
.gender-row {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16rpx;
}
.choice,
.mbti-chip,
.tag-choice {
height: 40rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
justify-content: center;
color: rgba(224, 214, 243, 0.72);
border: 1rpx solid rgba(151, 111, 255, 0.42);
background: rgba(255, 255, 255, 0.02);
font-size: 22rpx;
box-sizing: border-box;
}
.choice.active,
.mbti-chip.active,
.tag-choice.active {
color: #fff;
border-color: rgba(206, 82, 255, 0.95);
background: linear-gradient(180deg, rgba(169, 61, 255, 0.62), rgba(107, 41, 206, 0.5));
box-shadow: 0 0 18rpx rgba(168, 67, 255, 0.52), inset 0 1rpx 0 rgba(255, 255, 255, 0.22);
}
.astro-panel {
display: flex;
flex-direction: column;
gap: 22rpx;
margin-top: 10rpx;
padding-top: 16rpx;
border-top: 1rpx solid rgba(180, 139, 255, 0.16);
}
.astro-col {
min-width: 0;
padding: 0;
}
.mbti-col {
border-left: 0;
padding-top: 18rpx;
border-top: 1rpx solid rgba(180, 139, 255, 0.12);
}
.astro-title-row {
display: grid;
grid-template-columns: 34rpx 76rpx 1fr 18rpx;
align-items: center;
gap: 8rpx;
}
.zodiac-head-icon,
.mbti-head-icon {
width: 28rpx;
height: 28rpx;
color: #a855ff;
}
.zodiac-head-icon {
font-size: 28rpx;
line-height: 1;
}
.mbti-head-icon {
border: 4rpx solid currentColor;
border-radius: 4rpx;
box-sizing: border-box;
}
.astro-title {
color: rgba(222, 211, 240, 0.76);
font-size: 23rpx;
}
.astro-current {
color: rgba(255, 255, 255, 0.9);
font-size: 22rpx;
text-align: right;
}
.select-title {
display: block;
margin-top: 16rpx;
color: rgba(222, 211, 240, 0.72);
font-size: 21rpx;
}
.zodiac-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 14rpx 10rpx;
margin-top: 14rpx;
}
.zodiac-item {
min-width: 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 7rpx;
color: rgba(226, 217, 246, 0.84);
font-size: 20rpx;
}
.zodiac-bubble {
width: 44rpx;
height: 44rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #a855ff;
font-size: 26rpx;
background: rgba(124, 58, 237, 0.28);
border: 1rpx solid rgba(173, 84, 255, 0.36);
}
.zodiac-item.active .zodiac-bubble {
color: #fff;
border-color: rgba(215, 128, 255, 0.95);
background: linear-gradient(135deg, rgba(168, 85, 247, 0.8), rgba(98, 47, 190, 0.66));
box-shadow: 0 0 22rpx rgba(190, 92, 255, 0.72);
}
.mbti-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12rpx 14rpx;
margin-top: 14rpx;
}
.mbti-chip {
height: 40rpx;
font-size: 20rpx;
}
.tag-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 14rpx 24rpx;
margin-top: 20rpx;
}
.tag-choice {
height: 40rpx;
font-size: 21rpx;
}
.tag-choice.dashed {
border-style: dashed;
color: #c06dff;
}
.manage-link {
color: #c06dff;
font-size: 23rpx;
}
.bio-panel {
margin-bottom: 0;
}
.bio {
width: 100%;
height: 116rpx;
box-sizing: border-box;
margin-top: 16rpx;
padding: 18rpx 20rpx;
border-radius: 18rpx;
border: 1rpx solid rgba(151, 111, 255, 0.22);
background: rgba(10, 12, 40, 0.66);
color: #fff;
font-size: 23rpx;
line-height: 1.5;
}
.bio-count {
display: block;
margin-top: 8rpx;
text-align: right;
color: rgba(224, 214, 243, 0.56);
font-size: 20rpx;
}
.placeholder {
color: rgba(214, 204, 235, 0.42);
}
</style>