22 KiB
Mini Program Life Event Detail Actions Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 调整小程序人生轨迹详情页,把事件标题卡与事件描述卡合并,移除顶部悬浮操作 icon,并将收藏、分享、分析、编辑、收起和“聊聊”入口按新布局展示。
Architecture: 只修改 mini-program/src/pages/life-event/detail.vue。模板层用新的 event-content-card 替换 floating-nav、hero-card、description-card 组合;脚本层增加 scrollTop、currentScrollTop、onContentScroll 与 scrollToAnalysis 来支持分析按钮定位;样式层复用当前宇宙玻璃风格并新增局部按钮和 Markdown 折叠样式。
Tech Stack: UniApp, Vue 3 <script setup>, CSS scoped, existing mini-program/src/components/Markdown.vue, npm scripts from mini-program/package.json.
File Structure
-
Modify:
mini-program/src/pages/life-event/detail.vue- Template: 合并事件标题区和事件描述区,迁移操作按钮,底部只保留居中的聊天主按钮。
- Script: 删除
useMenuButtonSafeArea使用,新增scrollTop、currentScrollTop、onContentScroll、scrollToAnalysis,保留现有业务方法。 - Style: 删除顶部悬浮导航样式,新增合并卡、正文 Markdown 折叠、内容区按钮和底部聊天按钮样式。
-
Verify only:
mini-program/src/components/Markdown.vue- 不改文件,仅复用
<Markdown :content="eventDescription" />。
- 不改文件,仅复用
Task 1: Template Restructure
Files:
-
Modify:
mini-program/src/pages/life-event/detail.vue -
Step 1: Remove the floating nav block
Delete this block from the template:
<view class="floating-nav" :style="floatingTopStyle">
<view class="circle-btn" @click="goBack">
<view class="back-icon"></view>
</view>
<view class="circle-btn" @click="openMore">
<view class="more-dot"></view>
<view class="more-dot"></view>
<view class="more-dot"></view>
</view>
</view>
Expected result: 页面左上角和右上角不再渲染悬浮 icon。
- Step 2: Add scroll state binding to the page scroll-view
Change the current scroll-view opening tag from:
<scroll-view class="content" scroll-y :show-scrollbar="false">
to:
<scroll-view
class="content"
scroll-y
:show-scrollbar="false"
:scroll-top="scrollTop"
@scroll="onContentScroll"
scroll-with-animation
>
Expected result: scrollToAnalysis can move the scroll-view to the AI 分析 section and account for the current scroll position.
- Step 3: Replace hero-card and description-card with the merged event-content-card
Replace the whole existing hero-card and description-card blocks with:
<view class="event-content-card glass-card">
<view class="event-hero-section">
<view class="hero-timeline">
<view class="timeline-glow"></view>
<view class="timeline-node"></view>
<view class="timeline-line"></view>
</view>
<view class="hero-copy">
<text class="year">{{ eventYear }}</text>
<view class="date-row">
<text class="date-range">{{ dateRange }}</text>
<text class="type-chip">{{ primaryTag }}</text>
</view>
<text class="event-title">{{ displayEvent.title || '决定全职做自己热爱的AI产品' }}</text>
<view class="location-row">
<view class="pin-icon"></view>
<text>{{ locationText }}</text>
</view>
</view>
<view class="memory-cover">
<view class="cover-sky"></view>
<view class="cover-window"></view>
<view class="cover-desk"></view>
<view class="cover-screen main"></view>
<view class="cover-screen small left"></view>
<view class="cover-screen small right"></view>
</view>
<view class="hero-star">★</view>
</view>
<view class="event-body-section">
<view class="event-body-toolbar">
<view class="content-action favorite-action" @click="toggleFavorite">
<view class="bookmark-icon" :class="{ active: isFavorite }"></view>
<text>{{ isFavorite ? '已收藏' : '收藏' }}</text>
</view>
<view class="toolbar-right">
<view class="analysis-pill" @click="scrollToAnalysis">
<text>分析</text>
</view>
<view class="content-action share-action" @click="shareCurrent">
<view class="share-icon">
<view></view>
<view></view>
<view></view>
</view>
<text>分享</text>
</view>
</view>
</view>
<view class="section-title-row description-title-row">
<view class="orbit-icon"></view>
<text>事件描述</text>
</view>
<view class="description-markdown" :class="{ collapsed: isDescriptionCollapsed }">
<Markdown :content="eventDescription" />
</view>
<view class="event-body-footer">
<view class="content-action edit-action" @click="editEvent">
<view class="edit-icon"></view>
<text>编辑</text>
</view>
<text class="collapse-action" @click="isDescriptionCollapsed = !isDescriptionCollapsed">
{{ isDescriptionCollapsed ? '展开' : '收起' }} ^
</text>
</view>
</view>
</view>
Expected result: 事件信息和事件描述在同一张卡内,正文通过 Markdown 组件渲染。
- Step 4: Add the analysis anchor data attribute
Change the current AI analysis opening block from:
<view class="analysis-panel">
to:
<view class="analysis-panel" id="analysisPanel">
Expected result: uni.createSelectorQuery() can locate the AI 分析 block.
- Step 5: Replace bottom action bar with centered chat action
Replace the existing bottom action bar:
<view class="bottom-actions" :style="{ paddingBottom: bottomInset + 'px' }">
<view class="bottom-action" @click="editEvent">
<view class="edit-icon"></view>
<text>编辑</text>
</view>
<view class="bottom-action" @click="toggleFavorite">
<view class="bookmark-icon" :class="{ active: isFavorite }"></view>
<text>{{ isFavorite ? '已收藏' : '收藏' }}</text>
</view>
<view class="chat-action" @click="chatEvent">
<view class="chat-badge">AI</view>
<text>和开开聊聊这段经历</text>
</view>
<view class="bottom-action" @click="shareCurrent">
<view class="share-icon">
<view></view>
<view></view>
<view></view>
</view>
<text>分享</text>
</view>
</view>
with:
<view class="bottom-actions" :style="{ paddingBottom: bottomInset + 'px' }">
<view class="chat-action" @click="chatEvent">
<view class="chat-badge">AI</view>
<text>聊聊</text>
</view>
</view>
Expected result: 底部只保留居中的紫色主按钮,文案为“聊聊”。
Task 2: Script State And Methods
Files:
-
Modify:
mini-program/src/pages/life-event/detail.vue -
Step 1: Remove the unused safe-area composable import
Change imports from:
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useAppStore } from '../../stores/app.js'
import Markdown from '../../components/Markdown.vue'
import { useMenuButtonSafeArea } from '../../composables/useMenuButtonSafeArea.js'
import analytics from '../../services/analytics.js'
to:
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useAppStore } from '../../stores/app.js'
import Markdown from '../../components/Markdown.vue'
import analytics from '../../services/analytics.js'
Expected result: 没有未使用的 useMenuButtonSafeArea import。
- Step 2: Replace floating nav state with scroll state
Change:
const store = useAppStore()
const pagePath = '/pages/life-event/detail'
const { floatingTopStyle } = useMenuButtonSafeArea()
const safeAreaBottom = ref(0)
const eventId = ref('')
const cachedEvent = ref(null)
const isFavorite = ref(false)
const isDescriptionCollapsed = ref(false)
to:
const store = useAppStore()
const pagePath = '/pages/life-event/detail'
const safeAreaBottom = ref(0)
const eventId = ref('')
const cachedEvent = ref(null)
const isFavorite = ref(false)
const isDescriptionCollapsed = ref(false)
const scrollTop = ref(0)
const currentScrollTop = ref(0)
Expected result: scroll-view has a reactive value available for animated positioning.
- Step 3: Add scroll methods before editEvent
Insert these methods before const editEvent = () => {:
const onContentScroll = (event) => {
currentScrollTop.value = event.detail?.scrollTop || 0
}
const scrollToAnalysis = () => {
uni.createSelectorQuery()
.select('#analysisPanel')
.boundingClientRect((rect) => {
if (!rect) return
scrollTop.value = Math.max(0, currentScrollTop.value + rect.top - 24)
})
.exec()
}
Expected result: 点击“分析”后 scroll-view 会向 AI 分析卡移动。
- Step 4: Keep existing business methods unchanged
Do not change the bodies of these existing methods:
const editEvent = () => {
if (!eventId.value) return
uni.navigateTo({ url: `/pages/life-event/form?id=${eventId.value}` })
}
const toggleFavorite = async () => {
if (!eventId.value) return
const next = !isFavorite.value
const result = await store.favoriteEvent({ id: eventId.value, favorite: next })
if (!result.success) {
uni.showToast({ title: result.error || '收藏失败', icon: 'none' })
return
}
isFavorite.value = next
if (next) uni.setStorageSync(`event_favorite_${eventId.value}`, '1')
else uni.removeStorageSync(`event_favorite_${eventId.value}`)
analytics.track('life_event_favorite', {
life_event_id: eventId.value,
favorite: next
}, { eventType: 'life_event', pagePath })
uni.showToast({ title: next ? '已收藏' : '已取消收藏', icon: 'success' })
}
const chatEvent = async () => {
const result = await store.chatAboutEvent({
id: eventId.value,
title: displayEvent.value.title,
content: displayEvent.value.content
})
uni.showModal({
title: '和开开聊聊',
content: result.success ? result.data?.reply || '聊天能力已准备' : result.error || '聊天服务暂不可用',
showCancel: false
})
}
const shareCurrent = async () => {
const result = await store.shareEvent({
id: eventId.value,
title: displayEvent.value.title,
content: displayEvent.value.content
})
if (!result.success) {
uni.showToast({ title: result.error || '分享失败', icon: 'none' })
return
}
analytics.track('life_event_share', {
life_event_id: eventId.value,
tag_count: Array.isArray(displayEvent.value.tags) ? displayEvent.value.tags.length : 0
}, { eventType: 'life_event', pagePath })
uni.setClipboardData({
data: result.data?.shareText || `分享我的人生轨迹:${displayEvent.value.title || ''}`,
success: () => uni.showToast({ title: '分享文案已复制', icon: 'success' })
})
}
Expected result: 编辑、收藏、分享、聊天功能沿用现有行为。
Task 3: Styles For Merged Card And Actions
Files:
-
Modify:
mini-program/src/pages/life-event/detail.vue -
Step 1: Update z-index selector
Change:
.floating-nav,
.content,
.bottom-actions {
position: relative;
z-index: 1;
}
to:
.content,
.bottom-actions {
position: relative;
z-index: 1;
}
Expected result: CSS no longer references removed floating-nav.
- Step 2: Delete removed floating navigation styles
Delete these style blocks:
.floating-nav {
height: 108rpx;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 32rpx;
box-sizing: border-box;
}
.circle-btn {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
gap: 5rpx;
color: #fff;
border: 1rpx solid rgba(118, 82, 225, 0.35);
background: rgba(22, 18, 63, 0.72);
box-shadow: inset 0 0 20rpx rgba(137, 91, 255, 0.1);
}
.back-icon {
width: 23rpx;
height: 23rpx;
border-left: 6rpx solid #fff;
border-bottom: 6rpx solid #fff;
transform: rotate(45deg);
margin-left: 7rpx;
}
.more-dot {
width: 7rpx;
height: 7rpx;
border-radius: 50%;
background: #fff;
}
Expected result: no dead CSS for removed top icons.
- Step 3: Adjust scroll content spacing
Change:
.content {
flex: 1;
height: 0;
min-height: 0;
box-sizing: border-box;
padding: 0 32rpx 134rpx;
}
to:
.content {
flex: 1;
height: 0;
min-height: 0;
box-sizing: border-box;
padding: 36rpx 32rpx 154rpx;
}
Expected result: merged card starts with breathing room after top safe area removal, and bottom content is not covered by the chat button.
- Step 4: Replace hero-card styles with event-content-card and event-hero-section
Replace:
.hero-card {
position: relative;
min-height: 264rpx;
border-radius: 28rpx;
display: grid;
grid-template-columns: 70rpx 1fr 148rpx;
gap: 20rpx;
padding: 38rpx 34rpx 32rpx;
box-sizing: border-box;
}
with:
.event-content-card {
position: relative;
overflow: hidden;
border-radius: 30rpx;
}
.event-hero-section {
position: relative;
min-height: 264rpx;
display: grid;
grid-template-columns: 70rpx 1fr 148rpx;
gap: 20rpx;
padding: 40rpx 34rpx 34rpx;
box-sizing: border-box;
background:
radial-gradient(circle at 86% 12%, rgba(129, 76, 255, 0.22), transparent 36%),
linear-gradient(145deg, rgba(6, 8, 35, 0.98) 0%, rgba(15, 13, 58, 0.96) 58%, rgba(23, 16, 72, 0.92) 100%);
box-shadow: inset 0 -1rpx 0 rgba(164, 116, 255, 0.22), inset 0 0 42rpx rgba(63, 45, 148, 0.16);
}
Expected result: top half of the merged card is visually deeper than the body area.
- Step 5: Replace description-card styles with event body styles
Replace:
.description-card,
.tags-card {
border-radius: 28rpx;
margin-top: 18rpx;
padding: 28rpx 30rpx;
box-sizing: border-box;
}
with:
.event-body-section {
position: relative;
padding: 26rpx 30rpx 24rpx;
box-sizing: border-box;
background:
radial-gradient(circle at 92% 0%, rgba(159, 98, 255, 0.12), transparent 36%),
rgba(13, 16, 52, 0.62);
}
.tags-card {
border-radius: 28rpx;
margin-top: 18rpx;
padding: 28rpx 30rpx;
box-sizing: border-box;
}
Expected result: description body sits inside the merged card and related tags keep their card styling.
- Step 6: Add content action toolbar styles before
.section-title-row
Insert:
.event-body-toolbar,
.event-body-footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 18rpx;
}
.event-body-toolbar {
min-height: 64rpx;
}
.toolbar-right {
display: flex;
align-items: center;
gap: 14rpx;
}
.content-action {
min-height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 10rpx;
color: rgba(238, 231, 255, 0.88);
font-size: 23rpx;
font-weight: 800;
}
.favorite-action,
.share-action,
.edit-action {
padding: 0 4rpx;
}
.analysis-pill {
min-width: 92rpx;
height: 54rpx;
padding: 0 24rpx;
box-sizing: border-box;
border-radius: 0 18rpx 18rpx 18rpx;
display: flex;
align-items: center;
justify-content: center;
color: rgba(231, 217, 255, 0.86);
font-size: 23rpx;
font-weight: 900;
background: rgba(157, 104, 255, 0.18);
border: 1rpx solid rgba(193, 154, 255, 0.24);
box-shadow: inset 0 0 18rpx rgba(185, 124, 255, 0.08);
}
.description-title-row {
margin-top: 16rpx;
}
Expected result: 收藏与分享同一水平线,分析按钮弱化并位于右上凹位。
- Step 7: Replace description text styles with Markdown wrapper styles
Replace:
.description-text {
display: block;
margin-top: 24rpx;
color: rgba(227, 218, 246, 0.73);
font-size: 25rpx;
line-height: 1.75;
}
.description-text.collapsed {
display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
.collapse-action {
display: block;
margin-top: 14rpx;
text-align: right;
color: #a970ff;
font-size: 24rpx;
font-weight: 800;
}
with:
.description-markdown {
margin-top: 22rpx;
overflow: hidden;
}
.description-markdown.collapsed {
max-height: 168rpx;
}
.event-body-footer {
margin-top: 18rpx;
}
.collapse-action {
min-height: 64rpx;
display: flex;
align-items: center;
justify-content: flex-end;
color: #a970ff;
font-size: 24rpx;
font-weight: 800;
}
Expected result: Markdown content can collapse without changing Markdown.vue.
- Step 8: Replace bottom action layout styles
Replace the current .bottom-actions, .bottom-action, and .chat-action blocks:
.bottom-actions {
position: absolute;
left: 0;
right: 0;
bottom: 0;
min-height: 108rpx;
box-sizing: border-box;
display: grid;
grid-template-columns: 0.76fr 0.76fr 2fr 0.76fr;
align-items: center;
gap: 8rpx;
padding: 14rpx 32rpx 0;
border-radius: 36rpx 36rpx 0 0;
border-top: 1rpx solid rgba(126, 87, 255, 0.32);
background: rgba(13, 13, 43, 0.94);
box-shadow: 0 -14rpx 44rpx rgba(0, 0, 0, 0.45), inset 0 0 28rpx rgba(143, 92, 255, 0.1);
backdrop-filter: blur(28rpx);
-webkit-backdrop-filter: blur(28rpx);
}
.bottom-action,
.chat-action {
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 10rpx;
color: rgba(238, 231, 255, 0.9);
font-size: 24rpx;
font-weight: 800;
}
.bottom-action {
min-width: 0;
}
.chat-action {
border-radius: 999rpx;
color: #fff;
background: linear-gradient(135deg, #b246ff, #742fff 58%, #5126ff);
box-shadow: 0 0 28rpx rgba(168, 85, 247, 0.58), inset 0 1rpx 0 rgba(255, 255, 255, 0.18);
}
with:
.bottom-actions {
position: absolute;
left: 0;
right: 0;
bottom: 0;
min-height: 108rpx;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
padding: 14rpx 32rpx 0;
border-radius: 36rpx 36rpx 0 0;
border-top: 1rpx solid rgba(126, 87, 255, 0.32);
background: rgba(13, 13, 43, 0.94);
box-shadow: 0 -14rpx 44rpx rgba(0, 0, 0, 0.45), inset 0 0 28rpx rgba(143, 92, 255, 0.1);
backdrop-filter: blur(28rpx);
-webkit-backdrop-filter: blur(28rpx);
}
.chat-action {
width: min(420rpx, 100%);
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
border-radius: 999rpx;
color: #fff;
font-size: 26rpx;
font-weight: 900;
background: linear-gradient(135deg, #b246ff, #742fff 58%, #5126ff);
box-shadow: 0 0 28rpx rgba(168, 85, 247, 0.58), inset 0 1rpx 0 rgba(255, 255, 255, 0.18);
}
Expected result: “聊聊” button is centered and visually primary.
- Step 9: Confirm shared icon styles still work in new locations
Keep the existing .edit-icon, .bookmark-icon, .share-icon, and .chat-badge blocks unchanged.
Expected result: content toolbar and footer reuse the existing drawn icons with no new asset dependency.
Task 4: Build Verification
Files:
-
Verify:
mini-program/src/pages/life-event/detail.vue -
Verify:
mini-program/package.json -
Step 1: Run H5 production build
Run:
cd mini-program
npm run build:h5
Expected: command exits with code 0 and Vite/UniApp writes the H5 build output under mini-program/dist/build/h5 or the configured UniApp output directory.
- Step 2: Fix compile errors by editing only detail.vue
If the build reports a compile error in detail.vue, fix the exact syntax issue in mini-program/src/pages/life-event/detail.vue, then rerun:
cd mini-program
npm run build:h5
Expected: command exits with code 0.
Task 5: Browser Verification
Files:
-
Verify:
mini-program/src/pages/life-event/detail.vue -
Step 1: Start H5 dev server
Run:
cd mini-program
npm run dev:h5
Expected: dev server prints a local URL, usually http://localhost:5173.
- Step 2: Open the app in a browser
Use the available browser automation tool to open:
http://localhost:5173
Expected: app loads without a blank screen.
- Step 3: Navigate to a life event detail page
Use the app UI to reach 人生轨迹详情页. If the local dev data has no event entry, open a detail URL with the current route format after creating or selecting an event through the UI.
Expected: the detail page renders and uses local store/cached fallback data when available.
- Step 4: Verify visual requirements
Check these exact outcomes:
- The left-top edit icon and right-top more icon are absent.
- Event title information and event description are inside one merged card.
- The upper title area is visibly darker than the body area.
- Event description is rendered through Markdown formatting.
- 收藏 is at the body area's left top.
- 分享 is horizontally aligned with 收藏.
- 分析 is a lighter pill at the right-top side of the body area.
- 编辑 is at the body area's left bottom.
- 编辑 and 收起 or 展开 are horizontally aligned.
- 分析 and 收起 or 展开 are vertically aligned on the right side.
- The bottom button is centered and displays “聊聊”.
- The page has no overlapping text at mobile width.
Expected: all checks pass.
- Step 5: Verify button behavior and console health
Click these controls:
- 收藏
- 分享
- 分析
- 编辑
- 收起 or 展开
- 聊聊
Expected:
- 收藏 toggles visual state or shows the existing failure toast when unauthenticated.
- 分享 copies or shows the existing failure toast.
- 分析 scrolls to the AI 分析 section.
- 编辑 navigates to the event form when
eventIdexists. - 收起/展开 toggles Markdown body height.
- 聊聊 opens the existing chat modal or shows existing failure text.
- Browser Console contains no new errors from
detail.vue.
Self-Review
Spec coverage:
- Top icons removed: Task 1 Step 1, Task 3 Step 1-2.
- Title and content merged: Task 1 Step 3, Task 3 Step 4-5.
- Darker title area: Task 3 Step 4.
- Markdown description rendering: Task 1 Step 3, Task 3 Step 7.
- Button placement: Task 1 Step 3 and Step 5, Task 3 Step 6-8.
- Existing behavior preserved: Task 2 Step 4.
- Browser verification required by repository rules: Task 5.
Placeholder scan:
- This plan contains no placeholder or incomplete implementation steps.
Type and property consistency:
scrollTopis declared asref(0)and bound as:scroll-top="scrollTop".currentScrollTopis declared asref(0)and updated by@scroll="onContentScroll".scrollToAnalysisis referenced by the template and defined in script.analysisPanelis the exact id used by selector query.