feat: 优化管理后台页面UI、修复TS编译错误、新增人生事件模块

- 优化 AI 配置列表页面:重构统计卡片、搜索表单、表格列展示
- 修复 3 处 TypeScript TS6133 编译错误,恢复构建
- 新增管理员修改密码和重置密码功能
- 优化小程序多个页面样式和交互
- 人生事件模块完善

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 23:23:09 +08:00
parent 60c63850ee
commit 755059807a
62 changed files with 4661 additions and 3019 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+734 -161
View File
@@ -1,248 +1,821 @@
<template>
<view class="mine-view">
<view class="profile-card kos-card">
<view class="avatar">{{ avatarText }}</view>
<view class="profile-main">
<view class="name-row">
<text class="name">{{ profile.nickname || 'Zoey' }}</text>
<text class="star"></text>
<view class="script-library">
<view class="page-head">
<view class="title-row">
<text class="spark"></text>
<text class="page-title">我的剧本</text>
</view>
<view class="head-actions">
<view class="circle-btn" @click="openSearch">
<view class="search-icon"></view>
</view>
<text class="signature">{{ signature }}</text>
<view class="chips">
<text v-for="chip in chips" :key="chip" class="chip kos-pill">{{ chip }}</text>
<view class="circle-btn" @click="openMoreMenu">
<view class="more-icon">
<view></view>
<view></view>
<view></view>
</view>
</view>
</view>
<view class="edit-btn kos-pill" @click="editProfile">编辑资料</view>
</view>
<view class="stats-grid">
<view class="stat-card kos-card">
<text class="stat-value">{{ eventsCount }}</text>
<text class="stat-label">人生记录</text>
</view>
<view class="stat-card kos-card">
<text class="stat-value">{{ scriptsCount }}</text>
<text class="stat-label">生成剧本</text>
<view class="type-tabs">
<text
v-for="tab in typeTabs"
:key="tab.value"
class="type-tab"
:class="{ active: activeType === tab.value }"
@click="activeType = tab.value"
>{{ tab.label }}</text>
<view class="new-script" @click="createScript">
<text class="plus"></text>
<text>新建剧本</text>
</view>
</view>
<view class="section-card kos-card">
<view class="section-title">兴趣爱好</view>
<view class="tag-cloud">
<text v-for="tag in hobbyTags" :key="tag" class="tag kos-pill">{{ tag }}</text>
<view class="filter-bar">
<scroll-view class="status-scroll" scroll-x :show-scrollbar="false">
<view class="status-row">
<text
v-for="filter in statusFilters"
:key="filter.value"
class="status-chip"
:class="{ active: activeStatus === filter.value }"
@click="activeStatus = filter.value"
>{{ filter.label }}</text>
</view>
</scroll-view>
<view class="sort-tools">
<text class="sort-text" @click="toggleSort">{{ sortLabel }}</text>
<view class="grid-icon" :class="{ active: viewMode === 'grid' }" @click="toggleViewMode">
<view v-for="i in 4" :key="i"></view>
</view>
</view>
</view>
<view class="section-card kos-card">
<view class="section-title">生命摘要</view>
<view class="memory-line">
<text class="memory-label">童年</text>
<text class="memory-text">{{ profile.childhood?.text || '还没有写下最早的光。' }}</text>
</view>
<view class="memory-line">
<text class="memory-label">高光</text>
<text class="memory-text">{{ profile.joy?.text || '等待记录一次会发光的瞬间。' }}</text>
</view>
<view class="memory-line">
<text class="memory-label">未来</text>
<text class="memory-text">{{ profile.future?.vision || '未来档案还在生成中。' }}</text>
<view v-if="visibleScripts.length" class="script-list" :class="{ grid: viewMode === 'grid' }">
<view
v-for="(script, index) in visibleScripts"
:key="script.id || index"
class="script-card"
@click="viewScript(script)"
>
<view class="cover" :class="'cover-' + (index % 6)">
<text>{{ getInitial(script) }}</text>
</view>
<view class="card-main">
<view class="card-top">
<view class="title-wrap">
<text class="script-title">{{ script.title }}</text>
<text class="length-badge">{{ getLengthLabel(script.length) }}</text>
</view>
<view class="right-state">
<text class="state-pill" :class="'state-' + getStatus(script)">{{ getStatusLabel(script) }}</text>
<text class="ellipsis" @click.stop="openScriptMenu(script)"></text>
</view>
</view>
<view class="tag-row">
<text v-for="tag in getTags(script)" :key="tag" class="tag">{{ tag }}</text>
</view>
<text class="summary">{{ script.summary || script.content || '一段正在生成中的平行人生剧本。' }}</text>
<view class="meta-row">
<text>{{ getChapterCount(script) }}</text>
<text>|</text>
<text>{{ getWordCount(script) }}</text>
<text>|</text>
<text>{{ getDateText(script) }}</text>
</view>
<view v-if="getStatus(script) === 'progress'" class="progress-row">
<view class="progress-track">
<view class="progress-fill" :style="{ width: getProgress(script) + '%' }"></view>
</view>
<text>{{ getProgress(script) }}%</text>
</view>
<view v-else-if="isFavorite(script)" class="favorite-row">
<text class="favorite-star"></text>
<text>已收藏</text>
</view>
</view>
</view>
</view>
<button class="logout kos-pill" @click="handleLogout">退出登录</button>
<view v-else class="empty-card">
<view class="empty-book">
<view></view>
<view></view>
</view>
<text class="empty-title">还没有人生剧本</text>
<text class="empty-text">去爽文生成页写下一句灵感生成你的第一段平行人生</text>
<view class="empty-action" @click="createScript">新建剧本</view>
</view>
</view>
</template>
<script setup>
import { computed } from 'vue'
import { computed, ref } from 'vue'
import { useAppStore } from '../../stores/app.js'
const store = useAppStore()
const profile = computed(() => store.userProfile || store.registrationData || {})
const eventsCount = computed(() => store.events?.length || 0)
const scriptsCount = computed(() => store.scripts?.length || 0)
const activeType = ref('long')
const activeStatus = ref('all')
const keyword = ref('')
const sortMode = ref('updated')
const viewMode = ref('list')
const localFavorites = ref(uni.getStorageSync('script_favorites') || {})
const avatarText = computed(() => (profile.value.nickname || 'Z').slice(0, 1))
const chips = computed(() => [profile.value.zodiac, profile.value.mbti, profile.value.profession].filter(Boolean))
const hobbyTags = computed(() => {
const hobbies = profile.value.hobbies
if (Array.isArray(hobbies) && hobbies.length) return hobbies
return ['阅读', '旅行', '音乐', '创作']
const typeTabs = [
{ label: '长篇', value: 'long' },
{ label: '短篇', value: 'short' },
{ label: '风格', value: 'style' }
]
const statusFilters = [
{ label: '全部', value: 'all' },
{ label: '进行中', value: 'progress' },
{ label: '已完成', value: 'done' },
{ label: '草稿箱', value: 'draft' },
{ label: '收藏夹', value: 'favorite' }
]
const fallbackScripts = [
{
id: 'demo-1',
title: '逆袭人生:从低谷到巅峰',
length: 'long',
status: 'progress',
tags: ['逆袭成长', '都市', '事业', '热血'],
summary: '从被分手、被否定的低谷开始,凭借天赋、努力与智慧,一步步逆袭成为行业巅峰,收获事业、财富...',
chapterCount: 28,
wordCount: 128000,
updatedAt: '今天 21:30',
progress: 28
},
{
id: 'demo-2',
title: '如果那年我没有放弃',
length: 'long',
status: 'done',
tags: ['成长治愈', '校园', '爱情', '温暖'],
summary: '重回十八岁,弥补遗憾,勇敢追梦,守护那些曾经错过的人和事。',
chapterCount: 36,
wordCount: 156000,
completedAt: '2025.05.10',
isFavorite: true
},
{
id: 'demo-3',
title: '重生之我在未来等你',
length: 'long',
status: 'progress',
tags: ['重生', '科幻', '爱情', '未来'],
summary: '一觉醒来,回到十年前的那一天。这一次,我不仅要改变自己的人生,还要找到你。',
chapterCount: 18,
wordCount: 83000,
updatedAt: '昨天 18:47',
progress: 46
},
{
id: 'demo-4',
title: '天才作曲家的璀璨之路',
length: 'long',
status: 'draft',
tags: ['音乐', '励志', '天赋', '梦想'],
summary: '从默默无闻到享誉全球,用音符征服世界,写下属于自己的传奇乐章。',
chapterCount: 9,
wordCount: 31000,
createdAt: '2025.05.08'
},
{
id: 'demo-5',
title: '咖啡馆里的奇遇',
length: 'short',
status: 'done',
tags: ['生活', '治愈', '奇幻', '温暖'],
summary: '一杯咖啡,一次奇遇,改变了我平凡的生活,也让我遇见了最特别的你。',
chapterCount: 1,
wordCount: 23000,
completedAt: '2025.05.01',
isFavorite: true
},
{
id: 'demo-6',
title: '赛博时代的追光者',
length: 'long',
status: 'draft',
tags: ['科幻', '未来', '冒险', '热血'],
summary: '在数据与代码构建的世界里,我追寻光明,也在黑暗中寻找真正的自由。',
chapterCount: 3,
wordCount: 12000,
createdAt: '2025.05.12'
}
]
const scripts = computed(() => {
const list = store.scripts || []
return list.length ? list : fallbackScripts
})
const signature = computed(() => profile.value.future?.ideal || '正在把人生整理成一份会发光的档案。')
const editProfile = () => {
uni.navigateTo({ url: '/pages/onboarding/index?edit=1' })
const visibleScripts = computed(() => {
const filtered = scripts.value.filter(script => {
const status = getStatus(script)
if (keyword.value) {
const haystack = [script.title, script.summary, script.content, script.style, ...(script.tags || [])].join(' ')
if (!haystack.includes(keyword.value)) return false
}
if (activeStatus.value === 'favorite') return isFavorite(script)
if (activeStatus.value !== 'all' && status !== activeStatus.value) return false
if (activeType.value === 'short') return script.length === 'short'
if (activeType.value === 'long') return script.length !== 'short'
return true
})
return [...filtered].sort((a, b) => {
if (sortMode.value === 'words') return Number(b.wordCount || 0) - Number(a.wordCount || 0)
if (sortMode.value === 'progress') return getProgress(b) - getProgress(a)
return String(b.updateTime || b.updatedAt || b.createTime || b.date || '').localeCompare(String(a.updateTime || a.updatedAt || a.createTime || a.date || ''))
})
})
const sortLabel = computed(() => {
const map = { updated: '最近更新⌄', words: '字数最多⌄', progress: '进度最高⌄' }
return map[sortMode.value] || '最近更新⌄'
})
const getStatus = (script) => {
if (script.status) return script.status
if (script.isDraft) return 'draft'
if (script.isCompleted || script.completedAt) return 'done'
return script.progress ? 'progress' : 'done'
}
const handleLogout = () => {
const getStatusLabel = (script) => {
const map = { progress: '进行中', done: '已完成', draft: '草稿' }
return map[getStatus(script)] || '已完成'
}
const getLengthLabel = (length) => {
return length === 'short' ? '短篇' : '长篇'
}
const getTags = (script) => {
if (Array.isArray(script.tags) && script.tags.length) return script.tags.slice(0, 4)
return [script.style || '逆袭成长', '都市', '事业', '热血']
}
const getChapterCount = (script) => script.chapterCount || script.chapters || Math.max(1, Math.round((script.wordCount || 30000) / 4500))
const getWordCount = (script) => {
const count = Number(script.wordCount || 0)
if (!count) return '3.1万字'
if (count >= 10000) return `${(count / 10000).toFixed(1)}万字`
return `${count}`
}
const getDateText = (script) => {
if (getStatus(script) === 'done') return `完成于:${script.completedAt || script.date || '2025.05.10'}`
if (getStatus(script) === 'draft') return `创建于:${script.createdAt || script.date || '2025.05.08'}`
return `最近更新:${script.updatedAt || script.date || '今天 21:30'}`
}
const getProgress = (script) => Math.max(1, Math.min(99, Number(script.progress || 28)))
const getInitial = (script) => (script.title || '剧').slice(0, 1)
const isFavorite = (script) => {
return Boolean(script.isFavorite || script.favorite || localFavorites.value[String(script.id)])
}
const viewScript = (script) => {
if (!script?.id || String(script.id).startsWith('demo-')) return
uni.navigateTo({ url: `/pages/main/ScriptDetailView?id=${script.id}` })
}
const createScript = () => {
uni.$emit('switchTab', 'script')
}
const openSearch = () => {
uni.showModal({
title: '退出登录',
content: '确定要离开当前数字生命档案吗?',
success: async (res) => {
if (res.confirm) {
await store.logout()
uni.reLaunch({ url: '/pages/login/index' })
title: '搜索剧本',
editable: true,
placeholderText: '输入标题、标签或关键词',
success: (res) => {
if (res.confirm) keyword.value = String(res.content || '').trim()
}
})
}
const openMoreMenu = () => {
uni.showActionSheet({
itemList: ['清空搜索', '只看收藏', '查看全部'],
success: ({ tapIndex }) => {
if (tapIndex === 0) keyword.value = ''
if (tapIndex === 1) activeStatus.value = 'favorite'
if (tapIndex === 2) {
keyword.value = ''
activeStatus.value = 'all'
}
}
})
}
const toggleSort = () => {
const order = ['updated', 'words', 'progress']
sortMode.value = order[(order.indexOf(sortMode.value) + 1) % order.length]
}
const toggleViewMode = () => {
viewMode.value = viewMode.value === 'list' ? 'grid' : 'list'
}
const openScriptMenu = (script) => {
const favorite = isFavorite(script)
uni.showActionSheet({
itemList: [favorite ? '取消收藏' : '收藏剧本', '查看详情', '映射路径'],
success: ({ tapIndex }) => {
if (tapIndex === 0) {
const next = { ...localFavorites.value }
if (favorite) delete next[String(script.id)]
else next[String(script.id)] = true
localFavorites.value = next
uni.setStorageSync('script_favorites', next)
uni.showToast({ title: favorite ? '已取消收藏' : '已收藏', icon: 'success' })
}
if (tapIndex === 1) viewScript(script)
if (tapIndex === 2) mapScript(script)
}
})
}
const mapScript = async (script) => {
if (!script?.id || String(script.id).startsWith('demo-')) {
uni.showToast({ title: '示例剧本暂不可映射', icon: 'none' })
return
}
const res = await store.selectScript(script.id)
if (!res.success) {
uni.showToast({ title: res.error || '映射失败', icon: 'none' })
return
}
uni.navigateTo({ url: '/pages/main/PathView' })
}
</script>
<style scoped>
.mine-view {
.script-library {
display: flex;
flex-direction: column;
gap: 24rpx;
padding-bottom: 32rpx;
padding-bottom: 26rpx;
}
.profile-card {
border-radius: 34rpx;
padding: 32rpx;
.page-head,
.title-row,
.head-actions,
.type-tabs,
.filter-bar,
.sort-tools,
.card-top,
.title-wrap,
.right-state,
.meta-row,
.progress-row,
.favorite-row {
display: flex;
align-items: center;
gap: 22rpx;
}
.avatar {
width: 112rpx;
height: 112rpx;
flex-shrink: 0;
.page-head {
justify-content: space-between;
}
.title-row {
gap: 14rpx;
}
.spark {
color: #ffd58c;
font-size: 34rpx;
text-shadow: 0 0 20rpx rgba(255, 202, 125, 0.5);
}
.page-title {
color: #fff;
font-size: 42rpx;
font-weight: 900;
}
.head-actions {
gap: 20rpx;
}
.circle-btn {
width: 58rpx;
height: 58rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 50rpx;
border: 1rpx solid rgba(142, 105, 255, 0.36);
background: rgba(10, 11, 38, 0.72);
}
.search-icon {
width: 25rpx;
height: 25rpx;
border: 4rpx solid #fff;
border-radius: 50%;
position: relative;
}
.search-icon::after {
content: '';
position: absolute;
right: -9rpx;
bottom: -8rpx;
width: 14rpx;
height: 4rpx;
border-radius: 999rpx;
background: #fff;
transform: rotate(45deg);
}
.more-icon {
display: flex;
gap: 5rpx;
}
.more-icon view {
width: 6rpx;
height: 6rpx;
border-radius: 50%;
background: #fff;
}
.type-tabs {
justify-content: space-between;
border-bottom: 1rpx solid rgba(126, 87, 255, 0.18);
padding-bottom: 16rpx;
}
.type-tab {
position: relative;
color: rgba(224, 214, 243, 0.7);
font-size: 31rpx;
font-weight: 900;
background: linear-gradient(135deg, #b245ff, #2a7dff);
box-shadow: 0 0 36rpx rgba(168, 85, 255, 0.55);
padding: 0 20rpx 14rpx;
}
.profile-main {
flex: 1;
min-width: 0;
.type-tab.active {
color: #fff;
}
.name-row {
.type-tab.active::after {
content: '';
position: absolute;
left: 20rpx;
right: 20rpx;
bottom: -17rpx;
height: 5rpx;
border-radius: 999rpx;
background: #b246ff;
box-shadow: 0 0 18rpx rgba(178, 70, 255, 0.8);
}
.new-script {
margin-left: auto;
height: 64rpx;
padding: 0 24rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
gap: 10rpx;
}
.name {
gap: 8rpx;
color: #fff;
font-size: 38rpx;
font-weight: 900;
}
.star {
color: #ffd184;
}
.signature {
display: block;
margin-top: 10rpx;
color: rgba(224, 211, 246, 0.66);
font-size: 24rpx;
line-height: 1.5;
font-weight: 800;
background: linear-gradient(135deg, #b346ff, #7330ff);
box-shadow: 0 0 26rpx rgba(168, 85, 247, 0.54);
}
.chips,
.tag-cloud {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
margin-top: 18rpx;
.plus {
font-size: 32rpx;
line-height: 1;
}
.chip,
.tag {
height: 44rpx;
padding: 0 16rpx;
.filter-bar {
gap: 14rpx;
}
.status-scroll {
flex: 1;
min-width: 0;
white-space: nowrap;
}
.status-row {
display: inline-flex;
gap: 16rpx;
}
.status-chip {
height: 52rpx;
min-width: 88rpx;
padding: 0 24rpx;
border-radius: 999rpx;
display: inline-flex;
align-items: center;
color: rgba(244, 235, 255, 0.86);
font-size: 21rpx;
justify-content: center;
color: rgba(224, 214, 243, 0.78);
font-size: 23rpx;
border: 1rpx solid rgba(151, 111, 255, 0.42);
background: rgba(255, 255, 255, 0.02);
}
.edit-btn {
align-self: flex-start;
height: 54rpx;
padding: 0 18rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
color: #caa0ff;
font-size: 22rpx;
}
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18rpx;
}
.stat-card {
border-radius: 28rpx;
padding: 28rpx;
}
.stat-value {
display: block;
.status-chip.active {
color: #fff;
font-size: 48rpx;
font-weight: 900;
border-color: rgba(206, 82, 255, 0.92);
background: rgba(130, 48, 220, 0.42);
box-shadow: 0 0 18rpx rgba(168, 67, 255, 0.46);
}
.stat-label {
display: block;
margin-top: 6rpx;
color: rgba(224, 211, 246, 0.62);
.sort-tools {
gap: 14rpx;
flex-shrink: 0;
}
.sort-text {
color: #c99fff;
font-size: 23rpx;
}
.section-card {
border-radius: 30rpx;
padding: 30rpx;
}
.section-title {
color: #fff;
font-size: 30rpx;
font-weight: 900;
}
.memory-line {
.grid-icon {
width: 48rpx;
height: 48rpx;
border-radius: 18rpx;
display: grid;
grid-template-columns: 86rpx 1fr;
grid-template-columns: repeat(2, 1fr);
gap: 6rpx;
padding: 11rpx;
box-sizing: border-box;
border: 1rpx solid rgba(151, 111, 255, 0.32);
}
.grid-icon view {
border: 2rpx solid rgba(230, 222, 250, 0.78);
border-radius: 3rpx;
}
.script-list {
display: flex;
flex-direction: column;
gap: 18rpx;
padding: 22rpx 0;
border-bottom: 1rpx solid rgba(180, 139, 255, 0.14);
}
.memory-line:last-child {
border-bottom: 0;
.script-list.grid {
display: grid;
grid-template-columns: 1fr 1fr;
}
.memory-label {
color: #b56cff;
.script-list.grid .script-card {
grid-template-columns: 1fr;
}
.script-list.grid .cover {
width: 100%;
}
.grid-icon.active {
border-color: rgba(206, 82, 255, 0.9);
background: rgba(130, 48, 220, 0.28);
}
.script-card {
display: grid;
grid-template-columns: 150rpx 1fr;
gap: 22rpx;
min-height: 196rpx;
padding: 20rpx;
border-radius: 24rpx;
border: 1rpx solid rgba(105, 79, 210, 0.34);
background:
radial-gradient(circle at 100% 0%, rgba(112, 72, 255, 0.14), transparent 38%),
rgba(9, 12, 42, 0.72);
box-shadow: inset 0 0 28rpx rgba(92, 57, 197, 0.08);
}
.cover {
width: 150rpx;
height: 150rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 54rpx;
font-weight: 900;
overflow: hidden;
background: linear-gradient(135deg, #3b1a90, #d65cff);
}
.cover-0 { background: linear-gradient(135deg, #29135f, #9037ff 48%, #191b5e); }
.cover-1 { background: linear-gradient(135deg, #3c1c4a, #f2b3cc 48%, #16143b); }
.cover-2 { background: linear-gradient(135deg, #1a225f, #7d4cff 48%, #0a0f2c); }
.cover-3 { background: linear-gradient(135deg, #2f240b, #f7b44a 48%, #0d0a16); }
.cover-4 { background: linear-gradient(135deg, #3f2417, #d8b58a 48%, #17101d); }
.cover-5 { background: linear-gradient(135deg, #141451, #cc46ff 48%, #0c0b28); }
.card-main {
min-width: 0;
}
.card-top {
justify-content: space-between;
gap: 14rpx;
}
.title-wrap {
min-width: 0;
gap: 10rpx;
}
.script-title {
color: #fff;
font-size: 27rpx;
line-height: 1.25;
font-weight: 900;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.length-badge {
flex-shrink: 0;
padding: 4rpx 9rpx;
border-radius: 8rpx;
color: #c985ff;
font-size: 18rpx;
border: 1rpx solid rgba(182, 92, 255, 0.5);
background: rgba(128, 55, 204, 0.22);
}
.right-state {
flex-shrink: 0;
gap: 14rpx;
}
.state-pill {
height: 34rpx;
padding: 0 14rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
font-size: 19rpx;
}
.state-progress {
color: #ffbf4c;
background: rgba(170, 103, 20, 0.22);
}
.state-done {
color: #79e6a9;
background: rgba(44, 146, 88, 0.2);
}
.state-draft {
color: rgba(224, 214, 243, 0.76);
background: rgba(255, 255, 255, 0.06);
}
.ellipsis {
color: rgba(224, 214, 243, 0.66);
font-size: 24rpx;
letter-spacing: 3rpx;
}
.tag-row {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
margin-top: 16rpx;
}
.tag {
height: 34rpx;
padding: 0 14rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
color: #d49cff;
font-size: 19rpx;
background: rgba(149, 55, 255, 0.2);
}
.summary {
display: -webkit-box;
margin-top: 14rpx;
color: rgba(226, 215, 246, 0.72);
font-size: 22rpx;
line-height: 1.55;
overflow: hidden;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.meta-row {
gap: 14rpx;
margin-top: 14rpx;
color: rgba(218, 204, 243, 0.66);
font-size: 21rpx;
}
.progress-row {
justify-content: flex-end;
gap: 14rpx;
margin-top: 14rpx;
color: #bd72ff;
font-size: 22rpx;
font-weight: 800;
}
.memory-text {
color: rgba(224, 211, 246, 0.72);
font-size: 24rpx;
line-height: 1.55;
.progress-track {
width: 118rpx;
height: 6rpx;
border-radius: 999rpx;
background: rgba(173, 160, 210, 0.18);
overflow: hidden;
}
.logout {
height: 72rpx;
.progress-fill {
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, #b246ff, #d878ff);
}
.favorite-row {
justify-content: flex-end;
gap: 8rpx;
margin-top: 14rpx;
color: #b768ff;
font-size: 23rpx;
font-weight: 800;
}
.favorite-star {
font-size: 28rpx;
}
.empty-card {
margin-top: 30rpx;
border-radius: 26rpx;
padding: 44rpx 30rpx;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
border: 1rpx solid rgba(105, 79, 210, 0.34);
background: rgba(9, 12, 42, 0.72);
}
.empty-book {
display: flex;
gap: 6rpx;
margin-bottom: 18rpx;
}
.empty-book view {
width: 32rpx;
height: 46rpx;
border: 4rpx solid #b768ff;
border-radius: 8rpx 4rpx 4rpx 8rpx;
}
.empty-title {
color: #fff;
font-size: 28rpx;
font-weight: 900;
}
.empty-text {
margin-top: 12rpx;
color: rgba(226, 215, 246, 0.68);
font-size: 22rpx;
line-height: 1.5;
}
.empty-action {
margin-top: 22rpx;
height: 56rpx;
padding: 0 30rpx;
border-radius: 999rpx;
color: rgba(224, 211, 246, 0.74);
font-size: 25rpx;
display: flex;
align-items: center;
color: #fff;
font-size: 23rpx;
font-weight: 800;
background: linear-gradient(135deg, #b346ff, #7330ff);
}
</style>
+34 -2
View File
@@ -63,10 +63,42 @@ const loadPath = async (scriptId) => {
if (!scriptId) return
try {
const res = await lifePathService.getPathByScriptId(scriptId)
pathData.value = res.data || null
pathData.value = res.data || await createPlaceholderPath(scriptId)
store.setCurrentPath(pathData.value)
} catch (error) {
pathData.value = null
pathData.value = await createPlaceholderPath(scriptId)
store.setCurrentPath(pathData.value)
}
}
const createPlaceholderPath = async (scriptId) => {
const script = selectedScript.value || {}
const title = script.title ? `${script.title} · 实现路径` : '我的实现路径'
const steps = [
{ phase: '阶段1', task: '整理目标', desc: '把剧本中的关键目标拆成可以执行的小目标。', content: '把剧本中的关键目标拆成可以执行的小目标。', done: true },
{ phase: '阶段2', task: '建立习惯', desc: '选择一个最小行动,每天稳定推进。', content: '选择一个最小行动,每天稳定推进。', done: false },
{ phase: '阶段3', task: '复盘迭代', desc: '每周回看进展,根据现实反馈调整路径。', content: '每周回看进展,根据现实反馈调整路径。', done: false }
]
try {
const res = await lifePathService.createPath({
scriptId,
title,
description: script.summary || '根据选中的人生剧本生成的占位实现路径,后续可接入AI生成更细的行动计划。',
steps,
progress: 8,
status: 'active'
})
return lifePathService.transformToFrontendFormat(res.data)
} catch (error) {
return {
id: `local-${scriptId}`,
scriptId,
title,
description: script.summary || '占位实现路径',
steps,
progress: 8,
status: 'active'
}
}
}
+141 -108
View File
@@ -66,7 +66,7 @@
</view>
<text class="section-subtitle">你的成长之路正在展开</text>
</view>
<view class="map-btn kos-pill">
<view class="map-btn kos-pill" @click="openMap">
<view class="map-icon"></view>
<text>轨迹地图</text>
</view>
@@ -81,7 +81,7 @@
:class="{ active: activeFilter === filter.value }"
@click="activeFilter = filter.value"
>{{ filter.label }}</text>
<text class="add-filter"></text>
<text class="add-filter" @click="addFilter"></text>
</view>
</scroll-view>
@@ -130,14 +130,16 @@ import { useAppStore } from '../../stores/app.js'
const store = useAppStore()
const activeFilter = ref('all')
const customFilters = ref([])
const filters = [
const baseFilters = [
{ label: '全部', value: 'all' },
{ label: '童年', value: 'childhood' },
{ label: '高光', value: 'highlight' },
{ label: '低谷', value: 'valley' },
{ label: '美好的瞬间', value: 'daily_log' }
]
const filters = computed(() => [...baseFilters, ...customFilters.value])
const sampleEvents = [
{
@@ -192,6 +194,10 @@ const avatar = computed(() => {
const displayEvents = computed(() => {
if (activeFilter.value === 'all') return events.value
if (activeFilter.value.startsWith('custom_')) {
const label = activeFilter.value.replace('custom_', '')
return events.value.filter(event => Array.isArray(event.tags) && event.tags.includes(label))
}
return events.value.filter(event => event.eventType === activeFilter.value || event.emotionType === activeFilter.value)
})
@@ -222,28 +228,50 @@ const createEvent = () => {
}
const openDetail = (event) => {
if (String(event.id || '').startsWith('sample-')) return
uni.setStorageSync('current_life_event', JSON.parse(JSON.stringify(event || {})))
if (!event?.id) return
uni.navigateTo({ url: `/pages/life-event/detail?id=${event.id}` })
}
const editProfile = () => {
uni.navigateTo({ url: '/pages/onboarding/index?edit=1' })
}
const openMap = () => {
uni.navigateTo({ url: '/pages/main/PathView' })
}
const addFilter = () => {
uni.showModal({
title: '新增筛选',
editable: true,
placeholderText: '输入标签名',
success: (res) => {
const value = String(res.content || '').trim()
if (!res.confirm || !value) return
const filter = { label: value, value: `custom_${value}` }
if (!customFilters.value.some(item => item.label === value)) {
customFilters.value.push(filter)
}
activeFilter.value = filter.value
}
})
}
</script>
<style scoped>
.record-view {
display: flex;
flex-direction: column;
gap: 28rpx;
padding-bottom: 34rpx;
gap: 24rpx;
padding-bottom: 22rpx;
}
.profile-card {
position: relative;
overflow: hidden;
border-radius: 34rpx;
padding: 40rpx 34rpx 32rpx;
padding: 34rpx 30rpx 28rpx;
}
.profile-card::after {
@@ -251,8 +279,8 @@ const editProfile = () => {
position: absolute;
right: -28rpx;
bottom: -20rpx;
width: 250rpx;
height: 180rpx;
width: 220rpx;
height: 156rpx;
background: radial-gradient(circle, rgba(122, 58, 255, 0.35), transparent 62%);
border: 1rpx solid rgba(158, 88, 255, 0.26);
border-radius: 50%;
@@ -269,18 +297,18 @@ const editProfile = () => {
.profile-top {
display: flex;
align-items: center;
gap: 24rpx;
gap: 22rpx;
}
.avatar-wrap {
position: relative;
flex-shrink: 0;
width: 134rpx;
height: 134rpx;
padding: 6rpx;
width: 112rpx;
height: 112rpx;
padding: 5rpx;
border-radius: 50%;
background: linear-gradient(135deg, #fff, #9b54ff 38%, #4a67ff);
box-shadow: 0 0 42rpx rgba(149, 89, 255, 0.56);
box-shadow: 0 0 34rpx rgba(149, 89, 255, 0.52);
}
.avatar {
@@ -292,10 +320,10 @@ const editProfile = () => {
.avatar-edit {
position: absolute;
right: -6rpx;
bottom: -6rpx;
width: 48rpx;
height: 48rpx;
right: -5rpx;
bottom: -5rpx;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background: linear-gradient(135deg, #8b4dff, #4a2cff);
box-shadow: 0 0 20rpx rgba(158, 91, 255, 0.6);
@@ -308,7 +336,7 @@ const editProfile = () => {
border-radius: 6rpx;
background: #fff;
transform: rotate(-45deg);
margin: 21rpx auto;
margin: 17rpx auto;
}
.profile-info {
@@ -324,33 +352,33 @@ const editProfile = () => {
.profile-name {
color: #fff;
font-size: 44rpx;
font-size: 36rpx;
line-height: 1.1;
font-weight: 800;
}
.star {
color: #ffd589;
font-size: 30rpx;
font-size: 26rpx;
}
.profile-subtitle {
display: block;
margin-top: 14rpx;
margin-top: 10rpx;
color: rgba(239, 232, 255, 0.78);
font-size: 27rpx;
font-size: 24rpx;
}
.edit-profile {
flex-shrink: 0;
height: 64rpx;
padding: 0 22rpx;
height: 56rpx;
padding: 0 18rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
gap: 10rpx;
gap: 8rpx;
color: #dccbff;
font-size: 24rpx;
font-size: 22rpx;
}
.tiny-pen {
@@ -363,12 +391,12 @@ const editProfile = () => {
position: relative;
z-index: 1;
height: 1rpx;
margin: 32rpx 0 24rpx;
margin: 28rpx 0 20rpx;
background: rgba(180, 139, 255, 0.22);
}
.profile-divider.small {
margin: 24rpx 0;
margin: 20rpx 0;
}
.meta-grid {
@@ -380,23 +408,28 @@ const editProfile = () => {
min-width: 0;
display: flex;
align-items: center;
gap: 14rpx;
padding-right: 18rpx;
gap: 10rpx;
padding-right: 12rpx;
border-right: 1rpx solid rgba(180, 139, 255, 0.22);
}
.meta-item + .meta-item {
padding-left: 22rpx;
padding-left: 16rpx;
}
.meta-item.no-border {
border-right: 0;
}
.meta-item > view,
.hobby-row > view {
min-width: 0;
}
.meta-icon,
.heart {
color: #a855ff;
font-size: 42rpx;
font-size: 34rpx;
line-height: 1;
text-shadow: 0 0 24rpx rgba(168, 85, 255, 0.7);
}
@@ -404,35 +437,35 @@ const editProfile = () => {
.person-icon,
.job-icon {
position: relative;
width: 38rpx;
height: 38rpx;
width: 32rpx;
height: 32rpx;
flex-shrink: 0;
}
.person-icon::before {
content: '';
position: absolute;
left: 10rpx;
left: 8rpx;
top: 0;
width: 16rpx;
height: 16rpx;
border: 4rpx solid #a855ff;
width: 14rpx;
height: 14rpx;
border: 3rpx solid #a855ff;
border-radius: 50%;
}
.person-icon::after {
content: '';
position: absolute;
left: 2rpx;
left: 1rpx;
bottom: 0;
width: 30rpx;
height: 18rpx;
border: 4rpx solid #a855ff;
width: 28rpx;
height: 16rpx;
border: 3rpx solid #a855ff;
border-radius: 18rpx 18rpx 4rpx 4rpx;
}
.job-icon {
border: 5rpx solid #a855ff;
border: 4rpx solid #a855ff;
border-radius: 8rpx;
box-sizing: border-box;
}
@@ -440,11 +473,11 @@ const editProfile = () => {
.job-icon::before {
content: '';
position: absolute;
left: 10rpx;
top: -10rpx;
left: 8rpx;
top: -8rpx;
width: 12rpx;
height: 8rpx;
border: 4rpx solid #a855ff;
height: 7rpx;
border: 3rpx solid #a855ff;
border-bottom: 0;
border-radius: 8rpx 8rpx 0 0;
}
@@ -457,14 +490,14 @@ const editProfile = () => {
.meta-label {
color: rgba(219, 204, 247, 0.54);
font-size: 22rpx;
font-size: 20rpx;
}
.meta-value,
.hobby-text {
margin-top: 4rpx;
margin-top: 3rpx;
color: #fff;
font-size: 27rpx;
font-size: 23rpx;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
@@ -474,7 +507,7 @@ const editProfile = () => {
.hobby-row {
display: flex;
align-items: center;
gap: 28rpx;
gap: 22rpx;
}
.section-head {
@@ -486,12 +519,12 @@ const editProfile = () => {
.title-line {
display: flex;
align-items: center;
gap: 14rpx;
gap: 12rpx;
}
.section-title {
color: #fff;
font-size: 44rpx;
font-size: 40rpx;
line-height: 1.1;
font-weight: 800;
}
@@ -502,26 +535,26 @@ const editProfile = () => {
.section-subtitle {
display: block;
margin-top: 14rpx;
margin-top: 10rpx;
color: rgba(210, 194, 242, 0.68);
font-size: 26rpx;
}
.map-btn {
height: 64rpx;
padding: 0 24rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
gap: 12rpx;
color: #caa9ff;
font-size: 24rpx;
}
.map-btn {
height: 56rpx;
padding: 0 20rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
gap: 10rpx;
color: #caa9ff;
font-size: 22rpx;
}
.map-icon {
width: 28rpx;
height: 22rpx;
border: 4rpx solid currentColor;
width: 24rpx;
height: 20rpx;
border: 3rpx solid currentColor;
border-radius: 4rpx;
transform: skewY(-12deg);
}
@@ -533,21 +566,21 @@ const editProfile = () => {
.filter-row {
display: inline-flex;
gap: 18rpx;
gap: 16rpx;
align-items: center;
}
.filter-chip,
.add-filter {
height: 62rpx;
min-width: 104rpx;
padding: 0 30rpx;
height: 54rpx;
min-width: 92rpx;
padding: 0 24rpx;
border-radius: 999rpx;
display: inline-flex;
align-items: center;
justify-content: center;
color: rgba(224, 214, 243, 0.72);
font-size: 25rpx;
font-size: 22rpx;
font-weight: 600;
}
@@ -559,11 +592,11 @@ const editProfile = () => {
}
.add-filter {
min-width: 62rpx;
min-width: 56rpx;
padding: 0;
border: 2rpx dashed rgba(155, 112, 255, 0.45);
color: #c49cff;
font-size: 36rpx;
font-size: 32rpx;
}
.timeline {
@@ -573,8 +606,8 @@ const editProfile = () => {
.timeline-item {
display: grid;
grid-template-columns: 88rpx 1fr;
min-height: 188rpx;
grid-template-columns: 76rpx 1fr;
min-height: 170rpx;
}
.rail {
@@ -586,19 +619,19 @@ const editProfile = () => {
.node {
position: relative;
z-index: 2;
width: 52rpx;
height: 52rpx;
width: 46rpx;
height: 46rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 4rpx solid currentColor;
box-shadow: 0 0 28rpx currentColor;
box-shadow: 0 0 24rpx currentColor;
}
.node view {
width: 24rpx;
height: 24rpx;
width: 20rpx;
height: 20rpx;
border-radius: 50%;
background: currentColor;
}
@@ -610,7 +643,7 @@ const editProfile = () => {
.line {
position: absolute;
top: 56rpx;
top: 50rpx;
bottom: -6rpx;
width: 4rpx;
background: linear-gradient(180deg, currentColor, rgba(255,255,255,0.14));
@@ -618,14 +651,14 @@ const editProfile = () => {
}
.event-card {
min-height: 160rpx;
margin-bottom: 22rpx;
min-height: 146rpx;
margin-bottom: 18rpx;
border-radius: 28rpx;
display: grid;
grid-template-columns: 116rpx 1rpx 1fr 108rpx 24rpx;
grid-template-columns: 96rpx 1rpx 1fr 88rpx 20rpx;
align-items: center;
gap: 22rpx;
padding: 22rpx;
gap: 18rpx;
padding: 20rpx;
}
.date-box {
@@ -635,16 +668,16 @@ const editProfile = () => {
.date-month,
.date-age {
display: block;
font-size: 25rpx;
font-size: 23rpx;
}
.date-age {
margin-top: 8rpx;
margin-top: 6rpx;
}
.event-divider {
width: 1rpx;
height: 92rpx;
height: 82rpx;
background: rgba(180, 139, 255, 0.18);
}
@@ -655,17 +688,17 @@ const editProfile = () => {
.event-title {
display: block;
color: #fff;
font-size: 27rpx;
font-size: 24rpx;
line-height: 1.25;
font-weight: 800;
}
.event-tag {
display: inline-flex;
margin-top: 14rpx;
padding: 6rpx 14rpx;
margin-top: 10rpx;
padding: 5rpx 12rpx;
border-radius: 999rpx;
font-size: 20rpx;
font-size: 19rpx;
}
.tag-0 { color: #8effc7; background: rgba(50, 196, 128, 0.18); }
@@ -675,9 +708,9 @@ const editProfile = () => {
.event-summary {
display: -webkit-box;
margin-top: 12rpx;
margin-top: 9rpx;
color: rgba(226, 215, 246, 0.66);
font-size: 24rpx;
font-size: 22rpx;
line-height: 1.45;
overflow: hidden;
-webkit-line-clamp: 2;
@@ -685,14 +718,14 @@ const editProfile = () => {
}
.thumb {
width: 96rpx;
height: 96rpx;
border-radius: 22rpx;
width: 78rpx;
height: 78rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 44rpx;
font-size: 36rpx;
font-weight: 900;
overflow: hidden;
}
@@ -704,7 +737,7 @@ const editProfile = () => {
.chevron {
color: rgba(223, 213, 245, 0.68);
font-size: 54rpx;
font-size: 46rpx;
}
.empty-card {
@@ -733,20 +766,20 @@ const editProfile = () => {
display: flex;
flex-direction: column;
align-items: center;
gap: 10rpx;
gap: 8rpx;
color: #caa6ff;
font-size: 24rpx;
font-size: 22rpx;
}
.plus-core {
width: 92rpx;
height: 92rpx;
width: 78rpx;
height: 78rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 56rpx;
font-size: 46rpx;
background: linear-gradient(135deg, #b348ff, #582cff);
box-shadow: 0 0 38rpx rgba(171, 72, 255, 0.62);
}
+335 -103
View File
@@ -16,7 +16,10 @@
<view class="mode-tabs kos-card">
<view class="mode-tab" :class="{ active: mode === 'inspiration' }" @click="mode = 'inspiration'">
<text class="tab-icon"></text>
<view class="tab-icon sparkles-mini">
<view class="spark main"></view>
<view class="spark dot"></view>
</view>
<view>
<text class="tab-title">灵感模式</text>
<text class="tab-sub">一句话生成爽文</text>
@@ -34,37 +37,37 @@
<view v-if="mode === 'inspiration'" class="prompt-panel kos-card">
<view class="panel-head">
<view class="panel-title-row">
<text class="spark"></text>
<view class="panel-sparkles">
<view class="panel-spark large"></view>
<view class="panel-spark small"></view>
</view>
<text class="panel-title">写下你想要的故事设定或人生想法</text>
</view>
<view class="help-chip" @click="shuffleInspirations">不知道写什么?</view>
<view class="help-chip" @click="shuffleInspirations">
<view class="bulb-icon"></view>
<text>不知道写什么?</text>
</view>
</view>
<textarea
class="prompt-box"
maxlength="500"
v-model="prompt"
placeholder="例如:&#10;“如果我没有分手,现在会怎样?”&#10;“我成为顶级作曲家的人生”&#10;“重生回18岁改变一切”&#10;“从小镇做题家到世界首富”"
:placeholder="promptPlaceholder"
placeholder-class="placeholder"
/>
<view class="prompt-tools">
<view class="tool-pill kos-pill">语音输入</view>
<text class="counter">{{ prompt.length }}/500</text>
<view class="tool-pill kos-pill" @click="shuffleInspirations">随机灵感</view>
</view>
<scroll-view class="style-scroll" scroll-x :show-scrollbar="false">
<view class="style-row">
<text
v-for="item in styleOptions"
:key="item.value"
class="style-chip kos-pill"
:class="{ active: style === item.value }"
@click="style = item.value"
>{{ item.label }}</text>
<view class="tool-pill kos-pill" @click="handleVoiceInput">
<view class="mic-icon"></view>
<text>语音输入</text>
</view>
</scroll-view>
<text class="counter">{{ prompt.length }}/500</text>
<view class="tool-pill kos-pill" @click="shuffleInspirations">
<view class="refresh-icon"></view>
<text>随机灵感</text>
</view>
</view>
<button class="generate-btn kos-primary" :disabled="generating || !prompt.trim()" :loading="generating" @click="generateByPrompt">
<view>
@@ -72,7 +75,11 @@
<text class="generate-sub">今日剩余生成次数{{ remainingCount }}</text>
</view>
<view class="planet-badge">
<view></view>
<view class="planet-face">
<view class="planet-eye left"></view>
<view class="planet-eye right"></view>
<view class="planet-mouth"></view>
</view>
</view>
</button>
</view>
@@ -171,6 +178,13 @@ const generating = ref(false)
const remainingCount = ref(3)
const style = ref('career')
const randomRecommendations = ref([])
const promptPlaceholder = [
'例如:',
'“如果我没有分手,现在会怎样?”',
'“我成为顶级作曲家的人生”',
'“重生回18岁改变一切”',
'“从小镇做题家到世界首富”'
].join('\n')
const custom = reactive({
theme: '',
style: 'career',
@@ -249,6 +263,7 @@ const generateByPrompt = async () => {
}
prompt.value = ''
if (typeof res.data?.remainingCount === 'number') remainingCount.value = res.data.remainingCount
mode.value = 'list'
uni.showToast({ title: '剧本已生成', icon: 'success' })
}
@@ -286,14 +301,28 @@ const selectScript = async (id) => {
}
uni.navigateTo({ url: '/pages/main/PathView' })
}
const handleVoiceInput = () => {
uni.showModal({
title: '语音输入',
content: '语音识别入口已保留。后续接入微信录音和AI语音转文字后,会把识别结果自动填入灵感输入框。',
confirmText: '填入示例',
cancelText: '知道了',
success: (res) => {
if (res.confirm && !prompt.value.trim()) {
prompt.value = '如果我能重新选择一次,我想把人生过成更勇敢的版本'
}
}
})
}
</script>
<style scoped>
.script-view {
display: flex;
flex-direction: column;
gap: 28rpx;
padding-bottom: 32rpx;
gap: 24rpx;
padding-bottom: 24rpx;
}
.page-head,
@@ -313,7 +342,7 @@ const selectScript = async (id) => {
.title {
color: #fff;
font-size: 46rpx;
font-size: 40rpx;
line-height: 1.08;
font-weight: 900;
}
@@ -326,23 +355,24 @@ const selectScript = async (id) => {
.subtitle {
display: block;
margin-top: 16rpx;
margin-top: 14rpx;
color: rgba(219, 203, 247, 0.74);
font-size: 27rpx;
font-size: 24rpx;
}
.script-list-btn,
.help-chip,
.tool-pill,
.rewrite-btn {
height: 60rpx;
padding: 0 22rpx;
height: 56rpx;
padding: 0 20rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
justify-content: center;
color: #caa0ff;
font-size: 24rpx;
font-size: 22rpx;
gap: 9rpx;
}
.list-icon {
@@ -354,7 +384,7 @@ const selectScript = async (id) => {
}
.mode-tabs {
height: 94rpx;
height: 88rpx;
border-radius: 28rpx;
padding: 6rpx;
display: grid;
@@ -366,27 +396,209 @@ const selectScript = async (id) => {
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
gap: 14rpx;
color: rgba(216, 207, 238, 0.62);
}
.mode-tab.active {
color: #fff;
border: 1rpx solid rgba(191, 91, 255, 0.85);
background: rgba(115, 45, 255, 0.2);
box-shadow: 0 0 30rpx rgba(167, 60, 255, 0.5);
background:
radial-gradient(circle at 18% 12%, rgba(208, 118, 255, 0.22), transparent 42%),
rgba(115, 45, 255, 0.22);
box-shadow: 0 0 30rpx rgba(167, 60, 255, 0.48), inset 0 0 22rpx rgba(190, 92, 255, 0.18);
}
.tab-icon {
position: relative;
width: 34rpx;
height: 34rpx;
color: #d875ff;
font-size: 34rpx;
flex-shrink: 0;
}
.gear-icon {
position: relative;
width: 30rpx;
height: 30rpx;
border: 3rpx solid currentColor;
border-radius: 50%;
box-sizing: border-box;
color: currentColor;
}
.gear-icon::before {
content: '';
position: absolute;
left: 8rpx;
top: 8rpx;
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: currentColor;
}
.spark,
.panel-spark,
.book-sparkle {
position: absolute;
transform: rotate(45deg);
}
.spark::before,
.spark::after,
.panel-spark::before,
.panel-spark::after {
content: '';
position: absolute;
left: 50%;
top: 50%;
border-radius: 999rpx;
background: currentColor;
transform: translate(-50%, -50%);
}
.spark::before,
.panel-spark::before {
width: 4rpx;
height: 100%;
}
.spark::after,
.panel-spark::after {
width: 100%;
height: 4rpx;
}
.spark.main {
left: 8rpx;
top: 7rpx;
width: 19rpx;
height: 19rpx;
}
.spark.dot {
right: 3rpx;
top: 2rpx;
width: 10rpx;
height: 10rpx;
opacity: 0.8;
}
.panel-sparkles {
position: relative;
width: 38rpx;
height: 38rpx;
flex-shrink: 0;
color: #cf78ff;
}
.panel-spark.large {
left: 9rpx;
top: 8rpx;
width: 24rpx;
height: 24rpx;
}
.panel-spark.small {
left: 1rpx;
bottom: 3rpx;
width: 12rpx;
height: 12rpx;
opacity: 0.78;
}
.bulb-icon {
position: relative;
width: 26rpx;
height: 30rpx;
flex-shrink: 0;
}
.bulb-icon::before {
content: '';
position: absolute;
left: 5rpx;
top: 2rpx;
width: 16rpx;
height: 18rpx;
border: 3rpx solid currentColor;
border-radius: 50% 50% 45% 45%;
box-sizing: border-box;
}
.bulb-icon::after {
content: '';
position: absolute;
left: 9rpx;
bottom: 2rpx;
width: 10rpx;
height: 8rpx;
border-top: 3rpx solid currentColor;
border-bottom: 3rpx solid currentColor;
box-sizing: border-box;
}
.mic-icon {
position: relative;
width: 24rpx;
height: 30rpx;
flex-shrink: 0;
}
.mic-icon::before {
content: '';
position: absolute;
left: 7rpx;
top: 1rpx;
width: 10rpx;
height: 19rpx;
border: 3rpx solid currentColor;
border-radius: 999rpx;
box-sizing: border-box;
}
.mic-icon::after {
content: '';
position: absolute;
left: 4rpx;
bottom: 1rpx;
width: 16rpx;
height: 11rpx;
border: 3rpx solid currentColor;
border-top: 0;
border-radius: 0 0 999rpx 999rpx;
box-sizing: border-box;
}
.refresh-icon {
position: relative;
width: 28rpx;
height: 28rpx;
border: 4rpx solid currentColor;
flex-shrink: 0;
}
.refresh-icon::before {
content: '';
position: absolute;
inset: 5rpx;
border: 3rpx solid currentColor;
border-left-color: transparent;
border-radius: 50%;
transform: rotate(-28deg);
}
.refresh-icon::after {
content: '';
position: absolute;
right: 3rpx;
top: 3rpx;
width: 0;
height: 0;
border-left: 8rpx solid currentColor;
border-top: 5rpx solid transparent;
border-bottom: 5rpx solid transparent;
transform: rotate(25deg);
}
.tab-title,
@@ -395,45 +607,51 @@ const selectScript = async (id) => {
}
.tab-title {
font-size: 27rpx;
font-size: 24rpx;
font-weight: 800;
}
.tab-sub {
margin-top: 4rpx;
font-size: 20rpx;
font-size: 19rpx;
color: rgba(224, 214, 243, 0.52);
}
.prompt-panel,
.custom-panel {
border-radius: 32rpx;
padding: 30rpx;
padding: 28rpx;
border-color: rgba(173, 84, 255, 0.36);
background:
radial-gradient(circle at 16% 0%, rgba(143, 64, 255, 0.18), transparent 42%),
rgba(12, 12, 42, 0.62);
box-shadow: inset 0 0 34rpx rgba(132, 56, 255, 0.14), 0 0 26rpx rgba(140, 55, 255, 0.12);
}
.panel-title-row {
display: flex;
align-items: center;
gap: 14rpx;
gap: 12rpx;
min-width: 0;
}
.panel-title {
color: #c783ff;
font-size: 29rpx;
font-size: 26rpx;
font-weight: 900;
}
.prompt-box {
width: 100%;
height: 216rpx;
height: 214rpx;
box-sizing: border-box;
margin-top: 28rpx;
padding: 30rpx;
margin-top: 26rpx;
padding: 26rpx 28rpx;
border-radius: 24rpx;
border: 1rpx solid rgba(151, 111, 255, 0.26);
background: rgba(13, 15, 43, 0.72);
background: rgba(12, 14, 42, 0.86);
color: #f7f1ff;
font-size: 27rpx;
font-size: 24rpx;
line-height: 1.55;
}
@@ -442,50 +660,28 @@ const selectScript = async (id) => {
grid-template-columns: 1fr auto 1fr;
gap: 16rpx;
align-items: center;
margin-top: 18rpx;
margin-top: 16rpx;
}
.counter {
color: rgba(222, 211, 240, 0.62);
font-size: 23rpx;
}
.style-scroll {
width: 100%;
margin-top: 20rpx;
white-space: nowrap;
}
.style-row {
display: inline-flex;
gap: 14rpx;
}
.style-chip {
display: inline-flex;
height: 52rpx;
padding: 0 20rpx;
border-radius: 999rpx;
align-items: center;
color: rgba(224, 214, 243, 0.66);
font-size: 22rpx;
}
.style-chip.active {
color: #fff;
background: rgba(152, 62, 255, 0.34);
border-color: rgba(191, 91, 255, 0.82);
}
.generate-btn {
width: 100%;
height: 104rpx;
margin-top: 28rpx;
margin-top: 24rpx;
border-radius: 28rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 28rpx;
overflow: visible;
background:
radial-gradient(circle at 92% 44%, rgba(244, 187, 255, 0.42), transparent 22%),
linear-gradient(135deg, #b64cff 0%, #762fff 48%, #4a20ff 100%);
box-shadow: 0 0 34rpx rgba(168, 85, 247, 0.58), inset 0 1rpx 0 rgba(255, 255, 255, 0.25);
}
.generate-title,
@@ -495,38 +691,72 @@ const selectScript = async (id) => {
}
.generate-title {
font-size: 30rpx;
font-size: 28rpx;
font-weight: 900;
}
.generate-sub {
margin-top: 8rpx;
font-size: 22rpx;
font-size: 21rpx;
color: rgba(255, 255, 255, 0.74);
}
.planet-badge {
position: relative;
width: 68rpx;
height: 68rpx;
width: 78rpx;
height: 78rpx;
border-radius: 50%;
background: radial-gradient(circle, #e8b7ff, #8b37ff 62%, #4c1d95);
box-shadow: 0 0 24rpx rgba(219, 143, 255, 0.7);
background: radial-gradient(circle at 36% 28%, #f0c7ff, #a64cff 62%, #5a22d6);
box-shadow: 0 0 30rpx rgba(219, 143, 255, 0.82);
flex-shrink: 0;
}
.planet-badge::after {
content: '';
position: absolute;
left: -12rpx;
top: 27rpx;
width: 90rpx;
height: 20rpx;
left: -20rpx;
top: 34rpx;
width: 124rpx;
height: 24rpx;
border: 4rpx solid rgba(231, 201, 255, 0.72);
border-top-color: transparent;
border-radius: 50%;
transform: rotate(-18deg);
}
.planet-face {
position: absolute;
inset: 0;
z-index: 1;
}
.planet-eye {
position: absolute;
top: 31rpx;
width: 7rpx;
height: 12rpx;
border-radius: 999rpx;
background: #16062f;
}
.planet-eye.left {
left: 27rpx;
}
.planet-eye.right {
right: 27rpx;
}
.planet-mouth {
position: absolute;
left: 34rpx;
bottom: 23rpx;
width: 14rpx;
height: 9rpx;
border-bottom: 3rpx solid #16062f;
border-radius: 0 0 999rpx 999rpx;
}
.field {
margin-top: 24rpx;
}
@@ -577,43 +807,44 @@ const selectScript = async (id) => {
.section-title {
color: #c684ff;
font-size: 31rpx;
font-size: 28rpx;
font-weight: 900;
}
.refresh {
color: #c99fff;
font-size: 24rpx;
font-size: 22rpx;
}
.recommend-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18rpx;
gap: 16rpx;
margin-top: 18rpx;
}
.recommend-card {
min-height: 118rpx;
min-height: 110rpx;
border-radius: 22rpx;
padding: 24rpx;
padding: 22rpx;
background: rgba(12, 12, 42, 0.54);
}
.recommend-text {
display: block;
color: rgba(248, 244, 255, 0.9);
font-size: 25rpx;
font-size: 22rpx;
line-height: 1.5;
}
.recommend-tag {
display: inline-flex;
margin-top: 18rpx;
padding: 8rpx 16rpx;
margin-top: 16rpx;
padding: 7rpx 14rpx;
border-radius: 999rpx;
color: #d985ff;
background: rgba(151, 66, 255, 0.18);
font-size: 21rpx;
font-size: 20rpx;
}
.recent-section,
@@ -625,8 +856,9 @@ const selectScript = async (id) => {
.script-card {
border-radius: 24rpx;
padding: 22rpx;
gap: 20rpx;
padding: 20rpx;
gap: 18rpx;
background: rgba(12, 12, 42, 0.6);
}
.script-card.selected {
@@ -634,15 +866,15 @@ const selectScript = async (id) => {
}
.script-cover {
width: 92rpx;
height: 92rpx;
width: 86rpx;
height: 86rpx;
flex-shrink: 0;
border-radius: 18rpx;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 42rpx;
font-size: 38rpx;
font-weight: 900;
background: linear-gradient(135deg, #7d35ff, #1bb7ff);
}
@@ -660,20 +892,20 @@ const selectScript = async (id) => {
.script-title {
color: #fff;
font-size: 27rpx;
font-size: 24rpx;
font-weight: 900;
}
.script-date {
margin-top: 8rpx;
margin-top: 7rpx;
color: rgba(218, 204, 243, 0.62);
font-size: 22rpx;
font-size: 21rpx;
}
.script-summary {
margin-top: 8rpx;
margin-top: 7rpx;
color: rgba(218, 204, 243, 0.68);
font-size: 23rpx;
font-size: 22rpx;
line-height: 1.45;
overflow: hidden;
text-overflow: ellipsis;
@@ -682,7 +914,7 @@ const selectScript = async (id) => {
.empty-panel {
border-radius: 24rpx;
padding: 28rpx;
padding: 26rpx;
color: rgba(230, 218, 250, 0.66);
}
</style>
+148 -72
View File
@@ -17,24 +17,27 @@
<MusicPlayer ref="musicPlayer" />
<view class="bottom-nav">
<view class="nav-inner" :style="{ paddingBottom: safeAreaBottom + 'px' }">
<view class="nav-inner">
<view class="nav-item" :class="{ active: activeTab === 'record' }" @click="switchTab('record')">
<view class="planet-icon">
<view class="tab-icon planet-ring-icon">
<view class="planet-core"></view>
<view class="planet-ring"></view>
</view>
<text>人生轨迹</text>
</view>
<view class="nav-item" :class="{ active: activeTab === 'script' }" @click="switchTab('script')">
<view class="book-icon">
<view></view>
<view></view>
<view class="tab-icon book-star-icon">
<view class="book-page left"></view>
<view class="book-page right"></view>
<view class="book-sparkle"></view>
</view>
<text>爽文生成</text>
</view>
<view class="nav-item" :class="{ active: activeTab === 'mine' }" @click="switchTab('mine')">
<view class="smile-icon">
<view class="eye left"></view>
<view class="eye right"></view>
<view class="tab-icon smile-face-icon">
<view class="smile-eye left"></view>
<view class="smile-eye right"></view>
<view class="smile-mouth"></view>
</view>
<text>我的</text>
</view>
@@ -156,7 +159,7 @@ onUnmounted(() => {
height: 0;
min-height: 0;
box-sizing: border-box;
padding: 0 28rpx 156rpx;
padding: 0 28rpx 132rpx;
}
.bottom-nav {
@@ -171,16 +174,21 @@ onUnmounted(() => {
}
.nav-inner {
height: 108rpx;
height: 104rpx;
display: grid;
grid-template-columns: repeat(3, 1fr);
align-items: center;
border-radius: 34rpx;
border: 1rpx solid rgba(153, 112, 255, 0.32);
background: rgba(11, 9, 35, 0.84);
box-shadow: inset 0 0 38rpx rgba(129, 65, 255, 0.12), 0 18rpx 70rpx rgba(0, 0, 0, 0.36);
backdrop-filter: blur(26rpx);
-webkit-backdrop-filter: blur(26rpx);
border: 1rpx solid rgba(150, 95, 255, 0.26);
background:
radial-gradient(circle at 18% 14%, rgba(137, 78, 255, 0.18), transparent 36%),
linear-gradient(180deg, rgba(20, 13, 52, 0.9), rgba(12, 7, 34, 0.94));
box-shadow:
inset 0 1rpx 0 rgba(255, 255, 255, 0.08),
inset 0 0 34rpx rgba(130, 72, 255, 0.12),
0 14rpx 42rpx rgba(0, 0, 0, 0.36);
backdrop-filter: blur(28rpx);
-webkit-backdrop-filter: blur(28rpx);
}
.nav-item {
@@ -189,93 +197,161 @@ onUnmounted(() => {
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8rpx;
color: rgba(216, 208, 235, 0.58);
font-size: 23rpx;
font-weight: 600;
transition: transform 0.22s ease, color 0.22s ease;
gap: 5rpx;
color: rgba(174, 165, 199, 0.72);
font-size: 22rpx;
font-weight: 800;
letter-spacing: 0;
transition: transform 0.32s cubic-bezier(0.23, 1, 0.32, 1), color 0.32s ease, text-shadow 0.32s ease;
}
.nav-item.active {
color: #b86cff;
transform: translateY(-4rpx);
text-shadow: 0 0 22rpx rgba(178, 91, 255, 0.8);
color: #a855f7;
transform: translateY(-6rpx);
text-shadow: 0 0 24rpx rgba(168, 85, 247, 0.86);
}
.planet-icon,
.book-icon,
.smile-icon {
.tab-icon {
position: relative;
width: 46rpx;
height: 42rpx;
width: 48rpx;
height: 48rpx;
color: currentColor;
}
.planet-core {
position: absolute;
left: 10rpx;
top: 8rpx;
width: 26rpx;
height: 26rpx;
top: 11rpx;
width: 28rpx;
height: 28rpx;
border-radius: 50%;
background: currentColor;
box-shadow: 0 0 24rpx currentColor;
box-shadow: 0 0 26rpx rgba(168, 85, 247, 0.74);
}
.planet-icon::before {
.planet-core::after {
content: '';
position: absolute;
left: 2rpx;
top: 14rpx;
width: 42rpx;
height: 14rpx;
left: 9rpx;
top: 7rpx;
width: 8rpx;
height: 8rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.58);
}
.planet-ring {
position: absolute;
left: 1rpx;
top: 16rpx;
width: 48rpx;
height: 16rpx;
border: 4rpx solid currentColor;
border-top-color: transparent;
border-radius: 50%;
transform: rotate(-22deg);
}
.book-icon {
display: flex;
gap: 4rpx;
justify-content: center;
align-items: center;
}
.book-icon view {
width: 17rpx;
height: 34rpx;
border-radius: 6rpx 3rpx 3rpx 6rpx;
border: 4rpx solid currentColor;
box-shadow: 0 0 18rpx currentColor;
}
.smile-icon {
border-radius: 50%;
border: 5rpx solid currentColor;
box-sizing: border-box;
transform: rotate(-22deg);
opacity: 0.96;
}
.eye {
.book-page {
position: absolute;
top: 12rpx;
width: 6rpx;
height: 6rpx;
border-radius: 50%;
background: currentColor;
top: 13rpx;
width: 20rpx;
height: 26rpx;
border: 4rpx solid currentColor;
box-sizing: border-box;
opacity: 0.95;
}
.eye.left { left: 12rpx; }
.eye.right { right: 12rpx; }
.book-page.left {
left: 5rpx;
border-radius: 8rpx 3rpx 3rpx 8rpx;
transform: skewY(5deg);
}
.smile-icon::after {
.book-page.right {
right: 5rpx;
border-radius: 3rpx 8rpx 8rpx 3rpx;
transform: skewY(-5deg);
}
.book-star-icon::after {
content: '';
position: absolute;
left: 12rpx;
right: 12rpx;
bottom: 10rpx;
left: 23rpx;
top: 15rpx;
width: 3rpx;
height: 23rpx;
border-radius: 999rpx;
background: currentColor;
opacity: 0.55;
}
.book-sparkle {
position: absolute;
right: 4rpx;
top: 4rpx;
width: 14rpx;
height: 14rpx;
transform: rotate(45deg);
}
.book-sparkle::before,
.book-sparkle::after {
content: '';
position: absolute;
left: 50%;
top: 50%;
border-radius: 999rpx;
background: currentColor;
transform: translate(-50%, -50%);
}
.book-sparkle::before {
width: 4rpx;
height: 100%;
}
.book-sparkle::after {
width: 100%;
height: 4rpx;
}
.smile-face-icon {
border-radius: 50%;
background: currentColor;
box-shadow: 0 0 18rpx rgba(174, 165, 199, 0.22);
}
.nav-item.active .smile-face-icon {
box-shadow: 0 0 24rpx rgba(168, 85, 247, 0.62);
}
.smile-eye {
position: absolute;
top: 16rpx;
width: 7rpx;
height: 7rpx;
border-radius: 50%;
background: #100719;
}
.smile-eye.left {
left: 13rpx;
}
.smile-eye.right {
right: 13rpx;
}
.smile-mouth {
position: absolute;
left: 16rpx;
right: 16rpx;
bottom: 14rpx;
height: 8rpx;
border-bottom: 4rpx solid currentColor;
border-radius: 0 0 20rpx 20rpx;
border-bottom: 4rpx solid #100719;
border-radius: 0 0 18rpx 18rpx;
}
</style>
+568 -206
View File
@@ -5,92 +5,133 @@
<view class="topbar">
<text class="back" @click="goBack"></text>
<text class="title">{{ isEdit ? '编辑资料' : '初始化档案' }} </text>
<text class="title">编辑资料 <text class="gold"></text></text>
<text class="save" @click="saveProfile">保存</text>
</view>
<scroll-view class="scroll" scroll-y :show-scrollbar="false">
<view class="hero-card kos-card">
<view class="avatar">{{ avatarText }}</view>
<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">
<text class="hero-name">{{ form.nickname || 'Zoey' }} </text>
<view class="hero-name-row">
<text class="hero-name">{{ form.nickname || 'Zoey' }}</text>
<text class="gold hero-star"></text>
</view>
<text class="hero-sub">正在成为更清晰的自己</text>
<text class="quote">生活是自己的选择也是</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 kos-card">
<view class="panel-title">基础信息</view>
<view class="line-field">
<text class="field-label">昵称</text>
<input class="line-input" v-model="form.nickname" placeholder="输入你的昵称" placeholder-class="placeholder" />
<view class="panel glass-card">
<view class="section-head">
<view class="section-icon basic-icon"></view>
<text class="section-title">基础信息</text>
</view>
<view class="line-field">
<text class="field-label">性别</text>
<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="line-field">
<text class="field-label">生日</text>
<picker mode="date" :value="birthday" @change="onBirthday">
<view class="line-value">{{ birthday || '选择生日' }} </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="line-field">
<text class="field-label">所在城市</text>
<input class="line-input" v-model="form.city" placeholder="填写城市" placeholder-class="placeholder" />
<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>
<view class="dual-panel kos-card">
<view class="select-block">
<view class="block-title">星座</view>
<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"
>
<text class="zodiac-symbol">{{ item.symbol }}</text>
<text>{{ item.name }}</text>
<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 class="select-block mbti-block">
<view class="block-title">MBTI</view>
<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 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 kos-card">
<view class="panel-title">职业信息</view>
<view class="line-field">
<text class="field-label">职业</text>
<input class="line-input" v-model="form.profession" placeholder="产品经理" placeholder-class="placeholder" />
<view class="panel glass-card">
<view class="section-head">
<view class="section-icon smile-title-icon"></view>
<text class="section-title">性格标签</text>
<text class="section-hint">最多选择5个</text>
</view>
<view class="line-field">
<text class="field-label">行业</text>
<input class="line-input" v-model="form.industry" placeholder="互联网" placeholder-class="placeholder" />
</view>
<view class="line-field">
<text class="field-label">公司可选</text>
<input class="line-input" v-model="form.company" placeholder="填写公司名称" placeholder-class="placeholder" />
</view>
</view>
<view class="panel kos-card">
<view class="panel-title">性格标签最多选择5个</view>
<view class="tag-grid">
<text
v-for="tag in personalityTags"
@@ -103,10 +144,14 @@
</view>
</view>
<view class="panel kos-card">
<view class="panel-head">
<text class="panel-title">兴趣爱好最多选择5个</text>
<text class="custom"> 自定义兴趣</text>
<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="custom" @click="addCustomHobby"> 自定义兴趣</text>
</view>
<view class="tag-grid">
<text
@@ -119,18 +164,21 @@
</view>
</view>
<view class="panel kos-card">
<view class="panel-title">个人简介</view>
<textarea class="bio" v-model="form.future.ideal" maxlength="200" placeholder="热爱阅读和旅行,喜欢用文字和镜头记录生活。相信真诚和努力能让世界变得更美好。" placeholder-class="placeholder" />
<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 class="bottom-bar">
<button class="submit kos-primary" :loading="saving" :disabled="saving || !form.nickname.trim()" @click="saveProfile">
{{ saving ? '正在保存' : '保存生命档案' }}
</button>
</view>
</view>
</template>
@@ -143,12 +191,23 @@ const statusBarHeight = ref(20)
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: '♓' }
{ 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 = ['理性', '感性', '乐观', '独立', '有创造力', '坚韧', '细腻', '好奇', '内敛', '冒险', '自由']
@@ -171,27 +230,37 @@ const form = reactive({
future: { vision: '', ideal: '' }
})
const avatarText = computed(() => (form.nickname || 'Z').slice(0, 1))
const avatarUrl = computed(() => {
if (avatarLocal.value) return avatarLocal.value
const nickname = form.nickname || 'Zoey'
return `https://api.dicebear.com/7.x/avataaars/svg?seed=${encodeURIComponent(nickname)}&backgroundColor=b982ff`
})
const birthdayDisplay = computed(() => {
if (!birthday.value) return '1998年06月18日'
const [year, month, day] = birthday.value.split('-')
return `${year}${month}${day}`
})
const syncFromStore = () => {
const source = store.userProfile || store.registrationData || {}
Object.assign(form, {
nickname: source.nickname || '',
gender: source.gender || '',
zodiac: source.zodiac || '',
mbti: source.mbti || '',
profession: source.profession || '',
city: source.city || '',
industry: source.industry || '',
gender: source.gender || '',
zodiac: source.zodiac || '巨蟹座',
mbti: source.mbti || 'ENTJ',
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] : [],
personalityTags: Array.isArray(source.personalityTags) && source.personalityTags.length ? [...source.personalityTags] : ['理性', '乐观', '独立', '有创造力', '坚韧'],
hobbies: Array.isArray(source.hobbies) && source.hobbies.length ? [...source.hobbies] : ['阅读', '旅行', '音乐'],
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 || '' }
future: { vision: source.future?.vision || '', ideal: source.future?.ideal || '热爱阅读和旅行,喜欢用文字和镜头记录生活。\n相信真诚和努力能让世界变得更美好。' }
})
birthday.value = source.birthday || ''
birthday.value = source.birthday || '1998-06-18'
}
const onBirthday = (event) => {
@@ -211,6 +280,32 @@ const toggleList = (list, tag, max) => {
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 addCustomHobby = () => {
uni.showModal({
title: '自定义兴趣',
editable: true,
placeholderText: '输入兴趣名称',
success: (res) => {
const value = String(res.content || '').trim()
if (!res.confirm || !value) return
if (!hobbyOptions.includes(value)) hobbyOptions.push(value)
toggleList(form.hobbies, value, 5)
}
})
}
const saveProfile = async () => {
if (!form.nickname.trim() || saving.value) return
saving.value = true
@@ -259,48 +354,52 @@ onMounted(() => {
position: absolute;
inset: 0;
background:
radial-gradient(circle at 86% 10%, rgba(124, 58, 237, 0.3), transparent 30%),
radial-gradient(circle at 10% 32%, rgba(48, 112, 255, 0.16), transparent 28%),
linear-gradient(180deg, #07091d 0%, #07031a 52%, #04020e 100%);
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,
.bottom-bar {
.scroll {
position: relative;
z-index: 1;
}
.status-space,
.topbar,
.bottom-bar {
.topbar {
flex-shrink: 0;
}
.topbar {
height: 92rpx;
display: grid;
grid-template-columns: 80rpx 1fr 80rpx;
grid-template-columns: 90rpx 1fr 90rpx;
align-items: center;
padding: 0 28rpx;
padding: 0 32rpx;
}
.back {
font-size: 66rpx;
color: #fff;
font-size: 68rpx;
line-height: 1;
}
.title {
text-align: center;
color: #fff;
font-size: 36rpx;
font-weight: 900;
}
.save,
.custom {
color: #c06dff;
font-size: 27rpx;
.gold {
color: #ffd58c;
text-shadow: 0 0 20rpx rgba(255, 202, 125, 0.45);
}
.save {
color: #b94cff;
font-size: 28rpx;
text-align: right;
}
@@ -312,177 +411,443 @@ onMounted(() => {
padding: 0 28rpx 28rpx;
}
.hero-card,
.panel,
.dual-panel {
border-radius: 28rpx;
margin-bottom: 22rpx;
padding: 28rpx;
.glass-card {
position: relative;
overflow: hidden;
border: 1rpx solid rgba(124, 75, 255, 0.34);
background:
radial-gradient(circle at 92% 12%, rgba(104, 66, 255, 0.14), transparent 34%),
rgba(10, 13, 43, 0.74);
box-shadow: inset 0 0 34rpx rgba(123, 60, 255, 0.08), 0 14rpx 48rpx rgba(0, 0, 0, 0.22);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
}
.hero-card {
min-height: 190rpx;
border-radius: 24rpx;
margin-bottom: 22rpx;
padding: 22rpx 28rpx;
display: flex;
align-items: center;
gap: 28rpx;
min-height: 150rpx;
}
.avatar {
width: 132rpx;
height: 132rpx;
.avatar-wrap {
position: relative;
width: 136rpx;
height: 136rpx;
flex-shrink: 0;
padding: 5rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 58rpx;
font-weight: 900;
background: linear-gradient(135deg, #b245ff, #2a7dff);
box-shadow: 0 0 38rpx rgba(168, 85, 255, 0.52);
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: 42rpx;
height: 42rpx;
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: 18rpx auto;
border-radius: 999rpx;
background: #fff;
transform: rotate(-45deg);
}
.hero-info {
position: relative;
z-index: 2;
flex: 1;
min-width: 0;
}
.hero-name,
.hero-sub,
.quote,
.panel-title,
.block-title {
display: block;
.hero-name-row {
display: flex;
align-items: center;
gap: 12rpx;
}
.hero-name {
color: #fff;
font-size: 42rpx;
font-weight: 900;
line-height: 1.1;
}
.hero-star {
font-size: 26rpx;
}
.hero-sub {
margin-top: 8rpx;
color: rgba(239, 232, 255, 0.78);
display: block;
margin-top: 12rpx;
color: rgba(239, 232, 255, 0.84);
font-size: 25rpx;
}
.quote {
.hero-line {
width: 280rpx;
height: 1rpx;
margin-top: 14rpx;
color: #c06dff;
font-size: 24rpx;
background: rgba(180, 139, 255, 0.22);
}
.panel-title,
.block-title {
margin-bottom: 20rpx;
color: #eadcff;
font-size: 28rpx;
font-weight: 900;
.hero-quote {
display: block;
margin-top: 14rpx;
color: #b94cff;
font-size: 25rpx;
font-weight: 700;
}
.panel-head {
.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: 24rpx;
margin-bottom: 18rpx;
padding: 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;
}
.line-field {
min-height: 76rpx;
display: grid;
grid-template-columns: 150rpx 1fr;
align-items: center;
border-top: 1rpx solid rgba(180, 139, 255, 0.14);
}
.field-label {
color: rgba(221, 207, 246, 0.72);
.section-title {
color: rgba(239, 232, 255, 0.9);
font-size: 25rpx;
font-weight: 800;
}
.line-input,
.line-value {
color: #fff;
font-size: 26rpx;
.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: 64rpx;
display: grid;
grid-template-columns: 154rpx 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: 24rpx;
}
.row-input,
.row-value {
min-width: 0;
color: rgba(255, 255, 255, 0.9);
font-size: 24rpx;
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: 44rpx;
line-height: 1;
text-align: right;
}
.gender-row {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12rpx;
gap: 16rpx;
}
.choice,
.mbti-chip,
.tag-choice {
height: 52rpx;
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.28);
background: rgba(255, 255, 255, 0.035);
font-size: 23rpx;
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(202, 97, 255, 0.9);
background: rgba(149, 55, 255, 0.38);
box-shadow: 0 0 24rpx rgba(168, 67, 255, 0.38);
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);
}
.dual-panel {
.astro-panel {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24rpx;
margin-top: 8rpx;
border-top: 1rpx solid rgba(180, 139, 255, 0.16);
}
.mbti-block {
border-left: 1rpx solid rgba(180, 139, 255, 0.14);
padding-left: 24rpx;
.astro-col {
min-width: 0;
padding: 18rpx 18rpx 0 0;
}
.mbti-col {
border-left: 1rpx solid rgba(180, 139, 255, 0.18);
padding-left: 18rpx;
padding-right: 0;
}
.astro-title-row {
display: grid;
grid-template-columns: 34rpx 74rpx 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: 24rpx;
}
.astro-current {
color: rgba(255, 255, 255, 0.9);
font-size: 23rpx;
text-align: right;
}
.select-title {
display: block;
margin-top: 22rpx;
color: rgba(222, 211, 240, 0.72);
font-size: 21rpx;
}
.zodiac-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12rpx;
gap: 14rpx 8rpx;
margin-top: 14rpx;
}
.zodiac-item {
min-height: 88rpx;
border-radius: 18rpx;
min-width: 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
color: rgba(226, 217, 246, 0.84);
font-size: 19rpx;
}
.zodiac-bubble {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: rgba(224, 214, 243, 0.76);
}
.zodiac-item.active {
color: #fff;
background: rgba(149, 55, 255, 0.26);
box-shadow: inset 0 0 18rpx rgba(168, 67, 255, 0.28);
}
.zodiac-symbol {
color: #a855ff;
font-size: 32rpx;
font-size: 28rpx;
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(2, 1fr);
grid-template-columns: repeat(4, 1fr);
gap: 12rpx;
margin-top: 14rpx;
}
.mbti-chip {
height: 42rpx;
font-size: 20rpx;
}
.tag-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 14rpx;
grid-template-columns: repeat(5, 1fr);
gap: 14rpx 24rpx;
margin-top: 20rpx;
}
.tag-choice {
height: 40rpx;
font-size: 21rpx;
}
.tag-choice.dashed {
@@ -490,41 +855,38 @@ onMounted(() => {
color: #c06dff;
}
.custom {
color: #c06dff;
font-size: 23rpx;
}
.bio-panel {
margin-bottom: 0;
}
.bio {
width: 100%;
height: 140rpx;
height: 116rpx;
box-sizing: border-box;
padding: 22rpx;
border-radius: 22rpx;
border: 1rpx solid rgba(151, 111, 255, 0.24);
background: rgba(12, 15, 46, 0.72);
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: 25rpx;
line-height: 1.55;
font-size: 23rpx;
line-height: 1.5;
}
.bio-count {
display: block;
margin-top: 10rpx;
margin-top: 8rpx;
text-align: right;
color: rgba(224, 214, 243, 0.56);
font-size: 22rpx;
font-size: 20rpx;
}
.bottom-bar {
height: 116rpx;
box-sizing: border-box;
padding: 14rpx 28rpx 22rpx;
background: rgba(5, 6, 21, 0.72);
backdrop-filter: blur(24rpx);
}
.submit {
width: 100%;
height: 80rpx;
border-radius: 999rpx;
color: #fff;
font-size: 28rpx;
font-weight: 900;
.placeholder {
color: rgba(214, 204, 235, 0.42);
}
</style>