feat(fe): 新增 seg-tabs 与 stat-ring 组件
seg-tabs 合并三套长得像但尺寸各不同的横向切换:plan 的 .inline-tabs
(灰槽白块)、community 的 .feed-tab(白药丸+阴影)、record 的 .rf-chip
(白药丸无阴影,高度还差 10rpx)。variant="solid|pill" 二选一,items
同时支持字符串数组和 {key,label} 数组。
stat-ring 是首页门面用的完成度环。用 SVG data-uri 画,和 record 页的
体重折线图同一套路——canvas 在 scroll-view 里的层级、滚动跟随和截图
行为全是坑。中心内容走 slot。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
// 横向切换控件。此前有三套长得像但尺寸各不同的实现:
|
||||
// plan 的 .inline-tabs(灰槽 + 白块)、community 的 .feed-tab(白药丸 + 橙选中 + 阴影)、
|
||||
// record 的 .rf-chip(白药丸 + 橙选中 + 无阴影,高度还差 10rpx)
|
||||
// 这里合成一个:variant="solid" 对应灰槽白块,variant="pill" 对应横滑药丸。
|
||||
//
|
||||
// items 支持两种写法:
|
||||
// ['推荐','关注','经验'] —— current 传下标
|
||||
// [{key:'',label:'全部'},{key:'weight',label:'体重'}] —— current 传 key
|
||||
Component({
|
||||
options: { addGlobalClass: true },
|
||||
properties: {
|
||||
items: { type: Array, value: [] },
|
||||
current: { type: null, value: 0 },
|
||||
variant: { type: String, value: 'pill' },
|
||||
},
|
||||
data: { list: [] },
|
||||
observers: {
|
||||
'items,current': function (items, current) {
|
||||
const byKey = typeof current === 'string';
|
||||
const list = (items || []).map((it, i) => {
|
||||
const obj = it && typeof it === 'object';
|
||||
const key = obj ? it.key : i;
|
||||
return {
|
||||
key,
|
||||
label: obj ? it.label : it,
|
||||
active: byKey ? key === current : i === Number(current),
|
||||
};
|
||||
});
|
||||
this.setData({ list });
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onTap(e) {
|
||||
const { index, key } = e.currentTarget.dataset;
|
||||
this.triggerEvent('change', { index: Number(index), key });
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<scroll-view wx:if="{{variant === 'pill'}}" class="st st-pill" scroll-x="{{true}}" enhanced="{{true}}" show-scrollbar="{{false}}">
|
||||
<view wx:for="{{list}}" wx:key="key" class="st-item {{item.active ? 'on' : ''}}"
|
||||
data-index="{{index}}" data-key="{{item.key}}" bindtap="onTap">{{item.label}}</view>
|
||||
</scroll-view>
|
||||
|
||||
<view wx:else class="st st-solid">
|
||||
<view wx:for="{{list}}" wx:key="key" class="st-item {{item.active ? 'on' : ''}}"
|
||||
data-index="{{index}}" data-key="{{item.key}}" bindtap="onTap">{{item.label}}</view>
|
||||
</view>
|
||||
@@ -0,0 +1,21 @@
|
||||
.st{white-space:nowrap}
|
||||
|
||||
/* 横滑药丸:数量不定、可滚动(社区分类、记录类型筛选) */
|
||||
.st-pill{padding:0 0 var(--sp-4)}
|
||||
.st-pill .st-item{
|
||||
display:inline-block;height:var(--h-sm);line-height:var(--h-sm);padding:0 var(--sp-4);
|
||||
margin-right:var(--sp-2);border-radius:var(--r-full);border:1rpx solid var(--line);
|
||||
background:#fff;color:var(--muted);font-size:var(--fs-sm);font-weight:var(--fw-b);
|
||||
}
|
||||
.st-pill .st-item.on{color:var(--primary-dark);background:var(--primary-soft);border-color:var(--primary-line)}
|
||||
|
||||
/* 灰槽白块:数量固定的互斥视图(计划页三段) */
|
||||
.st-solid{
|
||||
display:flex;gap:var(--sp-1);padding:var(--sp-1);border-radius:var(--r-md);
|
||||
background:#EFE8DE;margin-bottom:var(--sp-4);
|
||||
}
|
||||
.st-solid .st-item{
|
||||
flex:1;height:var(--h-sm);line-height:var(--h-sm);text-align:center;border-radius:var(--r-sm);
|
||||
color:var(--muted);font-size:var(--fs-md);font-weight:var(--fw-b);
|
||||
}
|
||||
.st-solid .st-item.on{background:#fff;color:var(--primary-dark)}
|
||||
@@ -0,0 +1,41 @@
|
||||
// 完成度环。用 SVG data-uri 画(和 record 页的体重折线图同一套路),不用 canvas:
|
||||
// canvas 在 scroll-view 里的层级、滚动跟随和截图行为全是坑。
|
||||
const TAU = 2 * Math.PI;
|
||||
const R = 42; // viewBox 100 下的半径,留出描边宽度
|
||||
|
||||
function ringSvg(percent, stroke, track, fill) {
|
||||
const c = TAU * R;
|
||||
const p = Math.max(0, Math.min(100, Number(percent) || 0));
|
||||
const off = c * (1 - p / 100);
|
||||
const circle = (color, dash) =>
|
||||
'<circle cx="50" cy="50" r="' + R + '" fill="none" stroke="' + color +
|
||||
'" stroke-width="' + stroke + '" stroke-linecap="round"' + dash + '/>';
|
||||
return (
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">' +
|
||||
circle(track, '') +
|
||||
'<g transform="rotate(-90 50 50)">' +
|
||||
circle(fill, ' stroke-dasharray="' + c.toFixed(1) + '" stroke-dashoffset="' + off.toFixed(1) + '"') +
|
||||
'</g></svg>'
|
||||
);
|
||||
}
|
||||
|
||||
Component({
|
||||
options: { addGlobalClass: true },
|
||||
properties: {
|
||||
percent: { type: Number, value: 0 },
|
||||
size: { type: Number, value: 180 }, // rpx
|
||||
stroke: { type: Number, value: 8 }, // viewBox 单位,即直径的百分比
|
||||
track: { type: String, value: '#FFE6BA' },
|
||||
fill: { type: String, value: '#F5A947' },
|
||||
},
|
||||
data: { src: '', boxStyle: '' },
|
||||
observers: {
|
||||
'percent,size,stroke,track,fill': function (percent, size, stroke, track, fill) {
|
||||
const svg = ringSvg(percent, stroke, track, fill);
|
||||
this.setData({
|
||||
src: 'data:image/svg+xml,' + encodeURIComponent(svg),
|
||||
boxStyle: 'width:' + size + 'rpx;height:' + size + 'rpx',
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<view class="ring" style="{{boxStyle}}">
|
||||
<image class="ring-img" src="{{src}}" mode="widthFix"></image>
|
||||
<view class="ring-center"><slot></slot></view>
|
||||
</view>
|
||||
@@ -0,0 +1,6 @@
|
||||
.ring{position:relative;flex:none}
|
||||
.ring-img{width:100%;height:100%;display:block}
|
||||
.ring-center{
|
||||
position:absolute;left:0;top:0;right:0;bottom:0;
|
||||
display:flex;flex-direction:column;align-items:center;justify-content:center;
|
||||
}
|
||||
Reference in New Issue
Block a user