diff --git a/package.json b/package.json index 423aa96..5b90753 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,21 @@ "@emotion/react": "^11.11.0", "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.11.16", + "@mui/lab": "^5.0.0-alpha.133", "@mui/material": "^5.13.3", + "@reduxjs/toolkit": "^1.9.5", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "http-proxy-middleware": "^2.0.6", + "md5": "^2.3.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-redux": "^8.0.7", "react-router-dom": "^6.11.2", "react-scripts": "5.0.1", + "sha1": "^1.1.1", + "sha256": "^0.2.0", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/public/index.html b/public/index.html index aa069f2..85173bf 100644 --- a/public/index.html +++ b/public/index.html @@ -1,43 +1,43 @@ - - - - - - - - - - - - - React App - - - -
- - - + + + + + + + + + + + + + 纽曼AI语记 + + + +
+ + + diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000..13be521 Binary files /dev/null and b/public/logo.png differ diff --git a/public/logo512.png b/public/logo512.png deleted file mode 100644 index a4e47a6..0000000 Binary files a/public/logo512.png and /dev/null differ diff --git a/public/manifest.json b/public/manifest.json index 080d6c7..62edef1 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -11,11 +11,6 @@ "src": "logo192.png", "type": "image/png", "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" } ], "start_url": ".", diff --git a/src/LoginPage.js b/src/LoginPage.js index 41e2048..c12a105 100644 --- a/src/LoginPage.js +++ b/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 TextField from '@mui/material/TextField'; - +import yzs from "./business/request.js"; 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 () { + const [value, setValue] = useState("1"); const [username, setUsername] = 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) => { @@ -18,33 +69,76 @@ export default function () { const handleSubmit = (event) => { 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 ( -
- - - - +
+
+ +

纽曼AI语记

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ + +
); } \ No newline at end of file diff --git a/src/LoginPage.module.css b/src/LoginPage.module.css index eb5b217..c94deee 100644 --- a/src/LoginPage.module.css +++ b/src/LoginPage.module.css @@ -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; flex-direction: column; justify-content: center; diff --git a/src/assets/background@2x.png b/src/assets/background@2x.png new file mode 100644 index 0000000..6e11d7e Binary files /dev/null and b/src/assets/background@2x.png differ diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..13be521 Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/assets/logo@2x.png b/src/assets/logo@2x.png new file mode 100644 index 0000000..902c038 Binary files /dev/null and b/src/assets/logo@2x.png differ diff --git a/src/business/ipSlice.js b/src/business/ipSlice.js new file mode 100644 index 0000000..9f58ffb --- /dev/null +++ b/src/business/ipSlice.js @@ -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 \ No newline at end of file diff --git a/src/business/request.js b/src/business/request.js new file mode 100644 index 0000000..9474310 --- /dev/null +++ b/src/business/request.js @@ -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; \ No newline at end of file diff --git a/src/business/store.js b/src/business/store.js new file mode 100644 index 0000000..725f359 --- /dev/null +++ b/src/business/store.js @@ -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, + } +}) \ No newline at end of file diff --git a/src/business/userSlice.js b/src/business/userSlice.js new file mode 100644 index 0000000..1eb6d33 --- /dev/null +++ b/src/business/userSlice.js @@ -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 \ No newline at end of file diff --git a/src/components/DynamicCodeForm.js b/src/components/DynamicCodeForm.js new file mode 100644 index 0000000..e17d31c --- /dev/null +++ b/src/components/DynamicCodeForm.js @@ -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 + + + + + ), + }} + /> + + + + ), + + endAdornment: + 发送动态码 + + }} + /> + + + + + 同意 《纽曼隐私协议》 + + + + +} \ No newline at end of file diff --git a/src/components/PasswordForm.js b/src/components/PasswordForm.js new file mode 100644 index 0000000..34e4d17 --- /dev/null +++ b/src/components/PasswordForm.js @@ -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 + + + + ), + }} + /> + + + + ), + }} + /> + + +} \ No newline at end of file diff --git a/src/index.js b/src/index.js index 52a3c75..3382ffc 100644 --- a/src/index.js +++ b/src/index.js @@ -2,15 +2,19 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; +import store from './business/store'; import reportWebVitals from './reportWebVitals'; import { BrowserRouter } from "react-router-dom"; +import { Provider } from "react-redux"; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( - - - + + + + + ); diff --git a/src/setupProxy.js b/src/setupProxy.js new file mode 100644 index 0000000..4cbd576 --- /dev/null +++ b/src/setupProxy.js @@ -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'); + }, + }) + ); +}; \ No newline at end of file