feat: refresh UI and add vite ci workflow

This commit is contained in:
你çšnepiedg
2026-03-18 19:24:51 +08:00
parent 31e504a997
commit 55f5c216bd
50 changed files with 13304 additions and 437 deletions
+19
View File
@@ -0,0 +1,19 @@
name: ci
on:
push:
branches: ["main", "master"]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build:h5
- run: npm run build:mp-weixin
+4 -1
View File
@@ -1 +1,4 @@
unpackage/* node_modules/
dist/
unpackage/
.hbuilderx/
+58 -29
View File
@@ -1,6 +1,6 @@
# 戒烟助手小程序(smt # 戒烟助手小程序(smt
基于 `uni-app + Vue 3 + Pinia` 的戒烟辅助小程序前端,核心目标是帮助用户记录抽烟/忍住行为,并通过首页看板、统计分析和 AI 建议逐步降低吸烟频率。 基于 `uni-app + Vue 3 + Pinia + Vite` 的戒烟辅助小程序前端,核心目标是帮助用户记录抽烟/忍住行为,并通过首页看板、统计分析和 AI 建议逐步降低吸烟频率。
## 项目状态 ## 项目状态
@@ -43,7 +43,7 @@
## 页面与路由 ## 页面与路由
`pages.json` 中注册了以下页面: `src/pages.json` 中注册了以下页面:
- `pages/index/index`:首页 - `pages/index/index`:首页
- `pages/stats/index`:统计 - `pages/stats/index`:统计
@@ -56,51 +56,79 @@
```text ```text
smt/ smt/
├── api/ # 接口封装 ├── src/
├── components/ # 业务组件(含 smoke-record-dialog │ ├── api/ # 接口封装
├── config/ # 环境配置(BASE_URL 等 ├── components/ # 业务组件(含 smoke-record-dialog
│ ├── config/ # 环境配置(BASE_URL 等)
│ ├── hooks/ # 组合式逻辑(如 useLogin
│ ├── pages/ # 页面
│ ├── stores/ # Pinia 状态管理
│ ├── utils/ # 工具函数与本地存储封装
│ ├── App.vue
│ ├── main.js
│ ├── manifest.json
│ └── pages.json
├── docs/ # PRD、API、算法与认证文档 ├── docs/ # PRD、API、算法与认证文档
├── hooks/ # 组合式逻辑(如 useLogin ├── index.html
├── pages/ # 页面 ├── package.json
── stores/ # Pinia 状态管理 ── vite.config.js
├── utils/ # 工具函数与本地存储封装
├── App.vue
├── main.js
├── manifest.json
└── pages.json
``` ```
## 本地开发 ## 本地开发
## 1. 环境准备 ## 1. 环境准备
- 推荐使用 `HBuilderX` 打开项目目录 - Node.js 22
- 微信开发者工具(用于运行到微信小程序) - npm
- 微信开发者工具(用于运行微信小程序)
> 说明:本仓库当前未包含 `package.json`,默认按 HBuilderX/uni-app 工程方式运行。
## 2. 配置后端地址 ## 2. 配置后端地址
编辑 `config/index.js` 编辑 `src/config/index.js`
- `development.BASE_URL`:开发环境 API 地址 - `development.BASE_URL`:开发环境 API 地址
- `production.BASE_URL`:生产环境 API 地址 - `production.BASE_URL`:生产环境 API 地址
- `MINI_PROGRAM_ID`:后端登录接口使用的小程序 ID - `MINI_PROGRAM_ID`:后端登录接口使用的小程序 ID
## 3. 小程序配置 ## 3. 安装依赖
`manifest.json` 中包含: ```bash
npm install
```
## 4. 小程序配置
`src/manifest.json` 中包含:
- `mp-weixin.appid` - `mp-weixin.appid`
- 微信端 `urlCheck` 等基础配置 - 微信端 `urlCheck` 等基础配置
## 4. 运行 ## 5. 运行
在 HBuilderX 中 微信小程序本地开发
1. 导入项目目录 ```bash
2. 运行到 `微信开发者工具` npm run dev:mp-weixin
3. 确认接口可访问后开始联调 ```
微信开发者工具导入目录:
```text
dist/dev/mp-weixin
```
H5 调试:
```bash
npm run dev:h5
```
构建:
```bash
npm run build:h5
npm run build:mp-weixin
```
## 接口与文档 ## 接口与文档
@@ -109,21 +137,22 @@ smt/
- 抽烟记录 API`docs/api.md` - 抽烟记录 API`docs/api.md`
- 登录认证:`docs/auth.md` - 登录认证:`docs/auth.md`
- 算法说明:`docs/ALGORITHM.md` - 算法说明:`docs/ALGORITHM.md`
- 组件文档:`components/smoke-record-dialog/README.md` - 组件文档:`src/components/smoke-record-dialog/README.md`
## 关键实现说明 ## 关键实现说明
- 全局启动时在 `App.vue` 发起静默登录 - 全局启动时在 `src/App.vue` 发起静默登录
- 业务页面通过 `useLogin().waitForLogin()` 确保登录完成后请求接口 - 业务页面通过 `useLogin().waitForLogin()` 确保登录完成后请求接口
- `api/request.js` 统一处理请求: - `src/api/request.js` 统一处理请求:
- 自动注入 `Authorization: Bearer <session_key>` - 自动注入 `Authorization: Bearer <session_key>`
- 401 自动登录并重试一次 - 401 自动登录并重试一次
## 注意事项 ## 注意事项
- `config/index.js` 里默认开发地址是局域网 IP,跨设备调试需改成可访问地址 - `src/config/index.js` 里默认开发地址是局域网 IP,跨设备调试需改成可访问地址
- 历史记录与统计页依赖后端返回格式,联调时请以 `docs/api.md` 为准 - 历史记录与统计页依赖后端返回格式,联调时请以 `docs/api.md` 为准
- 仓库中 `docs/*` 可能存在进行中的改动,提交前建议按需选择暂存文件 - 仓库中 `docs/*` 可能存在进行中的改动,提交前建议按需选择暂存文件
- GitHub Actions CI 会在 `master` / `main` 推送和 PR 时执行 `npm ci``build:h5``build:mp-weixin`
## License ## License
+1 -1
View File
@@ -15,6 +15,6 @@
</head> </head>
<body> <body>
<div id="app"><!--app-html--></div> <div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script> <script type="module" src="/src/main.js"></script>
</body> </body>
</html> </html>
+12151
View File
File diff suppressed because it is too large Load Diff
+42
View File
@@ -0,0 +1,42 @@
{
"name": "smt",
"type": "module",
"version": "1.0.0",
"private": true,
"scripts": {
"dev:h5": "uni -p h5",
"dev:mp-weixin": "uni -p mp-weixin",
"build:h5": "uni build -p h5",
"build:mp-weixin": "uni build -p mp-weixin",
"about": "uni --help"
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-4080720251210001",
"@dcloudio/uni-app-harmony": "3.0.0-4080720251210001",
"@dcloudio/uni-app-plus": "3.0.0-4080720251210001",
"@dcloudio/uni-components": "3.0.0-4080720251210001",
"@dcloudio/uni-h5": "3.0.0-4080720251210001",
"@dcloudio/uni-mp-alipay": "3.0.0-4080720251210001",
"@dcloudio/uni-mp-baidu": "3.0.0-4080720251210001",
"@dcloudio/uni-mp-harmony": "3.0.0-4080720251210001",
"@dcloudio/uni-mp-jd": "3.0.0-4080720251210001",
"@dcloudio/uni-mp-kuaishou": "3.0.0-4080720251210001",
"@dcloudio/uni-mp-lark": "3.0.0-4080720251210001",
"@dcloudio/uni-mp-qq": "3.0.0-4080720251210001",
"@dcloudio/uni-mp-toutiao": "3.0.0-4080720251210001",
"@dcloudio/uni-mp-weixin": "3.0.0-4080720251210001",
"@dcloudio/uni-mp-xhs": "3.0.0-4080720251210001",
"@dcloudio/uni-quickapp-webview": "3.0.0-4080720251210001",
"pinia": "2.1.7",
"vue": "3.4.21"
},
"devDependencies": {
"@dcloudio/types": "3.4.19",
"@dcloudio/uni-cli-shared": "3.0.0-4080720251210001",
"@dcloudio/vite-plugin-uni": "3.0.0-4080720251210001",
"@uni-helper/plugin-uni": "0.1.0",
"@uni-helper/unh": "^0.2.10",
"sass": "1.64.2",
"vite": "5.2.8"
}
}
+49 -16
View File
@@ -48,7 +48,10 @@ export default {
<style> <style>
page { page {
background: linear-gradient(to bottom, #D1FAE5 0%, #F0FDF4 45%, #FFFFFF 100%); background:
radial-gradient(circle at top left, rgba(52, 200, 160, 0.14), transparent 28%),
radial-gradient(circle at top right, rgba(255, 255, 255, 0.78), transparent 24%),
linear-gradient(180deg, #eef3f8 0%, #f5f7fb 38%, #fbfdff 100%);
color: #111827; color: #111827;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
font-size: 28rpx; font-size: 28rpx;
@@ -62,28 +65,30 @@ page {
} }
.card { .card {
background-color: #FFFFFF; background: rgba(255, 255, 255, 0.82);
border-radius: 24rpx; border-radius: 28rpx;
padding: 32rpx; padding: 32rpx;
margin-bottom: 24rpx; margin-bottom: 24rpx;
border: 2rpx solid #ECFDF3; border: 2rpx solid rgba(255, 255, 255, 0.64);
box-shadow: 0 8rpx 20rpx rgba(16, 185, 129, 0.08); box-shadow: 0 10rpx 30rpx rgba(15, 23, 42, 0.06);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.card-light { .card-light {
background-color: #F0FDF4; background: rgba(255, 255, 255, 0.62);
} }
.text-primary { .text-primary {
color: #10B981; color: #1AA37A;
} }
.text-secondary { .text-secondary {
color: #6B7280; color: #667085;
} }
.text-muted { .text-muted {
color: #9CA3AF; color: #98A2B3;
} }
.text-center { .text-center {
@@ -160,26 +165,54 @@ page {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: 96rpx; height: 96rpx;
border-radius: 48rpx; border-radius: 999rpx;
font-size: 32rpx; font-size: 32rpx;
font-weight: 500; font-weight: 600;
} }
.btn-primary { .btn-primary {
background-color: #10B981; background: linear-gradient(180deg, #32c59d 0%, #1aa37a 100%);
color: #FFFFFF; color: #FFFFFF;
box-shadow: 0 12rpx 28rpx rgba(26, 163, 122, 0.22);
} }
.btn-secondary { .btn-secondary {
background-color: #FFFFFF; background: rgba(255, 255, 255, 0.82);
color: #111827; color: #111827;
border: 2rpx solid #E5E7EB; border: 2rpx solid rgba(15, 23, 42, 0.08);
} }
.btn-outline { .btn-outline {
background-color: transparent; background-color: transparent;
color: #10B981; color: #1AA37A;
border: 2rpx solid #10B981; border: 2rpx solid rgba(26, 163, 122, 0.32);
}
.glass-card {
background: rgba(255, 255, 255, 0.68);
border: 2rpx solid rgba(255, 255, 255, 0.66);
box-shadow: 0 12rpx 32rpx rgba(15, 23, 42, 0.07);
backdrop-filter: blur(28rpx);
-webkit-backdrop-filter: blur(28rpx);
}
.surface-card {
background: rgba(255, 255, 255, 0.9);
border: 2rpx solid rgba(15, 23, 42, 0.06);
box-shadow: 0 10rpx 30rpx rgba(15, 23, 42, 0.05);
}
.pill-chip {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 12rpx 22rpx;
border-radius: 999rpx;
background: rgba(255, 255, 255, 0.72);
border: 2rpx solid rgba(255, 255, 255, 0.68);
color: #475467;
font-size: 22rpx;
font-weight: 600;
} }
.safe-area-bottom { .safe-area-bottom {
View File
View File
View File
@@ -248,7 +248,7 @@ export default {
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
background-color: rgba(0, 0, 0, 0.35); background-color: rgba(15, 23, 42, 0.22);
z-index: 9999; z-index: 9999;
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
@@ -257,12 +257,15 @@ export default {
.dialog-container { .dialog-container {
width: 100%; width: 100%;
max-height: 85vh; max-height: 85vh;
background-color: #FFFFFF; background: rgba(248, 250, 252, 0.9);
border-radius: 36rpx 36rpx 0 0; border-radius: 36rpx 36rpx 0 0;
overflow: hidden; overflow: hidden;
transform: translateY(100%); transform: translateY(100%);
transition: transform 0.3s ease-out; transition: transform 0.3s ease-out;
padding-bottom: 16rpx; padding-bottom: 16rpx;
border-top: 2rpx solid rgba(255, 255, 255, 0.72);
backdrop-filter: blur(28rpx);
-webkit-backdrop-filter: blur(28rpx);
} }
.dialog-show { .dialog-show {
@@ -272,7 +275,7 @@ export default {
.dialog-handle { .dialog-handle {
width: 80rpx; width: 80rpx;
height: 8rpx; height: 8rpx;
background-color: #E5E7EB; background-color: rgba(152, 162, 179, 0.45);
border-radius: 999rpx; border-radius: 999rpx;
margin: 16rpx auto 0; margin: 16rpx auto 0;
} }
@@ -297,9 +300,9 @@ export default {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 44rpx; font-size: 44rpx;
color: #9CA3AF; color: #98A2B3;
line-height: 1; line-height: 1;
background-color: #F3F4F6; background-color: rgba(255, 255, 255, 0.78);
border-radius: 50%; border-radius: 50%;
} }
@@ -320,10 +323,10 @@ export default {
} }
.input-card { .input-card {
background-color: #F9FAFB; background-color: rgba(255, 255, 255, 0.78);
border-radius: 24rpx; border-radius: 24rpx;
padding: 20rpx 24rpx; padding: 20rpx 24rpx;
border: 2rpx solid #F3F4F6; border: 2rpx solid rgba(255, 255, 255, 0.72);
min-height: 120rpx; min-height: 120rpx;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -392,11 +395,11 @@ export default {
} }
.section-card { .section-card {
background-color: #FFFFFF; background-color: rgba(255, 255, 255, 0.84);
border-radius: 24rpx; border-radius: 24rpx;
padding: 24rpx; padding: 24rpx;
border: 2rpx solid #F3F4F6; border: 2rpx solid rgba(255, 255, 255, 0.72);
box-shadow: 0 4rpx 12rpx rgba(15, 23, 42, 0.04); box-shadow: 0 10rpx 24rpx rgba(15, 23, 42, 0.05);
margin-bottom: 24rpx; margin-bottom: 24rpx;
} }
@@ -448,7 +451,7 @@ export default {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 20rpx; gap: 20rpx;
background-color: #F8FAFC; background-color: rgba(247, 249, 252, 0.92);
padding: 12rpx 16rpx; padding: 12rpx 16rpx;
border-radius: 999rpx; border-radius: 999rpx;
border: 2rpx solid #F1F5F9; border: 2rpx solid #F1F5F9;
@@ -461,12 +464,12 @@ export default {
width: 56rpx; width: 56rpx;
height: 56rpx; height: 56rpx;
border-radius: 50%; border-radius: 50%;
background-color: #E8FFF1; background-color: rgba(255, 255, 255, 0.9);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 36rpx; font-size: 36rpx;
color: #22C55E; color: #1aa37a;
font-weight: 600; font-weight: 600;
} }
@@ -488,8 +491,8 @@ export default {
.level-badge { .level-badge {
padding: 8rpx 18rpx; padding: 8rpx 18rpx;
border-radius: 999rpx; border-radius: 999rpx;
background-color: #DCFCE7; background-color: rgba(52, 200, 160, 0.14);
color: #22C55E; color: #1aa37a;
font-size: 24rpx; font-size: 24rpx;
font-weight: 600; font-weight: 600;
} }
@@ -515,16 +518,16 @@ export default {
.remark-card { .remark-card {
margin-top: 16rpx; margin-top: 16rpx;
background-color: #F9FAFB; background-color: rgba(255, 255, 255, 0.78);
border-radius: 20rpx; border-radius: 20rpx;
padding: 8rpx; padding: 8rpx;
border: 2rpx solid #F3F4F6; border: 2rpx solid rgba(255, 255, 255, 0.72);
} }
.form-textarea { .form-textarea {
width: 100%; width: 100%;
min-height: 180rpx; min-height: 180rpx;
background-color: #F9FAFB; background-color: rgba(255, 255, 255, 0);
border-radius: 16rpx; border-radius: 16rpx;
padding: 24rpx 20rpx; padding: 24rpx 20rpx;
font-size: 28rpx; font-size: 28rpx;
@@ -540,19 +543,19 @@ export default {
.dialog-btn-primary { .dialog-btn-primary {
height: 96rpx; height: 96rpx;
border-radius: 32rpx; border-radius: 32rpx;
background-color: #22C55E; background: linear-gradient(180deg, #32c59d 0%, #1aa37a 100%);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 16rpx; gap: 16rpx;
box-shadow: 0 10rpx 24rpx rgba(34, 197, 94, 0.3); box-shadow: 0 12rpx 28rpx rgba(26, 163, 122, 0.22);
} }
.btn-icon { .btn-icon {
width: 44rpx; width: 44rpx;
height: 44rpx; height: 44rpx;
border-radius: 50%; border-radius: 50%;
background-color: #0F172A; background-color: rgba(255, 255, 255, 0.24);
position: relative; position: relative;
} }
@@ -571,6 +574,6 @@ export default {
.btn-text { .btn-text {
font-size: 30rpx; font-size: 30rpx;
font-weight: 600; font-weight: 600;
color: #0F172A; color: #FFFFFF;
} }
</style> </style>
View File
View File
+8 -8
View File
@@ -51,7 +51,7 @@
{ {
"path": "pages/profile/index", "path": "pages/profile/index",
"style": { "style": {
"navigationBarTitleText": "个人中心" "navigationStyle": "custom"
} }
}, },
{ {
@@ -70,15 +70,15 @@
"globalStyle": { "globalStyle": {
"navigationBarTextStyle": "black", "navigationBarTextStyle": "black",
"navigationBarTitleText": "戒烟助手", "navigationBarTitleText": "戒烟助手",
"navigationBarBackgroundColor": "#F0FDF4", "navigationBarBackgroundColor": "#F5F7FB",
"backgroundColor": "#F0FDF4", "backgroundColor": "#F5F7FB",
"backgroundColorTop": "#D1FAE5", "backgroundColorTop": "#EEF3F8",
"backgroundColorBottom": "#FFFFFF" "backgroundColorBottom": "#FBFDFF"
}, },
"tabBar": { "tabBar": {
"color": "#9CA3AF", "color": "#98A2B3",
"selectedColor": "#10B981", "selectedColor": "#1AA37A",
"backgroundColor": "#FFFFFF", "backgroundColor": "#F9FBFD",
"borderStyle": "white", "borderStyle": "white",
"list": [ "list": [
{ {
+60 -24
View File
@@ -1,5 +1,7 @@
<template> <template>
<view class="page"> <view class="page">
<view class="page-glow page-glow-a"></view>
<view class="page-glow page-glow-b"></view>
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view> <view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
<view class="container"> <view class="container">
<view class="hero-card"> <view class="hero-card">
@@ -20,7 +22,7 @@
</view> </view>
<view v-if="suggestedClock" class="suggested-pill"> <view v-if="suggestedClock" class="suggested-pill">
<text class="suggested-pill-label"> 下次建议</text> <text class="suggested-pill-label">下次建议</text>
<text class="suggested-pill-value">{{ suggestedClock }}</text> <text class="suggested-pill-value">{{ suggestedClock }}</text>
</view> </view>
@@ -211,25 +213,58 @@ onShow(async () => {
<style scoped> <style scoped>
.page { .page {
min-height: 100vh; min-height: 100vh;
background: linear-gradient(to bottom, #D1FAE5 0%, #F0FDF4 45%, #FFFFFF 100%); position: relative;
background:
radial-gradient(circle at top left, rgba(52, 200, 160, 0.16), transparent 30%),
radial-gradient(circle at top right, rgba(255, 255, 255, 0.92), transparent 24%),
linear-gradient(180deg, #edf2f8 0%, #f5f7fb 38%, #fbfdff 100%);
overflow: hidden;
} }
.status-bar { .status-bar {
background: linear-gradient(to bottom, #D1FAE5, #E9FDF2); background: transparent;
}
.page-glow {
position: absolute;
border-radius: 50%;
filter: blur(24rpx);
opacity: 0.72;
pointer-events: none;
}
.page-glow-a {
top: 80rpx;
left: -140rpx;
width: 360rpx;
height: 360rpx;
background: rgba(52, 200, 160, 0.14);
}
.page-glow-b {
top: 320rpx;
right: -120rpx;
width: 320rpx;
height: 320rpx;
background: rgba(255, 255, 255, 0.9);
} }
.container { .container {
padding: 24rpx 32rpx 80rpx; padding: 24rpx 32rpx 80rpx;
position: relative;
z-index: 1;
} }
.hero-card, .hero-card,
.section-card, .section-card,
.advice-card, .advice-card,
.empty-card { .empty-card {
background-color: #FFFFFF; background: rgba(255, 255, 255, 0.8);
border-radius: 28rpx; border-radius: 32rpx;
border: 2rpx solid #D9FBE7; border: 2rpx solid rgba(255, 255, 255, 0.66);
box-shadow: 0 10rpx 24rpx rgba(16, 185, 129, 0.08); box-shadow: 0 16rpx 36rpx rgba(15, 23, 42, 0.06);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.hero-card { .hero-card {
@@ -239,7 +274,7 @@ onShow(async () => {
.hero-label { .hero-label {
font-size: 22rpx; font-size: 22rpx;
color: #059669; color: #1a7f61;
display: block; display: block;
margin-bottom: 12rpx; margin-bottom: 12rpx;
} }
@@ -254,7 +289,7 @@ onShow(async () => {
.hero-desc { .hero-desc {
font-size: 24rpx; font-size: 24rpx;
color: #6B7280; color: #667085;
line-height: 1.6; line-height: 1.6;
} }
@@ -279,7 +314,7 @@ onShow(async () => {
.section-subtitle { .section-subtitle {
font-size: 22rpx; font-size: 22rpx;
color: #6B7280; color: #667085;
display: block; display: block;
margin-top: 8rpx; margin-top: 8rpx;
} }
@@ -287,7 +322,8 @@ onShow(async () => {
.primary-btn { .primary-btn {
padding: 10rpx 24rpx; padding: 10rpx 24rpx;
border-radius: 999rpx; border-radius: 999rpx;
background: linear-gradient(135deg, #34D399, #10B981); background: linear-gradient(180deg, #32c59d 0%, #1aa37a 100%);
box-shadow: 0 12rpx 28rpx rgba(26, 163, 122, 0.22);
} }
.primary-btn-text { .primary-btn-text {
@@ -302,14 +338,14 @@ onShow(async () => {
gap: 12rpx; gap: 12rpx;
padding: 14rpx 22rpx; padding: 14rpx 22rpx;
border-radius: 999rpx; border-radius: 999rpx;
background: #ECFDF5; background: rgba(255, 255, 255, 0.88);
border: 2rpx solid #D1FAE5; border: 2rpx solid rgba(255, 255, 255, 0.72);
margin-bottom: 24rpx; margin-bottom: 24rpx;
} }
.suggested-pill-label { .suggested-pill-label {
font-size: 22rpx; font-size: 22rpx;
color: #059669; color: #1a7f61;
} }
.suggested-pill-value { .suggested-pill-value {
@@ -338,7 +374,7 @@ onShow(async () => {
width: 20rpx; width: 20rpx;
height: 20rpx; height: 20rpx;
border-radius: 50%; border-radius: 50%;
background-color: #D1FAE5; background-color: rgba(152, 162, 179, 0.4);
margin-bottom: 8rpx; margin-bottom: 8rpx;
} }
@@ -348,12 +384,12 @@ onShow(async () => {
left: calc(50% + 18rpx); left: calc(50% + 18rpx);
right: -50%; right: -50%;
height: 2rpx; height: 2rpx;
background-color: #D1FAE5; background-color: rgba(152, 162, 179, 0.32);
} }
.timeline-time { .timeline-time {
font-size: 22rpx; font-size: 22rpx;
color: #9CA3AF; color: #98A2B3;
} }
.timeline-node-past .timeline-dot { .timeline-node-past .timeline-dot {
@@ -365,37 +401,37 @@ onShow(async () => {
} }
.timeline-node-current .timeline-dot { .timeline-node-current .timeline-dot {
background-color: #10B981; background-color: #1aa37a;
width: 24rpx; width: 24rpx;
height: 24rpx; height: 24rpx;
box-shadow: 0 0 12rpx rgba(16, 185, 129, 0.5); box-shadow: 0 0 12rpx rgba(16, 185, 129, 0.5);
} }
.timeline-node-current .timeline-time { .timeline-node-current .timeline-time {
color: #059669; color: #1a7f61;
font-weight: 600; font-weight: 600;
} }
.timeline-node-future .timeline-time { .timeline-node-future .timeline-time {
color: #6B7280; color: #667085;
} }
.advice-card { .advice-card {
padding: 22rpx; padding: 22rpx;
background-color: #F0FDF4; background-color: rgba(247, 249, 252, 0.92);
} }
.advice-title { .advice-title {
font-size: 24rpx; font-size: 24rpx;
font-weight: 600; font-weight: 600;
color: #059669; color: #1a7f61;
display: block; display: block;
margin-bottom: 8rpx; margin-bottom: 8rpx;
} }
.advice-text { .advice-text {
font-size: 24rpx; font-size: 24rpx;
color: #374151; color: #344054;
line-height: 1.7; line-height: 1.7;
} }
@@ -413,7 +449,7 @@ onShow(async () => {
.empty-desc { .empty-desc {
font-size: 24rpx; font-size: 24rpx;
color: #6B7280; color: #667085;
line-height: 1.6; line-height: 1.6;
} }
</style> </style>
@@ -1,5 +1,7 @@
<template> <template>
<view class="page"> <view class="page">
<view class="page-glow page-glow-a"></view>
<view class="page-glow page-glow-b"></view>
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view> <view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
<view class="container"> <view class="container">
<view class="hero-card"> <view class="hero-card">
@@ -233,15 +235,46 @@ onShow(async () => {
<style scoped> <style scoped>
.page { .page {
min-height: 100vh; min-height: 100vh;
background: linear-gradient(to bottom, #D1FAE5 0%, #F0FDF4 45%, #FFFFFF 100%); position: relative;
background:
radial-gradient(circle at top left, rgba(52, 200, 160, 0.16), transparent 30%),
radial-gradient(circle at top right, rgba(255, 255, 255, 0.92), transparent 24%),
linear-gradient(180deg, #edf2f8 0%, #f5f7fb 38%, #fbfdff 100%);
overflow: hidden;
} }
.status-bar { .status-bar {
background: linear-gradient(to bottom, #D1FAE5, #E9FDF2); background: transparent;
}
.page-glow {
position: absolute;
border-radius: 50%;
filter: blur(24rpx);
opacity: 0.72;
pointer-events: none;
}
.page-glow-a {
top: 80rpx;
left: -140rpx;
width: 360rpx;
height: 360rpx;
background: rgba(52, 200, 160, 0.14);
}
.page-glow-b {
top: 320rpx;
right: -120rpx;
width: 320rpx;
height: 320rpx;
background: rgba(255, 255, 255, 0.9);
} }
.container { .container {
padding: 24rpx 32rpx 80rpx; padding: 24rpx 32rpx 80rpx;
position: relative;
z-index: 1;
} }
.hero-card, .hero-card,
@@ -249,10 +282,12 @@ onShow(async () => {
.summary-card, .summary-card,
.highlights-card, .highlights-card,
.suggestion-card { .suggestion-card {
background-color: #FFFFFF; background: rgba(255, 255, 255, 0.8);
border-radius: 28rpx; border-radius: 32rpx;
border: 2rpx solid #D9FBE7; border: 2rpx solid rgba(255, 255, 255, 0.66);
box-shadow: 0 10rpx 24rpx rgba(16, 185, 129, 0.08); box-shadow: 0 16rpx 36rpx rgba(15, 23, 42, 0.06);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.hero-card { .hero-card {
@@ -262,7 +297,7 @@ onShow(async () => {
.hero-label { .hero-label {
font-size: 22rpx; font-size: 22rpx;
color: #059669; color: #1a7f61;
display: block; display: block;
margin-bottom: 12rpx; margin-bottom: 12rpx;
} }
@@ -277,7 +312,7 @@ onShow(async () => {
.hero-desc { .hero-desc {
font-size: 24rpx; font-size: 24rpx;
color: #6B7280; color: #667085;
line-height: 1.6; line-height: 1.6;
} }
@@ -293,13 +328,13 @@ onShow(async () => {
flex: 1; flex: 1;
padding: 18rpx 22rpx; padding: 18rpx 22rpx;
border-radius: 20rpx; border-radius: 20rpx;
background: #F0FDF4; background: rgba(247, 249, 252, 0.92);
border: 2rpx solid #D1FAE5; border: 2rpx solid rgba(15, 23, 42, 0.06);
} }
.date-picker-label { .date-picker-label {
font-size: 22rpx; font-size: 22rpx;
color: #6B7280; color: #667085;
display: block; display: block;
margin-bottom: 6rpx; margin-bottom: 6rpx;
} }
@@ -313,7 +348,8 @@ onShow(async () => {
.primary-btn { .primary-btn {
padding: 14rpx 24rpx; padding: 14rpx 24rpx;
border-radius: 999rpx; border-radius: 999rpx;
background: linear-gradient(135deg, #34D399, #10B981); background: linear-gradient(180deg, #32c59d 0%, #1aa37a 100%);
box-shadow: 0 12rpx 28rpx rgba(26, 163, 122, 0.22);
} }
.primary-btn-text { .primary-btn-text {
@@ -333,12 +369,12 @@ onShow(async () => {
.loading-text { .loading-text {
font-size: 24rpx; font-size: 24rpx;
color: #6B7280; color: #667085;
} }
.summary-date { .summary-date {
font-size: 22rpx; font-size: 22rpx;
color: #059669; color: #1a7f61;
display: block; display: block;
margin-bottom: 12rpx; margin-bottom: 12rpx;
} }
@@ -354,13 +390,13 @@ onShow(async () => {
.suggestion-card { .suggestion-card {
margin-top: 20rpx; margin-top: 20rpx;
padding: 22rpx; padding: 22rpx;
background-color: #F9FFFB; background-color: rgba(247, 249, 252, 0.92);
} }
.block-title { .block-title {
font-size: 24rpx; font-size: 24rpx;
font-weight: 700; font-weight: 700;
color: #059669; color: #1a7f61;
display: block; display: block;
margin-bottom: 10rpx; margin-bottom: 10rpx;
} }
@@ -378,7 +414,7 @@ onShow(async () => {
.highlight-dot { .highlight-dot {
font-size: 28rpx; font-size: 28rpx;
color: #10B981; color: #1aa37a;
font-weight: 700; font-weight: 700;
line-height: 1.5; line-height: 1.5;
} }
@@ -387,7 +423,7 @@ onShow(async () => {
.suggestion-text, .suggestion-text,
.empty-desc { .empty-desc {
font-size: 24rpx; font-size: 24rpx;
color: #374151; color: #344054;
line-height: 1.6; line-height: 1.6;
} }
@@ -1,5 +1,7 @@
<template> <template>
<view class="page"> <view class="page">
<view class="page-glow page-glow-a"></view>
<view class="page-glow page-glow-b"></view>
<view class="nav-placeholder" :style="{ height: navBarHeight + 'px' }"></view> <view class="nav-placeholder" :style="{ height: navBarHeight + 'px' }"></view>
<view v-if="loading" class="skeleton"> <view v-if="loading" class="skeleton">
@@ -14,21 +16,25 @@
<view v-else class="dashboard"> <view v-else class="dashboard">
<view class="header-card"> <view class="header-card">
<view class="user-info"> <view class="header-copy">
<image class="avatar" :src="userAvatar" mode="aspectFill"></image> <text class="header-eyebrow">SMT</text>
<view class="greeting"> <text class="greeting-text">{{ greetingTitle }}</text>
<text class="greeting-text">{{ greetingTitle }}</text> <text class="greeting-sub">{{ greetingSubtitle }}</text>
<text class="greeting-sub">{{ greetingSubtitle }}</text>
</view>
</view> </view>
<view class="mode-chip" :class="isQuitMode ? 'mode-chip-quit' : 'mode-chip-record'"> <view class="header-side">
{{ isQuitMode ? '戒烟模式' : '记录模式' }} <image class="avatar" :src="userAvatar" mode="aspectFill"></image>
<view class="mode-chip" :class="isQuitMode ? 'mode-chip-quit' : 'mode-chip-record'">
{{ isQuitMode ? '戒烟模式' : '记录模式' }}
</view>
</view> </view>
</view> </view>
<view v-if="isQuitMode"> <view v-if="isQuitMode">
<view class="hero-card hero-card-quit"> <view class="hero-card hero-card-quit">
<text class="hero-label">已坚持</text> <view class="hero-meta-row">
<text class="hero-label">已坚持</text>
<text class="hero-inline-chip">{{ todayChecked ? '今日已打卡' : '今日待打卡' }}</text>
</view>
<view class="hero-value-row"> <view class="hero-value-row">
<text class="hero-value">{{ quitDays }}</text> <text class="hero-value">{{ quitDays }}</text>
<text class="hero-unit"></text> <text class="hero-unit"></text>
@@ -37,7 +43,7 @@
</view> </view>
<view class="primary-action" :class="{ 'primary-action-done': todayChecked }" @tap="handleQuitCheckin"> <view class="primary-action" :class="{ 'primary-action-done': todayChecked }" @tap="handleQuitCheckin">
<text class="primary-action-icon">{{ todayChecked ? '✓' : '🔥' }}</text> <view class="primary-action-icon">{{ todayChecked ? '✓' : '' }}</view>
<text class="primary-action-title">{{ todayChecked ? '今日已打卡' : '今天没抽,去打卡' }}</text> <text class="primary-action-title">{{ todayChecked ? '今日已打卡' : '今天没抽,去打卡' }}</text>
<text class="primary-action-desc">{{ todayChecked ? `已于 ${todayCheckinTime} 记录` : '把今天记成无烟的一天' }}</text> <text class="primary-action-desc">{{ todayChecked ? `已于 ${todayCheckinTime} 记录` : '把今天记成无烟的一天' }}</text>
</view> </view>
@@ -63,7 +69,10 @@
<view v-else> <view v-else>
<view class="hero-card hero-card-record"> <view class="hero-card hero-card-record">
<text class="hero-label">距上次抽烟</text> <view class="hero-meta-row">
<text class="hero-label">距上次抽烟</text>
<text v-if="nextSmokeTimeText" class="hero-inline-chip">建议 {{ nextSmokeTimeText }}</text>
</view>
<text class="hero-value hero-value-time">{{ timerDisplay }}</text> <text class="hero-value hero-value-time">{{ timerDisplay }}</text>
<text class="hero-sub">{{ nextSmokeTimeText ? `下次建议:${nextSmokeTimeText}` : '先把今天的抽烟情况记下来' }}</text> <text class="hero-sub">{{ nextSmokeTimeText ? `下次建议:${nextSmokeTimeText}` : '先把今天的抽烟情况记下来' }}</text>
</view> </view>
@@ -83,12 +92,12 @@
<view class="action-row"> <view class="action-row">
<view class="action-btn action-btn-record" @tap="openSmokeDialog"> <view class="action-btn action-btn-record" @tap="openSmokeDialog">
<text class="action-icon">🚬</text> <view class="action-icon"></view>
<text class="action-title">记录抽烟</text> <text class="action-title">记录抽烟</text>
<text class="action-desc">补记这一根</text> <text class="action-desc">补记这一根</text>
</view> </view>
<view class="action-btn action-btn-resist" @tap="openResistedDialog"> <view class="action-btn action-btn-resist" @tap="openResistedDialog">
<text class="action-icon">💪</text> <view class="action-icon"></view>
<text class="action-title">想抽忍住了</text> <text class="action-title">想抽忍住了</text>
<text class="action-desc">记一次成功抵抗</text> <text class="action-desc">记一次成功抵抗</text>
</view> </view>
@@ -461,18 +470,48 @@ onShareAppMessage(() => {
<style scoped> <style scoped>
.page { .page {
min-height: 100vh; min-height: 100vh;
position: relative;
background: background:
radial-gradient(circle at top left, rgba(16, 185, 129, 0.16), transparent 36%), radial-gradient(circle at top left, rgba(52, 200, 160, 0.18), transparent 34%),
linear-gradient(180deg, #ecfdf5 0%, #f0fdf4 42%, #ffffff 100%); radial-gradient(circle at top right, rgba(255, 255, 255, 0.9), transparent 26%),
linear-gradient(180deg, #edf2f8 0%, #f5f7fb 38%, #fbfdff 100%);
box-sizing: border-box; box-sizing: border-box;
overflow: hidden;
} }
.nav-placeholder { .page-glow {
background: transparent; position: absolute;
border-radius: 50%;
filter: blur(24rpx);
opacity: 0.7;
pointer-events: none;
}
.page-glow-a {
top: 108rpx;
left: -140rpx;
width: 360rpx;
height: 360rpx;
background: rgba(52, 200, 160, 0.16);
}
.page-glow-b {
top: 280rpx;
right: -120rpx;
width: 320rpx;
height: 320rpx;
background: rgba(255, 255, 255, 0.86);
}
.nav-placeholder,
.dashboard,
.skeleton {
position: relative;
z-index: 1;
} }
.dashboard { .dashboard {
padding: 24rpx 24rpx 160rpx; padding: 24rpx 24rpx 168rpx;
} }
.header-card, .header-card,
@@ -482,37 +521,57 @@ onShareAppMessage(() => {
.note-card, .note-card,
.action-btn, .action-btn,
.skeleton-card { .skeleton-card {
background: rgba(255, 255, 255, 0.88); background: rgba(255, 255, 255, 0.76);
border-radius: 28rpx; border-radius: 32rpx;
border: 2rpx solid rgba(236, 253, 245, 0.9); border: 2rpx solid rgba(255, 255, 255, 0.62);
box-shadow: 0 16rpx 42rpx rgba(15, 23, 42, 0.08); box-shadow: 0 16rpx 42rpx rgba(15, 23, 42, 0.08);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.header-card { .header-card {
display: flex; display: flex;
align-items: center; align-items: flex-start;
justify-content: space-between; justify-content: space-between;
padding: 28rpx; padding: 30rpx;
margin-bottom: 24rpx; margin-bottom: 28rpx;
}
.user-info {
display: flex;
align-items: center;
gap: 20rpx; gap: 20rpx;
} }
.header-copy {
flex: 1;
display: flex;
flex-direction: column;
}
.header-side {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 14rpx;
}
.header-eyebrow {
font-size: 20rpx;
font-weight: 700;
letter-spacing: 4rpx;
text-transform: uppercase;
color: #98a2b3;
}
.avatar { .avatar {
width: 88rpx; width: 92rpx;
height: 88rpx; height: 92rpx;
border-radius: 50%; border-radius: 50%;
border: 4rpx solid #a7f3d0; border: 4rpx solid rgba(255, 255, 255, 0.82);
background: #ecfdf5; background: rgba(255, 255, 255, 0.72);
} }
.greeting-text { .greeting-text {
display: block; display: block;
font-size: 34rpx; margin-top: 10rpx;
font-size: 42rpx;
line-height: 1.18;
font-weight: 700; font-weight: 700;
color: #111827; color: #111827;
} }
@@ -522,7 +581,7 @@ onShareAppMessage(() => {
margin-top: 8rpx; margin-top: 8rpx;
font-size: 24rpx; font-size: 24rpx;
line-height: 1.5; line-height: 1.5;
color: #6b7280; color: #667085;
} }
.mode-chip { .mode-chip {
@@ -530,38 +589,56 @@ onShareAppMessage(() => {
border-radius: 999rpx; border-radius: 999rpx;
font-size: 22rpx; font-size: 22rpx;
font-weight: 600; font-weight: 600;
border: 2rpx solid rgba(255, 255, 255, 0.64);
} }
.mode-chip-quit { .mode-chip-quit {
background: rgba(16, 185, 129, 0.12); background: rgba(52, 200, 160, 0.14);
color: #047857; color: #17795c;
} }
.mode-chip-record { .mode-chip-record {
background: rgba(248, 113, 113, 0.12); background: rgba(245, 158, 11, 0.12);
color: #b91c1c; color: #b56b09;
} }
.hero-card { .hero-card {
padding: 34rpx 30rpx; padding: 36rpx 32rpx;
margin-bottom: 24rpx; margin-bottom: 24rpx;
} }
.hero-card-quit { .hero-card-quit {
background: linear-gradient(135deg, rgba(220, 252, 231, 0.92), rgba(255, 255, 255, 0.92)); background: linear-gradient(135deg, rgba(246, 255, 251, 0.88), rgba(255, 255, 255, 0.7));
} }
.hero-card-record { .hero-card-record {
background: linear-gradient(135deg, rgba(254, 242, 242, 0.92), rgba(255, 255, 255, 0.92)); background: linear-gradient(135deg, rgba(255, 250, 243, 0.88), rgba(255, 255, 255, 0.7));
}
.hero-meta-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16rpx;
} }
.hero-label { .hero-label {
display: block; display: block;
font-size: 24rpx; font-size: 24rpx;
color: #6b7280; color: #667085;
letter-spacing: 2rpx; letter-spacing: 2rpx;
} }
.hero-inline-chip {
padding: 10rpx 18rpx;
border-radius: 999rpx;
background: rgba(255, 255, 255, 0.82);
border: 2rpx solid rgba(255, 255, 255, 0.7);
font-size: 22rpx;
font-weight: 600;
color: #475467;
}
.hero-value-row { .hero-value-row {
display: flex; display: flex;
align-items: baseline; align-items: baseline;
@@ -573,15 +650,15 @@ onShareAppMessage(() => {
.hero-value-time { .hero-value-time {
display: block; display: block;
margin-top: 18rpx; margin-top: 18rpx;
font-size: 84rpx; font-size: 82rpx;
line-height: 1; line-height: 1;
font-weight: 800; font-weight: 700;
color: #111827; color: #111827;
} }
.hero-unit { .hero-unit {
font-size: 28rpx; font-size: 28rpx;
color: #4b5563; color: #667085;
} }
.hero-sub { .hero-sub {
@@ -589,40 +666,50 @@ onShareAppMessage(() => {
margin-top: 18rpx; margin-top: 18rpx;
font-size: 26rpx; font-size: 26rpx;
line-height: 1.6; line-height: 1.6;
color: #4b5563; color: #475467;
} }
.primary-action { .primary-action {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: flex-start;
padding: 38rpx 30rpx; padding: 36rpx 32rpx;
margin-bottom: 24rpx; margin-bottom: 24rpx;
background: linear-gradient(135deg, #10b981 0%, #34d399 100%); background: linear-gradient(180deg, #32c59d 0%, #1aa37a 100%);
color: #ffffff; color: #ffffff;
box-shadow: 0 16rpx 36rpx rgba(26, 163, 122, 0.24);
} }
.primary-action-done { .primary-action-done {
background: linear-gradient(135deg, #059669 0%, #10b981 100%); background: linear-gradient(180deg, #1f9f7a 0%, #188564 100%);
} }
.primary-action-icon { .primary-action-icon {
font-size: 58rpx; width: 68rpx;
height: 68rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.18);
border: 2rpx solid rgba(255, 255, 255, 0.22);
font-size: 30rpx;
font-weight: 700; font-weight: 700;
} }
.primary-action-title { .primary-action-title {
display: block; display: block;
margin-top: 12rpx; margin-top: 18rpx;
font-size: 34rpx; font-size: 34rpx;
font-weight: 700; font-weight: 700;
} }
.primary-action-desc { .primary-action-desc {
display: block; display: block;
margin-top: 12rpx; margin-top: 10rpx;
font-size: 24rpx; font-size: 24rpx;
opacity: 0.88; line-height: 1.55;
opacity: 0.9;
} }
.stats-grid { .stats-grid {
@@ -633,28 +720,31 @@ onShareAppMessage(() => {
} }
.stat-card { .stat-card {
padding: 28rpx 24rpx; padding: 28rpx 24rpx 26rpx;
background: rgba(255, 255, 255, 0.86);
border: 2rpx solid rgba(15, 23, 42, 0.05);
box-shadow: 0 10rpx 30rpx rgba(15, 23, 42, 0.05);
} }
.stat-label { .stat-label {
display: block; display: block;
font-size: 24rpx; font-size: 24rpx;
color: #6b7280; color: #667085;
} }
.stat-value { .stat-value {
display: block; display: block;
margin-top: 18rpx; margin-top: 18rpx;
font-size: 48rpx; font-size: 46rpx;
line-height: 1.1; line-height: 1.1;
font-weight: 800; font-weight: 700;
color: #111827; color: #111827;
} }
.stat-unit { .stat-unit {
font-size: 26rpx; font-size: 26rpx;
font-weight: 600; font-weight: 600;
color: #6b7280; color: #667085;
} }
.stat-desc { .stat-desc {
@@ -662,18 +752,22 @@ onShareAppMessage(() => {
margin-top: 16rpx; margin-top: 16rpx;
font-size: 23rpx; font-size: 23rpx;
line-height: 1.5; line-height: 1.5;
color: #6b7280; color: #667085;
} }
.note-card { .note-card {
padding: 28rpx; padding: 28rpx;
background: rgba(255, 255, 255, 0.84);
border: 2rpx solid rgba(15, 23, 42, 0.05);
box-shadow: 0 10rpx 30rpx rgba(15, 23, 42, 0.05);
} }
.note-title { .note-title {
display: block; display: block;
font-size: 24rpx; font-size: 24rpx;
color: #047857; color: #1a7f61;
font-weight: 700; font-weight: 700;
letter-spacing: 1rpx;
} }
.note-text { .note-text {
@@ -681,7 +775,7 @@ onShareAppMessage(() => {
margin-top: 14rpx; margin-top: 14rpx;
font-size: 26rpx; font-size: 26rpx;
line-height: 1.7; line-height: 1.7;
color: #374151; color: #344054;
} }
.action-row { .action-row {
@@ -691,19 +785,32 @@ onShareAppMessage(() => {
} }
.action-btn { .action-btn {
padding: 30rpx 24rpx; padding: 30rpx 24rpx 28rpx;
background: rgba(255, 255, 255, 0.86);
border: 2rpx solid rgba(15, 23, 42, 0.05);
box-shadow: 0 10rpx 30rpx rgba(15, 23, 42, 0.05);
} }
.action-btn-record { .action-btn-record {
background: linear-gradient(135deg, rgba(254, 226, 226, 0.92), rgba(255, 255, 255, 0.92)); background: linear-gradient(135deg, rgba(255, 248, 239, 0.95), rgba(255, 255, 255, 0.88));
} }
.action-btn-resist { .action-btn-resist {
background: linear-gradient(135deg, rgba(220, 252, 231, 0.92), rgba(255, 255, 255, 0.92)); background: linear-gradient(135deg, rgba(245, 255, 251, 0.95), rgba(255, 255, 255, 0.88));
} }
.action-icon { .action-icon {
font-size: 40rpx; width: 60rpx;
height: 60rpx;
border-radius: 18rpx;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.9);
border: 2rpx solid rgba(255, 255, 255, 0.76);
font-size: 28rpx;
font-weight: 700;
color: #111827;
} }
.action-title { .action-title {
@@ -734,7 +841,7 @@ onShareAppMessage(() => {
.skeleton-card { .skeleton-card {
height: 180rpx; height: 180rpx;
background: linear-gradient(90deg, #d1fae5 25%, #ecfdf5 50%, #d1fae5 75%); background: linear-gradient(90deg, rgba(232, 238, 245, 0.92) 25%, rgba(255, 255, 255, 0.98) 50%, rgba(232, 238, 245, 0.92) 75%);
background-size: 200% 100%; background-size: 200% 100%;
animation: shimmer 1.5s infinite; animation: shimmer 1.5s infinite;
margin-bottom: 20rpx; margin-bottom: 20rpx;
+125 -34
View File
@@ -1,5 +1,13 @@
<template> <template>
<view class="page"> <view class="page">
<view class="page-glow page-glow-a"></view>
<view class="page-glow page-glow-b"></view>
<view class="page-header">
<text class="page-eyebrow">History</text>
<text class="page-title">记录历史</text>
<text class="page-subtitle">按时间查看抽烟和忍住记录</text>
</view>
<!-- 筛选标签 --> <!-- 筛选标签 -->
<view class="filters"> <view class="filters">
<view class="tabs"> <view class="tabs">
@@ -48,8 +56,8 @@
<view v-for="log in group" :key="log.id" class="log-card" :class="log.type === 'resisted' ? 'log-card-resisted' : 'log-card-smoke'"> <view v-for="log in group" :key="log.id" class="log-card" :class="log.type === 'resisted' ? 'log-card-resisted' : 'log-card-smoke'">
<view class="log-bar"></view> <view class="log-bar"></view>
<view class="log-icon" :class="log.type === 'resisted' ? 'icon-resisted' : 'icon-smoke'"> <view class="log-icon" :class="log.type === 'resisted' ? 'icon-resisted' : 'icon-smoke'">
<text v-if="log.type === 'resisted'">🌿</text> <text v-if="log.type === 'resisted'"></text>
<text v-else>🚬</text> <text v-else></text>
</view> </view>
<view class="log-main"> <view class="log-main">
<view class="log-top"> <view class="log-top">
@@ -91,7 +99,7 @@
<!-- 空状态 --> <!-- 空状态 -->
<view v-else class="empty-state"> <view v-else class="empty-state">
<text class="empty-icon">📝</text> <text class="empty-icon"></text>
<text class="empty-text">暂无记录</text> <text class="empty-text">暂无记录</text>
<text class="empty-hint">点击右下角按钮开始记录</text> <text class="empty-hint">点击右下角按钮开始记录</text>
</view> </view>
@@ -297,25 +305,94 @@ onShareAppMessage(() => {
<style scoped> <style scoped>
.page { .page {
min-height: 100vh; min-height: 100vh;
background: linear-gradient(to bottom, #D1FAE5 0%, #F3FFF8 45%, #FFFFFF 100%); position: relative;
background:
radial-gradient(circle at top left, rgba(52, 200, 160, 0.16), transparent 30%),
radial-gradient(circle at top right, rgba(255, 255, 255, 0.92), transparent 24%),
linear-gradient(180deg, #edf2f8 0%, #f5f7fb 38%, #fbfdff 100%);
box-sizing: border-box; box-sizing: border-box;
overflow: hidden;
}
.page-glow {
position: absolute;
border-radius: 50%;
filter: blur(24rpx);
opacity: 0.72;
pointer-events: none;
}
.page-glow-a {
top: 60rpx;
left: -140rpx;
width: 360rpx;
height: 360rpx;
background: rgba(52, 200, 160, 0.14);
}
.page-glow-b {
top: 360rpx;
right: -120rpx;
width: 320rpx;
height: 320rpx;
background: rgba(255, 255, 255, 0.9);
}
.page-header,
.filters,
.scroll-container,
.fab {
position: relative;
z-index: 1;
}
.page-header {
padding: 32rpx 32rpx 8rpx;
}
.page-eyebrow {
display: block;
font-size: 20rpx;
font-weight: 700;
letter-spacing: 4rpx;
text-transform: uppercase;
color: #98a2b3;
}
.page-title {
display: block;
margin-top: 10rpx;
font-size: 42rpx;
line-height: 1.18;
font-weight: 700;
color: #111827;
}
.page-subtitle {
display: block;
margin-top: 8rpx;
font-size: 24rpx;
line-height: 1.5;
color: #667085;
} }
.filters { .filters {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 16rpx; gap: 16rpx;
padding: 24rpx 32rpx 8rpx; padding: 16rpx 32rpx 8rpx;
} }
.tabs { .tabs {
display: flex; display: flex;
flex: 1; flex: 1;
background-color: #FFFFFF; background: rgba(255, 255, 255, 0.76);
border-radius: 20rpx; border-radius: 24rpx;
padding: 6rpx; padding: 6rpx;
box-shadow: 0 8rpx 20rpx rgba(16, 185, 129, 0.08); box-shadow: 0 12rpx 28rpx rgba(15, 23, 42, 0.06);
border: 2rpx solid #ECFDF3; border: 2rpx solid rgba(255, 255, 255, 0.66);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.tab { .tab {
@@ -330,9 +407,9 @@ onShareAppMessage(() => {
} }
.tab-active { .tab-active {
background-color: #10B981; background: rgba(255, 255, 255, 0.92);
color: #0B2F23; color: #111827;
box-shadow: 0 6rpx 16rpx rgba(16, 185, 129, 0.25); box-shadow: 0 8rpx 18rpx rgba(15, 23, 42, 0.06);
} }
.filter-btn { .filter-btn {
@@ -381,9 +458,10 @@ onShareAppMessage(() => {
} }
.skeleton-card { .skeleton-card {
background-color: #FFFFFF; background: rgba(255, 255, 255, 0.82);
border-radius: 24rpx; border-radius: 28rpx;
padding: 24rpx; padding: 24rpx;
border: 2rpx solid rgba(255, 255, 255, 0.66);
} }
.skeleton-line { .skeleton-line {
@@ -433,8 +511,8 @@ onShareAppMessage(() => {
.group-count { .group-count {
font-size: 22rpx; font-size: 22rpx;
color: #64748B; color: #667085;
background-color: #E8FFF1; background-color: rgba(255, 255, 255, 0.76);
padding: 6rpx 16rpx; padding: 6rpx 16rpx;
border-radius: 999rpx; border-radius: 999rpx;
} }
@@ -447,23 +525,25 @@ onShareAppMessage(() => {
.log-card { .log-card {
position: relative; position: relative;
background-color: #FFFFFF; background: rgba(255, 255, 255, 0.82);
border-radius: 24rpx; border-radius: 28rpx;
padding: 24rpx 24rpx 20rpx 24rpx; padding: 24rpx 24rpx 20rpx 24rpx;
box-shadow: 0 8rpx 20rpx rgba(15, 23, 42, 0.06); box-shadow: 0 14rpx 32rpx rgba(15, 23, 42, 0.06);
display: flex; display: flex;
gap: 20rpx; gap: 20rpx;
overflow: hidden; overflow: hidden;
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.log-card-resisted { .log-card-resisted {
background-color: #F6FFFA; background: linear-gradient(135deg, rgba(245, 255, 251, 0.95), rgba(255, 255, 255, 0.88));
border: 2rpx solid #E8FFF1; border: 2rpx solid rgba(255, 255, 255, 0.7);
} }
.log-card-smoke { .log-card-smoke {
background-color: #FFF7F5; background: linear-gradient(135deg, rgba(255, 248, 242, 0.95), rgba(255, 255, 255, 0.88));
border: 2rpx solid #FFE4E6; border: 2rpx solid rgba(255, 255, 255, 0.7);
} }
.log-bar { .log-bar {
@@ -486,7 +566,8 @@ onShareAppMessage(() => {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 36rpx; font-size: 26rpx;
font-weight: 700;
flex-shrink: 0; flex-shrink: 0;
} }
@@ -568,7 +649,7 @@ onShareAppMessage(() => {
.log-desc { .log-desc {
font-size: 24rpx; font-size: 24rpx;
color: #475569; color: #475467;
line-height: 1.5; line-height: 1.5;
margin-bottom: 10rpx; margin-bottom: 10rpx;
} }
@@ -588,8 +669,8 @@ onShareAppMessage(() => {
.log-interval { .log-interval {
font-size: 22rpx; font-size: 22rpx;
color: #64748B; color: #667085;
background-color: #F1F5F9; background-color: rgba(255, 255, 255, 0.72);
padding: 4rpx 12rpx; padding: 4rpx 12rpx;
border-radius: 999rpx; border-radius: 999rpx;
} }
@@ -604,10 +685,10 @@ onShareAppMessage(() => {
.action-btn { .action-btn {
font-size: 22rpx; font-size: 22rpx;
padding: 6rpx 14rpx; padding: 6rpx 14rpx;
border-radius: 12rpx; border-radius: 999rpx;
background-color: #FFFFFF; background-color: rgba(255, 255, 255, 0.8);
border: 2rpx solid #E2E8F0; border: 2rpx solid rgba(15, 23, 42, 0.08);
color: #64748B; color: #667085;
} }
.edit-btn { .edit-btn {
@@ -656,7 +737,17 @@ onShareAppMessage(() => {
} }
.empty-icon { .empty-icon {
font-size: 120rpx; width: 112rpx;
height: 112rpx;
border-radius: 36rpx;
background: rgba(255, 255, 255, 0.82);
border: 2rpx solid rgba(255, 255, 255, 0.68);
display: flex;
align-items: center;
justify-content: center;
font-size: 40rpx;
font-weight: 700;
color: #98a2b3;
margin-bottom: 24rpx; margin-bottom: 24rpx;
} }
@@ -690,12 +781,12 @@ onShareAppMessage(() => {
bottom: 140rpx; bottom: 140rpx;
width: 96rpx; width: 96rpx;
height: 96rpx; height: 96rpx;
background-color: #10B981; background: linear-gradient(180deg, #32c59d 0%, #1aa37a 100%);
border-radius: 50%; border-radius: 50%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
box-shadow: 0 12rpx 28rpx rgba(16, 185, 129, 0.35); box-shadow: 0 16rpx 34rpx rgba(26, 163, 122, 0.24);
transition: all 0.3s; transition: all 0.3s;
z-index: 100; z-index: 100;
} }
@@ -14,7 +14,7 @@
:class="{ 'mode-card-active': currentMode === 'quit' }" :class="{ 'mode-card-active': currentMode === 'quit' }"
@tap="selectMode('quit')" @tap="selectMode('quit')"
> >
<view class="mode-icon mode-icon-quit">🔥</view> <view class="mode-icon mode-icon-quit"></view>
<view class="mode-main"> <view class="mode-main">
<text class="mode-title">戒烟打卡</text> <text class="mode-title">戒烟打卡</text>
<text class="mode-desc">按天记录今天没抽用连续天数驱动坚持</text> <text class="mode-desc">按天记录今天没抽用连续天数驱动坚持</text>
@@ -27,7 +27,7 @@
:class="{ 'mode-card-active': currentMode === 'record' }" :class="{ 'mode-card-active': currentMode === 'record' }"
@tap="selectMode('record')" @tap="selectMode('record')"
> >
<view class="mode-icon mode-icon-record">🚬</view> <view class="mode-icon mode-icon-record"></view>
<view class="mode-main"> <view class="mode-main">
<text class="mode-title">记录抽烟</text> <text class="mode-title">记录抽烟</text>
<text class="mode-desc">继续按支数记录观察自己的频率和变化趋势</text> <text class="mode-desc">继续按支数记录观察自己的频率和变化趋势</text>
@@ -107,8 +107,9 @@ onMounted(async () => {
.page { .page {
min-height: 100vh; min-height: 100vh;
background: background:
radial-gradient(circle at top left, rgba(16, 185, 129, 0.18), transparent 34%), radial-gradient(circle at top left, rgba(52, 200, 160, 0.16), transparent 34%),
linear-gradient(180deg, #ecfdf5 0%, #f7fee7 42%, #ffffff 100%); radial-gradient(circle at top right, rgba(255, 255, 255, 0.92), transparent 24%),
linear-gradient(180deg, #edf2f8 0%, #f5f7fb 38%, #fbfdff 100%);
} }
.nav-placeholder { .nav-placeholder {
@@ -127,8 +128,8 @@ onMounted(async () => {
display: inline-flex; display: inline-flex;
padding: 8rpx 18rpx; padding: 8rpx 18rpx;
border-radius: 999rpx; border-radius: 999rpx;
background: rgba(16, 185, 129, 0.12); background: rgba(255, 255, 255, 0.78);
color: #047857; color: #667085;
font-size: 22rpx; font-size: 22rpx;
margin-bottom: 20rpx; margin-bottom: 20rpx;
} }
@@ -146,7 +147,7 @@ onMounted(async () => {
margin-top: 16rpx; margin-top: 16rpx;
font-size: 28rpx; font-size: 28rpx;
line-height: 1.6; line-height: 1.6;
color: #4b5563; color: #667085;
} }
.mode-card { .mode-card {
@@ -156,14 +157,16 @@ onMounted(async () => {
padding: 30rpx 28rpx; padding: 30rpx 28rpx;
margin-bottom: 24rpx; margin-bottom: 24rpx;
border-radius: 28rpx; border-radius: 28rpx;
background: rgba(255, 255, 255, 0.82); background: rgba(255, 255, 255, 0.8);
border: 2rpx solid rgba(255, 255, 255, 0.6); border: 2rpx solid rgba(255, 255, 255, 0.66);
box-shadow: 0 16rpx 44rpx rgba(15, 23, 42, 0.08); box-shadow: 0 16rpx 44rpx rgba(15, 23, 42, 0.08);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.mode-card-active { .mode-card-active {
border-color: rgba(16, 185, 129, 0.35); border-color: rgba(255, 255, 255, 0.72);
box-shadow: 0 20rpx 52rpx rgba(16, 185, 129, 0.14); box-shadow: 0 20rpx 52rpx rgba(26, 163, 122, 0.12);
} }
.mode-icon { .mode-icon {
@@ -173,16 +176,17 @@ onMounted(async () => {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 42rpx; font-size: 34rpx;
font-weight: 700;
flex-shrink: 0; flex-shrink: 0;
} }
.mode-icon-quit { .mode-icon-quit {
background: linear-gradient(135deg, #d1fae5 0%, #86efac 100%); background: linear-gradient(135deg, rgba(245, 255, 251, 0.95), rgba(219, 252, 231, 0.9));
} }
.mode-icon-record { .mode-icon-record {
background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%); background: linear-gradient(135deg, rgba(255, 248, 242, 0.95), rgba(254, 231, 214, 0.9));
} }
.mode-main { .mode-main {
@@ -112,7 +112,7 @@
<view class="footer"> <view class="footer">
<view v-if="step > 1" class="btn-secondary" @tap="prevStep">上一步</view> <view v-if="step > 1" class="btn-secondary" @tap="prevStep">上一步</view>
<view class="btn-primary" :class="{ 'btn-full': step === 1 }" @tap="nextStep"> <view class="btn-primary" :class="{ 'btn-full': step === 1 }" @tap="nextStep">
{{ step === 5 ? finishButtonText + ' 🚀' : '下一步' }} {{ step === 5 ? finishButtonText : '下一步' }}
</view> </view>
</view> </view>
</view> </view>
@@ -293,7 +293,10 @@ onShareAppMessage(() => {
<style scoped> <style scoped>
.page { .page {
min-height: 100vh; min-height: 100vh;
background: linear-gradient(to bottom, #D1FAE5 0%, #F0FDF4 45%, #FFFFFF 100%); background:
radial-gradient(circle at top left, rgba(52, 200, 160, 0.16), transparent 30%),
radial-gradient(circle at top right, rgba(255, 255, 255, 0.92), transparent 22%),
linear-gradient(180deg, #edf2f8 0%, #f5f7fb 38%, #fbfdff 100%);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
@@ -301,7 +304,7 @@ onShareAppMessage(() => {
.nav-area { .nav-area {
padding-left: 32rpx; padding-left: 32rpx;
padding-right: 32rpx; padding-right: 32rpx;
background: linear-gradient(to bottom, #D1FAE5, #E9FDF2); background: transparent;
} }
.nav-row { .nav-row {
@@ -314,8 +317,8 @@ onShareAppMessage(() => {
.step-indicator { .step-indicator {
font-size: 24rpx; font-size: 24rpx;
font-weight: 600; font-weight: 600;
color: #059669; color: #667085;
background-color: rgba(255, 255, 255, 0.7); background-color: rgba(255, 255, 255, 0.76);
padding: 6rpx 24rpx; padding: 6rpx 24rpx;
border-radius: 999rpx; border-radius: 999rpx;
} }
@@ -329,7 +332,7 @@ onShareAppMessage(() => {
.progress-fill { .progress-fill {
height: 100%; height: 100%;
background: linear-gradient(90deg, #10B981, #34D399); background: linear-gradient(90deg, #32c59d, #1aa37a);
border-radius: 999rpx; border-radius: 999rpx;
transition: width 0.3s ease; transition: width 0.3s ease;
} }
@@ -351,7 +354,7 @@ onShareAppMessage(() => {
margin-bottom: 16rpx; margin-bottom: 16rpx;
font-size: 24rpx; font-size: 24rpx;
font-weight: 600; font-weight: 600;
color: #047857; color: #667085;
} }
.mode-switch { .mode-switch {
@@ -362,15 +365,17 @@ onShareAppMessage(() => {
.mode-switch-item { .mode-switch-item {
padding: 24rpx; padding: 24rpx;
border-radius: 20rpx; border-radius: 24rpx;
background: rgba(255, 255, 255, 0.82); background: rgba(255, 255, 255, 0.8);
border: 2rpx solid #d1fae5; border: 2rpx solid rgba(255, 255, 255, 0.66);
box-shadow: 0 8rpx 20rpx rgba(16, 185, 129, 0.08); box-shadow: 0 12rpx 28rpx rgba(15, 23, 42, 0.06);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.mode-switch-item-active { .mode-switch-item-active {
background: #ecfdf5; background: rgba(255, 255, 255, 0.92);
border-color: #10b981; border-color: rgba(255, 255, 255, 0.78);
} }
.mode-switch-title { .mode-switch-title {
@@ -436,14 +441,14 @@ onShareAppMessage(() => {
width: 96rpx; width: 96rpx;
height: 96rpx; height: 96rpx;
border-radius: 50%; border-radius: 50%;
background-color: #FFFFFF; background-color: rgba(255, 255, 255, 0.86);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 48rpx; font-size: 48rpx;
color: #10B981; color: #1aa37a;
border: 2rpx solid #ECFDF3; border: 2rpx solid rgba(255, 255, 255, 0.72);
box-shadow: 0 8rpx 20rpx rgba(16, 185, 129, 0.12); box-shadow: 0 10rpx 24rpx rgba(15, 23, 42, 0.06);
} }
.input-value { .input-value {
@@ -472,12 +477,12 @@ onShareAppMessage(() => {
.option { .option {
padding: 28rpx 36rpx; padding: 28rpx 36rpx;
background-color: #FFFFFF; background-color: rgba(255, 255, 255, 0.84);
border-radius: 16rpx; border-radius: 16rpx;
font-size: 30rpx; font-size: 30rpx;
color: #111827; color: #111827;
border: 2rpx solid #ECFDF3; border: 2rpx solid rgba(255, 255, 255, 0.72);
box-shadow: 0 4rpx 12rpx rgba(16, 185, 129, 0.06); box-shadow: 0 8rpx 20rpx rgba(15, 23, 42, 0.05);
} }
.option-tag { .option-tag {
@@ -486,9 +491,9 @@ onShareAppMessage(() => {
} }
.option-active { .option-active {
background-color: #ECFDF5; background-color: rgba(255, 255, 255, 0.96);
border-color: #10B981; border-color: rgba(255, 255, 255, 0.82);
color: #059669; color: #1a7f61;
} }
.time-row { .time-row {
@@ -506,25 +511,25 @@ onShareAppMessage(() => {
} }
.time-picker { .time-picker {
background-color: #FFFFFF; background-color: rgba(255, 255, 255, 0.86);
padding: 32rpx; padding: 32rpx;
border-radius: 16rpx; border-radius: 16rpx;
font-size: 40rpx; font-size: 40rpx;
color: #111827; color: #111827;
text-align: center; text-align: center;
border: 2rpx solid #ECFDF3; border: 2rpx solid rgba(255, 255, 255, 0.72);
box-shadow: 0 4rpx 12rpx rgba(16, 185, 129, 0.06); box-shadow: 0 8rpx 20rpx rgba(15, 23, 42, 0.05);
} }
.price-input { .price-input {
display: flex; display: flex;
align-items: center; align-items: center;
background-color: #FFFFFF; background-color: rgba(255, 255, 255, 0.86);
padding: 24rpx 32rpx; padding: 24rpx 32rpx;
border-radius: 16rpx; border-radius: 16rpx;
gap: 8rpx; gap: 8rpx;
border: 2rpx solid #ECFDF3; border: 2rpx solid rgba(255, 255, 255, 0.72);
box-shadow: 0 4rpx 12rpx rgba(16, 185, 129, 0.06); box-shadow: 0 8rpx 20rpx rgba(15, 23, 42, 0.05);
} }
.price-prefix { .price-prefix {
@@ -550,15 +555,15 @@ onShareAppMessage(() => {
.btn-primary { .btn-primary {
flex: 1; flex: 1;
height: 96rpx; height: 96rpx;
background-color: #10B981; background: linear-gradient(180deg, #32c59d 0%, #1aa37a 100%);
border-radius: 48rpx; border-radius: 48rpx;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 32rpx; font-size: 32rpx;
font-weight: 500; font-weight: 600;
color: #FFFFFF; color: #FFFFFF;
box-shadow: 0 12rpx 28rpx rgba(16, 185, 129, 0.25); box-shadow: 0 12rpx 28rpx rgba(26, 163, 122, 0.22);
} }
.btn-full { flex: 1; } .btn-full { flex: 1; }
@@ -566,13 +571,13 @@ onShareAppMessage(() => {
.btn-secondary { .btn-secondary {
height: 96rpx; height: 96rpx;
padding: 0 48rpx; padding: 0 48rpx;
background-color: #FFFFFF; background-color: rgba(255, 255, 255, 0.86);
border-radius: 48rpx; border-radius: 48rpx;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 32rpx; font-size: 32rpx;
color: #111827; color: #111827;
border: 2rpx solid #E5E7EB; border: 2rpx solid rgba(15, 23, 42, 0.08);
} }
</style> </style>
@@ -1,14 +1,33 @@
<template> <template>
<view class="page"> <view class="page">
<view class="page-glow page-glow-a"></view>
<view class="page-glow page-glow-b"></view>
<view class="nav-placeholder" :style="{ height: navBarHeight + 'px' }"></view>
<view class="page-header">
<text class="header-eyebrow">Account</text>
<text class="header-title">个人中心</text>
<text class="header-subtitle">模式切换分享与基础设置</text>
</view>
<view class="user-section"> <view class="user-section">
<image class="avatar" :src="userAvatar" mode="aspectFill"></image> <image class="avatar" :src="userAvatar" mode="aspectFill"></image>
<text class="user-name">{{ userName }}</text> <view class="user-copy">
<text class="user-name">{{ userName }}</text>
<text class="user-desc">已连接戒烟记录与统计数据</text>
<view class="user-meta">
<text class="user-pill">{{ modeText }}</text>
<text class="user-pill user-pill-muted">{{ shareToken ? '分享已启用' : '分享未生成' }}</text>
</view>
</view>
</view> </view>
<view class="section"> <view class="section">
<view class="mode-card"> <view class="mode-card">
<view class="mode-card-header"> <view class="mode-card-header">
<view class="menu-icon menu-icon-green">🧭</view> <view class="menu-icon menu-icon-accent">
<text class="menu-glyph"></text>
</view>
<view class="menu-content"> <view class="menu-content">
<text class="menu-label">打卡模式</text> <text class="menu-label">打卡模式</text>
<text class="menu-desc">直接切换成戒烟打卡记录抽烟</text> <text class="menu-desc">直接切换成戒烟打卡记录抽烟</text>
@@ -31,7 +50,9 @@
<view class="menu-list"> <view class="menu-list">
<view class="menu-item"> <view class="menu-item">
<view class="menu-icon menu-icon-green">🔗</view> <view class="menu-icon menu-icon-accent">
<text class="menu-glyph"></text>
</view>
<view class="menu-content"> <view class="menu-content">
<text class="menu-label">分享戒烟记录</text> <text class="menu-label">分享戒烟记录</text>
<text class="menu-desc">{{ shareDesc }}</text> <text class="menu-desc">{{ shareDesc }}</text>
@@ -46,8 +67,12 @@
</button> </button>
</view> </view>
<view class="menu-divider"></view>
<view class="menu-item" @tap="goOnboarding"> <view class="menu-item" @tap="goOnboarding">
<view class="menu-icon menu-icon-green">📋</view> <view class="menu-icon menu-icon-accent">
<text class="menu-glyph"></text>
</view>
<view class="menu-content"> <view class="menu-content">
<text class="menu-label">重新填写问卷</text> <text class="menu-label">重新填写问卷</text>
<text class="menu-desc">修改吸烟基线与个人信息</text> <text class="menu-desc">修改吸烟基线与个人信息</text>
@@ -60,16 +85,25 @@
<view class="section"> <view class="section">
<view class="menu-list"> <view class="menu-list">
<view class="menu-item" @tap="clearCache"> <view class="menu-item" @tap="clearCache">
<view class="menu-icon menu-icon-gray">🗑</view> <view class="menu-icon menu-icon-muted">
<text class="menu-glyph"></text>
</view>
<view class="menu-content"> <view class="menu-content">
<text class="menu-label">清除缓存</text> <text class="menu-label">清除缓存</text>
<text class="menu-desc">仅清理本地缓存不影响云端记录</text>
</view> </view>
<text class="menu-arrow"></text> <text class="menu-arrow"></text>
</view> </view>
<view class="menu-divider"></view>
<view class="menu-item" @tap="copyInfo"> <view class="menu-item" @tap="copyInfo">
<view class="menu-icon menu-icon-gray">💬</view> <view class="menu-icon menu-icon-muted">
<text class="menu-glyph"></text>
</view>
<view class="menu-content"> <view class="menu-content">
<text class="menu-label">意见反馈</text> <text class="menu-label">意见反馈</text>
<text class="menu-desc">复制反馈邮箱发送使用建议或问题</text>
</view> </view>
<text class="menu-arrow"></text> <text class="menu-arrow"></text>
</view> </view>
@@ -81,7 +115,7 @@
</template> </template>
<script setup> <script setup>
import { computed, ref } from 'vue' import { computed, ref, onMounted } from 'vue'
import { onShareAppMessage, onShow } from '@dcloudio/uni-app' import { onShareAppMessage, onShow } from '@dcloudio/uni-app'
import * as api from '@/api' import * as api from '@/api'
import { useProfileStore } from '@/stores/profile' import { useProfileStore } from '@/stores/profile'
@@ -96,6 +130,7 @@ const shareToken = ref('')
const shareExpireAt = ref('') const shareExpireAt = ref('')
const shareLoading = ref(false) const shareLoading = ref(false)
const modeSaving = ref(false) const modeSaving = ref(false)
const navBarHeight = ref(0)
const modeOptions = [ const modeOptions = [
{ value: 'quit', label: '戒烟打卡', desc: '按天记录今天没抽' }, { value: 'quit', label: '戒烟打卡', desc: '按天记录今天没抽' },
{ value: 'record', label: '记录抽烟', desc: '按支数记录变化' } { value: 'record', label: '记录抽烟', desc: '按支数记录变化' }
@@ -123,6 +158,17 @@ const sharePath = computed(() => {
return `pages/share/index?share_token=${shareToken.value}` return `pages/share/index?share_token=${shareToken.value}`
}) })
function setupNavBar() {
const systemInfo = uni.getSystemInfoSync()
const statusBarH = systemInfo.statusBarHeight || 0
try {
const menuBtn = uni.getMenuButtonBoundingClientRect()
navBarHeight.value = menuBtn.bottom + (menuBtn.top - statusBarH)
} catch (e) {
navBarHeight.value = statusBarH + 44
}
}
function formatExpire(value) { function formatExpire(value) {
if (!value) return '--' if (!value) return '--'
const d = new Date(value) const d = new Date(value)
@@ -225,6 +271,10 @@ onShareAppMessage(() => {
} }
}) })
onMounted(() => {
setupNavBar()
})
onShow(async () => { onShow(async () => {
await waitForLogin() await waitForLogin()
await profileStore.fetchProfile() await profileStore.fetchProfile()
@@ -235,44 +285,156 @@ onShow(async () => {
<style scoped> <style scoped>
.page { .page {
min-height: 100vh; min-height: 100vh;
background: linear-gradient(to bottom, #D1FAE5 0%, #F0FDF4 45%, #FFFFFF 100%); position: relative;
padding: 32rpx; background:
padding-bottom: 160rpx; radial-gradient(circle at top left, rgba(52, 200, 160, 0.16), transparent 30%),
radial-gradient(circle at top right, rgba(255, 255, 255, 0.92), transparent 22%),
linear-gradient(180deg, #edf2f8 0%, #f5f7fb 38%, #fbfdff 100%);
padding: 0 24rpx 168rpx;
box-sizing: border-box; box-sizing: border-box;
overflow: hidden;
}
.page-glow {
position: absolute;
border-radius: 50%;
filter: blur(24rpx);
opacity: 0.72;
pointer-events: none;
}
.page-glow-a {
top: 100rpx;
left: -140rpx;
width: 360rpx;
height: 360rpx;
background: rgba(52, 200, 160, 0.15);
}
.page-glow-b {
top: 340rpx;
right: -120rpx;
width: 320rpx;
height: 320rpx;
background: rgba(255, 255, 255, 0.86);
}
.nav-placeholder,
.page-header,
.user-section,
.section,
.version {
position: relative;
z-index: 1;
}
.page-header {
padding: 24rpx 6rpx 18rpx;
}
.header-eyebrow {
display: block;
font-size: 20rpx;
font-weight: 700;
letter-spacing: 4rpx;
text-transform: uppercase;
color: #98a2b3;
}
.header-title {
display: block;
margin-top: 10rpx;
font-size: 42rpx;
line-height: 1.18;
font-weight: 700;
color: #111827;
}
.header-subtitle {
display: block;
margin-top: 8rpx;
font-size: 24rpx;
line-height: 1.5;
color: #667085;
} }
.user-section { .user-section {
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
padding: 48rpx 0 40rpx; gap: 24rpx;
padding: 12rpx 28rpx 32rpx;
margin-bottom: 24rpx;
background: rgba(255, 255, 255, 0.74);
border: 2rpx solid rgba(255, 255, 255, 0.66);
border-radius: 32rpx;
box-shadow: 0 16rpx 42rpx rgba(15, 23, 42, 0.08);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.avatar { .avatar {
width: 140rpx; width: 132rpx;
height: 140rpx; height: 132rpx;
border-radius: 50%; border-radius: 50%;
border: 4rpx solid #A7F3D0; border: 4rpx solid rgba(255, 255, 255, 0.82);
background-color: #ECFDF5; background-color: rgba(255, 255, 255, 0.7);
margin-bottom: 20rpx; }
.user-copy {
flex: 1;
display: flex;
flex-direction: column;
align-items: flex-start;
} }
.user-name { .user-name {
font-size: 38rpx; font-size: 40rpx;
font-weight: 700; font-weight: 700;
color: #111827; color: #111827;
} }
.user-desc {
display: block;
margin-top: 8rpx;
font-size: 24rpx;
line-height: 1.5;
color: #667085;
}
.user-meta {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
margin-top: 18rpx;
}
.user-pill {
padding: 10rpx 20rpx;
border-radius: 999rpx;
background: rgba(52, 200, 160, 0.12);
border: 2rpx solid rgba(255, 255, 255, 0.64);
font-size: 22rpx;
font-weight: 600;
color: #17795c;
}
.user-pill-muted {
background: rgba(255, 255, 255, 0.78);
color: #667085;
}
.section { .section {
margin-bottom: 24rpx; margin-bottom: 24rpx;
} }
.mode-card { .mode-card {
background-color: #FFFFFF; background: rgba(255, 255, 255, 0.76);
border-radius: 24rpx; border-radius: 32rpx;
padding: 28rpx 24rpx; padding: 30rpx 24rpx;
border: 2rpx solid #ECFDF3; border: 2rpx solid rgba(255, 255, 255, 0.66);
box-shadow: 0 8rpx 20rpx rgba(16, 185, 129, 0.08); box-shadow: 0 16rpx 42rpx rgba(15, 23, 42, 0.08);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
margin-bottom: 16rpx; margin-bottom: 16rpx;
} }
@@ -286,36 +448,50 @@ onShow(async () => {
.menu-list { .menu-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16rpx; background: rgba(255, 255, 255, 0.8);
border-radius: 32rpx;
border: 2rpx solid rgba(255, 255, 255, 0.66);
box-shadow: 0 16rpx 42rpx rgba(15, 23, 42, 0.08);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
overflow: hidden;
} }
.menu-item { .menu-item {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 24rpx; gap: 24rpx;
background-color: #FFFFFF;
border-radius: 24rpx;
padding: 28rpx 24rpx; padding: 28rpx 24rpx;
border: 2rpx solid #ECFDF3; }
box-shadow: 0 8rpx 20rpx rgba(16, 185, 129, 0.08);
.menu-divider {
margin: 0 24rpx;
height: 2rpx;
background: rgba(15, 23, 42, 0.06);
} }
.menu-icon { .menu-icon {
width: 64rpx; width: 64rpx;
height: 64rpx; height: 64rpx;
border-radius: 16rpx; border-radius: 20rpx;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 32rpx; border: 2rpx solid rgba(255, 255, 255, 0.7);
} }
.menu-icon-green { .menu-icon-accent {
background-color: #DCFCE7; background: rgba(52, 200, 160, 0.14);
} }
.menu-icon-gray { .menu-icon-muted {
background-color: #F3F4F6; background: rgba(255, 255, 255, 0.82);
}
.menu-glyph {
font-size: 24rpx;
font-weight: 700;
color: #111827;
} }
.menu-content { .menu-content {
@@ -328,11 +504,13 @@ onShow(async () => {
.menu-label { .menu-label {
font-size: 30rpx; font-size: 30rpx;
color: #111827; color: #111827;
font-weight: 600;
} }
.menu-desc { .menu-desc {
font-size: 24rpx; font-size: 24rpx;
color: #6B7280; line-height: 1.5;
color: #667085;
} }
.menu-actions { .menu-actions {
@@ -342,44 +520,49 @@ onShow(async () => {
flex-wrap: wrap; flex-wrap: wrap;
gap: 8rpx; gap: 8rpx;
font-size: 24rpx; font-size: 24rpx;
color: #10B981; color: #1aa37a;
} }
.menu-action { .menu-action {
color: #10B981; color: #1aa37a;
} }
.menu-action-sep { .menu-action-sep {
color: #9CA3AF; color: #98a2b3;
} }
.menu-arrow { .menu-arrow {
font-size: 36rpx; font-size: 36rpx;
color: #9CA3AF; color: #98a2b3;
} }
.menu-value { .menu-value {
font-size: 24rpx; font-size: 24rpx;
font-weight: 600; font-weight: 600;
color: #10B981; color: #1aa37a;
} }
.mode-switch { .mode-switch {
display: grid; display: flex;
grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12rpx;
gap: 16rpx; padding: 8rpx;
border-radius: 24rpx;
background: rgba(247, 249, 252, 0.92);
border: 2rpx solid rgba(15, 23, 42, 0.05);
} }
.mode-switch-item { .mode-switch-item {
padding: 22rpx 20rpx; flex: 1;
border-radius: 18rpx; padding: 22rpx 18rpx;
background: #F9FAFB; border-radius: 20rpx;
border: 2rpx solid #E5E7EB; background: transparent;
border: 2rpx solid transparent;
} }
.mode-switch-item-active { .mode-switch-item-active {
background: #ECFDF5; background: rgba(255, 255, 255, 0.94);
border-color: #10B981; border-color: rgba(255, 255, 255, 0.76);
box-shadow: 0 8rpx 18rpx rgba(15, 23, 42, 0.06);
} }
.mode-switch-title { .mode-switch-title {
@@ -394,29 +577,30 @@ onShow(async () => {
margin-top: 8rpx; margin-top: 8rpx;
font-size: 22rpx; font-size: 22rpx;
line-height: 1.5; line-height: 1.5;
color: #6B7280; color: #667085;
} }
.mode-hint { .mode-hint {
display: block; display: block;
margin-top: 16rpx; margin-top: 16rpx;
font-size: 22rpx; font-size: 22rpx;
color: #10B981; color: #1aa37a;
} }
.share-btn { .share-btn {
margin: 0; margin: 0;
padding: 10rpx 20rpx; padding: 12rpx 22rpx;
line-height: 1.4; line-height: 1.4;
font-size: 24rpx; font-size: 24rpx;
border: none; border: none;
border-radius: 999rpx; border-radius: 999rpx;
color: #FFFFFF; color: #FFFFFF;
background: #10B981; background: linear-gradient(180deg, #32c59d 0%, #1aa37a 100%);
box-shadow: 0 12rpx 28rpx rgba(26, 163, 122, 0.2);
} }
.share-btn[disabled] { .share-btn[disabled] {
background: #9CA3AF; background: #98a2b3;
color: #FFFFFF; color: #FFFFFF;
} }
@@ -428,7 +612,7 @@ onShow(async () => {
display: block; display: block;
text-align: center; text-align: center;
font-size: 22rpx; font-size: 22rpx;
color: #9CA3AF; color: #98a2b3;
margin-top: 32rpx; margin-top: 32rpx;
} }
</style> </style>
@@ -14,7 +14,7 @@
<view v-else> <view v-else>
<!-- 无计划状态 --> <!-- 无计划状态 -->
<view v-if="!planData" class="no-plan-card"> <view v-if="!planData" class="no-plan-card">
<view class="no-plan-icon">📋</view> <view class="no-plan-icon"></view>
<text class="no-plan-title">暂无戒烟计划</text> <text class="no-plan-title">暂无戒烟计划</text>
<text class="no-plan-desc">生成专属30天戒烟计划按阶段轻松戒烟</text> <text class="no-plan-desc">生成专属30天戒烟计划按阶段轻松戒烟</text>
<view class="generate-btn" @tap="handleGenerate"> <view class="generate-btn" @tap="handleGenerate">
@@ -356,12 +356,15 @@ onShareAppMessage(() => {
<style scoped> <style scoped>
.page { .page {
min-height: 100vh; min-height: 100vh;
background: linear-gradient(to bottom, #D1FAE5 0%, #F0FDF4 45%, #FFFFFF 100%); background:
radial-gradient(circle at top left, rgba(52, 200, 160, 0.16), transparent 30%),
radial-gradient(circle at top right, rgba(255, 255, 255, 0.92), transparent 24%),
linear-gradient(180deg, #edf2f8 0%, #f5f7fb 38%, #fbfdff 100%);
box-sizing: border-box; box-sizing: border-box;
} }
.status-bar { .status-bar {
background: linear-gradient(to bottom, #D1FAE5, #E9FDF2); background: transparent;
} }
.container { .container {
@@ -409,18 +412,29 @@ onShareAppMessage(() => {
/* 无计划状态 */ /* 无计划状态 */
.no-plan-card { .no-plan-card {
background-color: #FFFFFF; background: rgba(255, 255, 255, 0.82);
border-radius: 28rpx; border-radius: 32rpx;
padding: 60rpx 40rpx; padding: 60rpx 40rpx;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
box-shadow: 0 10rpx 28rpx rgba(16, 185, 129, 0.12); box-shadow: 0 16rpx 36rpx rgba(15, 23, 42, 0.06);
border: 2rpx solid #ECFDF3; border: 2rpx solid rgba(255, 255, 255, 0.66);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.no-plan-icon { .no-plan-icon {
font-size: 80rpx; width: 104rpx;
height: 104rpx;
border-radius: 32rpx;
background: rgba(247, 249, 252, 0.92);
display: flex;
align-items: center;
justify-content: center;
font-size: 36rpx;
font-weight: 700;
color: #1a7f61;
margin-bottom: 24rpx; margin-bottom: 24rpx;
} }
@@ -440,7 +454,7 @@ onShareAppMessage(() => {
} }
.generate-btn { .generate-btn {
background: linear-gradient(135deg, #10B981, #059669); background: linear-gradient(180deg, #32c59d 0%, #1aa37a 100%);
padding: 24rpx 60rpx; padding: 24rpx 60rpx;
border-radius: 48rpx; border-radius: 48rpx;
box-shadow: 0 8rpx 20rpx rgba(16, 185, 129, 0.3); box-shadow: 0 8rpx 20rpx rgba(16, 185, 129, 0.3);
@@ -454,20 +468,22 @@ onShareAppMessage(() => {
/* 阶段卡片 */ /* 阶段卡片 */
.stage-card { .stage-card {
background: #FFFFFF; background: rgba(255, 255, 255, 0.82);
border-radius: 28rpx; border-radius: 32rpx;
padding: 32rpx; padding: 32rpx;
margin-bottom: 32rpx; margin-bottom: 32rpx;
position: relative; position: relative;
box-shadow: 0 10rpx 28rpx rgba(16, 185, 129, 0.12); box-shadow: 0 16rpx 36rpx rgba(15, 23, 42, 0.06);
border: 2rpx solid #ECFDF3; border: 2rpx solid rgba(255, 255, 255, 0.66);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.stage-badge { .stage-badge {
position: absolute; position: absolute;
top: 24rpx; top: 24rpx;
right: 24rpx; right: 24rpx;
background-color: #10B981; background-color: #1aa37a;
color: #FFFFFF; color: #FFFFFF;
padding: 8rpx 20rpx; padding: 8rpx 20rpx;
border-radius: 20rpx; border-radius: 20rpx;
@@ -523,7 +539,7 @@ onShareAppMessage(() => {
.stage-progress-fill { .stage-progress-fill {
height: 100%; height: 100%;
background: linear-gradient(90deg, #10B981, #34D399); background: linear-gradient(90deg, #32c59d, #1aa37a);
border-radius: 6rpx; border-radius: 6rpx;
} }
@@ -554,11 +570,13 @@ onShareAppMessage(() => {
} }
.stage-info-card { .stage-info-card {
background-color: #FFFFFF; background-color: rgba(255, 255, 255, 0.82);
border-radius: 24rpx; border-radius: 28rpx;
padding: 28rpx; padding: 28rpx;
border: 2rpx solid #ECFDF3; border: 2rpx solid rgba(255, 255, 255, 0.66);
box-shadow: 0 8rpx 22rpx rgba(16, 185, 129, 0.08); box-shadow: 0 14rpx 30rpx rgba(15, 23, 42, 0.05);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.stage-item { .stage-item {
@@ -618,11 +636,13 @@ onShareAppMessage(() => {
/* 每日目标 */ /* 每日目标 */
.daily-tips-card { .daily-tips-card {
background-color: #FFFFFF; background-color: rgba(255, 255, 255, 0.82);
border-radius: 24rpx; border-radius: 28rpx;
padding: 28rpx; padding: 28rpx;
border: 2rpx solid #ECFDF3; border: 2rpx solid rgba(255, 255, 255, 0.66);
box-shadow: 0 8rpx 22rpx rgba(16, 185, 129, 0.08); box-shadow: 0 14rpx 30rpx rgba(15, 23, 42, 0.05);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.daily-tips-title { .daily-tips-title {
@@ -647,16 +667,18 @@ onShareAppMessage(() => {
} }
.day-item { .day-item {
background-color: #FFFFFF; background-color: rgba(255, 255, 255, 0.82);
border-radius: 20rpx; border-radius: 24rpx;
padding: 24rpx; padding: 24rpx;
border: 2rpx solid #ECFDF3; border: 2rpx solid rgba(255, 255, 255, 0.66);
box-shadow: 0 6rpx 16rpx rgba(16, 185, 129, 0.08); box-shadow: 0 12rpx 24rpx rgba(15, 23, 42, 0.05);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.day-item-today { .day-item-today {
border-color: #10B981; border-color: rgba(255, 255, 255, 0.82);
background: linear-gradient(135deg, #ECFDF5, #F0FDF4); background: linear-gradient(135deg, rgba(245, 255, 251, 0.95), rgba(255, 255, 255, 0.88));
} }
.day-item-past { .day-item-past {
@@ -723,8 +745,8 @@ onShareAppMessage(() => {
.days-loading, .days-loading,
.days-empty { .days-empty {
background-color: #FFFFFF; background-color: rgba(255, 255, 255, 0.82);
border-radius: 24rpx; border-radius: 28rpx;
padding: 40rpx; padding: 40rpx;
text-align: center; text-align: center;
} }
@@ -742,7 +764,7 @@ onShareAppMessage(() => {
} }
.reset-btn { .reset-btn {
background-color: #FFFFFF; background-color: rgba(255, 255, 255, 0.86);
border: 2rpx solid #EF4444; border: 2rpx solid #EF4444;
padding: 24rpx; padding: 24rpx;
border-radius: 24rpx; border-radius: 24rpx;
@@ -761,7 +783,7 @@ onShareAppMessage(() => {
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(15, 23, 42, 0.26);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -769,11 +791,14 @@ onShareAppMessage(() => {
} }
.modal-content { .modal-content {
background-color: #FFFFFF; background-color: rgba(248, 250, 252, 0.92);
border-radius: 28rpx; border-radius: 28rpx;
width: 600rpx; width: 600rpx;
max-height: 70vh; max-height: 70vh;
overflow: hidden; overflow: hidden;
border: 2rpx solid rgba(255, 255, 255, 0.72);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.modal-header { .modal-header {
@@ -323,7 +323,9 @@ onShareAppMessage(() => {
min-height: 100vh; min-height: 100vh;
padding: 24rpx; padding: 24rpx;
box-sizing: border-box; box-sizing: border-box;
background: #f5f7fa; background:
radial-gradient(circle at top left, rgba(52, 200, 160, 0.14), transparent 28%),
linear-gradient(180deg, #edf2f8 0%, #f5f7fb 38%, #fbfdff 100%);
} }
.state-wrap { .state-wrap {
@@ -339,7 +341,7 @@ onShareAppMessage(() => {
.retry-btn { .retry-btn {
margin-top: 24rpx; margin-top: 24rpx;
font-size: 26rpx; font-size: 26rpx;
background: #10b981; background: linear-gradient(180deg, #32c59d 0%, #1aa37a 100%);
color: #fff; color: #fff;
border: none; border: none;
border-radius: 16rpx; border-radius: 16rpx;
@@ -350,8 +352,12 @@ onShareAppMessage(() => {
align-items: center; align-items: center;
gap: 20rpx; gap: 20rpx;
padding: 24rpx; padding: 24rpx;
background: #ffffff; background: rgba(255, 255, 255, 0.82);
border-radius: 20rpx; border-radius: 28rpx;
border: 2rpx solid rgba(255, 255, 255, 0.68);
box-shadow: 0 16rpx 36rpx rgba(15, 23, 42, 0.06);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.avatar { .avatar {
@@ -387,8 +393,10 @@ onShareAppMessage(() => {
.overview-item { .overview-item {
padding: 20rpx; padding: 20rpx;
background: #fff; background: rgba(255, 255, 255, 0.82);
border-radius: 16rpx; border-radius: 24rpx;
border: 2rpx solid rgba(255, 255, 255, 0.68);
box-shadow: 0 12rpx 28rpx rgba(15, 23, 42, 0.05);
} }
.overview-label { .overview-label {
@@ -415,8 +423,12 @@ onShareAppMessage(() => {
.section { .section {
margin-top: 20rpx; margin-top: 20rpx;
padding: 20rpx; padding: 20rpx;
background: #fff; background: rgba(255, 255, 255, 0.82);
border-radius: 20rpx; border-radius: 28rpx;
border: 2rpx solid rgba(255, 255, 255, 0.68);
box-shadow: 0 16rpx 36rpx rgba(15, 23, 42, 0.06);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.section-header { .section-header {
@@ -446,8 +458,9 @@ onShareAppMessage(() => {
} }
.range-item.active { .range-item.active {
background: #10b981; background: rgba(255, 255, 255, 0.92);
color: #fff; color: #111827;
box-shadow: 0 8rpx 18rpx rgba(15, 23, 42, 0.06);
} }
.stats-row { .stats-row {
@@ -459,8 +472,8 @@ onShareAppMessage(() => {
.stats-block { .stats-block {
padding: 14rpx; padding: 14rpx;
border-radius: 14rpx; border-radius: 18rpx;
background: #f8fafc; background: rgba(247, 249, 252, 0.92);
} }
.stats-key { .stats-key {
@@ -527,8 +540,8 @@ onShareAppMessage(() => {
.log-item { .log-item {
padding: 18rpx; padding: 18rpx;
border-radius: 14rpx; border-radius: 20rpx;
background: #f8fafc; background: rgba(247, 249, 252, 0.92);
} }
.log-top { .log-top {
@@ -578,7 +591,7 @@ onShareAppMessage(() => {
font-size: 24rpx; font-size: 24rpx;
border-radius: 14rpx; border-radius: 14rpx;
border: none; border: none;
background: #ecfdf5; background: rgba(255, 255, 255, 0.88);
color: #047857; color: #1a7f61;
} }
</style> </style>
@@ -1,7 +1,14 @@
<template> <template>
<view class="page"> <view class="page">
<view class="page-glow page-glow-a"></view>
<view class="page-glow page-glow-b"></view>
<view class="sticky-bar"> <view class="sticky-bar">
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view> <view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
<view class="page-head">
<text class="page-eyebrow">Statistics</text>
<text class="page-title">数据统计</text>
<text class="page-subtitle">趋势恢复和储蓄会在这里汇总</text>
</view>
<view class="segment"> <view class="segment">
<view <view
v-for="tab in tabs" v-for="tab in tabs"
@@ -17,7 +24,7 @@
<view class="insight-card"> <view class="insight-card">
<view class="insight-icon" :class="insightIconClass"> <view class="insight-icon" :class="insightIconClass">
<text class="insight-emoji">{{ insightEmoji }}</text> <text class="insight-glyph">{{ insightEmoji }}</text>
</view> </view>
<view class="insight-content"> <view class="insight-content">
<text class="insight-title">每周洞察</text> <text class="insight-title">每周洞察</text>
@@ -68,7 +75,7 @@
<view class="savings-card"> <view class="savings-card">
<view class="savings-header"> <view class="savings-header">
<view class="savings-icon" :class="moneyIconClass"> <view class="savings-icon" :class="moneyIconClass">
<text class="icon-emoji">💰</text> <text class="icon-glyph"></text>
</view> </view>
<view class="savings-header-text"> <view class="savings-header-text">
<text class="savings-title">节省金额</text> <text class="savings-title">节省金额</text>
@@ -102,7 +109,7 @@
</view> </view>
</view> </view>
<view v-if="!moneyAvailable" class="card-empty"> <view v-if="!moneyAvailable" class="card-empty">
<text class="card-empty-icon">🔒</text> <text class="card-empty-icon"></text>
<text class="card-empty-text">完善基础信息后解锁节省金额</text> <text class="card-empty-text">完善基础信息后解锁节省金额</text>
</view> </view>
</view> </view>
@@ -111,15 +118,15 @@
<view class="health-header"> <view class="health-header">
<view class="health-title-row"> <view class="health-title-row">
<view class="health-icon" :class="healthIconClass"> <view class="health-icon" :class="healthIconClass">
<text class="icon-emoji"></text> <text class="icon-glyph"></text>
</view> </view>
<text class="health-title">健康恢复里程碑</text> <text class="health-title">健康恢复里程碑</text>
</view> </view>
<view class="health-badge" :class="{ 'health-badge-muted': !healthAvailable }">{{ healthStatusText }}</view> <view class="health-badge" :class="{ 'health-badge-muted': !healthAvailable }">{{ healthStatusText }}</view>
</view> </view>
<view v-if="healthAvailable" class="health-overview"> <view v-if="healthAvailable" class="health-overview">
<view class="health-metric"> <view class="health-metric">
<view class="health-metric-icon"></view> <view class="health-metric-icon"></view>
<text class="health-metric-value">{{ smokeFreeText }}</text> <text class="health-metric-value">{{ smokeFreeText }}</text>
<text class="health-metric-label">无烟时长</text> <text class="health-metric-label">无烟时长</text>
</view> </view>
@@ -131,7 +138,7 @@
</view> </view>
</view> </view>
<view v-else class="card-empty"> <view v-else class="card-empty">
<text class="card-empty-icon">🫁</text> <text class="card-empty-icon"></text>
<text class="card-empty-text">暂无健康数据记录一次后解锁</text> <text class="card-empty-text">暂无健康数据记录一次后解锁</text>
</view> </view>
<view class="health-list"> <view class="health-list">
@@ -154,7 +161,7 @@
<view class="stats-grid"> <view class="stats-grid">
<view class="mini-card"> <view class="mini-card">
<view class="mini-icon mini-icon-fire" :class="streakIconClass"> <view class="mini-icon mini-icon-fire" :class="streakIconClass">
<text class="icon-emoji">🔥</text> <text class="icon-glyph"></text>
</view> </view>
<text class="mini-label">连续记录</text> <text class="mini-label">连续记录</text>
<view class="mini-value-row"> <view class="mini-value-row">
@@ -165,7 +172,7 @@
</view> </view>
<view class="mini-card"> <view class="mini-card">
<view class="mini-icon mini-icon-block" :class="resistedIconClass"> <view class="mini-icon mini-icon-block" :class="resistedIconClass">
<text class="icon-emoji">🛡</text> <text class="icon-glyph"></text>
</view> </view>
<text class="mini-label">已拒绝</text> <text class="mini-label">已拒绝</text>
<view class="mini-value-row"> <view class="mini-value-row">
@@ -205,8 +212,8 @@ const changePercent = computed(() => {
}) })
const insightEmoji = computed(() => { const insightEmoji = computed(() => {
if (changePercent.value === null) return '' if (changePercent.value === null) return ''
return changePercent.value <= 0 ? '🌿' : '⚠️' return changePercent.value <= 0 ? '' : ''
}) })
const insightIconClass = computed(() => { const insightIconClass = computed(() => {
@@ -509,33 +516,94 @@ onShareAppMessage(() => {
<style scoped> <style scoped>
.page { .page {
min-height: 100vh; min-height: 100vh;
background: linear-gradient(to bottom, #D1FAE5 0%, #F0FDF4 45%, #FFFFFF 100%); position: relative;
background:
radial-gradient(circle at top left, rgba(52, 200, 160, 0.16), transparent 30%),
radial-gradient(circle at top right, rgba(255, 255, 255, 0.92), transparent 24%),
linear-gradient(180deg, #edf2f8 0%, #f5f7fb 38%, #fbfdff 100%);
padding: 0 32rpx 200rpx; padding: 0 32rpx 200rpx;
box-sizing: border-box; box-sizing: border-box;
overflow: hidden;
} }
.status-bar { .status-bar {
background: linear-gradient(to bottom, #D1FAE5, #F0FDF4); background: transparent;
height: 0; height: 0;
} }
.page-glow {
position: absolute;
border-radius: 50%;
filter: blur(24rpx);
opacity: 0.72;
pointer-events: none;
}
.page-glow-a {
top: 88rpx;
left: -140rpx;
width: 360rpx;
height: 360rpx;
background: rgba(52, 200, 160, 0.14);
}
.page-glow-b {
top: 340rpx;
right: -120rpx;
width: 320rpx;
height: 320rpx;
background: rgba(255, 255, 255, 0.9);
}
.sticky-bar { .sticky-bar {
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 20; z-index: 20;
background: linear-gradient(to bottom, #D1FAE5 0%, #F0FDF4 70%, rgba(255, 255, 255, 0.95) 100%); background: linear-gradient(180deg, rgba(237, 242, 248, 0.96) 0%, rgba(245, 247, 251, 0.92) 76%, rgba(251, 253, 255, 0) 100%);
padding-bottom: 8rpx; padding-bottom: 8rpx;
} }
.page-head {
padding: 18rpx 0 18rpx;
}
.page-eyebrow {
display: block;
font-size: 20rpx;
font-weight: 700;
letter-spacing: 4rpx;
text-transform: uppercase;
color: #98a2b3;
}
.page-title {
display: block;
margin-top: 10rpx;
font-size: 42rpx;
line-height: 1.18;
font-weight: 700;
color: #111827;
}
.page-subtitle {
display: block;
margin-top: 8rpx;
font-size: 24rpx;
line-height: 1.5;
color: #667085;
}
.segment { .segment {
display: flex; display: flex;
background-color: #FFFFFF; background: rgba(255, 255, 255, 0.76);
padding: 6rpx; padding: 6rpx;
border-radius: 20rpx; border-radius: 24rpx;
gap: 6rpx; gap: 6rpx;
margin-bottom: 16rpx; margin-bottom: 16rpx;
border: 2rpx solid #ECFDF3; border: 2rpx solid rgba(255, 255, 255, 0.66);
box-shadow: 0 10rpx 22rpx rgba(16, 185, 129, 0.08); box-shadow: 0 12rpx 28rpx rgba(15, 23, 42, 0.06);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.segment-item { .segment-item {
@@ -550,21 +618,23 @@ onShareAppMessage(() => {
} }
.segment-active { .segment-active {
background-color: #10B981; background: rgba(255, 255, 255, 0.92);
color: #0B2F23; color: #111827;
box-shadow: 0 6rpx 16rpx rgba(16, 185, 129, 0.25); box-shadow: 0 8rpx 18rpx rgba(15, 23, 42, 0.06);
} }
.insight-card { .insight-card {
display: flex; display: flex;
gap: 20rpx; gap: 20rpx;
align-items: flex-start; align-items: flex-start;
background-color: #ECFDF3; background: rgba(255, 255, 255, 0.78);
border: 2rpx solid #D9FBE7; border: 2rpx solid rgba(255, 255, 255, 0.66);
border-radius: 24rpx; border-radius: 30rpx;
padding: 24rpx; padding: 24rpx;
margin-bottom: 28rpx; margin-bottom: 28rpx;
box-shadow: 0 10rpx 20rpx rgba(16, 185, 129, 0.1); box-shadow: 0 14rpx 34rpx rgba(15, 23, 42, 0.06);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.insight-icon { .insight-icon {
@@ -593,8 +663,11 @@ onShareAppMessage(() => {
box-shadow: 0 10rpx 18rpx rgba(100, 116, 139, 0.12); box-shadow: 0 10rpx 18rpx rgba(100, 116, 139, 0.12);
} }
.insight-emoji { .insight-glyph,
font-size: 30rpx; .icon-glyph {
font-size: 24rpx;
font-weight: 700;
line-height: 1;
} }
.insight-title { .insight-title {
@@ -696,11 +769,13 @@ onShareAppMessage(() => {
} }
.trend-card { .trend-card {
background-color: #FFFFFF; background: rgba(255, 255, 255, 0.82);
border-radius: 28rpx; border-radius: 32rpx;
padding: 24rpx; padding: 24rpx;
border: 2rpx solid #ECFDF3; border: 2rpx solid rgba(255, 255, 255, 0.66);
box-shadow: 0 12rpx 24rpx rgba(16, 185, 129, 0.1); box-shadow: 0 16rpx 36rpx rgba(15, 23, 42, 0.06);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.trend-header { .trend-header {
@@ -813,12 +888,14 @@ onShareAppMessage(() => {
} }
.savings-card { .savings-card {
background-color: #FFFFFF; background: rgba(255, 255, 255, 0.82);
border-radius: 28rpx; border-radius: 32rpx;
padding: 24rpx; padding: 24rpx;
margin-top: 20rpx; margin-top: 20rpx;
border: 2rpx solid #FEF3C7; border: 2rpx solid rgba(255, 255, 255, 0.66);
box-shadow: 0 12rpx 24rpx rgba(245, 158, 11, 0.08); box-shadow: 0 16rpx 36rpx rgba(15, 23, 42, 0.06);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.savings-header { .savings-header {
@@ -916,23 +993,6 @@ onShareAppMessage(() => {
display: block; display: block;
} }
.icon-emoji {
font-size: 28rpx;
line-height: 1;
}
.savings-icon .icon-emoji {
font-size: 30rpx;
}
.health-icon .icon-emoji {
font-size: 26rpx;
}
.mini-icon .icon-emoji {
font-size: 26rpx;
}
.savings-metrics { .savings-metrics {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -1011,6 +1071,7 @@ onShareAppMessage(() => {
.card-empty-icon { .card-empty-icon {
font-size: 24rpx; font-size: 24rpx;
font-weight: 700;
} }
.card-empty-text { .card-empty-text {
@@ -1019,12 +1080,14 @@ onShareAppMessage(() => {
} }
.health-card { .health-card {
background-color: #FFFFFF; background: rgba(255, 255, 255, 0.82);
border-radius: 28rpx; border-radius: 32rpx;
padding: 24rpx; padding: 24rpx;
margin-top: 24rpx; margin-top: 24rpx;
border: 2rpx solid #ECFDF3; border: 2rpx solid rgba(255, 255, 255, 0.66);
box-shadow: 0 12rpx 24rpx rgba(16, 185, 129, 0.08); box-shadow: 0 16rpx 36rpx rgba(15, 23, 42, 0.06);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.health-header { .health-header {
@@ -1113,7 +1176,16 @@ onShareAppMessage(() => {
} }
.health-metric-icon { .health-metric-icon {
font-size: 22rpx; width: 40rpx;
height: 40rpx;
border-radius: 14rpx;
background: rgba(52, 200, 160, 0.14);
display: flex;
align-items: center;
justify-content: center;
font-size: 20rpx;
font-weight: 700;
color: #17795c;
} }
.health-metric-value { .health-metric-value {
@@ -1208,11 +1280,13 @@ onShareAppMessage(() => {
.mini-card { .mini-card {
flex: 1; flex: 1;
background-color: #FFFFFF; background: rgba(255, 255, 255, 0.82);
border-radius: 24rpx; border-radius: 28rpx;
padding: 24rpx; padding: 24rpx;
border: 2rpx solid #ECFDF3; border: 2rpx solid rgba(255, 255, 255, 0.66);
box-shadow: 0 12rpx 24rpx rgba(16, 185, 129, 0.06); box-shadow: 0 14rpx 32rpx rgba(15, 23, 42, 0.05);
backdrop-filter: blur(24rpx);
-webkit-backdrop-filter: blur(24rpx);
} }
.mini-icon { .mini-icon {
@@ -1299,4 +1373,3 @@ onShareAppMessage(() => {
</style> </style>

Before

Width:  |  Height:  |  Size: 902 B

After

Width:  |  Height:  |  Size: 902 B

Before

Width:  |  Height:  |  Size: 912 B

After

Width:  |  Height:  |  Size: 912 B

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 464 B

After

Width:  |  Height:  |  Size: 464 B

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File
View File
+13
View File
@@ -0,0 +1,13 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import Uni from '@uni-helper/plugin-uni'
export default defineConfig({
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
plugins: [Uni()]
})