feat: 修复 Redis 超时问题、固定小程序端口、新增人生事件模块及优化多个页面

- 修复 Redis 超时:添加 commons-pool2 依赖,启用 Lettuce 连接池,超时提升至 15s
- 固定 mini-program H5 端口为 5175,避免与 web 项目端口冲突
- 新增人生事件(life-event)模块:表单和详情页面
- 新增 EpicScript 灵感接口(Controller/Service/DTO)
- 优化登录、引导、主页、记录、剧本详情等多个页面
- 优化服务管理脚本和 Nginx 配置

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 11:38:35 +08:00
parent 507d1ebdab
commit 60c63850ee
36 changed files with 4545 additions and 3043 deletions
@@ -0,0 +1,310 @@
<template>
<view class="event-detail-page">
<view class="space-bg"></view>
<view class="top-safe" :style="{ height: safeAreaTop + 'px' }"></view>
<view class="header">
<text class="back" @click="goBack"></text>
<text class="title">人生轨迹内容</text>
<text class="more"></text>
</view>
<scroll-view class="content" scroll-y :show-scrollbar="false">
<view class="hero-card kos-card">
<view class="cover">{{ (event.title || '轨').slice(0, 1) }}</view>
<view class="hero-body">
<text class="date">{{ event.time || event.date || '未知日期' }}</text>
<text class="event-title">{{ event.title || '未命名经历' }}</text>
<text class="tag">{{ primaryTag }}</text>
</view>
</view>
<view class="panel kos-card">
<text class="panel-title">经历描述</text>
<text class="body-text">{{ event.content || event.description || '暂无内容。' }}</text>
</view>
<view class="panel ai-panel kos-card">
<view class="ai-head">
<text class="spark"></text>
<text>AI 人生解读</text>
</view>
<view v-for="block in analysisBlocks" :key="block.title" class="analysis-block">
<text class="analysis-title">{{ block.title }}</text>
<text class="analysis-text">{{ block.text }}</text>
</view>
</view>
<view class="related-tags">
<text v-for="tag in relatedTags" :key="tag" class="tag-pill kos-pill">{{ tag }}</text>
</view>
</scroll-view>
<view class="bottom-actions" :style="{ paddingBottom: safeAreaBottom + 14 + 'px' }">
<view class="action kos-pill" @click="notReady('编辑')">编辑</view>
<view class="action kos-pill" @click="notReady('收藏')">收藏</view>
<view class="action primary kos-primary" @click="notReady('聊天')">聊聊这段经历</view>
<view class="action kos-pill" @click="notReady('分享')">分享</view>
</view>
</view>
</template>
<script setup>
import { computed, ref, onMounted } from 'vue'
import { useAppStore } from '../../stores/app.js'
const store = useAppStore()
const safeAreaTop = ref(20)
const safeAreaBottom = ref(0)
const eventId = ref('')
onMounted(async () => {
const info = uni.getWindowInfo()
safeAreaTop.value = info.safeAreaInsets?.top || info.statusBarHeight || 20
safeAreaBottom.value = info.safeAreaInsets?.bottom || 0
const pages = getCurrentPages()
eventId.value = pages[pages.length - 1]?.options?.id || ''
if (!store.events?.length) await store.fetchEvents()
})
const event = computed(() => store.getEventById(eventId.value) || {})
const primaryTag = computed(() => Array.isArray(event.value.tags) && event.value.tags.length ? event.value.tags[0] : '人生片段')
const relatedTags = computed(() => {
if (Array.isArray(event.value.tags) && event.value.tags.length) return event.value.tags
return ['理解', '成长', '转折']
})
const analysisBlocks = computed(() => {
const ai = event.value.aiFeedback
if (!ai) {
return [
{ title: '情绪整理', text: '这段经历还没有 AI 解读,但它已经是一枚重要的生命坐标。' },
{ title: '成长意义', text: '你可以继续补充当时的选择、关系和感受,让它成为后续剧本生成的素材。' }
]
}
return [
{ title: '情绪整理', text: ai.slice(0, 90) },
{ title: '能力映射', text: '从这段经历里,可以看到你对变化的感知、承受和重新组织能力。' },
{ title: '下一步建议', text: '把这段经历与一个目标连接起来,它会更容易转化成行动路径。' }
]
})
const notReady = (label) => {
uni.showToast({ title: `${label}能力稍后开放`, icon: 'none' })
}
const goBack = () => {
uni.navigateBack()
}
</script>
<style scoped>
.event-detail-page {
position: relative;
height: 100vh;
min-height: 0;
overflow: hidden;
display: flex;
flex-direction: column;
color: #fff;
background: #050615;
}
.space-bg {
position: absolute;
inset: 0;
background:
radial-gradient(circle at 18% 2%, rgba(124, 58, 237, 0.3), transparent 34%),
radial-gradient(circle at 88% 22%, rgba(14, 165, 233, 0.18), transparent 28%),
linear-gradient(180deg, #07091d 0%, #07031a 52%, #04020e 100%);
}
.top-safe,
.header,
.content,
.bottom-actions {
position: relative;
z-index: 1;
}
.header {
height: 92rpx;
flex-shrink: 0;
display: grid;
grid-template-columns: 80rpx 1fr 80rpx;
align-items: center;
padding: 0 28rpx;
}
.back {
font-size: 66rpx;
color: #fff;
}
.title {
text-align: center;
font-size: 34rpx;
font-weight: 900;
}
.more {
text-align: right;
color: rgba(255, 255, 255, 0.68);
letter-spacing: 4rpx;
}
.content {
flex: 1;
height: 0;
min-height: 0;
box-sizing: border-box;
padding: 0 30rpx;
}
.hero-card,
.panel {
border-radius: 30rpx;
margin-bottom: 24rpx;
}
.hero-card {
overflow: hidden;
}
.cover {
height: 256rpx;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 88rpx;
font-weight: 900;
background:
radial-gradient(circle at 30% 20%, rgba(56, 189, 248, 0.3), transparent 30%),
linear-gradient(135deg, #7d35ff, #111827);
}
.hero-body,
.panel {
padding: 30rpx;
}
.date,
.tag,
.body-text,
.analysis-text {
display: block;
color: rgba(226, 216, 246, 0.72);
}
.date {
font-size: 22rpx;
letter-spacing: 3rpx;
}
.event-title {
display: block;
margin-top: 12rpx;
color: #fff;
font-size: 40rpx;
font-weight: 900;
line-height: 1.24;
}
.tag {
width: fit-content;
margin-top: 18rpx;
padding: 9rpx 18rpx;
border-radius: 999rpx;
color: #caa0ff;
background: rgba(124, 58, 237, 0.18);
}
.panel-title {
display: block;
margin-bottom: 16rpx;
color: #fff;
font-size: 30rpx;
font-weight: 900;
}
.body-text {
font-size: 26rpx;
line-height: 1.75;
}
.ai-head {
display: flex;
align-items: center;
gap: 12rpx;
margin-bottom: 18rpx;
color: #fff;
font-size: 30rpx;
font-weight: 900;
}
.spark {
color: #d982ff;
}
.analysis-block {
padding: 20rpx 0;
border-top: 1rpx solid rgba(180, 139, 255, 0.14);
}
.analysis-title {
display: block;
margin-bottom: 10rpx;
color: #dccbff;
font-size: 24rpx;
font-weight: 900;
}
.analysis-text {
font-size: 24rpx;
line-height: 1.65;
}
.related-tags {
display: flex;
flex-wrap: wrap;
gap: 14rpx;
padding-bottom: 34rpx;
}
.tag-pill {
height: 48rpx;
padding: 0 18rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
color: rgba(237, 233, 254, 0.78);
font-size: 22rpx;
}
.bottom-actions {
min-height: 124rpx;
box-sizing: border-box;
display: grid;
grid-template-columns: 0.8fr 0.8fr 1.5fr 0.8fr;
gap: 12rpx;
padding: 14rpx 22rpx;
background: rgba(5, 6, 21, 0.72);
backdrop-filter: blur(24rpx);
}
.action {
height: 72rpx;
border-radius: 999rpx;
display: flex;
align-items: center;
justify-content: center;
color: #caa0ff;
font-size: 23rpx;
font-weight: 700;
}
.action.primary {
color: #fff;
}
</style>