feat: 优化人生轨迹详情页布局与顶部安全区

- 合并事件标题卡与描述卡为统一的 event-content-card
- 移除顶部悬浮导航,避免与小程序原生胶囊按钮重叠
- 添加顶部安全区占位(参考 profile/index.vue 等页面)
- 调整按钮布局:收藏、分析、分享、编辑、收起放入内容区域
- 底部只保留居中的'聊聊'主按钮
- 使用 Markdown 组件渲染事件描述
- 添加 scrollToAnalysis 滚动定位功能
- 更新 AGENTS.md 添加操作系统命令执行规则
- 更新 CLAUDE.md 添加操作系统命令执行规则
This commit is contained in:
2026-06-10 20:45:01 +08:00
parent e021fe8443
commit cb3c5e6724
2 changed files with 572 additions and 141 deletions
+379
View File
@@ -0,0 +1,379 @@
# AGENTS.md
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
---
## 项目概述
情绪博物馆 (Emotion Museum) 是一款基于 AI 技术的心理健康应用,通过智能对话、情绪分析、个性化成长方案等功能,帮助用户建立健康的情绪管理习惯。
**生产环境地址**: `lifescript.happylifeos.com`
- 用户前端:https://lifescript.happylifeos.com/
- 管理后台:https://lifescript.happylifeos.com/emotion-museum-admin/
- 后端 APIhttps://lifescript.happylifeos.com/api
- WebSocketwss://lifescript.happylifeos.com/ws
---
## 项目结构
```
.
├── backend-single/ # Spring Boot 单体后端服务
├── web/ # 用户前端 (Vue3 + TS + Vite)
├── web-admin/ # 管理后台 (Vue3 + TS + Element Plus)
├── UniApp/ # 跨平台移动应用 (微信小程序/H5)
├── mini-program/ # 小程序项目
├── course-web/ # 课程 Web 项目
├── life-script/ # 生活脚本工具
└── tools/ # 工具脚本
```
---
## 常用命令
### 后端 (backend-single)
```bash
# 进入后端目录
cd backend-single
# 编译打包
mvn clean package -DskipTests
# 本地运行
mvn spring-boot:run
# 或直接运行 JAR
java -jar target/backend-single-1.0.0.jar
# 运行测试
mvn test
# 运行单个测试类
mvn test -Dtest=ClassNameTest
```
### 用户前端 (web)
```bash
# 进入前端目录
cd web
# 安装依赖
npm install
# 启动开发服务器 (端口 5173)
npm run dev
# 类型检查
npm run type-check
# 代码检查
npm run lint
# 构建生产版本
npm run build
# 运行测试
npm run test
# E2E 测试
npm run test:e2e
```
### 管理后台 (web-admin)
```bash
# 进入目录
cd web-admin
# 安装依赖
npm install
# 启动开发服务器 (端口 5174)
npm run dev
# 类型检查
npm run type-check
# 代码检查
npm run lint
# 构建生产版本
npm run build
```
### UniApp/小程序
```bash
# 进入目录
cd UniApp
# 开发微信小程序
npm run dev:mp-weixin
# 构建微信小程序
npm run build:mp-weixin
# 开发 H5
npm run dev:h5
# 构建 H5
npm run build:h5
```
### mini-program(推荐开发模式)
**H5 开发模式**(日常开发,推荐):
```bash
cd mini-program
# 或使用启动脚本
start-h5-dev.bat
```
访问 `http://localhost:5173`,登录态保留,支持热更新。
**微信小程序开发模式**(仅用于小程序特性验证):
```bash
cd mini-program
npm run dev:mp-weixin
```
在微信开发者工具中打开 `unpackage/dist/dev/mp-weixin`
**开发工作流说明**:详见 [小程序开发工作流程设计](docs/superpowers/specs/2026-04-07-mini-program-dev-workflow-design.md)
### 一键部署
```bash
# 部署所有服务(后端 + 前端 + 管理后台)到生产服务器
bash deploy-all.sh
# 仅部署后端
bash deploy-all.sh backend
# 仅部署前端
bash deploy-all.sh frontend
# 仅部署管理后台
bash deploy-all.sh admin
```
---
## 技术栈
### 后端
- **框架**: Spring Boot 2.7.18
- **ORM**: MyBatis-Plus 3.5.3.1
- **数据库**: MySQL 8.0.33
- **缓存**: Redis 7.0+
- **JWT**: io.jsonwebtoken 0.11.5
- **构建**: Maven 3.6+
- **JDK**: 17
### 前端
- **框架**: Vue 3.4.x + TypeScript 5.x
- **构建**: Vite 5.x
- **UI**: Element Plus 2.4.x
- **样式**: Tailwind CSS 3.4.x
- **状态管理**: Pinia 2.1.x
- **路由**: Vue Router 4.2.x
- **HTTP**: Axios 1.6.x
- **实时通信**: @stomp/stompjs + SockJS
- **图表**: ECharts 5.4.x
- **包管理**: npm 9+
### 移动端
- **框架**: UniApp (Vue 3)
- **平台**: 微信小程序、H5
---
## 架构规范
### 后端分层架构
- **Controller 层**: 接口定义,参数校验,禁止业务逻辑
- **Service 层**: 业务逻辑实现
- **Mapper 层**: 数据访问层
- **Entity 层**: 数据实体
- **Request/Response 层**: 入参出参封装
- **Config 层**: 配置类
### 接口设计规范
- Controller 层路由禁止添加 `/api` 前缀
- 使用 `@RequestParam` 传递路径参数,禁止使用 `@PathVariable`
- 接口方法参数不超过两个,超出时使用 Request/DTO 对象封装
- 使用项目已有的 `Result` 作为统一接口返回
- 禁止使用枚举类型作为接口入参
### 前端架构
- 使用 Vue 3 Composition API (`<script setup>`)
- 使用 TypeScript 进行类型检查
- 组件通信优先使用 Pinia 状态管理
- 前端访问后端通过网关统一调用
---
## 验证规则(强制)
**修改任何前后端代码后,必须使用浏览器进行验证**
### 验证步骤
1. **使用浏览器工具打开对应页面**
- 使用 `agent-browser` skill 或 MCP 工具
- 前台页面:通常是 `http://localhost:5173` 或项目指定端口
- 管理端页面:通常是 `http://localhost:5174` 或项目指定路径
2. **验证标准**(全部满足才算通过)
- ✅ 前端修改已生效
- ✅ 页面没有报错
- ✅ 可以正常访问
- ✅ 浏览器 Console 中没有任何错误
3. **只有验证通过才算任务完成**
---
## 热加载规则(强制)
**修改前后端代码后,优先利用热加载,避免不必要的重启**
### 判断逻辑
1. **不需要重启的场景**(热加载自动生效)
- 前端:修改 `.vue``.tsx``.ts``.js``.css``.scss` 等源码文件
- 后端:修改业务逻辑、API 接口、组件代码等支持热更新的文件
- 样式、模板、静态资源等修改
2. **需要重启的场景**(热加载无法生效)
- 前端:修改 `vite.config.ts``tsconfig.json``.env``package.json`、别名配置等
- 后端:修改启动配置、数据库连接、中间件注册、环境变量等
- 新增或删除依赖包后
### 操作原则
- 修改后先观察终端输出,确认热加载是否成功
- 只有修改了必须重启才能生效的配置时,才执行重启操作
- 重启前后端服务前,需告知用户
---
## 语言规则(强制)
- 所有对话和文档使用**中文**
- 代码内容(变量名、函数名、注释)使用英文
- 技术术语保持英文(API、HTTP、JSON 等)
---
## Git 提交规范
- 使用中文提交
- 格式:`类型:描述`
- 类型包括:feat, fix, docs, style, refactor, test, chore
---
## 开发规范(来自 Cursor Rules
### 基础设置
- 保持对话语言为中文
- 不允许在未经允许的情况下删除代码和文件
- 不允许破坏正常的业务代码
- 执行终端命令时要关注执行情况,避免无效等待
### 代码规范
- 生成代码时必须添加类级和函数级注释
- 使用 `import` 导包,禁止使用全限定名称引用类
- 禁止使用枚举类型作为 Entity、Request、Response、DTO 对象的字段
- 禁止以枚举类作为方法的入参
- 新增数据的 id 使用已存在的雪花算法生成器生成
### 架构规范
- Controller 层禁止添加业务逻辑
- 使用全局异常处理,禁止使用 try-catch
- 前端接口访问尽可能走网关调用
### 接口设计规范
- Controller 层接口定义:
- 入参使用 request 封装传递到 service 层
- service 层方法命名与 controller 层保持一致
- 出参使用 response 封装由 service 层传递到 controller 层
- 禁止在 controller 层做 entity 与 request/response 的转换
- 使用项目已有的 `Result` 做接口返回
- 接口和方法参数不允许超过两个,超过时使用 request 或 DTO 对象封装
- Controller 层路由禁止添加 `/api` 前缀
- Controller 层 `@RequestMapping` 的 value 属性值不允许重复且不允许为空
- 必须明确指定 value 属性值且使用驼峰结构命名,避免使用下划线
- 禁止使用 `@GetMapping()``@PostMapping` 等空注解形式
- 用户相关接口禁止直接传递用户 id,需要后端根据 token 获取当前登录用户信息
- 禁止使用 `/{param}` 格式的路径参数,避免网关路由冲突
- 路径参数统一使用 `@RequestParam` 而非 `@PathVariable`
- 接口路径命名应具有明确的语义,避免使用通用词汇如 `/get``/list`
- 批量操作接口应使用专门的 Request 对象封装参数,而非直接传递 List
- 接口路径应避免层级过深,建议不超过 3 级路径结构
### 数据库规范
- 所有数据表必须包含 `create_time``update_time` 字段
- 删除操作优先使用逻辑删除,添加 `deleted` 字段标识
- 数据库字段命名使用下划线分隔,Java 实体类使用驼峰命名
- 优先使用 `LambdaQueryWrapper` 构造条件查询
- 使用 Lambda 表达式引用实体类属性,提高代码可维护性
- 复杂查询条件应使用 `LambdaQueryWrapper` 的链式调用
### 安全规范
- 所有外部输入必须进行参数校验
- 敏感信息不得在日志中输出
- 数据库操作必须使用参数化查询,防止 SQL 注入
### 性能规范
- 避免 N+1 查询问题,合理使用批量查询
- 大数据量查询必须分页处理
- 缓存策略要考虑数据一致性问题
### 日志规范
- 关键业务操作必须记录操作日志
- 异常信息要包含足够的上下文信息
- 生产环境禁止输出 debug 级别日志
---
## 操作系统命令执行规则(强制)
**执行任何 Shell 命令前,必须根据当前操作系统(`win32` / `darwin` / `linux`)选择合适的命令语法。**
### 后台启动长期运行进程(dev server 等)
不同操作系统后台启动进程的方式完全不同,选错会导致命令阻塞或失败:
| 操作系统 | 正确方式 | 错误方式(禁止) |
|---------|---------|----------------|
| **Windows** | `Start-Process -FilePath "npm" -ArgumentList "run","dev" -WorkingDirectory "..." -WindowStyle Hidden` | `cmd /c "start /B npm run dev > log 2>&1"``cmd /c` 会阻塞等待重定向完成) |
| **macOS/Linux** | `nohup npm run dev > log 2>&1 &` | |
**核心原则:**
- Windows 下 `cmd /c "start /B ... > log 2>&1"` **绝对禁止**用于启动长期运行的后台服务。`cmd /c` 会等待重定向管道关闭,导致命令无限阻塞。
- Windows 下启动后台进程必须使用 PowerShell 的 `Start-Process` cmdlet,并通过 `-WindowStyle Hidden` 隐藏窗口。
### 启动前检查(强制)
**启动任何开发服务器前,必须先检查目标端口是否已被占用:**
```powershell
# Windows
netstat -ano | findstr "端口号"
# macOS/Linux
lsof -i :端口号
```
如果端口已被占用,必须先终止旧进程再启动新进程,**禁止重复启动**导致端口递增。
### 热加载优先原则
Vite / Webpack 等现代构建工具支持热更新(HMR):
- **启动一次即可**`npm run dev` 启动后,修改源码文件会自动热更新
- **禁止频繁重启**:除非修改了 `vite.config.ts``.env``package.json` 等配置文件,否则不需要重启 dev server
- 重启前必须告知用户原因
+193 -141
View File
@@ -2,61 +2,91 @@
<view class="detail-page">
<view class="space-bg"></view>
<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>
<scroll-view
class="content"
scroll-y
:show-scrollbar="false"
:scroll-top="scrollTop"
@scroll="onContentScroll"
scroll-with-animation
>
<view class="top-safe" :style="{ height: capsuleTopReservePx + 'px' }"></view>
<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>
<scroll-view class="content" scroll-y :show-scrollbar="false">
<view class="hero-card glass-card">
<view class="hero-timeline">
<view class="timeline-glow"></view>
<view class="timeline-node"></view>
<view class="timeline-line"></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="hero-copy">
<text class="year">{{ eventYear }}</text>
<view class="date-row">
<text class="date-range">{{ dateRange }}</text>
<text class="type-chip">{{ primaryTag }}</text>
<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>
<text class="event-title">{{ displayEvent.title || '决定全职做自己热爱的AI产品' }}</text>
<view class="location-row">
<view class="pin-icon"></view>
<text>{{ locationText }}</text>
<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 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="description-card glass-card">
<view class="section-title-row">
<view class="orbit-icon"></view>
<text>事件描述</text>
</view>
<text class="description-text" :class="{ collapsed: isDescriptionCollapsed }">{{ eventDescription }}</text>
<text class="collapse-action" @click="isDescriptionCollapsed = !isDescriptionCollapsed">
{{ isDescriptionCollapsed ? '展开' : '收起' }} ^
</text>
</view>
<view class="analysis-panel">
<view class="analysis-panel" id="analysisPanel">
<view class="section-title-row analysis-head">
<text class="sparkle"></text>
<text>AI 分析</text>
@@ -90,25 +120,9 @@
</scroll-view>
<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>
<text>聊聊</text>
</view>
</view>
</view>
@@ -118,17 +132,19 @@
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'
import { useMenuButtonSafeArea } from '../../composables/useMenuButtonSafeArea.js'
const store = useAppStore()
const pagePath = '/pages/life-event/detail'
const { floatingTopStyle } = useMenuButtonSafeArea()
const { capsuleTopReservePx } = useMenuButtonSafeArea({ extraTopPx: 10 })
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)
onMounted(async () => {
const info = uni.getWindowInfo()
@@ -280,6 +296,20 @@ const analysisBlocks = computed(() => {
]
})
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()
}
const editEvent = () => {
if (!eventId.value) return
uni.navigateTo({ url: `/pages/life-event/form?id=${eventId.value}` })
@@ -385,59 +415,18 @@ const goBack = () => {
background-position: 44rpx 44rpx, 10rpx 112rpx;
}
.floating-nav,
.content,
.bottom-actions {
position: relative;
z-index: 1;
}
.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;
}
.content {
flex: 1;
height: 0;
min-height: 0;
box-sizing: border-box;
padding: 0 32rpx 134rpx;
padding: 12rpx 32rpx 154rpx;
}
.glass-card {
@@ -450,15 +439,24 @@ const goBack = () => {
-webkit-backdrop-filter: blur(24rpx);
}
.hero-card {
.event-content-card {
position: relative;
overflow: hidden;
border-radius: 30rpx;
}
.event-hero-section {
position: relative;
min-height: 264rpx;
border-radius: 28rpx;
display: grid;
grid-template-columns: 70rpx 1fr 148rpx;
gap: 20rpx;
padding: 38rpx 34rpx 32rpx;
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);
}
.hero-timeline {
@@ -633,7 +631,15 @@ const goBack = () => {
text-shadow: 0 0 22rpx rgba(255, 198, 85, 0.9);
}
.description-card,
.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;
@@ -641,6 +647,62 @@ const goBack = () => {
box-sizing: border-box;
}
.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;
}
.section-title-row {
display: flex;
align-items: center;
@@ -671,25 +733,24 @@ const goBack = () => {
border-color: rgba(176, 112, 255, 0.62);
}
.description-text {
display: block;
margin-top: 24rpx;
color: rgba(227, 218, 246, 0.73);
font-size: 25rpx;
line-height: 1.75;
.description-markdown {
margin-top: 22rpx;
overflow: hidden;
}
.description-text.collapsed {
display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
.description-markdown.collapsed {
max-height: 168rpx;
}
.event-body-footer {
margin-top: 18rpx;
}
.collapse-action {
display: block;
margin-top: 14rpx;
text-align: right;
min-height: 64rpx;
display: flex;
align-items: center;
justify-content: flex-end;
color: #a970ff;
font-size: 24rpx;
font-weight: 800;
@@ -830,10 +891,9 @@ const goBack = () => {
bottom: 0;
min-height: 108rpx;
box-sizing: border-box;
display: grid;
grid-template-columns: 0.76fr 0.76fr 2fr 0.76fr;
display: flex;
align-items: center;
gap: 8rpx;
justify-content: center;
padding: 14rpx 32rpx 0;
border-radius: 36rpx 36rpx 0 0;
border-top: 1rpx solid rgba(126, 87, 255, 0.32);
@@ -843,25 +903,17 @@ const goBack = () => {
-webkit-backdrop-filter: blur(28rpx);
}
.bottom-action,
.chat-action {
width: min(420rpx, 100%);
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 {
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);
}