Browse Source

登录/退出登录接口与相关状态

feature/rail
zhangjiming 5 months ago
parent
commit
4c41afd9a6
  1. 34
      src/App.tsx
  2. 1
      src/assets/icon_logout.svg
  3. 50
      src/components/Header.tsx
  4. 38
      src/pages/login/Login.tsx
  5. 20
      src/services/httpRequest.ts
  6. 17
      src/services/user/user.ts
  7. 20
      src/services/wsTypes.ts
  8. 28
      src/store/features/contextSlice.ts
  9. 2
      src/store/index.ts

34
src/App.tsx

@ -1,21 +1,37 @@
import React from "react";
import React, { useEffect } from "react";
import "./App.scss";
import { Outlet } from "react-router";
import { Outlet, useNavigate } from "react-router";
import { Layout } from "antd";
import { default as AppHeader } from "./components/Header";
import { default as AppFooter } from "./components/Footer";
import SideMenu from "./components/SideMenu";
import { createWebSocket, sharedWsUrl } from "./services/socket";
import { useDispatch } from "react-redux";
import { updateUser } from "./store/features/contextSlice";
const { Header, Footer, Sider, Content } = Layout;
function App() {
console.log('app-------init--------')
//连接websocket
const wsClient = createWebSocket(sharedWsUrl);
wsClient.dataOb.subscribe(data => {
console.log('data---', data)
})
const navigate = useNavigate();
const dispatch = useDispatch();
useEffect(() => {
//连接websocket
const wsClient = createWebSocket(sharedWsUrl);
const subscription = wsClient.dataOb.subscribe(data => {
console.log("data---", data);
if (data.messageType === "DeviceContext") {
if (data.data.loginFlag) {
dispatch(updateUser(data.data));
navigate("/measure/config");
} else {
navigate("/login");
}
}
});
wsClient.connect();
return () => subscription.unsubscribe();
});
const headerStyle: React.CSSProperties = {
height: 64,
@ -31,8 +47,6 @@ function App() {
overflow: "hidden",
};
return (
<div className="">
<Layout style={layoutStyle}>

1
src/assets/icon_logout.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="30" height="26" viewBox="0 0 30 26"><g><path d="M17.0891,17.9292L16.8155,17.9292C13.9556,17.9292,11.0959,17.9295,8.23599,17.9292C7.81201,17.929,7.53569,17.7222,7.46756,17.3522C7.45417,17.2783,7.45542,17.2007,7.45542,17.125C7.4549,14.4201,7.45484,11.7153,7.45507,9.01044C7.45516,8.4974,7.71619,8.22446,8.20573,8.22446C11.0785,8.22431,13.9513,8.22431,16.8243,8.22431L17.0892,8.22431L17.0892,7.97808C17.0892,5.57076,17.0889,3.16327,17.0895,0.755738C17.0895,0.42445,17.2378,0.160228,17.4892,0.0590212C17.7784,-0.0575289,18.0349,-0.00384412,18.2529,0.2413C18.3779,0.381876,18.5141,0.511186,18.6424,0.647902C20.2619,2.36783,21.8811,4.08854,23.5001,5.80847C25.5796,8.01701,27.6593,10.225,29.7389,12.4334C30.0851,12.8007,30.087,13.1882,29.7449,13.5515C26.7688,16.7136,23.7931,19.876,20.8161,23.0369C19.9695,23.9358,19.1183,24.8293,18.2724,25.729C18.0547,25.9609,17.8118,26.0625,17.5146,25.9424C17.2256,25.8257,17.0895,25.5835,17.0895,25.2028C17.089,22.8716,17.0892,20.5401,17.0892,18.209L17.0892,17.9292L17.0891,17.9292ZM11.7433,0.135609L11.7433,2.86892L11.5073,2.86892C9.32992,2.86892,7.15256,2.86861,4.97522,2.86907C3.59546,2.86929,2.57943,3.95294,2.57937,5.42437C2.57916,10.5159,2.58793,15.6074,2.57361,20.6985C2.56966,22.0456,3.53254,23.0161,4.42275,23.1934C4.61945,23.2321,4.82267,23.2497,5.02291,23.2502C7.18074,23.2542,9.33867,23.2527,11.4966,23.2527L11.7393,23.2527L11.7393,25.9703C11.7086,25.9769,11.6728,25.9915,11.6369,25.9915C9.34253,25.9915,7.0479,26.0164,4.7541,25.9809C2.51226,25.9467,0.527001,24.1359,0.0991586,21.7921C0.0387777,21.4619,0.00924517,21.1201,0.0088943,20.7836C0.00333867,15.6366,-0.00943928,10.4898,0.0118768,5.3432C0.020795,3.19342,1.02432,1.65017,2.77683,0.647215C3.39511,0.293208,4.07807,0.133337,4.78203,0.131469C7.05682,0.124934,9.33165,0.12898,11.6065,0.129135C11.645,0.129166,11.6833,0.132745,11.7433,0.135609Z" fill="#3165D2" fill-opacity="1"/></g></svg>

50
src/components/Header.tsx

@ -1,11 +1,49 @@
import { useSelector } from "react-redux";
import icon_avatar from "../assets/icon_avatar.svg";
import icon_logout from "../assets/icon_logout.svg";
import icon_pwd from "../assets/icon_pwd.svg";
import { Dropdown, MenuProps, message } from "antd";
import { logout } from "../services/user/user";
export default function Header() {
//@ts-ignore
const context = useSelector(store => store.context);
const [messageApi, contextHolder] = message.useMessage();
const items: MenuProps["items"] = [
// {
// key: "1",
// label: "修改密码",
// icon: <img src={icon_pwd} alt="" className="w-[18px]" />,
// onClick: () => {
// messageApi.info('ok')
// },
// },
{
key: "2",
label: "退出登录",
icon: <img src={icon_logout} alt="icon" className="w-5" />,
onClick: () => {
logout({}).then(res => {
if (res.status !== 0) {
messageApi.error(res.data.info)
}
})
}
},
];
return (
<div className="bg-[--bgColor] h-full flex items-center">
<section className="ml-auto mr-8 flex items-center">
<img src={icon_avatar} alt="" className="h-8" />
<p className="text-base ml-2">Admin</p>
</section>
</div>
<>
{contextHolder}
<div className="bg-[--bgColor] h-full flex items-center">
<Dropdown menu={{ items }} trigger={["click"]}>
<section className="ml-auto mr-8 flex items-center">
<img src={icon_avatar} alt="" className="h-8" />
<p className="text-base ml-2">{context.loginFlag ? context.loginUser.nickname : "未登录"}</p>
</section>
</Dropdown>
</div>
</>
);
}

38
src/pages/login/Login.tsx

@ -1,22 +1,34 @@
import { Button, Form, Input } from "antd";
import { Button, Form, Input, message } from "antd";
import icon_user from "../../assets/icon_user.svg";
import icon_pwd from "../../assets/icon_pwd.svg";
import img_bg from "../../assets/img_bg.jpg";
import img_logo from "../../assets/icon_logo.png";
import { useNavigate } from "react-router";
import { login } from "../../services/user/user";
export default function Login() {
const navigate = useNavigate();
const navigate = useNavigate();
const [messageApi, contextHolder] = message.useMessage();
const onFinish = (values: any) => {
console.log("Received values of form: ", values);
navigate('/home');
login({ account: values["username"], password: values["password"] }).then(res => {
if (res.status !== 0) {
messageApi.open({
type: "error",
content: res.data.info,
});
} else {
navigate("/measure/config");
}
});
};
return (
<div
className="bg-cover bg-center flex justify-center items-center h-[100vh]"
style={{ backgroundImage: `url(${img_bg})` }}>
{contextHolder}
<div
className="flex justify-center items-center px-[40px] pt-[28px] rounded-lg text-text text-lg"
style={{
@ -25,13 +37,23 @@ export default function Login() {
}}>
<Form style={{ maxWidth: 360 }} onFinish={onFinish}>
<img src={img_logo} alt="" className="h-[40px] mx-auto mb-[28px]" />
<label className="text-text text-lg mb-2 block"></label>
<label className="text-text text-lg mb-2 block"></label>
<Form.Item name="username" rules={[{ required: true, message: "请输入用户名!" }]}>
<Input size="large" prefix={<img src={icon_user} alt="" className="w-4" />} placeholder="用户名" className="w-[280px]" />
<Input
size="large"
prefix={<img src={icon_user} alt="" className="w-4" />}
placeholder="用户名"
className="w-[280px]"
/>
</Form.Item>
<label className="text-text text-lg mb-2 block"></label>
<label className="text-text text-lg mb-2 block"></label>
<Form.Item name="password" rules={[{ required: true, message: "请输入密码!" }]}>
<Input size="large" prefix={<img src={icon_pwd} alt="" className="w-4" />} type="password" placeholder="密码" />
<Input
size="large"
prefix={<img src={icon_pwd} alt="" className="w-4" />}
type="password"
placeholder="密码"
/>
</Form.Item>
<Form.Item>
@ -39,7 +61,7 @@ export default function Login() {
</Button>
</Form.Item>
<p className="text-[#8799AB] text-center mt-[40px] mb-[16px]">CHINA LOGISTICS</p>
<p className="text-[#8799AB] text-center mt-[40px] mb-[16px]">CHINA LOGISTICS</p>
</Form>
</div>
</div>

20
src/services/httpRequest.ts

@ -1,10 +1,8 @@
import { Subject } from "rxjs";
export interface BaseResponse<T = unknown> {
success: boolean;
code: string;
msg: string;
export interface BaseResponse<T = any> {
data: T;
status: number;
}
type HttpReqParam = {
@ -20,16 +18,6 @@ export type ApiException = "invalidToken" | "serverError";
const exceptionSub = new Subject<ApiException>();
export const exceptionOb = exceptionSub.asObservable();
function extHandle(res: BaseResponse) {
if (res.code === "A0230") {
// 访问令牌无效或已过期
exceptionSub.next("invalidToken");
}
return {
...res,
success: res.code === "00000",
};
}
export default async function httpRequest<T>({ url, method = "GET", params = {}, encode = "json", headers = {} }: HttpReqParam) {
const token = sessionStorage.getItem("token");
@ -40,7 +28,7 @@ export default async function httpRequest<T>({ url, method = "GET", params = {},
const query = urlEncode(params);
const _url = query ? url + "?" + query : url;
const res = await fetch(_url, { headers });
return res.json().then(res => extHandle(res) as T);
return res.json() as T;
} else {
const body = encode === "json" ? JSON.stringify(params) : urlEncode(params);
const _headers =
@ -48,7 +36,7 @@ export default async function httpRequest<T>({ url, method = "GET", params = {},
? { "Content-Type": "application/json; charset=utf-8", ...headers }
: { "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", ...headers };
const res = await fetch(url, { method, headers: _headers, body });
return res.json().then(res => extHandle(res) as T);
return res.json() as T;
}
}
export function urlEncode(params?: Record<string, any>) {

17
src/services/user/user.ts

@ -0,0 +1,17 @@
import httpRequest, { BaseResponse } from "../httpRequest";
export function login(params: { account: string; password: string }) {
return httpRequest<BaseResponse>({
url: "/auth/login",
params,
method: "POST",
});
}
export function logout(params: {}) {
return httpRequest<BaseResponse>({
url: "/auth/logout",
params,
method: "POST",
});
}

20
src/services/wsTypes.ts

@ -1,4 +1,4 @@
// 开始、停止绘制
// 开始、停止绘制
export type TaskState = {
messageType: "EVENT";
data: {
@ -17,4 +17,20 @@ export type TrackRecordSig = {
path: "/measurement-task/profile-record-ctrl-sig";
};
export type Datagram = TrackRecordSig | TaskState;
export type ContextMessage = {
messageType: "DeviceContext";
data: {
loginFlag: Boolean;
loginUser: Partial<{
id: number;
account: string;
nickname: string;
password: string;
userRole: "Admin" | "User" | "Dev";
isBuiltInUser: boolean;
}>;
};
path: "/deviceContext";
};
export type Datagram = TrackRecordSig | TaskState | ContextMessage;

28
src/store/features/contextSlice.ts

@ -0,0 +1,28 @@
// counterSlice.ts 文件
import { createSlice } from "@reduxjs/toolkit";
import { ContextMessage } from "../../services/wsTypes";
const initialState: ContextMessage["data"] = {
loginFlag: false,
loginUser: {},
};
// 创建一个 Slice
export const contextSlice = createSlice({
name: "context",
initialState,
// 定义 reducers 并生成关联的操作
reducers: {
// 定义一个加的方法
updateUser: (state, { payload }) => {
state.loginFlag = payload.loginFlag;
state.loginUser = payload.loginUser;
},
},
});
// 导出加减的方法
export const { updateUser } = contextSlice.actions;
// 默认导出
export default contextSlice.reducer;

2
src/store/index.ts

@ -2,12 +2,14 @@
import { configureStore } from "@reduxjs/toolkit";
import counterSlice from "./features/counterSlice";
import contextSlice from "./features/contextSlice";
// configureStore创建一个redux数据
const store = configureStore({
// 合并多个Slice
reducer: {
counter: counterSlice,
context: contextSlice,
},
});

Loading…
Cancel
Save