- 将 doctor 目录重命名为 dealer 目录 - 更新页面标题从'会员注册'为'注册会员' - 删除银行卡管理、患者报备和订单消息功能 - 重命名组件 AddDoctor 为 AddUserAddress - 添加用户角色管理和默认角色写入逻辑 - 优化注册成功后跳转至用户中心页面 - 更新应用配置中的页面路径和子包结构 - 添加经销商资金管理、团队管理和二维码推广功能 - 更新租户信息配置,增加租户名称和版权信息 - 优化文章列表组件的类型定义和渲染方式 - 修复广告轮播图数据加载和图片兼容性问题
487 lines
15 KiB
TypeScript
487 lines
15 KiB
TypeScript
import {useEffect, useState, useRef} from "react";
|
||
import {Loading, CellGroup, Input, Form, Avatar, Button, Space} from '@nutui/nutui-react-taro'
|
||
import {Edit} from '@nutui/icons-react-taro'
|
||
import Taro from '@tarojs/taro'
|
||
import {View} from '@tarojs/components'
|
||
import FixedButton from "@/components/FixedButton";
|
||
import {useUser} from "@/hooks/useUser";
|
||
import {TenantId} from "@/config/app";
|
||
import {updateUser} from "@/api/system/user";
|
||
import {User} from "@/api/system/user/model";
|
||
import {getStoredInviteParams, handleInviteRelation} from "@/utils/invite";
|
||
import {addShopDealerUser} from "@/api/shop/shopDealerUser";
|
||
import {addUserRole, listUserRole, updateUserRole} from "@/api/system/userRole";
|
||
import { listRoles } from "@/api/system/role";
|
||
import type { UserRole } from "@/api/system/userRole/model";
|
||
|
||
// 类型定义
|
||
interface ChooseAvatarEvent {
|
||
detail: {
|
||
avatarUrl: string;
|
||
};
|
||
}
|
||
|
||
interface InputEvent {
|
||
detail: {
|
||
value: string;
|
||
};
|
||
}
|
||
|
||
const AddUserAddress = () => {
|
||
const {user, loginUser, fetchUserInfo} = useUser()
|
||
const [loading, setLoading] = useState<boolean>(true)
|
||
const [FormData, setFormData] = useState<User>()
|
||
const formRef = useRef<any>(null)
|
||
|
||
const reload = async () => {
|
||
const inviteParams = getStoredInviteParams()
|
||
if (inviteParams?.inviter) {
|
||
setFormData({
|
||
...user,
|
||
refereeId: Number(inviteParams.inviter),
|
||
// 清空昵称,强制用户手动输入
|
||
nickname: '',
|
||
})
|
||
} else {
|
||
// 如果没有邀请参数,也要确保昵称为空
|
||
setFormData({
|
||
...user,
|
||
nickname: '',
|
||
})
|
||
}
|
||
}
|
||
|
||
|
||
const uploadAvatar = ({detail}: ChooseAvatarEvent) => {
|
||
// 先更新本地显示的头像(临时显示)
|
||
const tempFormData = {
|
||
...FormData,
|
||
avatar: `${detail.avatarUrl}`,
|
||
}
|
||
setFormData(tempFormData)
|
||
|
||
Taro.uploadFile({
|
||
url: 'https://server.websoft.top/api/oss/upload',
|
||
filePath: detail.avatarUrl,
|
||
name: 'file',
|
||
header: {
|
||
'content-type': 'application/json',
|
||
TenantId
|
||
},
|
||
success: async (res) => {
|
||
const data = JSON.parse(res.data);
|
||
if (data.code === 0) {
|
||
const finalAvatarUrl = `${data.data.thumbnail}`
|
||
|
||
try {
|
||
// 使用 useUser hook 的 updateUser 方法更新头像
|
||
await updateUser({
|
||
avatar: finalAvatarUrl
|
||
})
|
||
|
||
Taro.showToast({
|
||
title: '头像上传成功',
|
||
icon: 'success',
|
||
duration: 1500
|
||
})
|
||
} catch (error) {
|
||
console.error('更新用户头像失败:', error)
|
||
}
|
||
|
||
// 无论用户信息更新是否成功,都要更新本地FormData
|
||
const finalFormData = {
|
||
...tempFormData,
|
||
avatar: finalAvatarUrl
|
||
}
|
||
setFormData(finalFormData)
|
||
|
||
// 同步更新表单字段
|
||
if (formRef.current) {
|
||
formRef.current.setFieldsValue({
|
||
avatar: finalAvatarUrl
|
||
})
|
||
}
|
||
} else {
|
||
// 上传失败,恢复原来的头像
|
||
setFormData({
|
||
...FormData,
|
||
avatar: user?.avatar || ''
|
||
})
|
||
Taro.showToast({
|
||
title: '上传失败',
|
||
icon: 'error'
|
||
})
|
||
}
|
||
},
|
||
fail: (error) => {
|
||
console.error('上传头像失败:', error)
|
||
Taro.showToast({
|
||
title: '上传失败',
|
||
icon: 'error'
|
||
})
|
||
// 恢复原来的头像
|
||
setFormData({
|
||
...FormData,
|
||
avatar: user?.avatar || ''
|
||
})
|
||
}
|
||
})
|
||
}
|
||
|
||
// 提交表单
|
||
const submitSucceed = async (values: User) => {
|
||
try {
|
||
// 验证必填字段
|
||
if (!values.phone && !FormData?.phone) {
|
||
Taro.showToast({
|
||
title: '请先获取手机号',
|
||
icon: 'error'
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 验证昵称:必须填写且不能是默认的微信昵称
|
||
const nickname = values.realName || FormData?.nickname || '';
|
||
if (!nickname || nickname.trim() === '') {
|
||
Taro.showToast({
|
||
title: '请填写昵称',
|
||
icon: 'error'
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 检查是否为默认的微信昵称(常见的默认昵称)
|
||
const defaultNicknames = ['微信用户', 'WeChat User', '微信昵称'];
|
||
if (defaultNicknames.includes(nickname.trim())) {
|
||
Taro.showToast({
|
||
title: '请填写真实昵称,不能使用默认昵称',
|
||
icon: 'error'
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 验证昵称长度
|
||
if (nickname.trim().length < 2) {
|
||
Taro.showToast({
|
||
title: '昵称至少需要2个字符',
|
||
icon: 'error'
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (!values.avatar && !FormData?.avatar) {
|
||
Taro.showToast({
|
||
title: '请上传头像',
|
||
icon: 'error'
|
||
});
|
||
return;
|
||
}
|
||
console.log(values,FormData)
|
||
|
||
if (!user?.userId) {
|
||
Taro.showToast({
|
||
title: '用户信息缺失,请先登录',
|
||
icon: 'error'
|
||
});
|
||
return;
|
||
}
|
||
|
||
let roles: UserRole[] = [];
|
||
try {
|
||
roles = await listUserRole({userId: user.userId})
|
||
console.log(roles, 'roles...')
|
||
} catch (e) {
|
||
// 新用户/权限限制时可能查不到角色列表,不影响基础注册流程
|
||
console.warn('查询用户角色失败,将尝试直接写入默认角色:', e)
|
||
roles = []
|
||
}
|
||
|
||
// 准备提交的数据
|
||
await updateUser({
|
||
userId: user.userId,
|
||
nickname: values.realName || FormData?.nickname,
|
||
phone: values.phone || FormData?.phone,
|
||
avatar: values.avatar || FormData?.avatar,
|
||
refereeId: values.refereeId || FormData?.refereeId
|
||
});
|
||
|
||
await addShopDealerUser({
|
||
userId: user.userId,
|
||
realName: values.realName || FormData?.nickname,
|
||
mobile: values.phone || FormData?.phone,
|
||
refereeId: Number(values.refereeId) || Number(FormData?.refereeId)
|
||
})
|
||
|
||
// 角色为空时这里会导致“注册成功但没有角色”,这里做一次兜底写入默认 user 角色
|
||
try {
|
||
// 1) 先尝试通过 roleCode=user 查询角色ID(避免硬编码)
|
||
// 2) 取不到就回退到旧的默认ID(1848)
|
||
let userRoleId: number | undefined;
|
||
try {
|
||
// 注意:当前 request.get 的封装不支持 axios 风格的 { params: ... },
|
||
// 某些自动生成的 API 可能无法按参数过滤;这里直接取全量再本地查找更稳。
|
||
const roleList = await listRoles();
|
||
userRoleId = roleList?.find(r => r.roleCode === 'user')?.roleId;
|
||
} catch (_) {
|
||
// ignore
|
||
}
|
||
if (!userRoleId) userRoleId = 1848;
|
||
|
||
const baseRolePayload = {
|
||
userId: user.userId,
|
||
tenantId: Number(TenantId),
|
||
roleId: userRoleId
|
||
};
|
||
|
||
// 后端若已创建 user-role 记录则更新;否则尝试“无id更新”触发创建(多数实现会 upsert)
|
||
if (roles.length > 0) {
|
||
await updateUserRole({
|
||
...roles[0],
|
||
roleId: userRoleId
|
||
});
|
||
} else {
|
||
try {
|
||
await addUserRole(baseRolePayload);
|
||
} catch (_) {
|
||
// 兼容后端仅支持 PUT upsert 的情况
|
||
await updateUserRole(baseRolePayload);
|
||
}
|
||
}
|
||
|
||
// 刷新一次用户信息,确保 roles 写回本地缓存,避免“我的”页显示为空/不一致
|
||
await fetchUserInfo();
|
||
} catch (e) {
|
||
console.warn('写入默认角色失败(不影响注册成功):', e)
|
||
}
|
||
|
||
|
||
Taro.showToast({
|
||
title: `注册成功`,
|
||
icon: 'success'
|
||
});
|
||
|
||
setTimeout(() => {
|
||
// “我的”是 tabBar 页面,注册完成后直接切到“我的”
|
||
Taro.switchTab({ url: '/pages/user/user' });
|
||
}, 1000);
|
||
|
||
} catch (error) {
|
||
console.error('验证邀请人失败:', error);
|
||
}
|
||
}
|
||
|
||
// 获取微信昵称
|
||
const getWxNickname = (nickname: string) => {
|
||
// 更新表单数据
|
||
const updatedFormData = {
|
||
...FormData,
|
||
nickname: nickname
|
||
}
|
||
setFormData(updatedFormData);
|
||
|
||
// 同步更新表单字段
|
||
if (formRef.current) {
|
||
formRef.current.setFieldsValue({
|
||
realName: nickname
|
||
})
|
||
}
|
||
}
|
||
|
||
/* 获取用户手机号 */
|
||
const handleGetPhoneNumber = ({detail}: { detail: { code?: string, encryptedData?: string, iv?: string } }) => {
|
||
const {code, encryptedData, iv} = detail
|
||
Taro.login({
|
||
success: (loginRes) => {
|
||
if (code) {
|
||
Taro.request({
|
||
url: 'https://server.websoft.top/api/wx-login/loginByMpWxPhone',
|
||
method: 'POST',
|
||
data: {
|
||
authCode: loginRes.code,
|
||
code,
|
||
encryptedData,
|
||
iv,
|
||
notVerifyPhone: true,
|
||
refereeId: 0,
|
||
sceneType: 'save_referee',
|
||
tenantId: TenantId
|
||
},
|
||
header: {
|
||
'content-type': 'application/json',
|
||
TenantId
|
||
},
|
||
success: async function (res) {
|
||
if (res.data.code == 1) {
|
||
Taro.showToast({
|
||
title: res.data.message,
|
||
icon: 'error',
|
||
duration: 2000
|
||
})
|
||
return false;
|
||
}
|
||
// 登录成功
|
||
const token = res.data.data.access_token;
|
||
const userData = res.data.data.user;
|
||
console.log(userData, 'userData...')
|
||
// 使用useUser Hook的loginUser方法更新状态
|
||
loginUser(token, userData);
|
||
|
||
if (userData.phone) {
|
||
console.log('手机号已获取', userData.phone)
|
||
const updatedFormData = {
|
||
...FormData,
|
||
phone: userData.phone,
|
||
// 不自动填充微信昵称,保持用户已输入的昵称
|
||
nickname: FormData?.nickname || '',
|
||
// 只在没有头像时才使用微信头像
|
||
avatar: FormData?.avatar || userData.avatar
|
||
}
|
||
setFormData(updatedFormData)
|
||
|
||
// 更新表单字段值
|
||
if (formRef.current) {
|
||
formRef.current.setFieldsValue({
|
||
phone: userData.phone,
|
||
// 不覆盖用户已输入的昵称
|
||
realName: FormData?.nickname || '',
|
||
avatar: FormData?.avatar || userData.avatar
|
||
})
|
||
}
|
||
|
||
Taro.showToast({
|
||
title: '手机号获取成功',
|
||
icon: 'success',
|
||
duration: 1500
|
||
})
|
||
}
|
||
|
||
|
||
// 处理邀请关系
|
||
if (userData?.userId) {
|
||
try {
|
||
const inviteSuccess = await handleInviteRelation(userData.userId)
|
||
if (inviteSuccess) {
|
||
Taro.showToast({
|
||
title: '邀请关系建立成功',
|
||
icon: 'success',
|
||
duration: 2000
|
||
})
|
||
}
|
||
} catch (error) {
|
||
console.error('处理邀请关系失败:', error)
|
||
}
|
||
}
|
||
|
||
// 显示登录成功提示
|
||
// Taro.showToast({
|
||
// title: '注册成功',
|
||
// icon: 'success',
|
||
// duration: 1500
|
||
// })
|
||
|
||
// 不需要重新启动小程序,状态已经通过useUser更新
|
||
// 可以选择性地刷新当前页面数据
|
||
// await reload();
|
||
}
|
||
})
|
||
} else {
|
||
console.log('登录失败!')
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// 处理固定按钮点击事件
|
||
const handleFixedButtonClick = () => {
|
||
// 触发表单提交
|
||
formRef.current?.submit();
|
||
};
|
||
|
||
const submitFailed = (error: any) => {
|
||
console.log(error, 'err...')
|
||
}
|
||
|
||
useEffect(() => {
|
||
reload().then(() => {
|
||
setLoading(false)
|
||
})
|
||
}, [user?.userId]); // 依赖用户ID,当用户变化时重新加载
|
||
|
||
// 当FormData变化时,同步更新表单字段值
|
||
useEffect(() => {
|
||
if (formRef.current && FormData) {
|
||
formRef.current.setFieldsValue({
|
||
refereeId: FormData.refereeId,
|
||
phone: FormData.phone,
|
||
avatar: FormData.avatar,
|
||
realName: FormData.nickname
|
||
});
|
||
}
|
||
}, [FormData]);
|
||
|
||
if (loading) {
|
||
return <Loading className={'px-2'}>加载中</Loading>
|
||
}
|
||
|
||
return (
|
||
<>
|
||
<Form
|
||
ref={formRef}
|
||
divider
|
||
initialValues={FormData}
|
||
labelPosition="left"
|
||
onFinish={(values) => submitSucceed(values)}
|
||
onFinishFailed={(errors) => submitFailed(errors)}
|
||
>
|
||
<View className={'bg-gray-100 h-3'}></View>
|
||
<CellGroup style={{padding: '4px 0'}}>
|
||
{/*<Form.Item name="refereeId" label="邀请人ID" initialValue={FormData?.refereeId} required>*/}
|
||
{/* <Input placeholder="邀请人ID" disabled={false}/>*/}
|
||
{/*</Form.Item>*/}
|
||
<Form.Item name="phone" label="手机号" initialValue={FormData?.phone} required>
|
||
<View className="flex items-center justify-between">
|
||
<Input
|
||
placeholder="请填写手机号"
|
||
disabled={true}
|
||
maxLength={11}
|
||
value={FormData?.phone || ''}
|
||
/>
|
||
<Button style={{color: '#ffffff'}} open-type="getPhoneNumber" onGetPhoneNumber={handleGetPhoneNumber}>
|
||
<Space>
|
||
<Button size="small">点击获取</Button>
|
||
</Space>
|
||
</Button>
|
||
</View>
|
||
</Form.Item>
|
||
{
|
||
FormData?.phone && <Form.Item name="avatar" label="头像" initialValue={FormData?.avatar} required>
|
||
<Button open-type="chooseAvatar" style={{height: '58px'}} onChooseAvatar={uploadAvatar}>
|
||
<Avatar src={FormData?.avatar || user?.avatar} size="54"/>
|
||
</Button>
|
||
</Form.Item>
|
||
}
|
||
<Form.Item name="realName" label="昵称" initialValue="" required>
|
||
<Input
|
||
type="nickname"
|
||
className="info-content__input"
|
||
placeholder="请获取微信昵称"
|
||
value={FormData?.nickname || ''}
|
||
onInput={(e: InputEvent) => getWxNickname(e.detail.value)}
|
||
/>
|
||
</Form.Item>
|
||
</CellGroup>
|
||
</Form>
|
||
|
||
{/* 底部浮动按钮 */}
|
||
<FixedButton
|
||
icon={<Edit/>}
|
||
text={'立即注册'}
|
||
onClick={handleFixedButtonClick}
|
||
/>
|
||
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default AddUserAddress;
|