恢复到没有加载效果的 除了效果别的还都是新的

This commit is contained in:
ylj20011123 2025-09-16 09:41:45 +08:00
parent 02b1425689
commit b07d192d20
14 changed files with 88 additions and 407 deletions

View File

@ -20,9 +20,9 @@ export default defineConfig({
hash: true, hash: true,
mock: false, mock: false,
antd: {}, antd: {},
// dva: { dva: {
// hmr: true hmr: true
// }, },
history: { history: {
type: REACT_APP_ENV === 'dev' ? "hash" : "memory", type: REACT_APP_ENV === 'dev' ? "hash" : "memory",
// type: "hash" // type: "hash"
@ -36,7 +36,7 @@ export default defineConfig({
baseNavigator: true, baseNavigator: true,
}, },
dynamicImport: { dynamicImport: {
loading: '@/components/SmartLoading/index', loading: '@/components/PageLoading/index',
}, },
targets: { targets: {
ie: 11, ie: 11,

View File

@ -7,7 +7,6 @@
* @Description: ,`customMade`, koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE * @Description: ,`customMade`, koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/ */
import globalState from './globalState'; import globalState from './globalState';
import { routePreloader } from './utils/routePreloader';
// import { getMicroAppRouteComponent } from 'umi'; // import { getMicroAppRouteComponent } from 'umi';
@ -30,22 +29,6 @@ export const qiankun = {
} }
// 应用启动时的初始化配置
export async function getInitialState() {
// 预加载关键路由以优化首屏加载
setTimeout(() => {
routePreloader.preloadCriticalRoutes().then(() => {
console.log('关键路由预加载完成');
}).catch(error => {
console.warn('关键路由预加载失败:', error);
});
}, 1000); // 延迟1秒后开始预加载避免影响首屏渲染
return {
preloadEnabled: true,
};
}
// export const patchRoutes = ({ routes }: any) => { // export const patchRoutes = ({ routes }: any) => {
// console.info('routes', routes); // console.info('routes', routes);
// routes[0].routes[1].routes[0].routes.forEach((item: any, index: number) => { // routes[0].routes[1].routes[0].routes.forEach((item: any, index: number) => {
@ -58,4 +41,4 @@ export async function getInitialState() {
// }))() // }))()
// } // }
// }); // });
// } // }

View File

@ -1,7 +0,0 @@
.page-loading-container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
width: 100%;
}

View File

@ -1,13 +1,5 @@
// import { PageLoading } from '@ant-design/pro-layout'; import { PageLoading } from '@ant-design/pro-layout';
// // loading components from code split // loading components from code split
// // https://umijs.org/plugin/umi-plugin-react.html#dynamicimport // https://umijs.org/plugin/umi-plugin-react.html#dynamicimport
// export default PageLoading; export default PageLoading;
import React from 'react';
import SkeletonLoading from '../SkeletonLoading';
const PageLoading: React.FC = () => {
return <SkeletonLoading type="page" />;
};
export default PageLoading;

View File

@ -3,7 +3,13 @@
// 默认不播放动画只有带animate类时才播放 // 默认不播放动画只有带animate类时才播放
&.animate { &.animate {
animation: pageEnter 0.5s cubic-bezier(0.4, 0, 0.2, 1); animation: pageEnter 0.5s cubic-bezier(0.4, 0, 0.2, 1);
will-change: opacity, transform; // 移除will-change避免HMR时样式冲突
// will-change: opacity, transform;
}
// 动画结束后重置will-change避免持续占用GPU资源
&.animate:not(:hover):not(:focus) {
animation-fill-mode: forwards;
} }
} }
@ -11,12 +17,12 @@
from { from {
opacity: 0; opacity: 0;
transform: translateY(30px) scale(0.96); transform: translateY(30px) scale(0.96);
filter: blur(4px); // filter: blur(4px); // 注释掉filter属性可能导致HMR问题
} }
to { to {
opacity: 1; opacity: 1;
transform: translateY(0) scale(1); transform: translateY(0) scale(1);
filter: blur(0); // filter: blur(0); // 注释掉filter属性
} }
} }

View File

@ -41,32 +41,37 @@ const PageTransition: React.FC<PageTransitionProps> = ({
); );
}; };
// 简化版本 - 仅使用CSS动画 // 简化版本 - 禁用所有动画
export const SimplePageTransition: React.FC<{ export const SimplePageTransition: React.FC<{
children: React.ReactNode; children: React.ReactNode;
className?: string; className?: string;
enableAnimation?: boolean; enableAnimation?: boolean;
onAnimationEnd?: () => void; onAnimationEnd?: () => void;
}> = ({ }> = ({
children, children,
className = '', className = '',
enableAnimation = true, enableAnimation = false, // 强制禁用动画
onAnimationEnd, onAnimationEnd,
}) => { }) => {
const handleAnimationEnd = () => { // 立即执行回调,不等待动画
if (enableAnimation && onAnimationEnd) { const handleAnimationEnd = () => {
onAnimationEnd(); if (onAnimationEnd) {
} onAnimationEnd();
}
};
// 立即执行动画结束回调
React.useEffect(() => {
if (onAnimationEnd) {
onAnimationEnd();
}
}, [onAnimationEnd]);
return (
<div className={`simple-page-transition ${className}`}>
{children}
</div>
);
}; };
return (
<div
className={`simple-page-transition ${enableAnimation ? 'animate' : ''} ${className}`}
onAnimationEnd={handleAnimationEnd}
>
{children}
</div>
);
};
export default PageTransition; export default PageTransition;

