ux: 动作收藏按钮从顶部浮窗移到底部,与加入计划并排 + 图标美化

- detail 顶部 favbtn 浮窗(需避让胶囊、丑)移除,改为底部 addbar 内与 CTA 并排的方形按钮
- fc-icon 新增 filled 属性:收藏态实心心形(Lucide 描边图标默认 fill=none,filled=true 时 fill=stroke 变实心)
- 收藏按钮美化:圆角 24rpx 方形 + 发丝边框,未收藏描边(text-2)、已收藏实心(accent)+ accent-soft 底色 + accent-ring 边框 + 点按 scale 反馈
- detail.js 清理 favRight/favTop 胶囊避让逻辑(不再需要)
This commit is contained in:
2026-07-22 23:25:17 +08:00
parent 4dba91a16e
commit a0894837bd
4 changed files with 31 additions and 35 deletions

View File

@@ -29,23 +29,28 @@ Component({
size: { type: Number, value: 20 }, size: { type: Number, value: 20 },
// 颜色:可传具体 hex或 var(--xxx)(自动解析为对应 token 色值) // 颜色:可传具体 hex或 var(--xxx)(自动解析为对应 token 色值)
color: { type: String, value: '' }, color: { type: String, value: '' },
// 是否实心填充(如收藏后实心心形)。默认描边
filled: { type: Boolean, value: false },
}, },
data: { src: '' }, data: { src: '' },
observers: { observers: {
name(n) { this._build(n, this.data.color); }, name() { this._build(); },
color(c) { this._build(this.data.name, c); }, color() { this._build(); },
filled() { this._build(); },
}, },
lifetimes: { lifetimes: {
attached() { this._build(this.data.name, this.data.color); }, attached() { this._build(); },
}, },
methods: { methods: {
_build(name, color) { _build() {
const { name, color, filled } = this.data;
const inner = iconSvg[name]; const inner = iconSvg[name];
if (!inner) { this.setData({ src: '' }); return; } if (!inner) { this.setData({ src: '' }); return; }
const stroke = resolveColor(color); const stroke = resolveColor(color);
const fill = filled ? stroke : 'none';
const body = inner.replace(/__C__/g, stroke); const body = inner.replace(/__C__/g, stroke);
const svg = const svg =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" ' + '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="' + fill + '" ' +
'stroke="' + stroke + '" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' + 'stroke="' + stroke + '" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
body + '</svg>'; body + '</svg>';
this.setData({ src: 'data:image/svg+xml,' + encodeURIComponent(svg) }); this.setData({ src: 'data:image/svg+xml,' + encodeURIComponent(svg) });

View File

@@ -7,8 +7,6 @@ const auth = require('../../utils/auth');
Page({ Page({
data: { data: {
statusBarHeight: 20, statusBarHeight: 20,
favRight: 8,
favTop: 20,
id: '', id: '',
planId: '', planId: '',
exercise: null, exercise: null,
@@ -23,18 +21,7 @@ Page({
onLoad(query) { onLoad(query) {
const id = query.id || ''; const id = query.id || '';
const planId = query.planId || ''; const planId = query.planId || '';
// 收藏按钮需避让微信右上角胶囊按钮(···◯):用胶囊实际位置算出安全右/上边距 this.setData({ statusBarHeight: app.globalData.statusBarHeight || 20, id, planId });
let favRight = 8;
let favTop = (app.globalData.statusBarHeight || 20) + 6;
try {
const menu = wx.getMenuButtonBoundingClientRect();
const win = (wx.getWindowInfo && wx.getWindowInfo()) || wx.getSystemInfoSync();
if (menu && win) {
favRight = Math.max(8, win.windowWidth - menu.left + 8);
favTop = menu.top + Math.max(0, (menu.height - 36) / 2);
}
} catch (e) {}
this.setData({ statusBarHeight: app.globalData.statusBarHeight || 20, favRight, favTop, id, planId });
// 防御id 缺失直接显示空态,避免请求 /api/exercises/ 导致错乱或 404 // 防御id 缺失直接显示空态,避免请求 /api/exercises/ 导致错乱或 404
if (!id) { if (!id) {
this.setData({ loading: false }); this.setData({ loading: false });

View File

@@ -1,10 +1,6 @@
<view class="page" wx:if="{{exercise}}"> <view class="page" wx:if="{{exercise}}">
<navbar title="{{exercise.displayName || exercise.name}}" show-back /> <navbar title="{{exercise.displayName || exercise.name}}" show-back />
<view class="favbtn" style="top: {{favTop}}px; right: {{favRight}}px;" bindtap="onToggleFav">
<fc-icon name="heart" size="44" color="{{favorited ? 'var(--accent)' : 'var(--text-3)'}}" />
</view>
<view class="hero"> <view class="hero">
<image <image
wx:if="{{heroSrc && !heroError}}" wx:if="{{heroSrc && !heroError}}"
@@ -68,7 +64,12 @@
</view> </view>
<view class="addbar"> <view class="addbar">
<view class="cta" bindtap="onAdd">{{ planId ? '加入该计划' : '加入计划' }}</view> <view class="addbar__inner">
<view class="fav {{favorited ? 'fav--on' : ''}}" hover-class="fav--hover" bindtap="onToggleFav">
<fc-icon name="heart" size="40" color="{{favorited ? 'var(--accent)' : 'var(--text-2)'}}" filled="{{favorited}}" />
</view>
<view class="cta" bindtap="onAdd">{{ planId ? '加入该计划' : '加入计划' }}</view>
</view>
</view> </view>
</view> </view>

View File

@@ -45,24 +45,27 @@
border-top: 1rpx solid var(--line); border-top: 1rpx solid var(--line);
padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
} }
.addbar__inner { display: flex; align-items: center; gap: 20rpx; }
.cta { .cta {
flex: 1;
display: flex; align-items: center; justify-content: center; display: flex; align-items: center; justify-content: center;
background: linear-gradient(135deg, var(--gold) 0%, var(--gold-2) 100%); background: linear-gradient(135deg, var(--gold) 0%, var(--gold-2) 100%);
color: var(--accent-on); font-weight: 700; font-size: 32rpx; color: var(--accent-on); font-weight: 700; font-size: 32rpx;
border-radius: 999rpx; padding: 26rpx; border-radius: 999rpx; height: 92rpx; box-sizing: border-box; padding: 0 24rpx;
} }
.fav {
flex: 0 0 auto;
width: 92rpx; height: 92rpx; box-sizing: border-box;
border-radius: 24rpx;
background: var(--surface);
border: 1rpx solid var(--line);
display: flex; align-items: center; justify-content: center;
transition: background 200ms var(--ease), border-color 200ms var(--ease), transform 120ms var(--ease);
}
.fav--on { background: var(--accent-soft); border-color: var(--accent-ring); }
.fav--hover { transform: scale(0.94); }
.empty-page { min-height: 100vh; background: var(--bg-grad); } .empty-page { min-height: 100vh; background: var(--bg-grad); }
.topbar { height: 88rpx; display: flex; align-items: center; padding: 0 24rpx; } .topbar { height: 88rpx; display: flex; align-items: center; padding: 0 24rpx; }
.topbar__back { font-size: 56rpx; color: var(--gold); width: 60rpx; line-height: 1; } .topbar__back { font-size: 56rpx; color: var(--gold); width: 60rpx; line-height: 1; }
.empty { color: var(--text-3); font-size: 28rpx; text-align: center; padding: 120rpx 0; } .empty { color: var(--text-3); font-size: 28rpx; text-align: center; padding: 120rpx 0; }
.favbtn {
position: fixed; right: 28rpx; z-index: 60;
width: 72rpx; height: 72rpx; border-radius: 999rpx;
background: var(--float-bg);
backdrop-filter: blur(8px);
border: 1rpx solid var(--gold-line);
display: flex; align-items: center; justify-content: center;
font-size: 44rpx; color: var(--gold-2); line-height: 1;
}