feat(doctor): 重构医生申请页面并优化用户体验
- 重构医生申请页面,移除旧的表单字段,添加微信昵称和头像获取功能 - 实现微信头像上传功能,支持临时显示和服务器上传 - 添加手机号获取功能,通过微信授权获取用户手机号 -优化表单验证逻辑,增加昵称和头像的必填验证 - 移除旧的用户类型选择和证书上传功能 - 更新页面标题从"AddApply"改为"AddDoctor"-修复邀请关系处理逻辑,确保邀请人ID正确传递- 优化页面加载逻辑,依赖用户ID变化重新加载数据 - 移除错误显示逻辑,简化页面结构 - 更新商品列表组件,优化瀑布流布局和样式 -优化商品详情页面,调整底部按钮样式和咨询按钮大小 - 移除页面中的邀请提示弹窗,改为控制台输出 - 更新分类页面逻辑,根据首页导航获取商品分类 - 移除配置加载时的冗余代码,优化加载逻辑 - 更新API接口,移除旧的文章模型和接口定义 - 添加导航和网站字段模型的新属性支持
This commit is contained in:
@@ -1,69 +1,208 @@
|
||||
import {useEffect, useState, useRef} from "react";
|
||||
import {Loading, CellGroup, Input, Form, Radio, InputNumber, TextArea, ConfigProvider} from '@nutui/nutui-react-taro'
|
||||
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 {Image} from '@tarojs/components';
|
||||
import {View} from '@tarojs/components'
|
||||
import FixedButton from "@/components/FixedButton";
|
||||
import {DictData} from "@/api/system/dict-data/model";
|
||||
import {listDictData} from "@/api/system/dict-data";
|
||||
import {addShopUser, getShopUser} from "@/api/shop/shopUser";
|
||||
import {ShopUser} from "@/api/shop/shopUser/model";
|
||||
import {uploadFile} from "@/api/system/file";
|
||||
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 {listUserRole, updateUserRole} from "@/api/system/userRole";
|
||||
|
||||
const customTheme = {
|
||||
nutuiInputnumberButtonWidth: '30px',
|
||||
nutuiInputnumberButtonHeight: '30px',
|
||||
nutuiInputnumberButtonBorderRadius: '2px',
|
||||
nutuiInputnumberButtonBackgroundColor: `#f4f4f4`,
|
||||
nutuiInputnumberInputHeight: '30px',
|
||||
nutuiInputnumberInputMargin: '0 2px',
|
||||
// 类型定义
|
||||
interface ChooseAvatarEvent {
|
||||
detail: {
|
||||
avatarUrl: string;
|
||||
};
|
||||
}
|
||||
|
||||
const AddApply = () => {
|
||||
const {user} = useUser()
|
||||
interface InputEvent {
|
||||
detail: {
|
||||
value: string;
|
||||
};
|
||||
}
|
||||
|
||||
const AddDoctor = () => {
|
||||
const {user, loginUser} = useUser()
|
||||
const [loading, setLoading] = useState<boolean>(true)
|
||||
const [FormData, setFormData] = useState<ShopUser>()
|
||||
const [userType, setUserType] = useState<DictData[]>()
|
||||
const [FormData, setFormData] = useState<User>()
|
||||
const formRef = useRef<any>(null)
|
||||
|
||||
const reload = async () => {
|
||||
const userId = Taro.getStorageSync('UserId')
|
||||
if(!userId || userId == '') return
|
||||
const shopUser = await getShopUser(Number(Taro.getStorageSync('UserId')))
|
||||
if(shopUser){
|
||||
console.log(shopUser.type,'shopUsershopUsershopUsershopUsershopUsershopUsershopUser')
|
||||
const inviteParams = getStoredInviteParams()
|
||||
if (inviteParams?.inviter) {
|
||||
setFormData({
|
||||
...shopUser,
|
||||
type : Number(shopUser.type)
|
||||
...user,
|
||||
refereeId: Number(inviteParams.inviter),
|
||||
// 清空昵称,强制用户手动输入
|
||||
nickname: '',
|
||||
})
|
||||
} else {
|
||||
// 如果没有邀请参数,也要确保昵称为空
|
||||
setFormData({
|
||||
...user,
|
||||
nickname: '',
|
||||
})
|
||||
}
|
||||
const userType = await listDictData({dictCode: 'UserType'})
|
||||
if(userType){
|
||||
setUserType(userType)
|
||||
}
|
||||
|
||||
|
||||
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: any) => {
|
||||
if (!FormData?.age || FormData?.age == 0) {
|
||||
Taro.showToast({
|
||||
title: `请填写年龄`,
|
||||
icon: 'error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
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)
|
||||
|
||||
const roles = await listUserRole({userId: user?.userId})
|
||||
console.log(roles, 'roles...')
|
||||
|
||||
// 准备提交的数据
|
||||
await addShopUser({
|
||||
userId: Taro.getStorageSync('UserId'),
|
||||
type: values.type,
|
||||
username: user?.username,
|
||||
nickname: user?.nickname,
|
||||
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,
|
||||
phone: values.phone || FormData?.phone
|
||||
mobile: values.phone || FormData?.phone,
|
||||
refereeId: values.refereeId || FormData?.refereeId
|
||||
})
|
||||
|
||||
if (roles.length > 0) {
|
||||
await updateUserRole({
|
||||
...roles[0],
|
||||
roleId: 1848
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Taro.showToast({
|
||||
title: `注册成功`,
|
||||
icon: 'success'
|
||||
@@ -78,6 +217,127 @@ const AddApply = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取微信昵称
|
||||
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 = () => {
|
||||
// 触发表单提交
|
||||
@@ -88,25 +348,17 @@ const AddApply = () => {
|
||||
console.log(error, 'err...')
|
||||
}
|
||||
|
||||
const uploadImg1 = () => {
|
||||
uploadFile().then(data => {
|
||||
setFormData({
|
||||
...FormData,
|
||||
uploadImg1: data.url
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
reload().then(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}, []); // 依赖用户ID,当用户变化时重新加载
|
||||
}, [user?.userId]); // 依赖用户ID,当用户变化时重新加载
|
||||
|
||||
// 当FormData变化时,同步更新表单字段值
|
||||
useEffect(() => {
|
||||
if (formRef.current && FormData) {
|
||||
formRef.current.setFieldsValue({
|
||||
refereeId: FormData.refereeId,
|
||||
phone: FormData.phone,
|
||||
avatar: FormData.avatar,
|
||||
realName: FormData.nickname
|
||||
@@ -128,106 +380,42 @@ const AddApply = () => {
|
||||
onFinish={(values) => submitSucceed(values)}
|
||||
onFinishFailed={(errors) => submitFailed(errors)}
|
||||
>
|
||||
<View className={'bg-gray-100 h-3'}></View>
|
||||
<CellGroup style={{padding: '4px 0'}}>
|
||||
{JSON.stringify(FormData)}
|
||||
<Form.Item label="用户类型" name="type" initialValue={FormData?.type} required>
|
||||
<Radio.Group
|
||||
defaultValue="0"
|
||||
direction="horizontal"
|
||||
value={FormData?.type}
|
||||
onChange={(value: any) => {
|
||||
setFormData({
|
||||
...FormData,
|
||||
type: value
|
||||
})
|
||||
}}
|
||||
>
|
||||
{userType?.map((item) => (
|
||||
<Radio key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</Radio>
|
||||
))}
|
||||
</Radio.Group>
|
||||
<Form.Item name="refereeId" label="邀请人ID" initialValue={FormData?.refereeId} required>
|
||||
<Input placeholder="邀请人ID" disabled={true}/>
|
||||
</Form.Item>
|
||||
<Form.Item label="姓名" name="realName" required>
|
||||
<Input
|
||||
placeholder={'请填写真实姓名'}
|
||||
value={FormData?.realName || ''}
|
||||
onChange={(value) => {
|
||||
setFormData({
|
||||
...FormData,
|
||||
realName: value
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="性别" name="sex" required>
|
||||
<Radio.Group
|
||||
direction="horizontal"
|
||||
value={FormData?.sex || "0"}
|
||||
>
|
||||
<Radio value="1">
|
||||
男
|
||||
</Radio>
|
||||
<Radio value="2">
|
||||
女
|
||||
</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label="年龄" name="age" initialValue={FormData?.age} required>
|
||||
<ConfigProvider theme={customTheme}>
|
||||
<InputNumber
|
||||
value={FormData?.age}
|
||||
onChange={(value) => {
|
||||
setFormData({
|
||||
...FormData,
|
||||
age: Number(value)
|
||||
})
|
||||
}}
|
||||
min={0}
|
||||
max={120}
|
||||
step={1}
|
||||
<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 || ''}
|
||||
/>
|
||||
</ConfigProvider>
|
||||
<Button style={{color: '#ffffff'}} open-type="getPhoneNumber" onGetPhoneNumber={handleGetPhoneNumber}>
|
||||
<Space>
|
||||
<Button size="small">点击获取</Button>
|
||||
</Space>
|
||||
</Button>
|
||||
</View>
|
||||
</Form.Item>
|
||||
<Form.Item label={FormData?.type == 1 ? '医生简介' : '个人简介'} name="introduction">
|
||||
<TextArea
|
||||
style={{
|
||||
height: '50px',
|
||||
backgroundColor: '#fafafa',
|
||||
padding: '10px',
|
||||
}}
|
||||
value={FormData?.introduction || ''}
|
||||
{
|
||||
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>
|
||||
{FormData?.type == 1 && (
|
||||
<>
|
||||
<Form.Item label="证书编号" name="userCode" required>
|
||||
<Input
|
||||
placeholder={'请填证书编号'}
|
||||
value={FormData?.userCode || ''}
|
||||
onChange={(value) => {
|
||||
setFormData({
|
||||
...FormData,
|
||||
idCard: value
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={'上传证件'}
|
||||
name="uploadImg1"
|
||||
required
|
||||
rules={[{message: '请上传医师资格证书'}
|
||||
]}
|
||||
>
|
||||
<div onClick={uploadImg1}>
|
||||
<Image src={FormData.uploadImg1 || ''} mode={'scaleToFill'} lazyLoad={false} />
|
||||
</div>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
</CellGroup>
|
||||
</Form>
|
||||
|
||||
@@ -242,4 +430,4 @@ const AddApply = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default AddApply;
|
||||
export default AddDoctor;
|
||||
|
||||
Reference in New Issue
Block a user