View File

@ -1,5 +1,4 @@
// @import '~antd/es/style/themes/default.less'; @import '~antd/es/style/themes/default.less';
@import '~antd/dist/antd.less';
html, html,
body, body,
@ -25,51 +24,6 @@ body {
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
// 骨架屏优化样式
.ant-skeleton {
.ant-skeleton-content {
.ant-skeleton-title,
.ant-skeleton-paragraph>li {
background: linear-gradient(90deg, #f2f2f2 25%, #e6e6e6 50%, #f2f2f2 75%);
background-size: 200% 100%;
animation: loading 1.4s ease-in-out infinite;
}
}
.ant-skeleton-avatar {
background: linear-gradient(90deg, #f2f2f2 25%, #e6e6e6 50%, #f2f2f2 75%);
background-size: 200% 100%;
animation: loading 1.4s ease-in-out infinite;
}
.ant-skeleton-input,
.ant-skeleton-button {
background: linear-gradient(90deg, #f2f2f2 25%, #e6e6e6 50%, #f2f2f2 75%);
background-size: 200% 100%;
animation: loading 1.4s ease-in-out infinite;
}
}
@keyframes loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
// 页面切换动画优化
.ant-layout-content {
transition: opacity 0.2s ease-in-out;
&.loading {
opacity: 0.7;
}
}
ul, ul,
ol { ol {
list-style: none; list-style: none;
@ -101,18 +55,4 @@ ol {
body .ant-design-pro>.ant-layout { body .ant-design-pro>.ant-layout {
min-height: 100vh; min-height: 100vh;
} }
}
// 页面过渡优化
* {
box-sizing: border-box;
}
// 增强页面切换体验
.ant-tabs-content-holder {
overflow: hidden;
}
.ant-tabs-tabpane {
outline: none;
} }

View File

@ -1,5 +1,4 @@
@import '~antd/es/style/themes/default.less'; @import '~antd/es/style/themes/default.less';
@import '../components/PageTransition/index.less';
.ant-pro-global-header { .ant-pro-global-header {
box-shadow: none !important; box-shadow: none !important;

View File

@ -28,9 +28,6 @@ import * as Icon from '@ant-design/icons'
import IconFont from '@/components/IconFont'; import IconFont from '@/components/IconFont';
import type { CurrentUser } from '@/models/user' import type { CurrentUser } from '@/models/user'
import session from '@/utils/session'; import session from '@/utils/session';
import { SimplePageTransition } from '@/components/PageTransition';
import TabVirtualizer from '@/components/TabVirtualizer';
import { tabPerformanceManager, DEFAULT_CONFIG } from '@/utils/tabPerformanceManager';
import upMenu from '../assets/tab/upMenu.png' import upMenu from '../assets/tab/upMenu.png'
import { getFieldEnum, getFieldEnumTravel, getFieldEnumTree, getFieldGetFieEnumList, getTravelFieldEnumTree, handleGetFieldEnumTreeTravel, handleGetNestingFIELDENUMList } from "@/services/options"; import { getFieldEnum, getFieldEnumTravel, getFieldEnumTree, getFieldGetFieEnumList, getTravelFieldEnumTree, handleGetFieldEnumTreeTravel, handleGetNestingFIELDENUMList } from "@/services/options";
import { handleGetServerpartTree } from '@/pages/basicManage/serverpartAssets/service'; import { handleGetServerpartTree } from '@/pages/basicManage/serverpartAssets/service';
@ -95,10 +92,7 @@ const BasicLayout: React.FC<BasicLayoutProps> = (props) => {
const [activeKey, setActiveKey] = useState<string>(location?.pathname || '/') const [activeKey, setActiveKey] = useState<string>(location?.pathname || '/')
const [animatedPages, setAnimatedPages] = useState<Set<string>>(new Set());
const [reloadingTabs, setReloadingTabs] = useState<Set<string>>(new Set());
const menuDataRef = useRef<MenuDataItem[]>([]); const menuDataRef = useRef<MenuDataItem[]>([]);
const checkTimerRef = useRef<NodeJS.Timeout | null>(null);
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
useEffect(() => { useEffect(() => {
@ -146,57 +140,13 @@ const BasicLayout: React.FC<BasicLayoutProps> = (props) => {
// 改变panes // 改变panes
const handleTabsPanes = (payload: any): void => { const handleTabsPanes = (payload: any): void => {
if (dispatch) { if (dispatch) {
// 检查是否为新页面(从未打开过的页面才需要动画)
const isNewPage = !tabsPanes.some(tab => tab.path === payload.path);
if (isNewPage) {
// 标记新页面需要播放动画
setAnimatedPages(prev => new Set(prev).add(payload.path));
}
// 更新访问时间
const updatedPayload = {
...payload,
lastAccessTime: Date.now(),
isLoaded: true,
isLoading: false,
};
dispatch({ dispatch({
type: 'global/changeTabsRoutes', type: 'global/changeTabsRoutes',
payload: { data: updatedPayload, action: 'add' }, payload: { data: payload, action: 'add' },
}); });
} }
}; };
// 处理标签页重新加载
const handleTabReload = (tabPath: string): void => {
if (dispatch) {
// 标记为加载中
setReloadingTabs(prev => new Set(prev).add(tabPath));
// 重新加载时也要播放动画
setAnimatedPages(prev => new Set(prev).add(tabPath));
dispatch({
type: 'global/reloadTab',
payload: { tabPath },
});
// 模拟加载过程
setTimeout(() => {
setReloadingTabs(prev => {
const newSet = new Set(prev);
newSet.delete(tabPath);
return newSet;
});
// 切换到该标签页
history.push(tabPath);
setActiveKey(tabPath);
}, 800); // 800ms的加载时间
}
};
// 关闭当前标签 // 关闭当前标签
const handleEdit = (targetKey: string | React.MouseEvent<Element, MouseEvent> | React.KeyboardEvent<Element>, action: "add" | "remove"): void => { const handleEdit = (targetKey: string | React.MouseEvent<Element, MouseEvent> | React.KeyboardEvent<Element>, action: "add" | "remove"): void => {
@ -210,13 +160,6 @@ const BasicLayout: React.FC<BasicLayoutProps> = (props) => {
history.push(nextkey || '/') history.push(nextkey || '/')
setActiveKey(nextkey) setActiveKey(nextkey)
// 从动画记录中移除关闭的页面
setAnimatedPages(prev => {
const newSet = new Set(prev);
newSet.delete(targetKey as string);
return newSet;
});
// 缓存路由栈数据 // 缓存路由栈数据
dispatch({ dispatch({
type: 'global/changeTabsRoutes', type: 'global/changeTabsRoutes',
@ -927,31 +870,6 @@ const BasicLayout: React.FC<BasicLayoutProps> = (props) => {
}) })
} }
// 启动标签页性能管理
useEffect(() => {
// 启动定时检查
checkTimerRef.current = setInterval(() => {
if (dispatch && tabsPanes.length > 0) {
const tabsToUnload = tabPerformanceManager.getTabsToUnload(tabsPanes, activeKey);
if (tabsToUnload.length > 0) {
// 批量卸载标签页
tabsToUnload.forEach(tabPath => {
dispatch({
type: 'global/unloadTab',
payload: { tabPath },
});
});
}
}
}, DEFAULT_CONFIG.checkIntervalMinutes * 60 * 1000);
return () => {
if (checkTimerRef.current) {
clearInterval(checkTimerRef.current);
}
};
}, [dispatch, tabsPanes, activeKey]);
// 显示就调用 // 显示就调用
useEffect(() => { useEffect(() => {
handleGetAllFieldEnum() handleGetAllFieldEnum()
@ -1064,14 +982,6 @@ const BasicLayout: React.FC<BasicLayoutProps> = (props) => {
onChange={(value) => { onChange={(value) => {
history.push(value) history.push(value)
setActiveKey(value) setActiveKey(value)
// 更新标签页访问时间
if (dispatch) {
dispatch({
type: 'global/updateTabAccessTime',
payload: { tabPath: value },
});
}
}} }}
activeKey={activeKey} activeKey={activeKey}
onEdit={handleEdit} onEdit={handleEdit}
@ -1096,13 +1006,6 @@ const BasicLayout: React.FC<BasicLayoutProps> = (props) => {
break; break;
} }
// 从动画记录中移除关闭的页面
setAnimatedPages(prev => {
const newSet = new Set(prev);
closeTabKeys.forEach(key => newSet.delete(key));
return newSet;
});
dispatch({ dispatch({
type: 'global/changeTabsRoutes', type: 'global/changeTabsRoutes',
payload: { data: closeTabKeys, action: 'remove' }, payload: { data: closeTabKeys, action: 'remove' },
@ -1132,53 +1035,17 @@ const BasicLayout: React.FC<BasicLayoutProps> = (props) => {
} }
moreIcon={<DoubleRightOutlined style={{ color: "#7b828c" }} />} moreIcon={<DoubleRightOutlined style={{ color: "#7b828c" }} />}
> >
{tabsPanes && tabsPanes.map((item: tabsRoute) => { {tabsPanes && tabsPanes.map((item: tabsRoute) =>
const shouldAnimate = animatedPages.has(item.path); <TabPane
const isActive = activeKey === item.path; tab={item.title} key={item?.path}
const isLoaded = item.isLoaded !== false; // 默认为已加载状态 style={{ padding: 24, paddingTop: 0 }}>
const isLoading = reloadingTabs.has(item.path) || item.isLoading === true; <Suspense fallback={<div>Loading...</div>}>
<Authorized authority={authorized!.authority} noMatch={noMatch}>
return ( {item.children}
<TabPane </Authorized>
tab={item.title} </Suspense>
key={item?.path} </TabPane>)
style={{ padding: 24, paddingTop: 0 }} }
>
{/* 如果页面未加载或正在加载显示TabVirtualizer */}
{(!isLoaded || isLoading) ? (
<TabVirtualizer
tabKey={item.path}
isActive={isActive}
isLoaded={isLoaded}
isLoading={isLoading}
lastAccessTime={item.lastAccessTime}
onReload={handleTabReload}
>
{/* 空内容TabVirtualizer会处理显示 */}
</TabVirtualizer>
) : (
/* 页面已加载,直接显示内容,根据需要播放动画 */
<SimplePageTransition
enableAnimation={shouldAnimate}
onAnimationEnd={() => {
// 动画播放完成后,移除动画标记
setAnimatedPages(prev => {
const newSet = new Set(prev);
newSet.delete(item.path);
return newSet;
});
}}
>
<Suspense fallback={null}>
<Authorized authority={authorized!.authority} noMatch={noMatch}>
{item.children}
</Authorized>
</Suspense>
</SimplePageTransition>
)}
</TabPane>
);
})}
</Tabs> </Tabs>
{/* 不要标签栏删除tabs的代码使用下方的代码就可以 */} {/* 不要标签栏删除tabs的代码使用下方的代码就可以 */}
{/* <Authorized authority={authorized!.authority} noMatch={noMatch}> {/* <Authorized authority={authorized!.authority} noMatch={noMatch}>

View File

@ -15,101 +15,68 @@ type SecurityLayoutProps = {
type SecurityLayoutState = { type SecurityLayoutState = {
isReady: boolean; isReady: boolean;
shouldShowLoading: boolean;
initialLoadComplete: boolean;
}; };
class SecurityLayout extends React.Component<SecurityLayoutProps, SecurityLayoutState> { class SecurityLayout extends React.Component<SecurityLayoutProps, SecurityLayoutState> {
state: SecurityLayoutState = { state: SecurityLayoutState = {
isReady: false, isReady: false,
shouldShowLoading: false,
initialLoadComplete: false,
}; };
private loadingTimer?: NodeJS.Timeout;
componentDidMount() { componentDidMount() {
const { location } = history;
const { dispatch } = this.props;
// 检查是否有缓存的用户信息,避免不必要的加载状态
const cachedUser = session.get("currentUser");
// 设置防闪烁定时器只有加载时间超过300ms才显示loading
this.loadingTimer = setTimeout(() => {
if (!this.state.isReady && !this.state.initialLoadComplete) {
this.setState({
shouldShowLoading: true,
});
}
}, 300);
const { location } = history
const { dispatch } = this.props;
if (dispatch) { if (dispatch) {
dispatch({ dispatch({
type: 'user/fetch', type: 'user/fetch', callback: (user) => {
callback: (user) => {
// 清除定时器
if (this.loadingTimer) {
clearTimeout(this.loadingTimer);
}
if (user.code && location.pathname !== '/user/login') { if (user.code && location.pathname !== '/user/login') {
history.push('/user/login'); history.push('/user/login');
return; return
} }
console.log('secur')
dispatch({ dispatch({
type: 'global/getMenuData', type: 'global/getMenuData', payload: user.ID, callback: (menu) => {
payload: user.ID,
callback: (menu) => {
if (menu) { if (menu) {
this.setState({ this.setState({
isReady: true, isReady: true,
shouldShowLoading: false,
initialLoadComplete: true,
}); });
} }
} }
}); })
} }
}); })
} else { } else {
// 清除定时器
if (this.loadingTimer) {
clearTimeout(this.loadingTimer);
}
this.setState({ this.setState({
isReady: true, isReady: true,
shouldShowLoading: false,
initialLoadComplete: true,
}); });
} }
}
componentWillUnmount() {
if (this.loadingTimer) {
clearTimeout(this.loadingTimer);
}
} }
render() { render() {
const { isReady, shouldShowLoading, initialLoadComplete } = this.state; const { isReady } = this.state;
const { children, currentUser } = this.props; const { children, loading, currentUser } = this.props;
// const { location } = history;
// 用户认证规则
// You can replace it to your authentication rule (such as check token exists)
// You can replace it with your own login authentication rules (such as judging whether the token exists)
const isLogin = currentUser && currentUser.ID; const isLogin = currentUser && currentUser.ID;
// 如果初始化未完成且需要显示加载状态,才显示骨架屏
if (!isReady && shouldShowLoading) { if ((!isLogin && loading) || !isReady) {
return <PageLoading />; return <PageLoading />;
} }
// if (!isLogin && location.pathname !== '/user/login') {
// 如果还在初始化过程中但不需要显示loading返回null避免闪烁 // history.push('/user/login');
if (!isReady && !shouldShowLoading) { // }
return null;
}
return children; return children;
} }
} }

View File

@ -61,7 +61,7 @@ const UserLayout: React.FC<UserLayoutProps> = (props) => {
</div> </div>
<div className={styles.content}> <div className={styles.content}>
{children} {children}
</div> </div>
<DefaultFooter <DefaultFooter
copyright={`Copyright © 2013-${new Date().getFullYear()} Eshang Cloud. All Rights Reserved. 驿商云 版权所有 V${VERSION || ''}`} copyright={`Copyright © 2013-${new Date().getFullYear()} Eshang Cloud. All Rights Reserved. 驿商云 版权所有 V${VERSION || ''}`}

View File

@ -20,9 +20,6 @@ export type tabsRoute = {
path: string; path: string;
key: string; key: string;
children: any; children: any;
lastAccessTime?: number; // 最后访问时间
isLoaded?: boolean; // 是否已加载
isLoading?: boolean; // 是否正在加载
} }
export type GlobalModelState = { export type GlobalModelState = {
@ -42,9 +39,6 @@ export type GlobalModelType = {
changeNoticeReadState: Effect; changeNoticeReadState: Effect;
changeTabsRoutes: Effect; changeTabsRoutes: Effect;
getMenuData: Effect; getMenuData: Effect;
updateTabAccessTime: Effect;
unloadTab: Effect;
reloadTab: Effect;
}; };
reducers: { reducers: {
changeLayoutCollapsed: Reducer<GlobalModelState>; changeLayoutCollapsed: Reducer<GlobalModelState>;
@ -185,26 +179,10 @@ const GlobalModel: GlobalModelType = {
const index = state.global.tabsRoutes.findIndex(n => n.path === data.path) const index = state.global.tabsRoutes.findIndex(n => n.path === data.path)
if (index === -1) { // 没缓存 则添加 if (index === -1) { // 没缓存 则添加
return [...state.global.tabsRoutes, { return [...state.global.tabsRoutes, { ...data, index: state.global.tabsRoutes.length }]
...data,
index: state.global.tabsRoutes.length,
lastAccessTime: data.lastAccessTime || Date.now(),
isLoaded: data.isLoaded !== false,
isLoading: data.isLoading || false,
}]
} }
// 否则更新现有标签页信息 // 否则不操作
return state.global.tabsRoutes.map(tab => return [...state.global.tabsRoutes]
tab.path === data.path
? {
...tab,
...data,
lastAccessTime: data.lastAccessTime || Date.now(),
isLoaded: data.isLoaded !== false,
isLoading: data.isLoading || false,
}
: tab
);
} }
if (payload.action === 'removeAll') { if (payload.action === 'removeAll') {
return [] return []
@ -221,57 +199,8 @@ const GlobalModel: GlobalModelType = {
type: 'saveTabsRoutes', type: 'saveTabsRoutes',
payload: tabsRoutes, payload: tabsRoutes,
}); });
},
// 更新标签页访问时间
* updateTabAccessTime({ payload }, { put, select }) {
const { tabPath } = payload;
const tabsRoutes: tabsRoute[] = yield select((state: ConnectState) =>
state.global.tabsRoutes.map(tab =>
tab.path === tabPath
? { ...tab, lastAccessTime: Date.now(), isLoaded: true, isLoading: false }
: tab
)
);
yield put({
type: 'saveTabsRoutes',
payload: tabsRoutes,
});
},
// 卸载标签页
* unloadTab({ payload }, { put, select }) {
const { tabPath } = payload;
const tabsRoutes: tabsRoute[] = yield select((state: ConnectState) =>
state.global.tabsRoutes.map(tab =>
tab.path === tabPath
? { ...tab, isLoaded: false, isLoading: false }
: tab
)
);
yield put({
type: 'saveTabsRoutes',
payload: tabsRoutes,
});
},
// 重新加载标签页
* reloadTab({ payload }, { put, select }) {
const { tabPath } = payload;
const tabsRoutes: tabsRoute[] = yield select((state: ConnectState) =>
state.global.tabsRoutes.map(tab =>
tab.path === tabPath
? { ...tab, isLoaded: true, isLoading: false, lastAccessTime: Date.now() }
: tab
)
);
yield put({
type: 'saveTabsRoutes',
payload: tabsRoutes,
});
} }
}, },

View File

@ -221,9 +221,9 @@ const shopProcurement: React.FC<{ currentUser: CurrentUser }> = (props) => {
// ellipsis: true, // ellipsis: true,
// }, // },
{ {
title: <div style={{ textAlign: 'center' }}></div>, title: <div style={{ textAlign: 'center' }}></div>,
dataIndex: "RECEIVE_TOTALCOUNT", dataIndex: "RECEIVE_TOTALCOUNT",
align: 'right', align: 'right',
valueType: 'digit', valueType: 'digit',
width: 120, width: 120,
sorter: true, sorter: true,

View File

@ -53,7 +53,7 @@ export async function accountLogin(params: LoginParamsType): Promise<LoginResult
// }); // });
// return resp; // return resp;
} }
// export async function getFakeCaptcha(mobile: string) { // export async function getFakeCaptcha(mobile: string) {
// return request(`/api/login/captcha?mobile=${mobile}`); // return request(`/api/login/captcha?mobile=${mobile}`);
// } // }