Files
hms/apps/miniprogram/src/pages/login/index.tsx
iven 11101ac204
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled
feat(auth): 微信登录自动分配 patient 角色 + 创建患者档案
- 新增迁移 m20260510_000133:为所有租户创建 patient 角色并分配 19 个权限
- wechat_service: bind_phone 自动 assign_patient_role + ensure_patient_record
- find_or_create_user_by_phone 新用户自动获得 patient 角色和患者档案
- 小程序 auth store: bindPhone 抛出异常而非静默返回 false
- 小程序登录页: 捕获绑定错误并显示可操作的对话框
2026-05-10 09:57:45 +08:00

134 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from 'react';
import { View, Text, Button, ScrollView } from '@tarojs/components';
import Taro from '@tarojs/taro';
import { useAuthStore } from '../../stores/auth';
import { useElderClass } from '../../hooks/useElderClass';
import './index.scss';
export default function Login() {
const modeClass = useElderClass();
const [needBind, setNeedBind] = useState(false);
const [agreed, setAgreed] = useState(false);
const { login, bindPhone, loading, isMedicalStaff } = useAuthStore();
// 登录页不应用关怀模式(正常模式尺寸已足够大)
const loginClass = '';
const navigateAfterLogin = () => {
if (isMedicalStaff()) {
Taro.redirectTo({ url: '/pages/doctor/index' });
} else {
Taro.switchTab({ url: '/pages/index/index' });
}
};
const handleWechatLogin = async () => {
if (!agreed) {
Taro.showToast({ title: '请先阅读并同意用户协议', icon: 'none' });
return;
}
try {
const { code } = await Taro.login();
const result = await login(code);
if (result) {
navigateAfterLogin();
} else {
setNeedBind(true);
Taro.showToast({ title: '请授权手机号完成绑定', icon: 'none' });
}
} catch (err: any) {
const msg = err?.message || '登录失败,请重试';
Taro.showToast({ title: msg.substring(0, 20), icon: 'none', duration: 3000 });
}
};
const handleGetPhone = async (e: { detail: { errMsg: string; encryptedData: string; iv: string } }) => {
if (!agreed) {
Taro.showToast({ title: '请先阅读并同意用户协议', icon: 'none' });
return;
}
if (e.detail.errMsg !== 'getPhoneNumber:ok') {
Taro.showToast({ title: '需要授权手机号', icon: 'none' });
return;
}
const { encryptedData, iv } = e.detail;
try {
const success = await bindPhone(encryptedData, iv);
if (success) {
navigateAfterLogin();
}
} catch (err: any) {
const msg = err?.message || '绑定失败';
Taro.showModal({
title: '绑定手机号失败',
content: msg,
confirmText: '重新登录',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
setNeedBind(false);
}
},
});
}
};
return (
<ScrollView scrollY className={`login-scroll ${loginClass}`}>
<View className='login-page'>
{/* 品牌区 */}
<View className='login-brand'>
<View className='login-logo'>
<Text className='login-logo-mark'>+</Text>
</View>
<Text className='login-title'></Text>
<Text className='login-subtitle'></Text>
</View>
{/* 装饰线 */}
<View className='login-divider'>
<View className='login-divider-line' />
</View>
{/* 登录按钮 */}
<View className='login-body'>
{!needBind ? (
<Button className='login-btn' onClick={handleWechatLogin} loading={loading}>
</Button>
) : (
<Button
className='login-btn'
openType='getPhoneNumber'
onGetPhoneNumber={handleGetPhone}
loading={loading}
>
</Button>
)}
</View>
{/* 协议 */}
<View className='agreement-row'>
<View className={`agreement-check ${agreed ? 'checked' : ''}`} onClick={() => setAgreed(!agreed)}>
{agreed && <Text className='agreement-check-mark'>&#10003;</Text>}
</View>
<Text className='agreement-text'>
<Text className='agreement-link' onClick={() => Taro.navigateTo({ url: '/pages/legal/user-agreement' })}></Text>
<Text className='agreement-link' onClick={() => Taro.navigateTo({ url: '/pages/legal/privacy-policy' })}></Text>
</Text>
</View>
{/* 暂不登录 */}
<View className='skip-row'>
<Text className='skip-btn' onClick={() => Taro.reLaunch({ url: '/pages/index/index' })}>
</Text>
</View>
</View>
</ScrollView>
);
}