提交 216afbe3 作者: ZhangLingKun

功能:用户登录

上级 3ab26dad
流水线 #7350 已失败 于阶段
in 4 分 18 秒
import { InterFunction } from '@/api/interface';
// 获取二维码
export type GetAppletQRCode = InterFunction<
// 入参
{
page: string;
scene: string;
},
// 出参
string
>;
// 查询登录信息
export type GetLoginInfo = InterFunction<
{
randomLoginCode: string;
},
{
token: string;
openId: string;
userAccountId: number;
accountNo: null;
portType: number;
uid: string;
phoneNum: string;
userName: string;
nickName: string;
companyInfoVO: null;
roleInfo: null;
appUserAccountId: null;
}
>;
// 获取用户信息
export type GetAccountInfo = InterFunction<
{},
{
accountStatus: number;
accountType: number;
companyAuthStatus: number;
email: string;
id: number;
nickName: string;
phoneNum: string;
portType: number;
source: number;
uid: string;
userImg: string;
userName: string;
userSex: number;
realNameAuthStatus: number;
auditStatus: number;
totalPoints: number;
xzAuthStatus: number;
cooperationTagVOS: {
createTime: string;
id: number;
tagDescription: string;
tagImg: string;
tagName: string;
tagRequire: string;
}[];
coverPicture: string;
districtChildId: number;
region: string;
briefIntroduction: string;
companyInfoVO: {
address: string;
backImg: string;
backUserAccountId: number;
brandLogo: string;
brandName: string;
companyName: string;
companyType: number;
companyUserName: string;
content: string;
creditCode: string;
distance: number;
fullName: string;
id: number;
lat: number;
leader: number;
licenseImg: string;
lon: number;
phoneNum: number;
remark: string;
score: string;
userAccountId: number;
};
createTime: string;
}
>;
import {
GetAccountInfo,
GetAppletQRCode,
GetLoginInfo,
} from '@/api/interface/common';
import request from '../request';
export class CommonAPI {
......@@ -8,4 +13,16 @@ export class CommonAPI {
// 测试接口
static cooperationListTag: any = (params: any) =>
request.get('/userapp/cooperation/listTag', { params });
// 获取二维码
static getAppletQRCode: GetAppletQRCode = (params) =>
request.get('/userapp/wx/getAppletQRCode', { params });
// 查询登录信息
static getLoginInfo: GetLoginInfo = (params) =>
request.get('/userapp/temp-auth/getLoginInfo', { params });
// 获取用户信息
static getAccountInfo: GetAccountInfo = (params) =>
request.get('/userapp/user-account/info', { params });
}
......@@ -44,7 +44,7 @@ service.interceptors.response.use(
) {
message.error(data.message).then();
Cookies.remove('SHAREFLY-TOKEN');
localStorage.removeItem('roleId');
localStorage.removeItem('persist:SHAREFLY-WEB-STORAGE');
setTimeout(() => {
window.location.reload();
}, 1000);
......
import React, { useEffect } from 'react';
import { EnvironmentFilled } from '@ant-design/icons';
import { Button } from 'antd';
import { EnvironmentFilled, LogoutOutlined } from '@ant-design/icons';
import { Button, Dropdown, MenuProps, Modal } from 'antd';
import dayjs from 'dayjs';
import Cookies from 'js-cookie';
import { useRouter } from 'next/router';
import { useDispatch, useSelector } from 'react-redux';
import { CommonAPI } from '@/api';
import { HeaderWrap } from '@/components/layout/header/styled';
import { RootState } from '@/store';
import { AddressState, setAddress } from '@/store/address';
import { AddressState, setAddress } from '@/store/module/address';
import { setGlobalData } from '@/store/module/globalData';
import { setSystem, SystemState } from '@/store/module/system';
import { setUserInfo, UserInfoState } from '@/store/module/userInfo';
import getLocationByIP from '@/utils/getLocationByIP';
const HeaderView: React.FC<{
......@@ -18,18 +24,85 @@ const HeaderView: React.FC<{
const router = useRouter();
// store
const dispatch = useDispatch();
// system
const system = useSelector((state: RootState) => state.system) as SystemState;
// address
const address = useSelector(
(state: RootState) => state.address,
) as AddressState;
// userInfo
const userInfo = useSelector(
(state: RootState) => state.userInfo,
) as UserInfoState;
// 打开登录弹窗
const handleLogin = () => {
dispatch(
setGlobalData({
loginModalVisible: true,
}),
);
};
// 获取用户信息
const getAccountInfo = async () => {
const res = await CommonAPI.getAccountInfo();
if (res && res.code === '200') {
dispatch(setUserInfo(res.result));
}
};
// 计算天数与当前时间的差值
const getDiffDay = (date: string) => dayjs().diff(dayjs(date), 'day');
// 组件挂载
useEffect(() => {
// console.log('HeaderView --->', router);
// 获取当前地址
getLocationByIP().then((res) => {
dispatch(setAddress(res));
});
console.log('address --->', address);
}, [router]);
}, []);
// 当前是否登录
useEffect(() => {
if (system?.token) {
getAccountInfo().then();
}
}, [system?.token]);
// 右上角按钮
const items: MenuProps['items'] = [
{
key: '1',
label: (
<div style={{ textAlign: 'left', marginBottom: '20px' }}>
<div style={{ fontWeight: 'bold', marginBottom: '4px' }}>
用户信息:
</div>
<div>昵称:{userInfo?.nickName}</div>
<div>手机号:{userInfo?.phoneNum}</div>
<div>加入云享飞 {getDiffDay(userInfo?.createTime)}</div>
</div>
),
},
{
key: '0',
label: (
<Button
style={{ color: '#666666' }}
type="link"
icon={<LogoutOutlined />}
onClick={() => {
Modal.confirm({
title: '退出登录',
content: '退出后未保存数据将会丢失,确定登出吗?',
onOk: () => {
dispatch(setUserInfo(null));
dispatch(setSystem({ token: undefined }));
Cookies.remove('SHAREFLY-TOKEN');
},
});
}}
>
退出登录
</Button>
),
},
];
return (
<HeaderWrap>
<div className="header-wrap">
......@@ -62,10 +135,31 @@ const HeaderView: React.FC<{
联系客服
</Button>
</div>
{!!userInfo && (
<div className="nav-user">
<Dropdown
overlayStyle={{ textAlign: 'center' }}
menu={{ items }}
placement="bottomRight"
arrow
>
<div
className="user-avatar"
style={{ backgroundImage: `url(${userInfo?.userImg})` }}
></div>
</Dropdown>
</div>
)}
<div className="nav-action">
<Button type={'primary'} className="action-item">
登录
</Button>
{!userInfo && (
<Button
type={'primary'}
className="action-item"
onClick={handleLogin}
>
登录
</Button>
)}
<Button type={'primary'} className="action-item">
发布需求
</Button>
......
......@@ -50,6 +50,7 @@ export const HeaderWrap = styled.div`
justify-content: flex-end;
}
.nav-tab {
margin-right: 0.5rem;
.tab-item {
//font-size: 0.67rem;
font-weight: 500;
......@@ -62,11 +63,26 @@ export const HeaderWrap = styled.div`
}
}
.nav-action {
margin-left: 1rem;
margin-left: 0.5rem;
.action-item {
margin-left: 0.33rem;
}
}
.nav-user {
.user-avatar {
width: 2rem;
height: 2rem;
border-radius: 50%;
//background: #cdcdcd;
border: 1px solid #ffffff;
background-size: cover;
background-position: center;
&:active,
&:hover {
filter: brightness(0.9);
}
}
}
}
// 媒体查询
@media screen and (min-width: 1600px) {
......
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import ContentView from '@/components/layout/content';
import FooterView from '@/components/layout/footer';
import HeaderView from '@/components/layout/header';
import { LayoutWrap } from '@/components/layout/styled';
import LoginModalView from '@/components/loginModal';
import QrcodeModalView from '@/components/qrcodeModal';
import { RootState } from '@/store';
import { GlobalDataState, setGlobalData } from '@/store/module/globalData';
const LayoutView = ({ children }: { children?: React.ReactNode }) => {
// 打开二维码弹窗
const [qrcodeShow, setQrcodeShow] = useState<boolean>(false);
// store
const dispatch = useDispatch();
// system
const globalData = useSelector(
(state: RootState) => state.globalData,
) as GlobalDataState;
// 关闭弹窗
const handleClose = () => {
dispatch(
setGlobalData({
loginModalVisible: false,
}),
);
};
return (
<div className={'animate__animated animate__faster animate__fadeIn'}>
<LayoutWrap>
<div
onClick={() => {
setQrcodeShow(!qrcodeShow);
// setQrcodeShow(!qrcodeShow);
}}
>
<HeaderView></HeaderView>
<ContentView>{children}</ContentView>
<FooterView></FooterView>
</div>
{/* 登录弹窗 */}
<LoginModalView
open={globalData?.loginModalVisible}
onCancel={handleClose}
/>
{/* 功能正在完善中 */}
<QrcodeModalView
open={qrcodeShow}
onCancel={() => setQrcodeShow(false)}
......
import React, { useEffect, useState } from 'react';
import { ReloadOutlined } from '@ant-design/icons';
import { message, Modal } from 'antd';
import Cookies from 'js-cookie';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
import { CommonAPI } from '@/api';
import { setSystem } from '@/store/module/system';
import { setUserInfo } from '@/store/module/userInfo';
export const LoginModalWrap = styled.div`
position: relative;
width: 100%;
display: flex;
align-items: center;
justify-content: flex-start;
flex-direction: column;
box-sizing: border-box;
padding: 0.68rem 0;
.qrcode {
position: relative;
width: 10.68rem;
height: 10.68rem;
margin: 1.5rem 0 1rem 0;
//background-image: url('https://file.iuav.com/file/sharefly-qrcode-wx.jpg');
//background-size: 100% 100%;
//background-size: cover;
//background-position: center;
.image {
width: 100%;
height: 100%;
object-fit: cover;
}
.shadow {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
flex-direction: column;
background: rgba(0, 0, 0, 0.68);
.refresh {
position: relative;
width: 3rem;
height: 3rem;
border-radius: 50%;
background: #fff;
margin-bottom: 1rem;
&:active {
filter: brightness(0.9);
transform: rotate(360deg);
transition: all 0.3s ease-in-out;
}
}
.text {
color: #fff;
}
}
}
.title {
color: #222;
font-size: 16px;
font-weight: bold;
}
.text {
color: #333;
font-size: 12px;
line-height: 20px;
text-align: center;
}
.action {
color: #999;
font-size: 12px;
line-height: 20px;
text-align: center;
margin: 1rem 0;
text-decoration: underline;
cursor: pointer;
&:hover,
&:active {
color: #ff552d;
}
}
`;
// 定时器暂存
let timer: NodeJS.Timer;
const LoginModalView = ({
open,
onCancel,
}: {
open: boolean;
onCancel: () => void;
}) => {
// store
const dispatch = useDispatch();
// 获取小程序二维码唯一标识
const [randomLoginCode, setRandomLoginCode] = useState<string>(
`${parseInt(String(Math.random() * 10001), 10)}`,
);
// 二维码是否过期
const [qrCodeExpired, setQrCodeExpired] = useState<boolean>(false);
// 登录二维码的地址
const [qrCodeData, setQrCodeData] = useState<string>();
// 获取登录二维码
const getQrcodeLogin = async () => {
// 获取二维码
const res = await CommonAPI.getAppletQRCode({
page: 'page-identity/identity-empower/index',
scene: `randomLoginCode=${randomLoginCode}&port=0`,
});
if (res && res.code === '200') {
if (!res.result) {
message.warning('获取登录二维码失败');
return;
}
// 设置当前登录的二维码
setQrCodeData(`data:image/png;base64,${res.result}`);
// 设置二维码倒计时
setQrCodeExpiredTimer();
}
};
// 跳转管理平台
const handleBackEnd = () => {
window.open('https://admin.iuav.com');
};
// 刷新二维码
const handleRefresh = () => {
setRandomLoginCode(`${parseInt(String(Math.random() * 10001), 10)}`);
getQrcodeLogin().then(() => {
setQrCodeExpired(false);
});
};
// 定时器设置二维码过期
const setQrCodeExpiredTimer = () => {
setTimeout(
() => {
setQrCodeExpired(true);
// 关闭定时器
if (timer) clearInterval(timer);
},
1000 * 60 * 5,
);
};
// 获取登录信息
const getLoginInfo = async () => {
if (!randomLoginCode) return;
const res = await CommonAPI.getLoginInfo({
randomLoginCode,
});
// console.log('获取登录信息 --->', res);
if (res && res.code === '200') {
// 关闭定时器
if (timer) clearInterval(timer);
// 刷新二维码
setQrCodeExpired(true);
// 关闭弹窗
onCancel?.();
// 设置用户信息
dispatch(setUserInfo(res.result));
// 设置token
dispatch(setSystem({ token: res.result.token }));
// 设置token
Cookies.set('SHAREFLY-TOKEN', res.result.token);
message.success('登录成功');
}
};
// 设置定时器轮询获取信息
const setLoginInfoTimer = () => {
timer = setInterval(async () => {
await getLoginInfo();
}, 5000);
};
// 关闭弹窗
const handleClose = () => {
onCancel?.();
// 关闭定时器
if (timer) clearInterval(timer);
};
// 组件挂载
useEffect(() => {
if (!open) {
// 关闭定时器
if (timer) clearInterval(timer);
return;
}
// 获取二维码
getQrcodeLogin().then(() => {
setQrCodeExpired(false);
setLoginInfoTimer();
});
}, [open]);
return (
<Modal open={open} footer={null} onCancel={handleClose}>
<LoginModalWrap>
<div className="title">微信扫码登录</div>
<div className="qrcode">
{qrCodeData && (
<img src={qrCodeData} alt="登录二维码" className="image" />
)}
{qrCodeExpired && (
<div className="shadow flex-center animate__animated animate__fast animate__fadeIn">
<div className="refresh flex-center" onClick={handleRefresh}>
<ReloadOutlined style={{ fontSize: '20px' }} />
</div>
<div className="text">二维码已过期,请重新扫描</div>
</div>
)}
</div>
<div className="text">微信扫一扫,打开小程序</div>
<div className="text">即可登录/注册</div>
<div className="action" onClick={handleBackEnd}>
管理平台登录 {'>'}
</div>
<div className="text">「云享飞,让天空为世界所用」</div>
</LoginModalWrap>
</Modal>
);
};
export default LoginModalView;
......@@ -7,6 +7,7 @@ import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { wrapper } from '@/store';
import themeConfig from '../theme/themeConfig';
import zhCN from 'antd/locale/zh_CN';
const App = ({ Component, pageProps, ...rest }: AppProps) => {
const { store } = wrapper.useWrappedStore(rest);
......@@ -16,6 +17,7 @@ const App = ({ Component, pageProps, ...rest }: AppProps) => {
{/* eslint-disable-next-line no-underscore-dangle */}
<PersistGate persistor={(store as any)?.__persistor} loading={null}>
<ConfigProvider
locale={zhCN}
theme={{
algorithm: theme.compactAlgorithm,
...themeConfig,
......
......@@ -4,7 +4,7 @@ import styled from 'styled-components';
import { HomeAPI } from '@/api';
import MapContainer from '@/components/map';
import { RootState } from '@/store';
import { AddressState } from '@/store/address';
import { AddressState } from '@/store/module/address';
const HomeMapView = () => {
// 选择索引
......
// index.ts
import { combineReducers, configureStore } from '@reduxjs/toolkit';
import {
combineReducers,
configureStore,
getDefaultMiddleware,
} from '@reduxjs/toolkit';
import { createWrapper } from 'next-redux-wrapper';
import { persistReducer, persistStore } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import addressReducer from './address';
import userReducer from './user';
import addressReducer from './module/address';
import globalDataReducer from './module/globalData';
import systemReducer from './module/system';
import userInfoReducer from './module/userInfo';
// 单独创建rootReducer供服务端和客户端创建store使用;
const rootReducer = combineReducers({
user: userReducer,
userInfo: userInfoReducer,
address: addressReducer,
system: systemReducer,
globalData: globalDataReducer,
});
const makeStore = () => {
......@@ -23,13 +31,17 @@ const makeStore = () => {
}
const persistConfig = {
key: 'SHAREFLY-WEB-STORAGE',
whiteList: ['user', 'address'],
whiteList: ['userInfo', 'address', 'system'],
blacklist: ['globalData'],
storage,
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = configureStore({
reducer: persistedReducer,
devTools: process.env.NODE_ENV !== 'production',
middleware: getDefaultMiddleware({
serializableCheck: false, // 添加这一行
}),
});
// @ts-ignore 只使用客户端渲染不需要此种做法,只需导出persistor即可;
// eslint-disable-next-line no-underscore-dangle
......
// user.ts
import { createSlice } from '@reduxjs/toolkit';
import { HYDRATE } from 'next-redux-wrapper';
export type GlobalDataState = {
loginModalVisible: boolean;
};
const initialState: GlobalDataState = {
loginModalVisible: false,
};
const globalDataSlice = createSlice({
name: 'globalData',
initialState,
reducers: {
setGlobalData: (state, action) => {
return action.payload;
},
},
extraReducers: {
// hydrated 用于获取服务端注入的state并选择更新
[HYDRATE]: (state, action) => {
return {
...action.payload.globalData,
};
},
},
});
export const { setGlobalData } = globalDataSlice.actions;
export default globalDataSlice.reducer;
// user.ts
import { createSlice } from '@reduxjs/toolkit';
import { HYDRATE } from 'next-redux-wrapper';
export type SystemState = {
token?: string;
};
const initialState: SystemState = {
token: undefined,
};
const systemSlice = createSlice({
name: 'system',
initialState,
reducers: {
setSystem: (state, action) => {
return action.payload;
},
},
extraReducers: {
// hydrated 用于获取服务端注入的state并选择更新
[HYDRATE]: (state, action) => {
return {
...action.payload.system,
};
},
},
});
export const { setSystem } = systemSlice.actions;
export default systemSlice.reducer;
// user.ts
import { createSlice } from '@reduxjs/toolkit';
import { HYDRATE } from 'next-redux-wrapper';
import { InterDataType } from '@/api/interface';
import { GetAccountInfo } from '@/api/interface/common';
// @ts-ignore
export type UserState = API.User.UserInfo;
export type UserInfoState = InterDataType<GetAccountInfo>;
const initialState: UserState = {
username: '', // 示例
avatar: '',
};
const initialState: UserInfoState | {} = {};
const userSlice = createSlice({
name: 'user',
const userInfoSlice = createSlice({
name: 'userInfo',
initialState,
reducers: {
setUser: (state, action) => {
setUserInfo: (state, action) => {
return action.payload;
},
},
......@@ -28,5 +26,5 @@ const userSlice = createSlice({
},
});
export const { setUser } = userSlice.actions;
export default userSlice.reducer;
export const { setUserInfo } = userInfoSlice.actions;
export default userInfoSlice.reducer;
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论