1.根据ui大致完成登录界面。
This commit is contained in:
parent
99d12b483d
commit
fcaae1860c
@ -6,14 +6,21 @@
|
|||||||
"@emotion/react": "^11.11.0",
|
"@emotion/react": "^11.11.0",
|
||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@mui/icons-material": "^5.11.16",
|
"@mui/icons-material": "^5.11.16",
|
||||||
|
"@mui/lab": "^5.0.0-alpha.133",
|
||||||
"@mui/material": "^5.13.3",
|
"@mui/material": "^5.13.3",
|
||||||
|
"@reduxjs/toolkit": "^1.9.5",
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^13.4.0",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"http-proxy-middleware": "^2.0.6",
|
||||||
|
"md5": "^2.3.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-redux": "^8.0.7",
|
||||||
"react-router-dom": "^6.11.2",
|
"react-router-dom": "^6.11.2",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
|
"sha1": "^1.1.1",
|
||||||
|
"sha256": "^0.2.0",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -1,43 +1,43 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="icon" href="%PUBLIC_URL%/logo.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="Web site created using create-react-app"
|
content="Web site created using create-react-app"
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
<!--
|
<!--
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
-->
|
-->
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
<!--
|
<!--
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
<title>React App</title>
|
<title>纽曼AI语记</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<!--
|
<!--
|
||||||
This HTML file is a template.
|
This HTML file is a template.
|
||||||
If you open it directly in the browser, you will see an empty page.
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
You can add webfonts, meta tags, or analytics to this file.
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
The build step will place the bundled scripts into the <body> tag.
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
To begin the development, run `npm start` or `yarn start`.
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
-->
|
-->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
BIN
public/logo.png
Normal file
BIN
public/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 9.4 KiB |
@ -11,11 +11,6 @@
|
|||||||
"src": "logo192.png",
|
"src": "logo192.png",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"sizes": "192x192"
|
"sizes": "192x192"
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "logo512.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "512x512"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"start_url": ".",
|
"start_url": ".",
|
||||||
|
148
src/LoginPage.js
148
src/LoginPage.js
@ -1,13 +1,64 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import TextField from '@mui/material/TextField';
|
import TextField from '@mui/material/TextField';
|
||||||
|
import yzs from "./business/request.js";
|
||||||
import styles from './LoginPage.module.css';
|
import styles from './LoginPage.module.css';
|
||||||
|
import { useSelector, useDispatch } from 'react-redux'
|
||||||
|
import { setAddress } from "./business/ipSlice.js"
|
||||||
|
import { setFlushToken, setAccessToken, setUserInfo, selectFlushToken } from "./business/userSlice.js"
|
||||||
|
import logo from './assets/logo.png'; // Tell webpack this JS file uses this image
|
||||||
|
import { Container, Tab, Box } from '@mui/material';
|
||||||
|
import TabPanel from '@mui/lab/TabPanel';
|
||||||
|
import { TabList } from '@mui/lab';
|
||||||
|
import TabContext from '@mui/lab/TabContext';
|
||||||
|
import DynamicCodeForm from './components/DynamicCodeForm.js';
|
||||||
|
import PasswordForm from './components/PasswordForm.js';
|
||||||
|
|
||||||
|
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
||||||
|
|
||||||
|
const theme = createTheme({
|
||||||
|
status: {
|
||||||
|
danger: '#e53e3e',
|
||||||
|
},
|
||||||
|
palette: {
|
||||||
|
primary: {
|
||||||
|
main: '#FF595A',
|
||||||
|
darker: '#FF595A',
|
||||||
|
},
|
||||||
|
neutral: {
|
||||||
|
main: '#64748B',
|
||||||
|
contrastText: '#fff',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
|
const [value, setValue] = useState("1");
|
||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
|
|
||||||
|
const handleChange = (event, newValue) => {
|
||||||
|
setValue(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const ip = useSelector(state => state.ip.value)
|
||||||
|
const flushToken = useSelector(selectFlushToken)
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
const getIp = async () => {
|
||||||
|
// Connect ipapi.co with fetch()
|
||||||
|
const response = await fetch("https://ipapi.co/json/")
|
||||||
|
const data = await response.json()
|
||||||
|
// Set the IP address to the constant `ip`
|
||||||
|
dispatch(setAddress(data.ip));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run `getIP` function above just once when the page is rendered
|
||||||
|
useEffect(() => {
|
||||||
|
getIp()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleInputChange = (event) => {
|
const handleInputChange = (event) => {
|
||||||
@ -18,33 +69,76 @@ export default function () {
|
|||||||
|
|
||||||
const handleSubmit = (event) => {
|
const handleSubmit = (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
console.log(`Username: ${username}\nPassword: ${password}`);
|
console.log(`Username: ${username}\nPassword: ${password} ip: ${ip}`);
|
||||||
|
|
||||||
|
yzs.login(ip.payload).then(token => {
|
||||||
|
dispatch(setFlushToken(token));
|
||||||
|
yzs.get_access_token(ip.payload, token).then(token => {
|
||||||
|
// yzs.update_access_token(ip.payload, token);
|
||||||
|
dispatch(setAccessToken(token));
|
||||||
|
yzs.get_user_info(ip.payload, token).then(info => {
|
||||||
|
dispatch(setUserInfo(info));
|
||||||
|
let passportId = info.passportId;
|
||||||
|
yzs.user_select(ip.payload, token).then(info => {
|
||||||
|
yzs.get_record_list(token, passportId)
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className={styles.form} onSubmit={handleSubmit}>
|
<div className={styles.loginPage}>
|
||||||
<TextField
|
<div className={styles.title}>
|
||||||
name="username"
|
<img className={styles.titleIcon} src={logo} />
|
||||||
label="请输入手机号码"
|
<h1 className={styles.titleText}>纽曼AI语记</h1>
|
||||||
variant="outlined"
|
</div>
|
||||||
value={username}
|
|
||||||
onChange={handleInputChange}
|
<div className={styles.loginFrame}>
|
||||||
/>
|
<ThemeProvider theme={theme}>
|
||||||
<TextField
|
<Container component="form" className={styles.form} onSubmit={handleSubmit}
|
||||||
name="password"
|
sx={{
|
||||||
label="请输入密码"
|
width: 360,
|
||||||
type="password"
|
height: 418,
|
||||||
variant="outlined"
|
backgroundColor: 'white',
|
||||||
value={password}
|
display: "flex",
|
||||||
onChange={handleInputChange}
|
flexDirection: "column",
|
||||||
/>
|
justifyContent: "center",
|
||||||
<Button
|
alignItems: "center",
|
||||||
type="submit"
|
boxShadow: "0px 5px 20px 0px rgba(146,0,1,0.1)",
|
||||||
variant="contained"
|
borderRadius: 4,
|
||||||
color="primary"
|
|
||||||
>
|
}}
|
||||||
登录
|
>
|
||||||
</Button>
|
<TabContext value={value}>
|
||||||
</form>
|
<Box>
|
||||||
|
|
||||||
|
<TabList
|
||||||
|
|
||||||
|
aria-label="basic tabs example" value={value} onChange={handleChange} >
|
||||||
|
<Tab label="手机动态码登录" value="1" />
|
||||||
|
<Tab label="账号密码登录" value="2" />
|
||||||
|
</TabList>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<TabPanel value="1" >
|
||||||
|
<DynamicCodeForm />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel value="2" >
|
||||||
|
<PasswordForm />
|
||||||
|
</TabPanel>
|
||||||
|
</TabContext>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</Container>
|
||||||
|
</ThemeProvider >
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -1,4 +1,29 @@
|
|||||||
.form {
|
.loginPage {
|
||||||
|
background-image: url(./assets/background@2x.png);
|
||||||
|
background-size: cover;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 72px;
|
||||||
|
padding-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titleIcon {
|
||||||
|
width: 54px;
|
||||||
|
height: 57px;
|
||||||
|
margin-right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titleText {
|
||||||
|
color: #FF595A;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.loginFrame {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
BIN
src/assets/background@2x.png
Normal file
BIN
src/assets/background@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 493 KiB |
BIN
src/assets/logo.png
Normal file
BIN
src/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
BIN
src/assets/logo@2x.png
Normal file
BIN
src/assets/logo@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.9 KiB |
22
src/business/ipSlice.js
Normal file
22
src/business/ipSlice.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { createSlice } from '@reduxjs/toolkit'
|
||||||
|
|
||||||
|
export const ipSlice = createSlice({
|
||||||
|
name: 'ip',
|
||||||
|
initialState: {
|
||||||
|
value: ""
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
setAddress: (state, ip) => {
|
||||||
|
// Redux Toolkit allows us to write "mutating" logic in reducers. It
|
||||||
|
// doesn't actually mutate the state because it uses the Immer library,
|
||||||
|
// which detects changes to a "draft state" and produces a brand new
|
||||||
|
// immutable state based off those changes
|
||||||
|
state.value = ip;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Action creators are generated for each case reducer function
|
||||||
|
export const { setAddress } = ipSlice.actions
|
||||||
|
|
||||||
|
export default ipSlice.reducer
|
170
src/business/request.js
Normal file
170
src/business/request.js
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
const appKey = "k5hfiei5eevouvjohkapjaudpk2gakpaxha22fiy";
|
||||||
|
const appSecret = "e65ffb25148b08207088148d5bce114d";
|
||||||
|
|
||||||
|
function constructParameter(body) {
|
||||||
|
let params = [];
|
||||||
|
for (let key in body) {
|
||||||
|
params.push(body[key].toString());
|
||||||
|
}
|
||||||
|
params.sort();
|
||||||
|
let digest = "";
|
||||||
|
for (let param of params) {
|
||||||
|
console.log(param)
|
||||||
|
digest += param;
|
||||||
|
}
|
||||||
|
let sha1 = require('sha1');
|
||||||
|
body.signature = sha1(digest).toUpperCase();
|
||||||
|
|
||||||
|
let p = '';
|
||||||
|
for (let key in body) {
|
||||||
|
p += key;
|
||||||
|
p += "=";
|
||||||
|
p += encodeURIComponent(body[key]);
|
||||||
|
p += "&";
|
||||||
|
}
|
||||||
|
p = p.slice(0, -1);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
const yzs = {
|
||||||
|
get_access_token: function (ip, flushToken) {
|
||||||
|
let body = {};
|
||||||
|
body.subsystemId = 16;
|
||||||
|
body.clientId = ip;
|
||||||
|
body.timestamp = parseInt(new Date().getTime() / 1000);
|
||||||
|
body.flushToken = flushToken;
|
||||||
|
|
||||||
|
return fetch("http://116.198.37.53:8080/rest/v2/token/get_access_token", {
|
||||||
|
method: "POST",
|
||||||
|
body: constructParameter(body),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
|
||||||
|
},
|
||||||
|
}).then(response => response.json()).then((json) => {
|
||||||
|
console.log(json);
|
||||||
|
return json.result.accessToken;
|
||||||
|
}).catch(error => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
update_access_token: function (ip, accessToken) {
|
||||||
|
let body = {};
|
||||||
|
body.subsystemId = 16;
|
||||||
|
body.clientId = ip;
|
||||||
|
body.timestamp = parseInt(new Date().getTime() / 1000);
|
||||||
|
body.accessToken = accessToken;
|
||||||
|
|
||||||
|
return fetch("http://116.198.37.53:8080/rest/v2/token/refresh_access_token", {
|
||||||
|
method: "POST",
|
||||||
|
body: constructParameter(body),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
|
||||||
|
},
|
||||||
|
}).then(response => response.json()).then((json) => {
|
||||||
|
console.log(json);
|
||||||
|
return json.result.accessToken;
|
||||||
|
}).catch(error => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
get_user_info: function (ip, accessToken) {
|
||||||
|
let body = {};
|
||||||
|
body.subsystemId = 16;
|
||||||
|
body.clientId = ip;
|
||||||
|
body.timestamp = parseInt(new Date().getTime() / 1000);
|
||||||
|
body.accessToken = accessToken;
|
||||||
|
|
||||||
|
return fetch("http://116.198.37.53:8080/rest/v2/user/get_user_info", {
|
||||||
|
method: "POST",
|
||||||
|
body: constructParameter(body),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
|
||||||
|
},
|
||||||
|
}).then(response => response.json()).then((json) => {
|
||||||
|
console.log(json);
|
||||||
|
return json.result;
|
||||||
|
}).catch(error => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
user_select: function (ip, accessToken) {
|
||||||
|
let sha256 = require('sha256');
|
||||||
|
let timestamp = new Date().getTime();
|
||||||
|
let sig = appKey + timestamp.toString() + appSecret;
|
||||||
|
sig = sha256(sig).toUpperCase();;
|
||||||
|
|
||||||
|
|
||||||
|
let url = `/api/app/app-voice-recorder/rest/v1/user/select?accessToken=${encodeURIComponent(accessToken)}&phoneUdid=${encodeURIComponent(ip)}`;
|
||||||
|
console.log("url: ", url)
|
||||||
|
|
||||||
|
return fetch(url, {
|
||||||
|
headers: {
|
||||||
|
'appKey': appKey,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'signature': sig,
|
||||||
|
},
|
||||||
|
}).then(response => {
|
||||||
|
console.log(response);
|
||||||
|
response.text()
|
||||||
|
}).then((json) => {
|
||||||
|
console.log(json)
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
get_record_list: function (accessToken, passportId) {
|
||||||
|
let sha256 = require('sha256');
|
||||||
|
let timestamp = new Date().getTime();
|
||||||
|
let sig = appKey + timestamp.toString() + appSecret;
|
||||||
|
sig = sha256(sig).toUpperCase();;
|
||||||
|
|
||||||
|
|
||||||
|
let url = `/api/app/app-voice-recorder/rest/v1/trans/info/list?accessToken=${encodeURIComponent(accessToken)}&passportId=${passportId}`;
|
||||||
|
console.log("url: ", url)
|
||||||
|
|
||||||
|
return fetch(url, {
|
||||||
|
headers: {
|
||||||
|
'appKey': appKey,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'signature': sig,
|
||||||
|
},
|
||||||
|
}).then(response => {
|
||||||
|
console.log(response);
|
||||||
|
response.text()
|
||||||
|
}).then((json) => {
|
||||||
|
console.log(json)
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
login: function (ip) {
|
||||||
|
let md5 = require('md5');
|
||||||
|
let body = {};
|
||||||
|
body.subsystemId = 16;
|
||||||
|
body.clientId = ip;
|
||||||
|
body.timestamp = parseInt(new Date().getTime() / 1000);
|
||||||
|
body.account = "13682423271";
|
||||||
|
body.password = md5("yzs123456");
|
||||||
|
|
||||||
|
return fetch("http://116.198.37.53:8080/rest/v2/user/login", {
|
||||||
|
method: "POST",
|
||||||
|
body: constructParameter(body),
|
||||||
|
// mode: "no-cors",
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
|
||||||
|
"Access-Control-Allow-Origin": "*",
|
||||||
|
},
|
||||||
|
}).then(response => response.json()).then((json) => {
|
||||||
|
console.log("flushToken: ", json.result.flushToken);
|
||||||
|
return json.result.flushToken;
|
||||||
|
}).catch(error => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default yzs;
|
10
src/business/store.js
Normal file
10
src/business/store.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { configureStore } from '@reduxjs/toolkit'
|
||||||
|
import ipReducer from "./ipSlice.js"
|
||||||
|
import userReducer from "./userSlice.js"
|
||||||
|
|
||||||
|
export default configureStore({
|
||||||
|
reducer: {
|
||||||
|
ip: ipReducer,
|
||||||
|
user: userReducer,
|
||||||
|
}
|
||||||
|
})
|
33
src/business/userSlice.js
Normal file
33
src/business/userSlice.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { createSlice } from '@reduxjs/toolkit'
|
||||||
|
|
||||||
|
export const userSlice = createSlice({
|
||||||
|
name: 'user',
|
||||||
|
initialState: {
|
||||||
|
flushToken: "",
|
||||||
|
accessToken: "",
|
||||||
|
info: {},
|
||||||
|
},
|
||||||
|
reducers: {
|
||||||
|
setFlushToken: (state, token) => {
|
||||||
|
// Redux Toolkit allows us to write "mutating" logic in reducers. It
|
||||||
|
// doesn't actually mutate the state because it uses the Immer library,
|
||||||
|
// which detects changes to a "draft state" and produces a brand new
|
||||||
|
// immutable state based off those changes
|
||||||
|
state.flushToken = token;
|
||||||
|
},
|
||||||
|
setAccessToken: (state, token) => {
|
||||||
|
state.accessToken = token;
|
||||||
|
},
|
||||||
|
setUserInfo: (state, info) => {
|
||||||
|
state.info = info;
|
||||||
|
// state.createTime = info.createTime;
|
||||||
|
// state.userName = info.userName;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Action creators are generated for each case reducer function
|
||||||
|
export const { setFlushToken, setAccessToken, setUserInfo } = userSlice.actions
|
||||||
|
export const selectFlushToken = (state) => state.user.flushToken
|
||||||
|
|
||||||
|
export default userSlice.reducer
|
90
src/components/DynamicCodeForm.js
Normal file
90
src/components/DynamicCodeForm.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { Container, TextField, InputAdornment, Link, Button, Stack, Typography } from "@mui/material";
|
||||||
|
import { CheckBox } from '@mui/icons-material';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import PhoneIphoneIcon from '@mui/icons-material/PhoneIphone';
|
||||||
|
import LockIcon from '@mui/icons-material/Lock';
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const [username, setUsername] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
|
||||||
|
const handleInputChange = (event) => {
|
||||||
|
const { name, value } = event.target;
|
||||||
|
if (name === 'username') setUsername(value);
|
||||||
|
if (name === 'password') setPassword(value);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return <Container disableGutters={true}
|
||||||
|
sx={{
|
||||||
|
width: 300,
|
||||||
|
height: 200,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
name="username"
|
||||||
|
label="请输入手机号码"
|
||||||
|
variant="outlined"
|
||||||
|
value={username}
|
||||||
|
color="primary"
|
||||||
|
fullWidth
|
||||||
|
onChange={handleInputChange}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">
|
||||||
|
<PhoneIphoneIcon />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
// sx={{ paddingTop: 4 }}
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
name="password"
|
||||||
|
label="请输入验证码"
|
||||||
|
type="password"
|
||||||
|
variant="outlined"
|
||||||
|
value={password}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">
|
||||||
|
<LockIcon />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
|
||||||
|
endAdornment: <InputAdornment position="end">
|
||||||
|
<Link>发送动态码</Link>
|
||||||
|
</InputAdornment>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
fullWidth
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#FF595A",
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: '#FF595A',
|
||||||
|
},
|
||||||
|
'&:active': {
|
||||||
|
backgroundColor: '#FF595A',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
注册/登录
|
||||||
|
</Button>
|
||||||
|
<Container>
|
||||||
|
<Stack direction="row" spacing={1}
|
||||||
|
sx={{ paddingTop: 2 }}
|
||||||
|
>
|
||||||
|
<CheckBox color="primary" />
|
||||||
|
<Typography>同意 <Link>《纽曼隐私协议》</Link></Typography>
|
||||||
|
</Stack>
|
||||||
|
</Container>
|
||||||
|
|
||||||
|
</Container>
|
||||||
|
}
|
74
src/components/PasswordForm.js
Normal file
74
src/components/PasswordForm.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Container, TextField, Button, InputAdornment } from "@mui/material";
|
||||||
|
import PhoneIphoneIcon from '@mui/icons-material/PhoneIphone';
|
||||||
|
import LockIcon from '@mui/icons-material/Lock';
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const [username, setUsername] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
|
||||||
|
const handleInputChange = (event) => {
|
||||||
|
const { name, value } = event.target;
|
||||||
|
if (name === 'username') setUsername(value);
|
||||||
|
if (name === 'password') setPassword(value);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return <Container disableGutters={true}
|
||||||
|
sx={{
|
||||||
|
width: 300,
|
||||||
|
height: 200,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
name="username"
|
||||||
|
label="请输入手机号码"
|
||||||
|
variant="outlined"
|
||||||
|
value={username}
|
||||||
|
fullWidth
|
||||||
|
onChange={handleInputChange}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">
|
||||||
|
<PhoneIphoneIcon />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
// sx={{ paddingTop: 4 }}
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
name="password"
|
||||||
|
label="请输入密码"
|
||||||
|
type="password"
|
||||||
|
variant="outlined"
|
||||||
|
value={password}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">
|
||||||
|
<LockIcon />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
fullWidth
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#FF595A",
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: '#FF595A',
|
||||||
|
},
|
||||||
|
'&:active': {
|
||||||
|
backgroundColor: '#FF595A',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
登录
|
||||||
|
</Button>
|
||||||
|
</Container>
|
||||||
|
}
|
10
src/index.js
10
src/index.js
@ -2,15 +2,19 @@ import React from 'react';
|
|||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
import store from './business/store';
|
||||||
import reportWebVitals from './reportWebVitals';
|
import reportWebVitals from './reportWebVitals';
|
||||||
import { BrowserRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
import { Provider } from "react-redux";
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<BrowserRouter >
|
<Provider store={store}>
|
||||||
<App />
|
<BrowserRouter >
|
||||||
</BrowserRouter>
|
<App />
|
||||||
|
</BrowserRouter>
|
||||||
|
</Provider>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
27
src/setupProxy.js
Normal file
27
src/setupProxy.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
const { createProxyMiddleware } = require('http-proxy-middleware');
|
||||||
|
|
||||||
|
module.exports = function (app) {
|
||||||
|
app.use(
|
||||||
|
'/api/app/app-voice-recorder/rest/v1/trans/info/list',
|
||||||
|
createProxyMiddleware({
|
||||||
|
target: 'http://ai-api.uat.hivoice.cn',
|
||||||
|
changeOrigin: true,
|
||||||
|
logger: console,
|
||||||
|
onProxyReq: (proxyReq, req, res) => {
|
||||||
|
proxyReq.setHeader('appKey', 'k5hfiei5eevouvjohkapjaudpk2gakpaxha22fiy');
|
||||||
|
// console.log("proxyReq", req)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
app.use(
|
||||||
|
'/api/app/app-voice-recorder/rest/v1/user/select',
|
||||||
|
createProxyMiddleware({
|
||||||
|
target: 'http://ai-api.uat.hivoice.cn',
|
||||||
|
changeOrigin: true,
|
||||||
|
logger: console,
|
||||||
|
onProxyReq: (proxyReq, req, res) => {
|
||||||
|
proxyReq.setHeader('appKey', 'k5hfiei5eevouvjohkapjaudpk2gakpaxha22fiy');
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user