Files
happy-life-star/docs/superpowers/plans/2026-06-09-mini-program-life-event-detail-actions.md
T

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-navhero-carddescription-card 组合;脚本层增加 scrollTopcurrentScrollToponContentScrollscrollToAnalysis 来支持分析按钮定位;样式层复用当前宇宙玻璃风格并新增局部按钮和 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 使用,新增 scrollTopcurrentScrollToponContentScrollscrollToAnalysis,保留现有业务方法。
    • 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 eventId exists.
  • 收起/展开 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:

  • scrollTop is declared as ref(0) and bound as :scroll-top="scrollTop".
  • currentScrollTop is declared as ref(0) and updated by @scroll="onContentScroll".
  • scrollToAnalysis is referenced by the template and defined in script.
  • analysisPanel is the exact id used by selector query.