作者 韩昌

登录接口和问诊订单样式

... ... @@ -8,6 +8,7 @@
"name": "react18-tsx-neteasemusic",
"version": "0.1.0",
"dependencies": {
"@ant-design/icons": "^5.2.6",
"@reduxjs/toolkit": "^1.9.5",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
... ...
... ... @@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@ant-design/icons": "^5.2.6",
"@reduxjs/toolkit": "^1.9.5",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
... ...
import hyRequest from "@/service";
import { Send_prescription_formType } from '@/types'
export const getTopBanner = () => hyRequest.get({ url: '/banner' })
// 公共
export const getSendMessage = (params: { phoneNum: string }) => hyRequest.get({ url: '/pet/login/sendMessage', params }) // 发送验证码
export const getMessageLogin = (params: any) => hyRequest.get({ url: '/pet/login/messageLogin', params }) // 验证码登录
export const updateSend_prescription_form = (params: Send_prescription_formType) => hyRequest.post({ url: '/veterinary/send_prescription_form', params }) // 发送处方单
... ...
body, textarea, select, input, button {
font-size: 12px;
color: #333;
font-family: Arial, Helvetica, sans-serif;
background-color: #f5f5f5;
// font-size: 12px;
// color: #333;
// font-family: Arial, Helvetica, sans-serif;
// background-color: #f5f5f5;
}
.wrap-v1 {
... ... @@ -60,10 +60,10 @@ body, textarea, select, input, button {
}
.ant-message .ant-message-notice-content {
position: fixed;
left: 50%;
transform: translateX(-50%);
bottom: 60px;
background-color: rgba(0, 0, 0, .7);
color: #fff;
// position: fixed;
// left: 50%;
// transform: translateX(-50%);
// bottom: 60px;
// background-color: rgba(0, 0, 0, .7);
// color: #fff;
}
... ...
import React, { useRef, useImperativeHandle, Ref } from 'react'
// 定义子组件的接口
interface ChildComponentProps {
// 定义子组件暴露给父组件的方法
increment: () => void
getCount: () => number
}
const ChildComponent = React.forwardRef((props: ChildComponentProps, ref: Ref<any>) => {
const internalState = {
count: 0
}
const incrementCount = () => {
internalState.count += 1
}
useImperativeHandle(ref, () => ({
increment: incrementCount,
getCount: () => internalState.count
}))
return (
<div>
<p>Count: {internalState.count}</p>
</div>
)
})
const ParentComponent = () => {
const childRef = useRef<ChildComponentProps | null>(null)
const handleIncrement = () => {
if (childRef.current) {
childRef.current.increment()
}
}
const handleGetCount = () => {
if (childRef.current) {
const count = childRef.current.getCount()
alert('Count from child component: ' + count)
}
}
return (
<div>
<h2>Parent Component</h2>
<button onClick={handleIncrement}>Increment Count in Child</button>
<button onClick={handleGetCount}>Get Count from Child</button>
<ChildComponent
ref={childRef}
increment={function (): void {
throw new Error('Function not implemented.')
}}
getCount={function (): number {
throw new Error('Function not implemented.')
}}
/>
</div>
)
}
export default ParentComponent
... ...
... ... @@ -6,6 +6,9 @@ export const AppHeaderWrapper = styled.div`
font-size: 14px;
box-sizing: border-box;
padding: 20px 32px;
box-shadow: 5px 5px 15px rgba(0,0,0,0.4);
z-index: 9999999999;
position: relative;
.title{
color: #fff;
font-size: 22px;
... ...
... ... @@ -8,9 +8,11 @@ interface IProps {
const AppLoading: FC<IProps> = memo(() => {
return (
<Space size="middle">
<Spin size="large" />
</Space>
<div className="flexC" style={{ width: '100%' }}>
<Space size="middle">
<Spin size="large" />
</Space>
</div>
)
})
... ...
// 按钮点击防抖
/**
* @使用方法
* @vue2
* 点击事件名:DebounceBy(function(args){ 写你自己的业务代码 },3000)
* @vue3
* const 点击事件名 = DebounceBy(async (args) => { 写你自己的业务代码 }, 3000)
*/
export const DebounceBy = (fn: any, t: number = 500) => {
let delay = t || 500
let timer: any
return function () {
let args = arguments
if (timer) {
clearTimeout(timer)
}
let callNow = !timer
timer = setTimeout(() => {
timer = null
}, delay)
if (callNow) fn(args)
// if (callNow) fn.apply(this, args)
}
}
// 节流:防止高频触发
export const Throttle = (fn: any, delay: number) => {
let flag = true
return function () {
if (flag) {
setTimeout(() => {
// fn.call(this)
flag = true
}, delay)
}
flag = false
}
}
... ...
import { useRef, useState } from 'react'
/**
* @import import useTimeHandler from '../../hooks/useTimeChange'
* @use const [ countdown, startCountdown ] = useTimeHandler() startCountdown(handler)
* @param executeHandler function
* @returns countdown , startCountDown => void
*/
type ExecuteHandlerType = () => void
type useCountdownType = [number, (fn: ExecuteHandlerType) => void]
const useCountdown = (): useCountdownType => {
let [countdown, setCountdown] = useState(60)
const newC = useRef(60)
const timeHandler = () => {
let timer = setInterval(() => {
console.log(countdown, '倒计时')
// let newC = countdown - 1
setCountdown(newC.current--)
if (newC.current === 0) {
newC.current = 60
setCountdown(60)
clearInterval(timer)
}
}, 1000)
}
const startCountdown = (executeHandler: ExecuteHandlerType) => {
if (countdown !== 60) return
executeHandler()
timeHandler()
}
return [countdown, startCountdown]
}
export default useCountdown
... ...
import { useRef, useState } from 'react'
/**
* @import import useTimeHandler from '../../hooks/useTimeChange'
* @use const [ countdown, startCountdown ] = useTimeHandler() startCountdown(handler)
* @param executeHandler function
* @returns countdown , startCountDown => void
*/
type ExecuteHandlerType = () => void
type useCountdownType = [number, (fn: ExecuteHandlerType) => void]
const useCountdown = (): useCountdownType => {
let [countdown, setCountdown] = useState(60)
const timeHandler = () => {
let timer = setInterval(() => {
let newC = countdown -= 1
setCountdown(newC--)
if (countdown === 0) {
setCountdown(60)
clearInterval(timer)
}
}, 1000)
}
const startCountdown = (executeHandler: ExecuteHandlerType) => {
if (countdown !== 60) return
timeHandler()
executeHandler()
}
return [countdown, startCountdown]
}
export default useCountdown
... ...
... ... @@ -5,7 +5,9 @@ import { BASE_URL, TIME_OUT } from './request/config'
const hyRequest = new HYRequest({
baseURL: BASE_URL,
timeout: TIME_OUT,
headers: {},
headers: {
'X-Access-Token': localStorage.getItem('token') || ''
},
interceptors: {
requestInterceptor: (config) => {
return config
... ...
const BASE_URL = 'http://codercba.com:9002/'
// const BASE_URL = 'http://codercba.com:9002/'
const BASE_URL = 'http://192.168.10.63:8080/jeecg-boot'
const TIME_OUT = 10000
console.log(process.env)
... ...
export * from './result'
export * from './params'
... ...
export interface PhoneCodeLoginParamsType {
phoneNum: string
code: string
}
export interface Send_prescription_formType {
/**
* 处方药
*/
drugList: DrugList[];
/**
* 问诊订单id
*/
id?: string;
/**
* 处方单金额
*/
prescriptionAmount: number;
/**
* 处方单文件
*/
prescriptionFile?: string;
/**
* 问诊病情描述
*/
prescriptionForm: string;
[property: string]: any;
}
export interface DrugList {
/**
* 单价
*/
amount: string;
/**
* 说明
*/
des: string;
/**
* 药物名称
*/
name: string;
/**
* 数量
*/
num: string;
[property: string]: any;
}
... ...
... ... @@ -13,6 +13,12 @@ export interface PageListType<PageListItemType> {
size: number
}
export interface PhoneCodeLoginType {
im_token: string
userId: string
token: string
}
export interface ConsultationOrderRecordsType {
id: string;
order_no: string;
... ...
import React, { memo, useState, forwardRef, useImperativeHandle } from 'react'
import type { FC, ReactNode } from 'react'
import { Input, Modal, Upload } from 'antd'
import { MinusCircleOutlined, PlusCircleOutlined, PlusOutlined } from '@ant-design/icons'
import type { RcFile, UploadProps } from 'antd/es/upload'
import type { UploadFile } from 'antd/es/upload/interface'
import { SendPrescriptionWrapper } from '../style'
import { BASE_URL } from '@/service/request/config'
import type { Send_prescription_formType, DrugList } from '@/types'
// /sys/common/appUpload
const { TextArea } = Input
interface IProps {
children?: ReactNode
ref?: any
}
const getBase64 = (file: RcFile): Promise<string> =>
new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = () => resolve(reader.result as string)
reader.onerror = (error) => reject(error)
})
const SendPrescription: FC<IProps> = memo(
forwardRef<HTMLDivElement, IProps>((props, ref) => {
const [previewOpen, setPreviewOpen] = useState(false)
const [previewImage, setPreviewImage] = useState('')
const [previewTitle, setPreviewTitle] = useState('')
const [fileList, setFileList] = useState<UploadFile[]>([])
const [Send_prescription_formData, setSend_prescription_formTypeData] = useState<Send_prescription_formType>({
drugList: [{ amount: '', des: '', name: '', num: '' }],
prescriptionForm: '',
prescriptionAmount: 0
})
const addFormItemHandler = () => {
const setdata = { ...Send_prescription_formData, drugList: [...Send_prescription_formData.drugList, { amount: '', des: '', name: '', num: '' }] }
setSend_prescription_formTypeData(setdata)
}
const removeFormItemHandler = (index: number) => {
if (Send_prescription_formData.drugList.length <= 1) return
const setdata = { ...Send_prescription_formData, drugList: [...Send_prescription_formData.drugList].filter((_, idx) => idx !== index) }
setSend_prescription_formTypeData(setdata)
}
const changeFormItemValueHandler = (index: number, value: string | number, key: string) => {
const setdata = {
...Send_prescription_formData,
drugList: Send_prescription_formData.drugList.map((_, idx) => (idx === index ? { ..._, [key]: value } : _))
}
setSend_prescription_formTypeData(setdata)
}
const handleCancel = () => setPreviewOpen(false)
const handlePreview = async (file: UploadFile) => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj as RcFile)
}
setPreviewImage(file.url || (file.preview as string))
setPreviewOpen(true)
setPreviewTitle(file.name || file.url!.substring(file.url!.lastIndexOf('/') + 1))
}
const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => setFileList(newFileList)
useImperativeHandle<HTMLDivElement, any>(ref, () => ({
Send_prescription_formData,
fileList
}))
const uploadButton = (
<div>
<PlusOutlined />
<div style={{ marginTop: 8 }}>上传证明</div>
</div>
)
return (
<SendPrescriptionWrapper ref={ref}>
<div className="title">问诊病情</div>
<TextArea
value={Send_prescription_formData.prescriptionForm}
onChange={(e) => setSend_prescription_formTypeData({ ...Send_prescription_formData, prescriptionForm: e.target.value })}
placeholder="请输入问诊病情"
autoSize={{ minRows: 3, maxRows: 6 }}
className="title"
/>
<div className="title">处方药</div>
{Send_prescription_formData.drugList.map((_, index) => (
<div className="flexX" key={index}>
<div className="lefticon" onClick={() => removeFormItemHandler(index)}>
<MinusCircleOutlined className="grayIcon" />
</div>
<div>
<Input
value={Send_prescription_formData.drugList[index].name}
onChange={(e) => changeFormItemValueHandler(index, e.target.value, 'name')}
className="title"
placeholder="请输入药品名称"
/>
<div className="flexA">
<Input
value={Send_prescription_formData.drugList[index].amount}
onChange={(e) => changeFormItemValueHandler(index, e.target.value, 'amount')}
className="title"
placeholder="请输入药品价格"
/>
<Input
value={Send_prescription_formData.drugList[index].num}
onChange={(e) => changeFormItemValueHandler(index, e.target.value, 'num')}
className="title"
placeholder="请输入药品数量"
/>
</div>
<TextArea
value={Send_prescription_formData.drugList[index].des}
onChange={(e) => changeFormItemValueHandler(index, e.target.value, 'des')}
className="title"
placeholder="请输入说明"
autoSize={{ minRows: 3, maxRows: 6 }}
/>
</div>
</div>
))}
<div className="add flexC" onClick={addFormItemHandler}>
<PlusCircleOutlined className="blueIcon" />
<span>添加处方药</span>
</div>
<div className="title">处方单证明:</div>
<Upload action={BASE_URL + '/sys/common/appUpload'} listType="picture-card" fileList={fileList} onPreview={handlePreview} onChange={handleChange}>
{fileList.length >= 3 ? null : uploadButton}
</Upload>
<Modal open={previewOpen} title={previewTitle} footer={null} onCancel={handleCancel}>
<img alt="example" style={{ width: '100%' }} src={previewImage} />
</Modal>
<div className="title">总金额:¥{Send_prescription_formData.prescriptionAmount}</div>
</SendPrescriptionWrapper>
)
})
)
export default SendPrescription
... ...
import React, { memo } from 'react'
import type { FC, ReactNode } from 'react'
import { Image } from 'antd'
import { ViewPrescriptionWrapper } from '../style'
interface IProps {
children?: ReactNode
}
const ViewPrescription: FC<IProps> = memo(() => {
return (
<ViewPrescriptionWrapper>
<div className="title">问诊病情:</div>
<div className="content">即该不低加造年周消养明价切公没家管发七议性原提何们领从很己发战</div>
<div className="title">处方药:</div>
<div className="listbox">
<div className="itemo">
<div className="flexJ black">
<div>药品名称</div>
<div>¥3.00</div>
</div>
<div className="flexJ gray">
<div>外用,一日3次,每次10毫升</div>
<div>x2</div>
</div>
</div>
</div>
<div className="title">处方单证明:</div>
<Image width={120} src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" className="title" />
<div className="black">总金额:¥26.00</div>
</ViewPrescriptionWrapper>
)
})
export default ViewPrescription
... ...
import React, { memo } from 'react'
import type { FC, ReactNode } from 'react'
import { Avatar, Image } from 'antd'
import { ViewSymptomWrapper } from '../style'
interface IProps {
children?: ReactNode
}
const url = 'https://p1.ssl.qhmsg.com/dr/270_500_/t010c2d50907f0a7b9c.png'
const ViewSymptom: FC<IProps> = memo(() => {
return (
<ViewSymptomWrapper>
<div className="flexJ">
<div className="flexA top">
<Avatar size="large" src={<img src={url} alt="avatar" />} />
<div className="username">卡卡罗特</div>
<div className="tag">图文问诊</div>
</div>
<div className="money">预计收入:¥26.00</div>
</div>
<div className="row">
就诊宠物:<span>蓝猫/2岁/女/绝育/1kg</span>
</div>
<div className="row">
免疫情况:<span>不详</span>
</div>
<div className="row">
喂养方式:<span>自制杂粮</span>
</div>
<div className="row">
洗澡频次:<span>一周一次</span>
</div>
<div className="row">
出现症状:<span>其他</span>
</div>
<div className="row">
症状时间:<span>{`<7天`}</span>
</div>
<div className="row">
症状描述:<span>快死了哦快死了哦快死了哦快死了哦快死了哦快死了哦快死了哦快死了哦快死了哦快死了哦快死了哦快死了哦快死了哦快死了哦快死了哦快死了哦</span>
</div>
<div className="row">
<div>上传图片</div>
<Image width={120} src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" />
</div>
</ViewSymptomWrapper>
)
})
export default ViewSymptom
... ...
import styled from "styled-components";
export const SendPrescriptionWrapper = styled.div`
max-height: 440px;
overflow: auto;
.title{
color: #323233;
font-size: 15px;
font-weight: 700;
margin: 4px 0;
}
.lefticon{
padding-top: 10px;
box-sizing: border-box;
margin-right: 5px;
}
.grayIcon{
font-size: 18px;
color: #000;
}
.add{
width: 100%;
box-sizing: border-box;
padding: 13px 0;
background: #F3FFFF;
.blueIcon{
font-size: 18px;
color: #09b9d3;
margin-right: 3px;
}
span{
color: #05b8d2;
font-size: 15px;
font-weight: 700;
}
}
`
export const ViewPrescriptionWrapper = styled.div`
max-height: 440px;
overflow: auto;
.title{
color: #323233;
font-size: 14px;
font-weight: 700;
margin-bottom: 8px;
}
.content{
color: #323233;
font-size: 14px;
}
.itemo{
margin-bottom: 20px;
}
.listbox{
box-sizing: border-box;
padding: 16px 14px;
background: #F8F8FA;
border-radius: 24px;
margin-bottom: 12px;
}
.black{
color: #323233;
font-size: 14px;
font-weight: 700;
margin-bottom: 4px;
}
.gray{
color: #999999;
font-size: 12px;
font-weight: 700;
}
`
export const ViewSymptomWrapper = styled.div`
max-height: 440px;
overflow: auto;
.top{
margin-bottom: 12px;
}
.username{
color: #242424;
font-size: 16px;
font-weight: 700;
margin: 0 10px;
}
.tag{
border-radius: 12px;
background: #05B8D2;
color: #fff;
font-size: 12px;
font-weight: 700;
box-sizing: border-box;
padding: 2px 7px;
}
.money{
color: #f33f2e;
font-size: 15px;
font-weight: 700;
}
.row{
margin-bottom: 12px;
color: #323233;
font-size: 28rpx;
font-weight: 700;
span{
color:#666666;
}
}
`
... ...
import hyRequest from '@/service'
import React, { memo, useEffect, useState } from 'react'
import React, { memo, useEffect, useState, useRef, ElementRef } from 'react'
import type { FC, ReactNode } from 'react'
import { Tabs, Avatar, Space, Divider, Button, Modal, Pagination } from 'antd'
import { Tabs, Avatar, Space, Divider, Button, Modal, Pagination, Carousel } from 'antd'
import type { TabsProps } from 'antd'
import { ConsultationOrderWrapper, ConsultationOrderItemWrapper } from './styled'
import { useAppDispatch, useAppSelector, shallowEqualApp } from '@/store'
import { updateSend_prescription_form } from '@/api'
import { Send_prescription_formType } from '@/types'
import { fetchOrderDataAction } from '@/store/modules/order'
import SendPrescription from '../../com/SendPrescription'
import ViewPrescription from '../../com/ViewPrescription'
import ViewSymptom from '../../com/ViewSymptom'
interface IProps {
children?: ReactNode
... ... @@ -18,13 +22,28 @@ const ShowOrderComHandler: FC<IProps> = (props) => {
const { orderList } = props
const [open, setOpenHandler] = useState(false)
const [confirmLoading, setConfirmLoading] = useState(false)
const [modalTitle, setModalTitle] = useState<string>(' ')
const [modalFlag, setModalFlag] = useState<number>(0)
const SendPrescriptionRef = useRef<ElementRef<typeof SendPrescription> | null>(null)
const showModalHandler = (flag: number) => {
console.log(flag, '按钮状态')
setModalFlag(flag)
setModalTitle({ 1: '查看症状', 2: '发送处方单', 3: '查看处方单' }[flag] as string)
setOpenHandler(true)
}
const handleOk = () => {
const handleOk = async () => {
if (modalFlag === 2) {
const instance = SendPrescriptionRef.current as any
await updateSend_prescription_form({
...instance.Send_prescription_formData,
drugList: JSON.stringify(instance.Send_prescription_formData.drugList),
prescriptionFile: instance.fileList.map((_: any) => _.response.message).join()
})
}
console.log(SendPrescriptionRef.current, 'SendPrescription 子组件实例')
setConfirmLoading(true)
setTimeout(() => {
setOpenHandler(false)
... ... @@ -34,12 +53,14 @@ const ShowOrderComHandler: FC<IProps> = (props) => {
const handleCancel = () => {
console.log('Clicked cancel button')
setOpenHandler(false)
}
return (
<ConsultationOrderItemWrapper>
<Modal title="Title" open={open} onOk={handleOk} cancelText="关闭" okText="确认" confirmLoading={confirmLoading} onCancel={handleCancel}>
<p>132</p>
<Modal title={modalTitle} open={open} onOk={handleOk} cancelText="关闭" okText="确认" confirmLoading={confirmLoading} onCancel={handleCancel}>
{{ 1: <ViewSymptom />, 2: <SendPrescription ref={SendPrescriptionRef} />, 3: <ViewPrescription /> }[modalFlag]}
</Modal>
{orderList?.length &&
orderList.map((_, index) => (
... ... @@ -89,7 +110,7 @@ const ConsultationOrder: FC<IProps> = memo(() => {
const dispatch = useAppDispatch()
useEffect(() => {
dispatch(fetchOrderDataAction())
// dispatch(fetchOrderDataAction())
}, [])
const { banners } = useAppSelector(
... ...
... ... @@ -7,6 +7,7 @@ export const ConsultationOrderWrapper = styled.div`
height: calc(100vh - 65px);
overflow: auto;
position: relative;
background: #f5f5f5;
.title{
font-size: 18px;
font-weight: 700;
... ...
import React, { memo, useState } from 'react'
import type { FC, ReactNode } from 'react'
import { LoginWrapper } from './style'
import { Input, Button } from 'antd'
import { Input, Button, message } from 'antd'
import { useNavigate } from 'react-router-dom'
import useTimeHandler from '@/hooks/useTimeChange'
import { DebounceBy } from '@/hooks/debounceBy'
import { getSendMessage, getMessageLogin } from '@/api'
import { PhoneCodeLoginParamsType, PhoneCodeLoginType } from '@/types'
message.config({
top: 400
})
interface IProps {
children?: ReactNode
}
... ... @@ -11,26 +17,71 @@ interface IProps {
const Login: FC<IProps> = memo(() => {
const naviate = useNavigate()
const loginHandler = () => {
const [loadingState, setLoadingState] = useState<boolean>(false)
const [messageApi, contextHolder] = message.useMessage()
const [form, setForm] = useState<PhoneCodeLoginParamsType>({ phoneNum: '', code: '10086' })
const [countdown, startCountdown] = useTimeHandler()
const warning = (content: string) => messageApi.open({ type: 'warning', content: content })
const success = (content: string) => messageApi.open({ type: 'success', content: content })
const sendCodeHandler = DebounceBy(() => {
if (!/^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[1589]))\d{8}$/.test(form.phoneNum))
return warning('请填写正确的手机号')
startCountdown(async () => {
await getSendMessage({ phoneNum: form.phoneNum })
success('发送成功')
})
}, 500)
const loginHandler = async () => {
if (!/^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[1589]))\d{8}$/.test(form.phoneNum))
return warning('请填写正确的手机号')
setLoadingState(true)
setTimeout(() => {
setLoadingState(false)
const { result }: { result: PhoneCodeLoginType } = await getMessageLogin(form)
localStorage.setItem('token', result.token)
localStorage.setItem('userId', result.userId)
localStorage.setItem('im_token', result.im_token)
success('登录成功')
setTimeout(() => {
naviate('/discover')
}, 2000)
}
const [loadingState, setLoadingState] = useState<boolean>(false)
setLoadingState(false)
}, 1000)
}
return (
<LoginWrapper>
{contextHolder}
<div className="loginBox">
<div className="title flexC">宠物问诊-兽医端</div>
<div className="subtitle">账号</div>
<Input placeholder="请输入手机号" className="inputbox" />
<Input placeholder="请输入手机号" className="inputbox" value={form.phoneNum} onChange={(e) => setForm({ ...form, phoneNum: e.target.value })} />
<div className="subtitle">验证码</div>
<Input placeholder="请输入密码" className="inputbox" suffix={<div style={{ color: '#05b8d2', fontSize: '17px' }}>获取验证码</div>} />
<Input
placeholder="请输入密码"
className="inputbox"
value={form.code}
onChange={(e) => setForm({ ...form, code: e.target.value })}
suffix={
<div style={{ color: '#05b8d2', fontSize: '17px', cursor: 'pointer' }} onClick={sendCodeHandler}>
{countdown === 60 ? '获取验证码' : `${countdown}秒后可重新获取`}
</div>
}
/>
<Button className="loginbtn" onClick={loginHandler} loading={loadingState}>
登录
</Button>
... ...