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
@@ -0,0 +1,29 @@
/**
* MessageBubble 聊天气泡(规范 §61-63)
*
* bubbleType: self / other / system
* self:右对齐,主红气泡
* other:左对齐,白色气泡
* system:居中,灰色文字(如"成功牵手"系统提示)
*
* 失败状态:sendStatus='failed' 时显示重试图标。
*/
Component({
options: { addGlobalClass: true },
properties: {
bubbleType: { type: String, value: 'other' },
content: { type: String, value: '' },
sentAt: { type: Number, value: 0 },
sentAtText: { type: String, value: '' },
avatarUrl: { type: String, value: '' },
showAvatar: { type: Boolean, value: true },
showTime: { type: Boolean, value: false },
sendStatus: { type: String, value: 'sent' }, // sending|sent|failed
},
methods: {
onRetry() {
if (this.data.sendStatus !== 'failed') return;
this.triggerEvent('retry', {});
},
},
});
@@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"ui-avatar": "../avatar/index"
}
}
@@ -0,0 +1,33 @@
<view class="ui-bubble ui-bubble--{{bubbleType}}">
<view wx:if="{{showTime && sentAtText}}" class="ui-bubble__time">
<text>{{sentAtText}}</text>
</view>
<block wx:if="{{bubbleType === 'system'}}">
<view class="ui-bubble__system">{{content}}</view>
</block>
<block wx:else>
<view class="ui-bubble__row">
<ui-avatar
wx:if="{{bubbleType === 'other' && showAvatar}}"
src="{{avatarUrl}}"
size="sm"
/>
<view class="ui-bubble__content-wrap">
<view class="ui-bubble__content">{{content}}</view>
<view
wx:if="{{bubbleType === 'self' && sendStatus === 'failed'}}"
class="ui-bubble__retry"
catchtap="onRetry"
>重发</view>
<view wx:if="{{bubbleType === 'self' && sendStatus === 'sending'}}" class="ui-bubble__sending">
<view class="ui-bubble__sending-dot"></view>
</view>
</view>
<ui-avatar
wx:if="{{bubbleType === 'self' && showAvatar}}"
src="{{avatarUrl}}"
size="sm"
/>
</view>
</block>
</view>
@@ -0,0 +1,78 @@
.ui-bubble {
padding: var(--spacing-1) var(--spacing-page-x);
}
.ui-bubble__time {
text-align: center;
margin: var(--spacing-3) 0;
font-size: var(--font-size-xs);
color: var(--color-text-tertiary);
}
.ui-bubble__system {
text-align: center;
font-size: var(--font-size-sm);
color: var(--color-text-tertiary);
background: var(--color-bg-disabled);
padding: var(--spacing-1) var(--spacing-3);
border-radius: var(--radius-sm);
display: inline-block;
margin: 0 auto;
}
.ui-bubble__row {
display: flex;
align-items: flex-end;
}
.ui-bubble--other .ui-bubble__row {
justify-content: flex-start;
}
.ui-bubble--self .ui-bubble__row {
justify-content: flex-end;
}
.ui-bubble__content-wrap {
display: flex;
flex-direction: column;
max-width: 70%;
margin: 0 var(--spacing-2);
}
.ui-bubble--self .ui-bubble__content-wrap {
align-items: flex-end;
}
.ui-bubble__content {
padding: var(--spacing-2) var(--spacing-3);
border-radius: var(--radius-lg);
font-size: var(--font-size-base);
line-height: var(--line-height-normal);
word-break: break-all;
}
.ui-bubble--other .ui-bubble__content {
background: var(--color-bg-card);
color: var(--color-text-primary);
border-top-left-radius: var(--radius-sm);
}
.ui-bubble--self .ui-bubble__content {
background: var(--color-primary);
color: #FFFFFF;
border-top-right-radius: var(--radius-sm);
}
.ui-bubble__retry {
margin-top: var(--spacing-1);
font-size: var(--font-size-xs);
color: var(--color-danger);
align-self: flex-end;
}
.ui-bubble__sending {
margin-top: var(--spacing-1);
display: flex;
align-items: center;
justify-content: flex-end;
}
.ui-bubble__sending-dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: var(--color-text-disabled);
animation: ui-bubble-pulse 1s ease-in-out infinite;
}
@keyframes ui-bubble-pulse {
0%, 100% { opacity: 0.3; }
50% { opacity: 1; }
}