feat: redesign onboarding and home UI

This commit is contained in:
nepiedg
2026-04-25 00:32:12 +08:00
parent 4bd79ded0c
commit ba0a712306
8 changed files with 2020 additions and 485 deletions
+89
View File
@@ -0,0 +1,89 @@
设计原则
整合四位专家的核心诉求:
冷静但不冷漠、有趣但不幼稚、净化而非刺激
最终界面布局(完整结构)
📐 整体高度:约手机一屏半,单页垂直流
【顶部区】个人状态栏(高度 30%
*.txt
Plaintext
┌─────────────────────────────────────────────────────┐
│ [头像] 意志骑士 Lv.3 💰 ×24 │ ← 毛玻璃背景
│ │ backdrop-blur: 12px
│ ╭─────────────────────────────────────────────╮ │
│ │ ████████████████░░░░░░░░░░░░░░░░░░░░░░░░░ │ │ ← HP生命槽
│ │ HP: 72% 浊→清 视觉语言 │ │ 渐变填充 #6EE7B7#67E8F9
│ ╰─────────────────────────────────────────────╯ │ 24px高,12px圆角
│ │
│ ✦ 深度含氧(已坚持 2 小时 17 分) │ ← Buff标签
│ [薰衣草紫边框] │ 胶囊形
└─────────────────────────────────────────────────────┘
设计决策说明:
保留游戏化元素(等级/称号),但降低视觉优先级——置于次要位置
采用色彩心理学专家的"浊清动效"替代碎裂动效,保护用户情绪
毛玻璃背景融合未来主义美学,但保持经典主义的秩序感
【中部区】核心控制台(高度 35%
*.txt
Plaintext
【中下区】健康仪表盘(高度 20%
*.txt
Plaintext
┌─────────────────────────────────────────────────────┐
│ │
│ ╭─────────╮ ╭────────────────────────╮ │
│ │ ◐ │ │ 呼吸系统 +12% │ │
│ │ 84% │ │ 心脏功能 +8% │ │
│ │ 健康 │ │ 血液纯净 +15% │ │
│ ╰─────────╯ │ ──────────────────── │ │
│ 健康指数 │ +3.2 小时 │ │ ← 生命回收
│ (圆环图) │ 生命已回收 │ │ 强调色24px
│ ╰────────────────────────╯ │
│ │
└─────────────────────────────────────────────────────┘
设计决策说明:
健康指数圆环图代替多个独立卡片,符合极简主义减法原则
生命回收概念来自未来主义,成为核心数据展示
【底部区】成长曲线(高度 15%
*.txt
Plaintext
┌─────────────────────────────────────────────────────┐
│ │
│ 7 天趋势 │
│ │ │
│ 5 ├ ● │ ← 琥珀金转折点
│ │ ●────────────╱ │
│ 3 ├──●────────────────╱ │
│ │╱ │
│ 0 └──┬────┬────┬────┬────┬────┬────┬────→ │
│ 周一 周二 周三 周四 周五 周六 周日 │
│ │
│ 背景:极淡青绿渐变 (#ECFDF5#F0F9FF) │
│ │
└─────────────────────────────────────────────────────┘
最终色板
用途 色值 来源 说明
HP高填充 #67E8F9 心理学专家调整 降低饱和度,更持久
HP低填充 #D97706 暗琥珀 心理学专家 警示非恐惧
主渐变起 #6EE7B7 心理学专家调整 生命力、成长
SOS/里程碑 #FBBF24 全场共识 温暖、成就
Buff标签 #A78BFA 薰衣草紫 心理学专家原创 成长、过渡
背景 #F8FAFC 微蓝白 经典主义 干净、呼吸感
文字主色 #1E293B 深蓝灰 经典主义 冷静、专业
动效规范
动效 实现 周期 意义
HP呼吸闪烁 微弱明度变化 ±5% 4-6秒 与身体节奏共振
浊清过渡 颜色从暗琥珀→清澈渐变 2秒 非破坏性反馈
呼吸线脉动 顶部细线不透明度 10-20% 6秒 身体自愈暗示
设计决策摘要
问题 最终决策 采纳来源
HP碎裂动效 ❌ 取消 → 改用"浊清"视觉语言 色彩心理学专家
等级/称号 ✅ 保留,但降低视觉优先级 未来主义
金币奖励 ✅ 保留图标形式,删除独立卡片 极简主义融合
游戏化整体 ⚠️ 保留数值化反馈,删除幼稚外壳 经典主义+未来主义融合
布局风格 瑞士网格+毛玻璃 经典主义+未来主义融合
配色饱和度 降低15-20% 经典主义+色彩心理学
一句话总结
"一个冷静的生物数据仪表盘,用颜色的语言告诉用户:你的每一次选择,都在让生命更加清澈。"
@@ -11,7 +11,8 @@
</view>
<view class="dialog-body">
<view v-if="quickModeActive" class="quick-banner">
<template v-if="quickModeActive">
<view class="quick-banner">
<view class="quick-banner-chip">
<text class="quick-banner-chip-label">默认时间</text>
<text class="quick-banner-chip-value">{{ formData.smoke_time_only }}</text>
@@ -20,10 +21,10 @@
<text class="quick-banner-chip-label">默认数量</text>
<text class="quick-banner-chip-value">{{ formData.num }} </text>
</view>
<text class="quick-banner-tip">选择原因和烟瘾等级即可快速保存需要时再展开高级项</text>
<text class="quick-banner-tip">选择系统场景如果选其他再补充自定义原因</text>
</view>
<view v-if="quickModeActive && type === 'smoke'" class="section-card level-section-quick">
<view v-if="type === 'smoke'" class="section-card level-section-quick">
<view class="level-header">
<text class="section-title">烟瘾等级</text>
<view class="level-badge">Level {{ formData.level }}</view>
@@ -46,10 +47,10 @@
</view>
</view>
<view class="section-card">
<view class="section-card scene-section">
<view class="section-heading">
<text class="section-title">{{ reasonSectionTitle }}</text>
<text class="section-caption">可多选</text>
<text class="section-title">{{ sceneSectionTitle }}</text>
<text class="section-caption">系统预设可多选</text>
</view>
<view class="reason-chip-grid">
<view
@@ -64,30 +65,31 @@
</view>
</view>
<view class="remark-section">
<view v-if="showCustomReasonInput" class="remark-section">
<view class="section-heading">
<text class="section-title">{{ remarkTitle }}</text>
<text class="section-caption">{{ remarkCaption }}</text>
<text class="section-title">其他场景</text>
<text class="section-caption">自填</text>
</view>
<view class="remark-card">
<textarea
class="form-textarea"
v-model="formData.remark"
:placeholder="remarkPlaceholder"
maxlength="200"
class="form-textarea form-textarea-compact"
v-model="formData.custom_reason"
:placeholder="customReasonPlaceholder"
maxlength="120"
/>
</view>
</view>
<view v-if="quickModeActive" class="advanced-toggle" @tap="showAdvanced = !showAdvanced">
<view class="advanced-toggle" @tap="showAdvanced = !showAdvanced">
<view>
<text class="advanced-toggle-title">{{ showAdvanced ? '收起高级设置' : '展开高级设置' }}</text>
<text class="advanced-toggle-desc">修改时间和数量</text>
</view>
<text class="advanced-toggle-arrow">{{ showAdvanced ? '⌃' : '⌄' }}</text>
</view>
</template>
<view v-if="showAdvanced || !quickModeActive" class="advanced-fields">
<view v-if="showAdvanced || !quickModeActive" class="advanced-fields detail-top-fields">
<view class="form-row">
<picker class="picker-card" mode="date" :value="formData.smoke_time" @change="onDateChange">
<view class="input-card">
@@ -121,7 +123,7 @@
</view>
</view>
<view v-if="!quickModeActive" class="section-card">
<view class="section-card">
<view class="level-header">
<text class="section-title">{{ type === 'smoke' ? '烟瘾程度' : '忍住强度' }}</text>
<view class="level-badge">Level {{ formData.level }}</view>
@@ -144,8 +146,57 @@
</view>
</view>
</view>
<template v-if="!quickModeActive">
<view class="section-card scene-section">
<view class="section-heading">
<text class="section-title">{{ sceneSectionTitle }}</text>
<text class="section-caption">系统预设可多选</text>
</view>
<view class="reason-chip-grid">
<view
v-for="item in quickReasonOptions"
:key="item.key"
class="reason-chip"
:class="{ 'reason-chip-active': isReasonSelected(item.key) }"
@tap="toggleReason(item.key)"
>
<text class="reason-chip-text">{{ item.label }}</text>
</view>
</view>
</view>
<view v-if="showCustomReasonInput" class="remark-section">
<view class="section-heading">
<text class="section-title">其他场景</text>
<text class="section-caption">自填</text>
</view>
<view class="remark-card">
<textarea
class="form-textarea form-textarea-compact"
v-model="formData.custom_reason"
:placeholder="customReasonPlaceholder"
maxlength="120"
/>
</view>
</view>
<view class="remark-section remark-section-bottom">
<view class="section-heading">
<text class="section-title">原因</text>
<text class="section-caption">可选放在最后补充</text>
</view>
<view class="remark-card">
<textarea
class="form-textarea"
v-model="formData.remark"
:placeholder="remarkPlaceholder"
maxlength="200"
/>
</view>
</view>
</template>
</view>
<view class="dialog-footer">
<view class="dialog-btn-primary" @tap="submit">
<view class="btn-icon"></view>
@@ -188,6 +239,7 @@ export default {
smoke_time_only: '',
smoke_at: '',
remark: '',
custom_reason: '',
reason_tags: [],
level: 2,
num: 1
@@ -212,9 +264,20 @@ export default {
quickReasonOptions() {
return getReasonOptions(this.type)
},
sceneSectionTitle() {
return this.type === 'smoke' ? '选择抽烟场景' : '选择忍住场景'
},
reasonSectionTitle() {
return this.type === 'smoke' ? '这次为什么会抽?' : '这次是怎么扛住的?'
},
showCustomReasonInput() {
return this.formData.reason_tags.includes('other')
},
customReasonPlaceholder() {
return this.type === 'smoke'
? '写下系统场景里没有覆盖的触发点...'
: '写下这次具体是怎么撑过去的...'
},
remarkTitle() {
return this.formData.reason_tags.includes('other') ? '补充说明' : '补充备注'
},
@@ -253,6 +316,7 @@ export default {
smoke_time_only: this.initialData.smoke_time_only || '',
smoke_at: this.initialData.smoke_at || '',
remark: this.initialData.remark || '',
custom_reason: '',
reason_tags: normalizeReasonTags(this.initialData.reason_tags),
level: this.initialData.level ?? 2,
num: this.resolveInitialNum(this.initialData)
@@ -273,6 +337,7 @@ export default {
smoke_time_only: timeStr,
smoke_at: datetimeStr,
remark: '',
custom_reason: '',
reason_tags: [],
level: 2,
num: this.type === 'smoke' ? 1 : 0
@@ -350,14 +415,16 @@ export default {
},
buildRemark() {
const customRemark = (this.formData.remark || '').trim()
const customReason = (this.formData.custom_reason || '').trim()
const reasonLabels = getReasonLabels(this.formData.reason_tags, this.type).filter(label => label !== '其他')
if (!reasonLabels.length) {
return customRemark
const parts = [...reasonLabels]
if (customReason) {
parts.push(customReason)
}
if (!customRemark) {
return reasonLabels.join('、')
if (customRemark) {
parts.push(customRemark)
}
return `${reasonLabels.join('')}${customRemark}`
return parts.join('')
},
submit() {
if (!this.isTimeValid()) {
@@ -546,6 +613,11 @@ export default {
box-shadow: 0 10rpx 18rpx rgba(26, 163, 122, 0.12);
}
.scene-section {
background:
linear-gradient(135deg, rgba(255, 255, 255, 0.86), rgba(240, 249, 255, 0.72));
}
.reason-chip-text {
font-size: 24rpx;
font-weight: 600;
@@ -576,6 +648,14 @@ export default {
box-sizing: border-box;
}
.form-textarea-compact {
min-height: 112rpx;
}
.remark-section-bottom {
margin-top: 4rpx;
}
.advanced-toggle {
display: flex;
align-items: center;
+1 -1
View File
@@ -1,6 +1,6 @@
const ENV = {
development: {
BASE_URL: 'http://localhost:8080/api/v1',
BASE_URL: 'http://192.168.31.73:9003/api/v1',
MINI_PROGRAM_ID: 2
},
production: {
+2 -2
View File
@@ -7,13 +7,13 @@
},
"pages": [
{
"path": "pages/mode-select/index",
"path": "pages/index/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/index/index",
"path": "pages/mode-select/index",
"style": {
"navigationStyle": "custom"
}
+1255 -257
View File
File diff suppressed because it is too large Load Diff
+154 -30
View File
@@ -115,8 +115,13 @@
<!-- 成就风格 -->
<view v-if="achievementThemes.length > 0" class="form-section">
<view class="theme-section-head">
<view>
<text class="section-label">成就称号风格</text>
<text class="section-hint">打卡越久称号越高</text>
<text class="section-hint">选择一套更能激励你的成长称号</text>
</view>
<text class="theme-section-badge">可更换</text>
</view>
<view class="theme-list">
<view
v-for="theme in achievementThemes"
@@ -125,16 +130,25 @@
:class="{ 'theme-card-active': formData.achievement_theme_id === theme.id }"
@tap="formData.achievement_theme_id = theme.id"
>
<view class="theme-glow"></view>
<view class="theme-header">
<view class="theme-icon-wrap">
<text class="theme-icon">{{ theme.icon }}</text>
</view>
<view class="theme-title-wrap">
<text class="theme-name">{{ theme.name }}</text>
<text class="theme-desc">打卡进度会逐步解锁称号</text>
</view>
<view class="theme-check">
<text v-if="formData.achievement_theme_id === theme.id"></text>
</view>
</view>
<view class="theme-levels">
<text
v-for="(level, idx) in theme.levels"
:key="level.id"
class="theme-level"
>{{ level.name }}<text v-if="idx < theme.levels.length - 1" class="theme-arrow"> </text></text>
>{{ level.name }}</text>
</view>
</view>
</view>
@@ -263,10 +277,6 @@ async function handleSubmit() {
uni.showLoading({ title: '保存中...' })
await profileStore.saveProfile(formData.value)
uni.hideLoading()
if (!formData.value.mode) {
uni.redirectTo({ url: '/pages/mode-select/index' })
return
}
uni.switchTab({ url: '/pages/index/index' })
} catch (e) {
uni.hideLoading()
@@ -539,66 +549,180 @@ onShareAppMessage(() => {
margin-bottom: 16rpx;
}
.theme-section-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 20rpx;
margin-bottom: 18rpx;
}
.theme-section-head .section-hint {
margin-bottom: 0;
}
.theme-section-badge {
flex-shrink: 0;
padding: 8rpx 16rpx;
border-radius: 999rpx;
background: rgba(110, 231, 183, 0.16);
border: 1rpx solid rgba(16, 185, 129, 0.16);
font-size: 20rpx;
font-weight: 700;
color: #0F766E;
}
.theme-list {
display: flex;
flex-direction: column;
gap: 12rpx;
gap: 18rpx;
}
.theme-card {
padding: 20rpx;
border-radius: 20rpx;
background: #F9FBFA;
border: 2rpx solid #F0F0F0;
position: relative;
overflow: hidden;
padding: 24rpx;
border-radius: 28rpx;
background:
linear-gradient(135deg, rgba(255, 255, 255, 0.96), rgba(248, 250, 252, 0.92));
border: 2rpx solid rgba(226, 232, 240, 0.9);
box-sizing: border-box;
box-shadow: 0 10rpx 28rpx rgba(15, 23, 42, 0.04);
}
.theme-card-active {
background: #E8F5F0;
border-color: #10B981;
background:
radial-gradient(circle at top right, rgba(103, 232, 249, 0.28), transparent 34%),
linear-gradient(135deg, rgba(236, 253, 245, 0.98), rgba(240, 249, 255, 0.94));
border-color: rgba(16, 185, 129, 0.72);
box-shadow: 0 18rpx 42rpx rgba(16, 185, 129, 0.13);
}
.theme-glow {
position: absolute;
top: -70rpx;
right: -70rpx;
width: 190rpx;
height: 190rpx;
border-radius: 50%;
background: rgba(110, 231, 183, 0.12);
}
.theme-card-active .theme-glow {
background: rgba(103, 232, 249, 0.28);
}
.theme-header {
position: relative;
display: flex;
align-items: center;
gap: 10rpx;
margin-bottom: 10rpx;
gap: 18rpx;
margin-bottom: 20rpx;
}
.theme-icon-wrap {
width: 72rpx;
height: 72rpx;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
background: rgba(241, 245, 249, 0.92);
border: 1rpx solid rgba(226, 232, 240, 0.9);
box-shadow: inset 0 1rpx 0 rgba(255, 255, 255, 0.7);
flex-shrink: 0;
}
.theme-card-active .theme-icon-wrap {
background: linear-gradient(135deg, #6EE7B7, #67E8F9);
border-color: rgba(255, 255, 255, 0.9);
box-shadow: 0 12rpx 26rpx rgba(20, 184, 166, 0.18);
}
.theme-icon {
font-size: 32rpx;
font-size: 36rpx;
line-height: 1;
}
.theme-title-wrap {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 6rpx;
}
.theme-name {
font-size: 28rpx;
font-weight: 700;
color: #1A1A1A;
font-size: 29rpx;
line-height: 1.25;
font-weight: 800;
color: #1E293B;
}
.theme-desc {
font-size: 21rpx;
line-height: 1.4;
color: #64748B;
}
.theme-card-active .theme-name {
color: #10B981;
color: #0F766E;
}
.theme-check {
width: 42rpx;
height: 42rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 2rpx solid rgba(148, 163, 184, 0.34);
background: rgba(255, 255, 255, 0.78);
color: #FFFFFF;
font-size: 24rpx;
font-weight: 900;
flex-shrink: 0;
}
.theme-card-active .theme-check {
border-color: transparent;
background: linear-gradient(135deg, #10B981, #06B6D4);
box-shadow: 0 8rpx 18rpx rgba(16, 185, 129, 0.2);
}
.theme-levels {
font-size: 22rpx;
color: #999999;
line-height: 1.6;
position: relative;
display: flex;
flex-wrap: wrap;
gap: 10rpx;
}
.theme-level {
font-size: 22rpx;
max-width: 100%;
padding: 8rpx 14rpx;
border-radius: 999rpx;
background: rgba(241, 245, 249, 0.95);
border: 1rpx solid rgba(226, 232, 240, 0.9);
font-size: 21rpx;
line-height: 1.35;
font-weight: 700;
color: #64748B;
}
.theme-card-active .theme-level {
color: #10B981;
background: rgba(255, 255, 255, 0.72);
border-color: rgba(16, 185, 129, 0.15);
color: #0F766E;
}
.theme-card-active .theme-level:first-child {
background: rgba(251, 191, 36, 0.18);
border-color: rgba(251, 191, 36, 0.22);
color: #B45309;
}
.theme-arrow {
color: #CCCCCC;
}
.theme-card-active .theme-arrow {
color: #6EE7B7;
display: none;
}
.bottom-space {
+343 -99
View File
@@ -1,13 +1,38 @@
<template>
<view class="page">
<view class="bg-orb bg-orb-main"></view>
<view class="bg-orb bg-orb-soft"></view>
<view class="header">
<view class="header-copy">
<text class="eyebrow">ACCOUNTABILITY</text>
<text class="title">监督人机制</text>
<text class="subtitle">邀请朋友监督你的戒烟旅程或者你来监督别人</text>
<text class="subtitle">邀请朋友监督你的戒烟旅程也可以成为别人的坚持搭子</text>
</view>
<view class="hero-panel">
<view class="hero-stat">
<text class="hero-stat-value">{{ supervisorItems.length }}/3</text>
<text class="hero-stat-label">监督我的人</text>
</view>
<view class="hero-divider"></view>
<view class="hero-stat">
<text class="hero-stat-value">{{ overviewItems.length }}</text>
<text class="hero-stat-label">我监督的人</text>
</view>
<view class="hero-divider"></view>
<view class="hero-stat">
<text class="hero-stat-value">{{ reminderEnabled ? 'ON' : 'OFF' }}</text>
<text class="hero-stat-label">提醒状态</text>
</view>
</view>
</view>
<view class="card">
<view class="card-head">
<view class="card-title-wrap">
<view class="card-icon">🤝</view>
<text class="card-title">邀请监督人</text>
</view>
<text class="card-meta">已绑定 {{ supervisorItems.length }}/3</text>
</view>
@@ -36,11 +61,15 @@
<view class="card">
<view class="card-head">
<view class="card-title-wrap">
<view class="card-icon card-icon-blue">👀</view>
<text class="card-title">我监督的人</text>
</view>
<text class="card-meta">{{ overviewItems.length }} </text>
</view>
<view v-if="overviewItems.length === 0" class="empty">
<text class="empty-icon">🫶</text>
<text class="empty-text">还没有绑定监督关系</text>
<text class="empty-hint">收到口令后可去绑定监督页面完成绑定</text>
<button class="btn btn-ghost" @tap="gotoBindPage">去绑定监督</button>
@@ -69,10 +98,14 @@
<view class="card">
<view class="card-head">
<view class="card-title-wrap">
<view class="card-icon card-icon-orange">🛡</view>
<text class="card-title">监督我的人</text>
</view>
<text class="card-meta">{{ supervisorItems.length }} </text>
</view>
<view v-if="supervisorItems.length === 0" class="empty">
<text class="empty-icon">🌱</text>
<text class="empty-text">还没有人监督你</text>
<text class="empty-hint">你可以先生成邀请口令发送给朋友</text>
</view>
@@ -89,7 +122,10 @@
<view class="card">
<view class="card-head">
<view class="card-title-wrap">
<view class="card-icon card-icon-purple">🔔</view>
<text class="card-title">提醒设置</text>
</view>
<text class="card-meta">默认关闭</text>
</view>
@@ -136,7 +172,10 @@
<view class="card">
<view class="card-head">
<view class="card-title-wrap">
<view class="card-icon card-icon-gray"></view>
<text class="card-title">提醒测试监督人</text>
</view>
<text class="card-meta">仅写日志</text>
</view>
<view class="settings">
@@ -417,122 +456,285 @@ onShow(async () => {
<style scoped>
.page {
position: relative;
min-height: 100vh;
padding: 28rpx 28rpx 40rpx;
padding: 30rpx 28rpx 48rpx;
box-sizing: border-box;
background: linear-gradient(180deg, #eef7f3 0%, #f7faf8 40%, #fbfdff 100%);
overflow: hidden;
background:
radial-gradient(circle at 16% 0%, rgba(31, 191, 143, 0.18), transparent 34%),
radial-gradient(circle at 92% 10%, rgba(96, 165, 250, 0.15), transparent 30%),
linear-gradient(180deg, #eef7f3 0%, #f7faf8 42%, #fbfdff 100%);
}
.bg-orb {
position: absolute;
border-radius: 999rpx;
pointer-events: none;
filter: blur(2rpx);
}
.bg-orb-main {
top: 126rpx;
right: -86rpx;
width: 230rpx;
height: 230rpx;
background: rgba(26, 163, 122, 0.12);
}
.bg-orb-soft {
top: 520rpx;
left: -100rpx;
width: 260rpx;
height: 260rpx;
background: rgba(96, 165, 250, 0.1);
}
.header,
.card,
.footer {
position: relative;
z-index: 1;
}
.header {
padding: 8rpx 6rpx 18rpx;
padding: 10rpx 4rpx 20rpx;
}
.header-copy {
padding: 4rpx 2rpx 24rpx;
}
.eyebrow {
display: inline-flex;
align-items: center;
align-self: flex-start;
padding: 8rpx 18rpx;
border-radius: 999rpx;
background: rgba(255, 255, 255, 0.74);
border: 1rpx solid rgba(26, 163, 122, 0.14);
color: #1aa37a;
font-size: 20rpx;
font-weight: 800;
letter-spacing: 1.8rpx;
}
.title {
display: block;
font-size: 40rpx;
font-weight: 800;
margin-top: 18rpx;
font-size: 48rpx;
line-height: 1.15;
font-weight: 900;
color: #0f172a;
letter-spacing: 0.5rpx;
letter-spacing: -0.8rpx;
}
.subtitle {
display: block;
margin-top: 10rpx;
font-size: 24rpx;
line-height: 1.6;
margin-top: 12rpx;
max-width: 620rpx;
font-size: 25rpx;
line-height: 1.7;
color: #64748b;
}
.hero-panel {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 20rpx;
border-radius: 30rpx;
background: linear-gradient(135deg, rgba(15, 118, 110, 0.94), rgba(26, 163, 122, 0.86));
box-shadow: 0 18rpx 44rpx rgba(15, 118, 110, 0.2);
overflow: hidden;
}
.hero-stat {
flex: 1;
min-width: 0;
text-align: center;
}
.hero-stat-value {
display: block;
font-size: 34rpx;
line-height: 1.2;
font-weight: 900;
color: #ffffff;
letter-spacing: -0.4rpx;
}
.hero-stat-label {
display: block;
margin-top: 8rpx;
font-size: 20rpx;
line-height: 1.35;
color: rgba(255, 255, 255, 0.76);
}
.hero-divider {
width: 1rpx;
height: 54rpx;
background: rgba(255, 255, 255, 0.24);
}
.card {
margin-top: 18rpx;
background: rgba(255, 255, 255, 0.92);
border-radius: 26rpx;
border: 1rpx solid rgba(15, 23, 42, 0.06);
padding: 22rpx 22rpx;
box-shadow: 0 10rpx 26rpx rgba(15, 23, 42, 0.05);
margin-top: 20rpx;
padding: 26rpx;
border-radius: 32rpx;
background: rgba(255, 255, 255, 0.9);
border: 1rpx solid rgba(255, 255, 255, 0.86);
box-shadow: 0 18rpx 44rpx rgba(15, 23, 42, 0.07);
backdrop-filter: blur(18rpx);
}
.card-head {
display: flex;
align-items: baseline;
align-items: center;
justify-content: space-between;
gap: 16rpx;
}
.card-title-wrap {
display: flex;
align-items: center;
gap: 12rpx;
min-width: 0;
}
.card-icon {
width: 50rpx;
height: 50rpx;
line-height: 50rpx;
border-radius: 18rpx;
text-align: center;
font-size: 25rpx;
background: rgba(26, 163, 122, 0.12);
}
.card-icon-blue {
background: rgba(96, 165, 250, 0.14);
}
.card-icon-orange {
background: rgba(251, 146, 60, 0.15);
}
.card-icon-purple {
background: rgba(168, 85, 247, 0.13);
}
.card-icon-gray {
background: rgba(100, 116, 139, 0.12);
}
.card-title {
font-size: 28rpx;
font-weight: 800;
font-size: 29rpx;
font-weight: 900;
color: #0f172a;
}
.card-meta {
font-size: 22rpx;
color: #94a3b8;
flex-shrink: 0;
padding: 6rpx 14rpx;
border-radius: 999rpx;
background: rgba(241, 245, 249, 0.78);
font-size: 21rpx;
font-weight: 700;
color: #64748b;
}
.invite-box {
margin-top: 18rpx;
position: relative;
margin-top: 22rpx;
padding: 26rpx;
border-radius: 28rpx;
background:
linear-gradient(135deg, rgba(236, 253, 245, 0.94), rgba(255, 255, 255, 0.92));
border: 1rpx solid rgba(26, 163, 122, 0.14);
overflow: hidden;
}
.invite-label {
display: block;
font-size: 22rpx;
font-weight: 800;
color: #64748b;
}
.invite-token {
display: block;
margin-top: 10rpx;
font-size: 36rpx;
margin-top: 12rpx;
font-size: 52rpx;
line-height: 1.15;
font-weight: 900;
letter-spacing: 2rpx;
letter-spacing: 4rpx;
color: #0f766e;
font-family: 'DIN Alternate', -apple-system, sans-serif;
}
.invite-hint {
display: block;
margin-top: 10rpx;
font-size: 22rpx;
line-height: 1.6;
margin-top: 14rpx;
font-size: 23rpx;
line-height: 1.65;
color: #475569;
}
.invite-actions {
display: flex;
gap: 14rpx;
margin-top: 16rpx;
margin-top: 22rpx;
}
.invite-actions .btn {
flex: 1;
}
.invite-expire {
display: block;
margin-top: 14rpx;
font-size: 22rpx;
margin-top: 16rpx;
font-size: 21rpx;
color: #94a3b8;
}
.invite-empty {
margin-top: 18rpx;
margin-top: 22rpx;
display: flex;
flex-direction: column;
gap: 16rpx;
gap: 18rpx;
padding: 22rpx;
border-radius: 26rpx;
background: rgba(248, 250, 252, 0.74);
border: 1rpx dashed rgba(26, 163, 122, 0.22);
}
.invite-empty-text {
font-size: 24rpx;
line-height: 1.6;
line-height: 1.65;
color: #475569;
}
.empty {
margin-top: 18rpx;
padding: 16rpx 6rpx 6rpx;
margin-top: 22rpx;
padding: 34rpx 24rpx;
border-radius: 28rpx;
text-align: center;
background: linear-gradient(180deg, rgba(248, 250, 252, 0.78), rgba(255, 255, 255, 0.76));
border: 1rpx dashed rgba(100, 116, 139, 0.18);
}
.empty-icon {
display: block;
font-size: 48rpx;
line-height: 1;
margin-bottom: 16rpx;
}
.empty-text {
display: block;
font-size: 24rpx;
font-weight: 700;
font-size: 26rpx;
font-weight: 900;
color: #0f172a;
}
@@ -544,28 +746,34 @@ onShow(async () => {
color: #64748b;
}
.empty .btn {
margin-top: 20rpx;
}
.list {
margin-top: 18rpx;
margin-top: 22rpx;
display: flex;
flex-direction: column;
gap: 14rpx;
gap: 16rpx;
}
.row {
display: flex;
gap: 16rpx;
gap: 18rpx;
align-items: flex-start;
padding: 16rpx;
border-radius: 20rpx;
background: rgba(241, 245, 249, 0.6);
border: 1rpx solid rgba(15, 23, 42, 0.04);
padding: 20rpx;
border-radius: 26rpx;
background: linear-gradient(180deg, rgba(248, 250, 252, 0.9), rgba(255, 255, 255, 0.9));
border: 1rpx solid rgba(15, 23, 42, 0.05);
}
.avatar {
width: 74rpx;
height: 74rpx;
width: 82rpx;
height: 82rpx;
border-radius: 50%;
background: #e2e8f0;
border: 4rpx solid #ffffff;
box-shadow: 0 10rpx 22rpx rgba(15, 23, 42, 0.09);
flex-shrink: 0;
}
@@ -577,24 +785,27 @@ onShow(async () => {
.name {
display: block;
font-size: 28rpx;
font-weight: 800;
line-height: 1.35;
font-weight: 900;
color: #0f172a;
}
.meta {
margin-top: 10rpx;
margin-top: 12rpx;
display: flex;
gap: 10rpx;
flex-wrap: wrap;
}
.pill {
padding: 8rpx 12rpx;
padding: 8rpx 13rpx;
border-radius: 999rpx;
background: #ffffff;
border: 1rpx solid rgba(15, 23, 42, 0.06);
font-size: 20rpx;
font-weight: 800;
color: #334155;
box-shadow: 0 6rpx 14rpx rgba(15, 23, 42, 0.04);
}
.pill-muted {
@@ -603,74 +814,94 @@ onShow(async () => {
.pill-up {
color: #0f766e;
background: rgba(204, 251, 241, 0.6);
background: rgba(204, 251, 241, 0.72);
border-color: rgba(15, 118, 110, 0.12);
}
.pill-down {
color: #b91c1c;
background: rgba(254, 226, 226, 0.8);
background: rgba(254, 226, 226, 0.86);
border-color: rgba(185, 28, 28, 0.1);
}
.status {
display: block;
margin-top: 10rpx;
margin-top: 12rpx;
font-size: 22rpx;
line-height: 1.55;
color: #64748b;
}
.row-actions {
margin-top: 12rpx;
margin-top: 14rpx;
display: flex;
justify-content: flex-end;
}
.mini-btn {
height: 56rpx;
line-height: 56rpx;
padding: 0 18rpx;
border-radius: 14rpx;
background: #ffffff;
border: 1rpx solid rgba(185, 28, 28, 0.28);
color: #b91c1c;
font-size: 22rpx;
font-weight: 700;
}
.footer {
margin-top: 20rpx;
margin-top: 22rpx;
padding-bottom: 20rpx;
display: flex;
justify-content: center;
}
.btn {
height: 76rpx;
line-height: 76rpx;
padding: 0 22rpx;
border-radius: 18rpx;
background: linear-gradient(180deg, #1aa37a 0%, #0f766e 100%);
color: #ffffff;
font-size: 26rpx;
font-weight: 700;
.btn,
.mini-btn {
box-sizing: border-box;
border: 0;
margin: 0;
}
.btn[disabled] {
opacity: 0.6;
.btn::after,
.mini-btn::after {
border: 0;
}
.btn {
height: 78rpx;
line-height: 78rpx;
padding: 0 28rpx;
border-radius: 22rpx;
background: linear-gradient(135deg, #1aa37a 0%, #0f766e 100%);
color: #ffffff;
font-size: 26rpx;
font-weight: 900;
box-shadow: 0 12rpx 24rpx rgba(15, 118, 110, 0.18);
}
.btn[disabled],
.mini-btn[disabled] {
opacity: 0.55;
box-shadow: none;
}
.btn-ghost {
background: #ffffff;
background: rgba(255, 255, 255, 0.82);
color: #0f766e;
border: 1rpx solid rgba(15, 118, 110, 0.25);
border: 1rpx solid rgba(15, 118, 110, 0.18);
box-shadow: none;
}
.mini-btn {
height: 58rpx;
line-height: 58rpx;
padding: 0 20rpx;
border-radius: 18rpx;
background: rgba(255, 255, 255, 0.9);
border: 1rpx solid rgba(185, 28, 28, 0.2);
color: #b91c1c;
font-size: 22rpx;
font-weight: 800;
}
.mini-btn-neutral {
border-color: rgba(15, 23, 42, 0.12);
min-width: 144rpx;
border-color: rgba(15, 23, 42, 0.1);
color: #334155;
}
.settings {
margin-top: 18rpx;
margin-top: 22rpx;
display: flex;
flex-direction: column;
gap: 16rpx;
@@ -678,20 +909,20 @@ onShow(async () => {
.setting-row {
display: flex;
align-items: flex-start;
align-items: center;
justify-content: space-between;
gap: 18rpx;
padding: 14rpx 12rpx;
border-radius: 18rpx;
background: rgba(241, 245, 249, 0.45);
border: 1rpx solid rgba(15, 23, 42, 0.04);
gap: 20rpx;
padding: 20rpx;
border-radius: 24rpx;
background: rgba(248, 250, 252, 0.8);
border: 1rpx solid rgba(15, 23, 42, 0.05);
}
.setting-label {
font-size: 24rpx;
font-weight: 800;
flex-shrink: 0;
font-size: 25rpx;
font-weight: 900;
color: #0f172a;
padding-top: 6rpx;
}
.setting-control {
@@ -704,13 +935,15 @@ onShow(async () => {
}
.num-input {
width: 200rpx;
height: 64rpx;
padding: 0 14rpx;
border-radius: 14rpx;
background: rgba(255, 255, 255, 0.92);
width: 190rpx;
height: 66rpx;
padding: 0 18rpx;
border-radius: 18rpx;
background: rgba(255, 255, 255, 0.96);
border: 1rpx solid rgba(15, 23, 42, 0.08);
font-size: 26rpx;
font-size: 27rpx;
font-weight: 800;
color: #0f172a;
text-align: right;
}
@@ -726,15 +959,26 @@ onShow(async () => {
gap: 14rpx;
}
.setting-actions .btn {
flex: 1;
}
.settings-note {
padding: 18rpx 20rpx;
border-radius: 22rpx;
background: rgba(236, 253, 245, 0.7);
font-size: 22rpx;
line-height: 1.6;
line-height: 1.65;
color: #64748b;
}
.run-result {
padding: 18rpx 20rpx;
border-radius: 22rpx;
background: rgba(204, 251, 241, 0.64);
font-size: 22rpx;
line-height: 1.6;
color: #0f766e;
font-weight: 700;
font-weight: 900;
}
</style>
+2 -2
View File
@@ -3,8 +3,8 @@
* 所有页面自动注入,无需手动 import
*/
@import './styles/variables';
@import './styles/mixins';
@import '@/styles/_variables.scss';
@import '@/styles/_mixins.scss';
/* 覆盖 uni-app 默认颜色变量 */
$uni-color-primary: $color-primary-dark;