update api

This commit is contained in:
kae 2026-05-22 18:59:15 +09:00
parent 8096f4a848
commit 4626bf2771
8 changed files with 34 additions and 18 deletions

View File

@ -10,7 +10,7 @@ export default defineConfig({
title: '木羽清风仓储系统', title: '木羽清风仓储系统',
locale: false, locale: false,
}, },
headScripts: [ styles: [
'https://fonts.googleapis.com/css2?family=Calistoga&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap', 'https://fonts.googleapis.com/css2?family=Calistoga&family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap',
], ],
proxy: { proxy: {

View File

@ -5,11 +5,13 @@ import type { CurrentUser } from '@/types/api';
import { antdTheme } from '@/types/theme'; import { antdTheme } from '@/types/theme';
const LOGIN_PATH = '/login'; const LOGIN_PATH = '/login';
const USER_CACHE_KEY = 'muyu_user_state';
let redirectingToLogin = false; let redirectingToLogin = false;
function clearSessionAndRedirect(tip?: string) { function clearSessionAndRedirect(tip?: string) {
localStorage.removeItem('token'); localStorage.removeItem('token');
localStorage.removeItem('tenantId'); localStorage.removeItem('tenantId');
localStorage.removeItem(USER_CACHE_KEY);
if (tip && !redirectingToLogin) { if (tip && !redirectingToLogin) {
message.warning(tip); message.warning(tip);
} }
@ -34,16 +36,30 @@ export async function getInitialState(): Promise<InitialState> {
return {}; return {};
} }
// Return cached state immediately to avoid blocking first render.
// An expired token will be caught lazily by the response interceptor on the next real API call.
const raw = localStorage.getItem(USER_CACHE_KEY);
if (raw) {
try {
return JSON.parse(raw) as InitialState;
} catch {
localStorage.removeItem(USER_CACHE_KEY);
}
}
// Cache miss (first load after login or cache cleared) — blocking fetch.
try { try {
const res = await fetch('/api/v1/auth/info', { const res = await fetch('/api/v1/auth/info', {
headers: { Authorization: `Bearer ${token}` }, headers: { Authorization: `Bearer ${token}` },
}); });
const data = await res.json(); const data = await res.json();
if (data.code === 0 && data.data) { if (data.code === 0 && data.data) {
return { const state: InitialState = {
currentUser: data.data, currentUser: data.data,
menuPaths: data.data.menuPaths || [], menuPaths: data.data.menuPaths || [],
}; };
localStorage.setItem(USER_CACHE_KEY, JSON.stringify(state));
return state;
} }
} catch { } catch {
// network failure — fall through to redirect // network failure — fall through to redirect
@ -85,6 +101,7 @@ export const layout: RunTimeLayoutConfig = (initialState) => ({
logout: () => { logout: () => {
localStorage.removeItem('token'); localStorage.removeItem('token');
localStorage.removeItem('tenantId'); localStorage.removeItem('tenantId');
localStorage.removeItem(USER_CACHE_KEY);
history.push(LOGIN_PATH); history.push(LOGIN_PATH);
}, },
menuDataRender: (menuData: any[]) => { menuDataRender: (menuData: any[]) => {

View File

@ -22,7 +22,7 @@ interface StatCardProps {
const StatCard: React.FC<StatCardProps> = ({ icon, title, value, suffix, prefix, gradient }) => ( const StatCard: React.FC<StatCardProps> = ({ icon, title, value, suffix, prefix, gradient }) => (
<Card <Card
bordered={false} variant="borderless"
style={{ style={{
borderRadius: 16, borderRadius: 16,
border: gradient ? '2px solid transparent' : '1px solid #E2E8F0', border: gradient ? '2px solid transparent' : '1px solid #E2E8F0',
@ -206,7 +206,7 @@ const DashboardPage: React.FC = () => {
].map((item) => ( ].map((item) => (
<Col xs={24} sm={12} lg={6} key={item.path}> <Col xs={24} sm={12} lg={6} key={item.path}>
<Card <Card
bordered={false} variant="borderless"
hoverable hoverable
style={{ style={{
borderRadius: 14, borderRadius: 14,

View File

@ -10,7 +10,7 @@ const ImportPage: React.FC = () => {
return ( return (
<PageContainer> <PageContainer>
<Card title="Excel 批量导入" bordered={false}> <Card title="Excel 批量导入" variant="borderless">
<Space direction="vertical" size={20} style={{ display: 'flex' }}> <Space direction="vertical" size={20} style={{ display: 'flex' }}>
<Alert <Alert
message="导入说明" message="导入说明"

View File

@ -59,7 +59,7 @@ const StatsPage: React.FC = () => {
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}> <Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
{stats.map((s) => ( {stats.map((s) => (
<Col xs={24} sm={12} lg={4} xl={4} key={s.title} style={{ flex: 1 }}> <Col xs={24} sm={12} lg={4} xl={4} key={s.title} style={{ flex: 1 }}>
<Card bordered={false} styles={{ body: { padding: '20px 18px' } }}> <Card variant="borderless" styles={{ body: { padding: '20px 18px' } }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
<span style={{ <span style={{
display: 'flex', justifyContent: 'center', alignItems: 'center', display: 'flex', justifyContent: 'center', alignItems: 'center',
@ -77,7 +77,7 @@ const StatsPage: React.FC = () => {
</Row> </Row>
<Row gutter={16}> <Row gutter={16}>
<Col xs={24} lg={12}> <Col xs={24} lg={12}>
<Card title="按颜色分类统计" bordered={false}> <Card title="按颜色分类统计" variant="borderless">
<Table<StockGroup> <Table<StockGroup>
dataSource={colorData} dataSource={colorData}
rowKey="name" rowKey="name"
@ -91,7 +91,7 @@ const StatsPage: React.FC = () => {
</Card> </Card>
</Col> </Col>
<Col xs={24} lg={12}> <Col xs={24} lg={12}>
<Card title="按位置分布统计" bordered={false}> <Card title="按位置分布统计" variant="borderless">
<Table<StockGroup> <Table<StockGroup>
dataSource={locationData} dataSource={locationData}
rowKey="name" rowKey="name"

View File

@ -5,6 +5,8 @@ import { LockOutlined, UserOutlined, BankOutlined } from '@ant-design/icons';
import { getUserInfo, login } from '@/services/api'; import { getUserInfo, login } from '@/services/api';
import type { LoginParams } from '@/types/api'; import type { LoginParams } from '@/types/api';
const USER_CACHE_KEY = 'muyu_user_state';
const LoginPage: React.FC = () => { const LoginPage: React.FC = () => {
const { setInitialState } = useModel('@@initialState'); const { setInitialState } = useModel('@@initialState');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@ -46,17 +48,14 @@ const LoginPage: React.FC = () => {
res.data.userInfo?.tenantId || values.tenantId || 't_default_001', res.data.userInfo?.tenantId || values.tenantId || 't_default_001',
); );
const infoRes = await getUserInfo(); const infoRes = await getUserInfo();
let state: { currentUser: any; menuPaths: string[] };
if (infoRes?.code === 0 && infoRes.data) { if (infoRes?.code === 0 && infoRes.data) {
await setInitialState({ state = { currentUser: infoRes.data, menuPaths: infoRes.data.menuPaths || [] };
currentUser: infoRes.data,
menuPaths: infoRes.data.menuPaths || [],
});
} else { } else {
await setInitialState({ state = { currentUser: res.data.userInfo, menuPaths: [] };
currentUser: res.data.userInfo,
menuPaths: [],
});
} }
await setInitialState(state);
localStorage.setItem(USER_CACHE_KEY, JSON.stringify(state));
message.success('登录成功'); message.success('登录成功');
history.push('/dashboard'); history.push('/dashboard');
} else { } else {

View File

@ -60,7 +60,7 @@ const ConfigsPage: React.FC = () => {
return ( return (
<PageContainer> <PageContainer>
<Card bordered={false}> <Card variant="borderless">
<Table<SystemConfig> <Table<SystemConfig>
columns={columns} columns={columns}
dataSource={data} dataSource={data}

View File

@ -177,7 +177,7 @@ const MenusPage: React.FC = () => {
return ( return (
<PageContainer> <PageContainer>
<Card bordered={false}> <Card variant="borderless">
<Button type="primary" icon={<PlusOutlined />} style={{ marginBottom: 16 }}> <Button type="primary" icon={<PlusOutlined />} style={{ marginBottom: 16 }}>
</Button> </Button>