在之前的一篇文章里,编写了一个简单的页面,现在要把这个页面变成一个组件,需要再优化一下。
页面布局
这次的布局,就不采用完全居中的布局了,而是像之前说的,用一个Box当flex容器,按列竖向排布,让第一个元素有一点上边距,就可以作出比较好看的登录页面了。基础的排布如下:
<Container maxWidth="xs">
<CssBaseline>
<Box sx={{display: "flex", flexDirection: "column", height: "100vh"}}>
<Typography align="center" mt={8}>
<AccountCircleOutlinedIcon color="success" fontSize="large"/>
</Typography>
......
第一个元素Typography
的上边距设置成了mt=8
,整体的样子基本就出来了。
布局细节
先把完整的组件布局的代码放上:
<Container maxWidth="xs">
<CssBaseline>
<Box sx={{display: "flex", flexDirection: "column", height: "100vh"}}>
<Typography align="center" mt={8}>
<AccountCircleOutlinedIcon color="success" fontSize="large"/>
</Typography>
<Typography align="center" variant="h6" sx={{marginBottom: "8px"}}>请登录</Typography>
<Box sx={{textAlign: "center"}}>
<TextField fullWidth error={!this.state.userValid}
id="outlined-username-input"
label="Username"
type="text"
autoComplete="current-password"
onChange={this.handleUsernameChange}
/>
</Box>
<Box sx={{textAlign: "center", marginTop: "16px"}}>
<TextField fullWidth error={!this.state.passwordValid}
id="outlined-password-input"
label="Password"
type="password"
autoComplete="current-password"
onChange={this.handlePasswordChange}
/>
</Box>
<Box>
<FormControlLabel
label="记住我"
control={<Checkbox onChange={this.handleRemember}/>}
/>
</Box>
<Grid container>
<Grid item xs={12} md>
<Link href="#" variant="body2">
忘记密码
</Link>
</Grid>
<Grid item>
<Link href="#" variant="body2">
注册新账户
</Link>
</Grid>
</Grid>
<Box sx={{textAlign: "center", marginTop: "16px"}}>
<Button fullWidth variant="contained" onClick={this.handleSubmit}>Sign In</Button>
</Box>
</Box>
</CssBaseline>
</Container>
这里发现一个细节,就是左侧的Grid不设置具体大小的时候,就会挤占右侧的Grid到最右,利用这一点就可以在忘记密码和注册列来实现简单的左右自动对齐和小屏幕上下重叠。
state
及事件
这里暂时还没用到Redux
,只考虑单独的当前页面,经过考虑,在state
中保存了用户名,密码,记住与否,以及两个字段的是否正确的信息。
初始state
组件通过读取state来更改当前显示的样式,error
属性控制正确还是错误样式。
初始的state如下:
constructor(props) {
super(props);
this.state = {
username: "",
password: "",
remember: false,
userValid: true,
passwordValid: true,
}
}
这里初始设置userValid
和passwordValid
是为了让一开始的文本输入框显示正常样式。
事件处理
到了事件处理的部分。先考虑将两个文本输入框变成受控组件,逻辑是一开始两个框默认是正常样式,之后一旦开始输入,如果输入为空或者包含空格,就显示错误样式,因此可以先编一个验证文本的函数:
isValid = (text) => {
return !(text.length === 0 || text.includes(" "))
}
如果输入为空,或者包含空格,就会判断为不通过。
之后就可以来编写事件处理,让这些输入框受控:
handleRemember = (e) => {
this.setState((state, props) => ({
remember: e.target.checked,
}))
}
handleUsernameChange = (e) => {
this.setState((state, props) => ({
username: e.target.value,
userValid: this.isValid(e.target.value)
}))
}
handlePasswordChange = (e) => {
this.setState((state, props) => ({
password: e.target.value,
passwordValid: this.isValid(e.target.value)
}))
}
两个文本框对应的事件,就是将文本输入框中的内容存入state
中,然后进行判断,如果判断不符合要求,那么就更改state
中的内容,这样框就会显示为错误样式。这样文本框一旦从开始的状态进行变化,就会触发这些机制,即使再删除掉其中所有内容,文本框依然会显示为错误状态,满足要求。
最后是编写提交表单的函数,这里只是虚拟提交一下,其中的逻辑是,如果两个框均为有效,就正常提交。如果两个框有错误,则不提交,并将框设置为错误。
这里要解决的问题就是如果以初始态提交要如何判断,只需要添加判断字符串长度是否为0即可。在不提交的分支,每次也要检查一下输入是否有效,然后去设置样式,检查输入有效只需要使用isValid
方法即可。然后在控制台中打印出提取后的三个变量的数值:
handleSubmit = (e) => {
e.preventDefault();
let {username, password, remember} = this.state;
if (this.state.userValid && this.state.passwordValid && this.state.username.length !== 0 && this.state.password.length !== 0) {
console.log("验证通过,已发送");
} else {
console.log("验证未通过,不发送");
this.setState((state, props) => ({
userValid: this.isValid(state.username),
passwordValid: this.isValid(state.password),
}))
}
console.log({username,password, remember});
}
完整代码
import React from 'react';
import {
Box,
Button,
Checkbox,
Container,
CssBaseline,
FormControlLabel,
Grid,
Link,
TextField,
Typography
} from "@mui/material";
import AccountCircleOutlinedIcon from "@mui/icons-material/AccountCircleOutlined";
class LoginPage extends React.Component {
handleRemember = (e) => {
this.setState((state, props) => ({
remember: e.target.checked,
}))
}
handleUsernameChange = (e) => {
this.setState((state, props) => ({
username: e.target.value,
userValid: this.isValid(e.target.value)
}))
}
handlePasswordChange = (e) => {
this.setState((state, props) => ({
password: e.target.value,
passwordValid: this.isValid(e.target.value)
}))
}
constructor(props) {
super(props);
this.state = {
username: "",
password: "",
remember: false,
userValid: true,
passwordValid: true,
}
}
handleSubmit = (e) => {
e.preventDefault();
let {username, password, remember} = this.state;
if (this.state.userValid && this.state.passwordValid && this.state.username.length !== 0 && this.state.password.length !== 0) {
console.log("验证通过,已发送");
} else {
console.log("验证未通过,不发送");
this.setState((state, props) => ({
userValid: this.isValid(state.username),
passwordValid: this.isValid(state.password),
}))
}
console.log({username,password, remember});
}
isValid = (text) => {
return !(text.length === 0 || text.includes(" "))
}
render() {
return (
<Container maxWidth="xs">
<CssBaseline>
<Box sx={{display: "flex", flexDirection: "column", height: "100vh"}}>
<Typography align="center" mt={8}>
<AccountCircleOutlinedIcon color="success" fontSize="large"/>
</Typography>
<Typography align="center" variant="h6" sx={{marginBottom: "8px"}}>请登录</Typography>
<Box sx={{textAlign: "center"}}>
<TextField fullWidth error={!this.state.userValid}
id="outlined-username-input"
label="Username"
type="text"
autoComplete="current-password"
onChange={this.handleUsernameChange}
/>
</Box>
<Box sx={{textAlign: "center", marginTop: "16px"}}>
<TextField fullWidth error={!this.state.passwordValid}
id="outlined-password-input"
label="Password"
type="password"
autoComplete="current-password"
onChange={this.handlePasswordChange}
/>
</Box>
<Box>
<FormControlLabel
label="记住我"
control={<Checkbox onChange={this.handleRemember}/>}
/>
</Box>
<Grid container>
<Grid item xs={12} md>
<Link href="#" variant="body2">
忘记密码
</Link>
</Grid>
<Grid item>
<Link href="#" variant="body2">
注册新账户
</Link>
</Grid>
</Grid>
<Box sx={{textAlign: "center", marginTop: "16px"}}>
<Button fullWidth variant="contained" onClick={this.handleSubmit}>Sign In</Button>
</Box>
</Box>
</CssBaseline>
</Container>
);
}
}
export default LoginPage;
一个小小的登录页面也有如此多的讲究,之后要更换验证,其实只需要更换isValid()
方法即可,而且这其中,注册链接和登录链接都还没有编写。实际环境中,在发送请求之后,应当改变一下请求按钮的样式,这要搭配与后端交互的代码,不着急,还是慢慢从react-router
玩起。