chore: establish V1.1 recovery baseline

This commit is contained in:
2026-09-09 22:05:00 +08:00
commit b46b48eb33
947 changed files with 155471 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
/**
* Loading 加载(规范 §21 推荐列表加载状态)
*
* 两种形态:
* - mode="spinner":圆形旋转(页面级首次加载)
* - mode="skeleton":骨架屏(列表渐进加载,避免抖动)
* 用法:
* <ui-loading mode="spinner" text="正在为你挑选…" />
* <ui-loading mode="skeleton" rows="3" />
*/
Component({
options: { addGlobalClass: true },
properties: {
mode: { type: String, value: 'spinner' }, // spinner|skeleton
text: { type: String, value: '' },
rows: { type: Number, value: 3 }, // skeleton 模式行数
inline: { type: Boolean, value: false }, // 是否行内显示
},
data: {
rowList: [],
},
observers: {
rows(n) {
const arr = [];
for (let i = 0; i < n; i++) arr.push(i);
this.setData({ rowList: arr });
},
},
lifetimes: {
attached() {
const arr = [];
for (let i = 0; i < this.data.rows; i++) arr.push(i);
this.setData({ rowList: arr });
},
},
});
+4
View File
@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}
+19
View File
@@ -0,0 +1,19 @@
<view class="ui-loading {{inline ? 'ui-loading--inline' : 'ui-loading--block'}}">
<block wx:if="{{mode === 'spinner'}}">
<view class="ui-loading__spinner">
<view class="ui-loading__spinner-ring"></view>
</view>
<text wx:if="{{text}}" class="ui-loading__text">{{text}}</text>
</block>
<block wx:elif="{{mode === 'skeleton'}}">
<view class="ui-loading__skeleton">
<view wx:for="{{rowList}}" wx:key="*this" class="ui-loading__skeleton-row">
<view class="ui-loading__skeleton-avatar"></view>
<view class="ui-loading__skeleton-content">
<view class="ui-loading__skeleton-line ui-loading__skeleton-line--title"></view>
<view class="ui-loading__skeleton-line ui-loading__skeleton-line--meta"></view>
</view>
</view>
</view>
</block>
</view>
+94
View File
@@ -0,0 +1,94 @@
.ui-loading--block {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: var(--spacing-8) var(--spacing-page-x);
}
.ui-loading--inline {
display: inline-flex;
align-items: center;
}
.ui-loading__spinner {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.ui-loading__spinner-ring {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
border: 4rpx solid var(--color-divider);
border-top-color: var(--color-primary);
animation: ui-loading-spin 0.8s linear infinite;
}
@keyframes ui-loading-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.ui-loading__text {
margin-top: var(--spacing-3);
font-size: var(--font-size-sm);
color: var(--color-text-tertiary);
}
/* Skeleton */
.ui-loading__skeleton {
width: 100%;
}
.ui-loading__skeleton-row {
display: flex;
align-items: center;
padding: var(--spacing-3) var(--spacing-page-x);
background: var(--color-bg-card);
border-bottom: 1rpx solid var(--color-divider);
}
.ui-loading__skeleton-avatar {
width: 88rpx;
height: 88rpx;
border-radius: 50%;
background: var(--color-bg-disabled);
margin-right: var(--spacing-3);
flex-shrink: 0;
}
.ui-loading__skeleton-content {
flex: 1;
min-width: 0;
}
.ui-loading__skeleton-line {
height: 24rpx;
background: var(--color-bg-disabled);
border-radius: var(--radius-sm);
position: relative;
overflow: hidden;
}
.ui-loading__skeleton-line--title {
width: 40%;
margin-bottom: var(--spacing-2);
}
.ui-loading__skeleton-line--meta {
width: 70%;
}
/* shimmer */
.ui-loading__skeleton-line::after,
.ui-loading__skeleton-avatar::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent 0%,
rgba(255, 255, 255, 0.55) 50%,
transparent 100%
);
animation: ui-loading-shimmer 1.4s ease-in-out infinite;
}
@keyframes ui-loading-shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}