docs:补充人生轨迹详情页调整实施计划
This commit is contained in:
@@ -0,0 +1,892 @@
|
|||||||
|
# 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:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<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:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<scroll-view class="content" scroll-y :show-scrollbar="false">
|
||||||
|
```
|
||||||
|
|
||||||
|
to:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<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:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<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:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<view class="analysis-panel">
|
||||||
|
```
|
||||||
|
|
||||||
|
to:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<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:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<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:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<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:
|
||||||
|
|
||||||
|
```js
|
||||||
|
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:
|
||||||
|
|
||||||
|
```js
|
||||||
|
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:
|
||||||
|
|
||||||
|
```js
|
||||||
|
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:
|
||||||
|
|
||||||
|
```js
|
||||||
|
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 = () => {`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
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:
|
||||||
|
|
||||||
|
```js
|
||||||
|
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:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.floating-nav,
|
||||||
|
.content,
|
||||||
|
.bottom-actions {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
to:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.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:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.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:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
height: 0;
|
||||||
|
min-height: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0 32rpx 134rpx;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
to:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.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:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.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:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.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:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.description-card,
|
||||||
|
.tags-card {
|
||||||
|
border-radius: 28rpx;
|
||||||
|
margin-top: 18rpx;
|
||||||
|
padding: 28rpx 30rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
with:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.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:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.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:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.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:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.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:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.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:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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:
|
||||||
|
|
||||||
|
```text
|
||||||
|
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.
|
||||||
Reference in New Issue
Block a user