feat(auth,mp): 患者登录流程优化 — 智能合并 + 角色冻结 + 页面冻结

- 智能合并:微信注册时用手机号盲索引匹配已有患者档案,避免重复建
  档(AuthState 添加 PiiCrypto + ensure_patient_record 增加盲索引查询)
- 角色冻结:小程序仅允许患者角色登录,医护角色被拦截
  (auth_service.rs 添加反向拦截 + 登录页移除 credential login 表单)
- 页面冻结:10 个非核心页面替换为 FrozenPage 占位组件(用药/知情同意
  /透析/家属/诊断/事件),移除 profile 导航入口,移除医生端预加载
- 医生端代码保留,仅隐藏入口,后续可零成本恢复

讨论记录:docs/discussions/2026-05-23-account-registration-login-flow.md
This commit is contained in:
iven
2026-05-23 12:27:14 +08:00
parent f7d98a59f0
commit f11dd59382
21 changed files with 328 additions and 1510 deletions

View File

@@ -1,5 +1,5 @@
import { useState } from 'react';
import { View, Text, Input, Button } from '@tarojs/components';
import { View, Text, Button } from '@tarojs/components';
import Taro from '@tarojs/taro';
import { safeNavigateTo } from '@/utils/navigate';
import { useAuthStore } from '../../stores/auth';
@@ -11,9 +11,6 @@ const IS_DEV = process.env.NODE_ENV !== 'production';
const IS_SIMULATOR = typeof __wxConfig !== 'undefined' && (__wxConfig as Record<string, unknown>)?.envVersion === 'develop';
export default function Login() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
const [agreed, setAgreed] = useState(false);
const [needBind, setNeedBind] = useState(false);
@@ -25,12 +22,9 @@ export default function Login() {
const navigateAfterLogin = () => {
if (isMedicalStaff()) {
// 使用 redirectTo 替代 reLaunch 避免分包加载超时
// redirectTo 只替换当前页面,不销毁整个页栈,分包预加载不会被中断
Taro.redirectTo({
url: '/pages/pkg-doctor-core/index',
fail: () => {
// fallback: 先跳首页再 redirectTo
Taro.switchTab({
url: '/pages/index/index',
success: () => {
@@ -46,32 +40,6 @@ export default function Login() {
}
};
const handleCredentialLogin = async () => {
if (!username.trim()) {
Taro.showToast({ title: '请输入账号', icon: 'none' });
return;
}
if (!password.trim()) {
Taro.showToast({ title: '请输入密码', icon: 'none' });
return;
}
if (!agreed) {
Taro.showToast({ title: '请先阅读并同意用户协议', icon: 'none' });
return;
}
try {
const success = await credentialLogin(username.trim(), password);
if (success) {
navigateAfterLogin();
} else {
Taro.showToast({ title: '账号或密码错误', icon: 'none' });
}
} catch (err) {
console.warn('[login] 登录失败:', err);
Taro.showToast({ title: '登录失败,请重试', icon: 'none' });
}
};
const handleWechatLogin = async () => {
if (!agreed) {
Taro.showToast({ title: '请先阅读并同意用户协议', icon: 'none' });
@@ -144,49 +112,6 @@ export default function Login() {
{!needBind ? (
<>
{/* 账号输入 */}
<View className="login-field">
<Input
className="login-input"
type="text"
placeholder="请输入账号"
placeholderClass="login-placeholder"
value={username}
onInput={(e) => setUsername(e.detail.value)}
/>
</View>
{/* 密码输入 */}
<View className="login-field">
<Input
className="login-input"
type="safe-password"
password={!showPassword}
placeholder="请输入密码"
placeholderClass="login-placeholder"
value={password}
onInput={(e) => setPassword(e.detail.value)}
/>
<Text
className="login-eye"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? '隐藏' : '显示'}
</Text>
</View>
{/* 登录按钮 */}
<View className="login-submit" onClick={handleCredentialLogin}>
<Text className="login-submit-text">{loading ? '登录中...' : '登录'}</Text>
</View>
{/* 分隔线 */}
<View className="login-divider">
<View className="login-divider-line" />
<Text className="login-divider-text"></Text>
<View className="login-divider-line" />
</View>
{/* 微信一键登录 */}
<View className="login-wechat-btn" onClick={handleWechatLogin}>
<Text className="login-wechat-icon"></Text>