fix(mp): Phase 3 品质打磨 — Loading优化+ErrorBoundary重试上限+登录安全+输入限制

- Loading 组件区分列表底部状态(无spinner)vs 加载中状态
- ErrorBoundary 添加 MAX_RETRIES=3 限制,超出提示重启
- login 页 IS_SIMULATOR 改为 === 'develop' 精确匹配
- login 密码输入 type 改为 safe-password 防截屏
- appointment/create 备注输入添加 maxlength=200
- GuestHome "查看全部" 导航到文章列表页
This commit is contained in:
iven
2026-05-21 16:30:50 +08:00
parent 4e9eb7b397
commit 7ad5ddb898
6 changed files with 34 additions and 14 deletions

View File

@@ -8,20 +8,24 @@ interface Props {
interface State {
hasError: boolean;
retryCount: number;
}
const MAX_RETRIES = 3;
export default class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
this.state = { hasError: false, retryCount: 0 };
}
static getDerivedStateFromError(): State {
static getDerivedStateFromError(): Partial<State> {
return { hasError: true };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
console.error('[ErrorBoundary]', error, info.componentStack);
this.setState((prev) => ({ retryCount: prev.retryCount + 1 }));
}
handleRetry = () => {
@@ -30,19 +34,24 @@ export default class ErrorBoundary extends Component<Props, State> {
render() {
if (this.state.hasError) {
const exceeded = this.state.retryCount >= MAX_RETRIES;
return (
<View className='error-boundary'>
<View className='error-icon-wrap'>
<Text className='error-icon-text'>!</Text>
</View>
<Text className='error-title'></Text>
<Text className='error-desc'></Text>
<View
className='error-retry-btn'
onClick={this.handleRetry}
>
<Text className='error-retry-text'></Text>
</View>
<Text className='error-desc'>
{exceeded ? '请重启小程序' : '请返回重试'}
</Text>
{!exceeded && (
<View
className='error-retry-btn'
onClick={this.handleRetry}
>
<Text className='error-retry-text'></Text>
</View>
)}
</View>
);
}

View File

@@ -28,3 +28,12 @@
font-size: var(--tk-font-body-sm);
color: var(--tk-text-secondary);
}
.loading-state--end {
padding: 24px 0;
.loading-state-text {
color: var(--tk-text-tertiary);
font-size: var(--tk-caption);
}
}

View File

@@ -7,9 +7,10 @@ interface LoadingProps {
}
export default React.memo(function Loading({ text = '加载中...' }: LoadingProps) {
const isListEnd = text !== '加载中...' && !text.includes('加载');
return (
<View className='loading-state'>
<View className='loading-spinner' />
<View className={`loading-state ${isListEnd ? 'loading-state--end' : ''}`}>
{!isListEnd && <View className='loading-spinner' />}
<Text className='loading-state-text'>{text}</Text>
</View>
);