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