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:
@@ -8,20 +8,24 @@ interface Props {
|
|||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
hasError: boolean;
|
hasError: boolean;
|
||||||
|
retryCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MAX_RETRIES = 3;
|
||||||
|
|
||||||
export default class ErrorBoundary extends Component<Props, State> {
|
export default class ErrorBoundary extends Component<Props, State> {
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = { hasError: false };
|
this.state = { hasError: false, retryCount: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDerivedStateFromError(): State {
|
static getDerivedStateFromError(): Partial<State> {
|
||||||
return { hasError: true };
|
return { hasError: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidCatch(error: Error, info: React.ErrorInfo) {
|
componentDidCatch(error: Error, info: React.ErrorInfo) {
|
||||||
console.error('[ErrorBoundary]', error, info.componentStack);
|
console.error('[ErrorBoundary]', error, info.componentStack);
|
||||||
|
this.setState((prev) => ({ retryCount: prev.retryCount + 1 }));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRetry = () => {
|
handleRetry = () => {
|
||||||
@@ -30,19 +34,24 @@ export default class ErrorBoundary extends Component<Props, State> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.hasError) {
|
if (this.state.hasError) {
|
||||||
|
const exceeded = this.state.retryCount >= MAX_RETRIES;
|
||||||
return (
|
return (
|
||||||
<View className='error-boundary'>
|
<View className='error-boundary'>
|
||||||
<View className='error-icon-wrap'>
|
<View className='error-icon-wrap'>
|
||||||
<Text className='error-icon-text'>!</Text>
|
<Text className='error-icon-text'>!</Text>
|
||||||
</View>
|
</View>
|
||||||
<Text className='error-title'>页面出了点问题</Text>
|
<Text className='error-title'>页面出了点问题</Text>
|
||||||
<Text className='error-desc'>请返回重试</Text>
|
<Text className='error-desc'>
|
||||||
<View
|
{exceeded ? '请重启小程序' : '请返回重试'}
|
||||||
className='error-retry-btn'
|
</Text>
|
||||||
onClick={this.handleRetry}
|
{!exceeded && (
|
||||||
>
|
<View
|
||||||
<Text className='error-retry-text'>重新加载</Text>
|
className='error-retry-btn'
|
||||||
</View>
|
onClick={this.handleRetry}
|
||||||
|
>
|
||||||
|
<Text className='error-retry-text'>重新加载</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,3 +28,12 @@
|
|||||||
font-size: var(--tk-font-body-sm);
|
font-size: var(--tk-font-body-sm);
|
||||||
color: var(--tk-text-secondary);
|
color: var(--tk-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-state--end {
|
||||||
|
padding: 24px 0;
|
||||||
|
|
||||||
|
.loading-state-text {
|
||||||
|
color: var(--tk-text-tertiary);
|
||||||
|
font-size: var(--tk-caption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ interface LoadingProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default React.memo(function Loading({ text = '加载中...' }: LoadingProps) {
|
export default React.memo(function Loading({ text = '加载中...' }: LoadingProps) {
|
||||||
|
const isListEnd = text !== '加载中...' && !text.includes('加载');
|
||||||
return (
|
return (
|
||||||
<View className='loading-state'>
|
<View className={`loading-state ${isListEnd ? 'loading-state--end' : ''}`}>
|
||||||
<View className='loading-spinner' />
|
{!isListEnd && <View className='loading-spinner' />}
|
||||||
<Text className='loading-state-text'>{text}</Text>
|
<Text className='loading-state-text'>{text}</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -272,6 +272,7 @@ export default function AppointmentCreate() {
|
|||||||
className='form-input'
|
className='form-input'
|
||||||
placeholder='请简要描述症状'
|
placeholder='请简要描述症状'
|
||||||
value={reason}
|
value={reason}
|
||||||
|
maxlength={200}
|
||||||
onInput={(e) => setReason(e.detail.value)}
|
onInput={(e) => setReason(e.detail.value)}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ function GuestHome({ modeClass }: { modeClass: string }) {
|
|||||||
<Text className='guest-section-title'>健康资讯</Text>
|
<Text className='guest-section-title'>健康资讯</Text>
|
||||||
<Text
|
<Text
|
||||||
className='guest-section-more'
|
className='guest-section-more'
|
||||||
onClick={() => Taro.switchTab({ url: '/pages/health/index' })}
|
onClick={() => safeNavigateTo('/pages/article/index')}
|
||||||
>
|
>
|
||||||
查看全部 ›
|
查看全部 ›
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useAuthStore } from '../../stores/auth';
|
|||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
const IS_DEV = process.env.NODE_ENV !== 'production';
|
const IS_DEV = process.env.NODE_ENV !== 'production';
|
||||||
const IS_SIMULATOR = typeof __wxConfig !== 'undefined' && (__wxConfig as any).envVersion !== 'release';
|
const IS_SIMULATOR = typeof __wxConfig !== 'undefined' && (__wxConfig as any).envVersion === 'develop';
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
@@ -158,7 +158,7 @@ export default function Login() {
|
|||||||
<View className="login-field">
|
<View className="login-field">
|
||||||
<Input
|
<Input
|
||||||
className="login-input"
|
className="login-input"
|
||||||
type="text"
|
type="safe-password"
|
||||||
password={!showPassword}
|
password={!showPassword}
|
||||||
placeholder="请输入密码"
|
placeholder="请输入密码"
|
||||||
placeholderClass="login-placeholder"
|
placeholderClass="login-placeholder"
|
||||||
|
|||||||
Reference in New Issue
Block a user