Sfoglia il codice sorgente

添加首页demo

dev
powersir 1 anno fa
parent
commit
88b774da41
20 ha cambiato i file con 785 aggiunte e 122 eliminazioni
  1. +70
    -82
      src/layout/index.tsx
  2. +14
    -0
      src/layout/menu.json
  3. +13
    -3
      src/layout/slide/menus.tsx
  4. +0
    -2
      src/models/user.ts
  5. +41
    -0
      src/pages/dashboard/column.tsx
  6. +55
    -0
      src/pages/dashboard/index.css
  7. +249
    -0
      src/pages/dashboard/index.tsx
  8. +84
    -0
      src/pages/dashboard/theme/dark-column-theme.json
  9. +84
    -0
      src/pages/dashboard/theme/light-column-theme.json
  10. +16
    -0
      src/pages/dashboard/tiny-area.tsx
  11. +35
    -0
      src/pages/dashboard/tiny-column.tsx
  12. +33
    -0
      src/pages/dashboard/tiny-line.tsx
  13. +13
    -2
      src/pages/login/index.tsx
  14. +6
    -26
      src/request/index.ts
  15. +1
    -1
      src/request/service/login.ts
  16. +6
    -3
      src/request/service/user.ts
  17. +33
    -0
      src/router/config.tsx
  18. +2
    -1
      src/router/router.tsx
  19. +6
    -2
      src/store/global/index.ts
  20. +24
    -0
      src/store/global/menu.ts

+ 70
- 82
src/layout/index.tsx Vedi File

@@ -4,37 +4,33 @@ import { lazy, useEffect, useState } from 'react';
import GloablLoading from '@/components/global-loading';
import Slide from './slide';
import Header from './header';
// import userService from '@/service';
// import { useRequest } from '@/hooks/use-request';
import { useUserStore } from '@/store/global/user';
import { useMenusStore } from "@/store/global/menu";
import { Menu } from '@/models';
// import { components } from '@/config/routes';
import { components } from '@/router/config';

import { replaceRoutes, router } from '@/router/router';
import Result404 from './404';

import './index.css'
// import { MenuType } from '@/pages/menu/interface';
import TabsLayout from './tabs-layout';
import Content from './content';
import homeService from '@/request/service/home';
import { useRequest } from "@/hooks/use-request";
import menuData from './menu.json'

const BasicLayout: React.FC = () => {

const [loading, setLoading] = useState(true);

const { refreshToken, lang, token } = useGlobalStore();
const { setCurrentUser, currentUser } = useUserStore();
const { lang } = useGlobalStore();
const { currentUser } = useUserStore();
const { setMenus } = useMenusStore();
const navigate = useNavigate();
const location = useLocation();
// const { setLatestMessage } = useMessageStore();

// const {
// data: currentUserDetail,
// run: getCurrentUserDetail,
// } = useRequest(
// userService.getCurrentUserDetail,
// { manual: true }
// );
const { runAsync: getAnnouncement } = useRequest(homeService.getAnnouncement, { manual: true });

const formatMenus = (
menus: Menu[],
@@ -62,71 +58,67 @@ const BasicLayout: React.FC = () => {
});
}

// useEffect(() => {
// if (!refreshToken) {
// navigate('/user/login');
// return;
// }
// getCurrentUserDetail();
// }, [refreshToken, getCurrentUserDetail, navigate]);

// useEffect(() => {
// if (!currentUserDetail) return;

// const { menus = [] } = currentUserDetail;

// const menuGroup = menus.reduce<Record<string, Menu[]>>((prev, menu) => {
// if (!menu.parentId) {
// return prev;
// }

// if (!prev[menu.parentId]) {
// prev[menu.parentId] = [];
// }

// prev[menu.parentId].push(menu);
// return prev;
// }, {});

// const routes: Menu[] = [];

// currentUserDetail.menus = formatMenus(menus.filter(o => !o.parentId), menuGroup, routes);

// currentUserDetail.authList = menus
// .filter(menu => menu.type === MenuType.BUTTON && menu.authCode)
// .map(menu => menu.authCode!);


// console.log(components, 'components');

// replaceRoutes('*', [
// ...routes.map(menu => ({
// path: `/*${menu.path}`,
// Component: menu.filePath ? lazy(components[menu.filePath]) : null,
// id: `/*${menu.path}`,
// handle: {
// parentPaths: menu.parentPaths,
// path: menu.path,
// name: menu.name,
// icon: menu.icon,
// },
// })), {
// id: '*',
// path: '*',
// Component: Result404,
// handle: {
// path: '404',
// name: '404',
// },
// }
// ]);

// setCurrentUser(currentUserDetail);
// setLoading(false);

// // replace一下当前路由,为了触发路由匹配
// router.navigate(`${location.pathname}${location.search}`, { replace: true });
// }, [currentUserDetail, setCurrentUser]);
useEffect(() => {
const menus:Array<Menu> = menuData as Array<Menu> ;
// {
// "id": 90121,
// "parentid": 901,
// "homeid": 9,
// "menuName": "国家管理",
// "parentMenuName": "基础配置",
// "pageUrl": "/modules/config/country_list.html",
// "sort": 13,
// "level": 3
// }

console.log(menus);

const menuGroup = menus.reduce<Record<string, Menu[]>>((prev, menu) => {
if (!menu.parentId) {
return prev;
}

if (!prev[menu.parentId]) {
prev[menu.parentId] = [];
}

prev[menu.parentId].push(menu);
return prev;
}, {});


const routes: Menu[] = [];

const formatedMenus = formatMenus(menus.filter(o => !o.parentId), menuGroup, routes);
setMenus(formatedMenus);
console.log(components, 'components');
replaceRoutes('*', [
...routes.map(menu => ({
path: `/*${menu.path}`,
Component: menu.filePath ? lazy(components[menu.filePath]) : null,
id: `/*${menu.path}`,
handle: {
parentPaths: menu.parentPaths,
path: menu.path,
name: menu.name,
icon: menu.icon,
},
})), {
id: '*',
path: '*',
Component: Result404,
handle: {
path: '404',
name: '404',
},
}
]);

setLoading(false);

// replace一下当前路由,为了触发路由匹配
router.navigate(`${location.pathname}${location.search}`, { replace: true });
}, [ currentUser ]);

useEffect(() => {
function storageChange(e: StorageEvent) {
@@ -142,10 +134,6 @@ const BasicLayout: React.FC = () => {
}
}, []);

setTimeout(()=>{
setLoading(false);
}, 3000)

if (loading) {
return (
<GloablLoading />


+ 14
- 0
src/layout/menu.json Vedi File

@@ -0,0 +1,14 @@
[
{
"id": "22582728306196480",
"parentId": "",
"name": "首页",
"icon": "HomeOutlined",
"type":1,
"route": "/dashboard",
"filePath": "/dashboard/index.tsx",
"orderNumber": 0,
"url":"",
"show":true
}
]

+ 13
- 3
src/layout/slide/menus.tsx Vedi File

@@ -1,11 +1,11 @@
import React, { useCallback, useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState, useMemo } from 'react';
import { Menu } from 'antd';
import type { ItemType } from 'antd/es/menu/hooks/useItems';
import { Link, useMatches } from 'react-router-dom';

import { useGlobalStore } from '@/store/global';
import { antdIcons } from '@/assets/antd-icons';
import { Menu as MenuType } from '@/models';
import { useMenusStore } from '@/store/global/menu';

import {
AccountBookOutlined,
@@ -240,6 +240,7 @@ const items: MenuItem[] = [
]),
];


const SlideMenu = () => {

const matches = useMatches();
@@ -294,13 +295,22 @@ const SlideMenu = () => {
})
}, []);


const { menus } = useMenusStore();
console.log(menus)
debugger

const menuData = useMemo(() => {
return treeMenuData(menus?.filter(menu => menu.show) || []);
}, [menus]);

return (
<Menu
className='bg-primary color-transition'
mode="inline"
selectedKeys={selectKeys}
style={{ height: '100%', borderRight: 0 }}
items={items}
items={menuData}
inlineCollapsed={collapsed}
openKeys={openKeys}
onOpenChange={setOpenKeys}


+ 0
- 2
src/models/user.ts Vedi File

@@ -72,7 +72,6 @@ export interface LoginRespDTO {
pop: Array<PopMenu>;
}


export interface Menu {
id: string;
parentId?: string;
@@ -88,7 +87,6 @@ export interface Menu {
path: string;
Component?: any;
parentPaths?: string[];
authCode?: string;
}

export interface User {


+ 41
- 0
src/pages/dashboard/column.tsx Vedi File

@@ -0,0 +1,41 @@
import { useState, useEffect } from 'react';
import { Column } from '@ant-design/plots';

import { useGlobalStore } from '@/store/global';

import columnDarkTheme from './theme/dark-column-theme.json'
import columnLightTheme from './theme/light-column-theme.json'

const DemoColumn = () => {
const [data, setData] = useState([]);

const { darkMode } = useGlobalStore();

useEffect(() => {
asyncFetch();
}, []);

const asyncFetch = () => {
fetch('https://gw.alipayobjects.com/os/antfincdn/8elHX%26irfq/stack-column-data.json')
.then((response) => response.json())
.then((json) => setData(json))
.catch((error) => {
console.log('fetch data failed', error);
});
};

const config: any = {
data,
isStack: true,
xField: 'year',
yField: 'value',
seriesField: 'type',
height: 480,
legend: {
position: 'bottom'
},
};
return <Column theme={darkMode ? columnDarkTheme : columnLightTheme} {...config} />;
};

export default DemoColumn;

+ 55
- 0
src/pages/dashboard/index.css Vedi File

@@ -0,0 +1,55 @@
.bg-card {
@apply before: (content-[''] absolute w-[210px] h-[210px] top-[-125px] right-[-15px] rounded-[50%] opacity-[.5]);
}

.dark .bg-card::before {
background: linear-gradient(140.9deg, rgb(101, 31, 255) -14.02%, rgba(144, 202, 249, 0) 85.5%);
}

.bg-card {
@apply after: (content-[''] absolute w-[210px] h-[210px] top-[-85px] right-[-95px] rounded-[50%] dark:opacity-[.5]);
}

.dark .bg-card::after {
background: linear-gradient(140.9deg, rgb(101, 31, 255) -14.02%, rgba(144, 202, 249, 0) 85.5%);
}

.dark .bg-card.theme1::before {
background: linear-gradient(140.9deg, rgb(30, 136, 229) -14.02%, rgba(144, 202, 249, 0) 82.5%);
}

.dark .bg-card.theme1::after {
background: linear-gradient(140.9deg, rgb(30, 136, 229) -14.02%, rgba(144, 202, 249, 0) 82.5%);
}

.dark .bg-card.theme2::before {
background: linear-gradient(140.9deg, rgb(255, 193, 7) -14.02%, rgba(144, 202, 249, 0) 70.5%);
}

.dark .bg-card.theme2::after {
background: linear-gradient(140.9deg, rgb(255, 193, 7) -14.02%, rgba(144, 202, 249, 0) 70.5%);
}

.bg-card::before {
background: rgb(69, 39, 160);
}

.bg-card::after {
background: rgb(69, 39, 160);
}

.bg-card.theme1::before {
background: rgb(21, 101, 192);
}

.bg-card.theme1::after {
background: rgb(21, 101, 192);
}

.dark .bg-card.theme2::before {
background: linear-gradient(140.9deg, rgb(255, 193, 7) -14.02%, rgba(144, 202, 249, 0) 70.5%);
}

.dark .bg-card.theme2::after {
background: linear-gradient(140.9deg, rgb(255, 193, 7) -14.02%, rgba(144, 202, 249, 0) 70.5%);
}

+ 249
- 0
src/pages/dashboard/index.tsx Vedi File

@@ -0,0 +1,249 @@
import { t } from '@/utils/i18n';
import { InfoCircleOutlined, CaretUpOutlined, DashOutlined, CaretDownOutlined } from '@ant-design/icons';
import { Col, Divider, Dropdown, Row, Select, Tooltip } from 'antd';

import DemoTinyArea from './tiny-area';
import DemoTinyColumn from './tiny-column';
import DemoColumn from './column';
import DemoTinyLine from './tiny-line';

import './index.css'

const Dashboard = () => {
return (
<>
<Row gutter={[16, 16]}>
<Col lg={24} xl={8} className='w-[100%]'>
<div className='color-transition dark:bg-[rgb(33,41,70)] w-[100%] bg-[rgb(94,53,177)] overflow-hidden h-[241px] relative rounded-md bg-card p-[32px] box-border'>
<div className='absolute top-[24px] right-[24px] z-10'>
<Tooltip title={t("yAdJryjx" /* 指标说明 */)}>
<InfoCircleOutlined className='text-[rgb(179,157,219)] text-[20px]' />
</Tooltip>
</div>
<div className="text-[rgba(229,224,216,0.45)] text-[16px]">
{t("nKMAkrqJ" /* 总销售额 */)}
</div>
<div className="text-white text-2xl mt-[28px] text-[30px]">
¥ 126,560
</div>
<div className='mt-[50px] text-[rgba(229,224,216,0.85)] text-[16px] flex gap-[24px]'>
<div className='flex items-center'>
<span>{t("NpRFMJyD" /* 周同比 */)}</span>
<span className='ml-[12px]'>12%</span>
<CaretDownOutlined className='ml-[6px] text-red-500' />
</div>
<div className='flex items-center'>
<span>{t("WOQnwYUS" /* 日同比 */)}</span>
<span className='ml-[12px]'>12%</span>
<CaretUpOutlined className='ml-[6px] text-green-500' />
</div>
</div>
<Divider className='dark:bg-[rgb(189,200,240)] bg-[rgb(227,232,239)] opacity-[0.2] my-[16px]' />
<div className='text-[rgba(229,224,216,0.85)] text-[16px]'>
<span>
{t("ZPCQOWAn" /* 日销售额 */)}
</span>
<span className='ml-[8px]'>
¥12,423
</span>
</div>
</div>
</Col>
<Col lg={24} xl={8} className='w-[100%]'>
<div className='color-transition dark:bg-[rgb(33,41,70)] bg-[rgb(30,136,229)] theme1 overflow-hidden h-[241px] relative rounded-md bg-card p-[32px] box-border'>
<div className='absolute top-[24px] right-[24px] z-10'>
<Tooltip title={t("iLyPEqwQ" /* 指标说明 */)}>
<InfoCircleOutlined className='text-[rgb(179,157,219)] text-[20px]' />
</Tooltip>
</div>
<div className="text-[rgba(229,224,216,0.45)] text-[16px]">
{t("ftuxZMpL" /* 访问量 */)}
</div>
<div className="text-white text-2xl mt-[20px] text-[30px]">
8,930
</div>
<div className='mt-[20px] text-[rgba(229,224,216,0.85)] text-[16px] flex gap-[24px]'>
<DemoTinyLine />
</div>
<Divider className='dark:bg-[rgb(189,200,240)] bg-[rgb(227,232,239)] opacity-[0.2] my-[16px]' />
<div className='text-[rgba(229,224,216,0.85)] text-[16px]'>
<span>
{t("sehypRaO" /* 日访问量 */)}
</span>
<span className='ml-[8px]'>
9,431
</span>
</div>
</div>
</Col>
<Col lg={24} xl={8} className='w-[100%]'>
<div className='color-transition dark:bg-[rgb(33,41,70)] bg-[rgb(94,53,177)] theme2 overflow-hidden h-[241px] relative rounded-md bg-card p-[32px] box-border'>
<div className='absolute top-[24px] right-[24px] z-10'>
<Tooltip title={t("sdOusITo" /* 指标说明 */)}>
<InfoCircleOutlined className='text-[rgb(179,157,219)] text-[20px]' />
</Tooltip>
</div>
<div className="text-[rgba(229,224,216,0.45)] text-[16px]">
{t("PIYkoguj" /* 支付笔数 */)}
</div>
<div className="text-white text-2xl mt-[20px] text-[30px]">
8,943
</div>
<div className='mt-[12px] text-[rgba(229,224,216,0.85)] text-[16px] flex gap-[24px]'>
<DemoTinyColumn />
</div>
<Divider className='dark:bg-[rgb(189,200,240)] bg-[rgb(227,232,239)] opacity-[0.2] my-[16px]' />
<div className='text-[rgba(229,224,216,0.85)] text-[16px]'>
<span>
{t("BUjwpMzX" /* 转化率 */)}
</span>
<span className='ml-[8px]'>
2,421
</span>
</div>
</div>
</Col>
<Col className='w-[100%]' lg={24} xl={16} >
<div className='color-transition dark:bg-[rgb(33,41,70)] bg-white h-[600px] rounded-md p-[24px] relative'>
<div className='flex justify-between items-center'>
<div>
<div className='text-[rgb(132,146,196)]'>{t("fHpiDHYH" /* 总增长 */)}</div>
<div className='dark:text-white text-[rgb(18,25,38)] mt-[8px] text-[18px]'>¥12,423</div>
</div>
<Select
options={[
{
label: t("yLkZTWbn" /* 今日 */),
value: 'today'
},
{
label: t("QFqMuZiD" /* 本月 */),
value: 'mouth'
},
{
label: t("lGOcGyrv" /* 本年 */),
value: 'year'
},
]}
defaultValue="today"
size="large"
dropdownMatchSelectWidth={false}
placement="bottomRight"
/>
</div>
<div className='mt-[50px] absolute bottom-[12px] w-[90%] box-border'>
<DemoColumn />
</div>
</div>
</Col>
<Col className='w-[100%]' lg={24} xl={8}>
<div className='color-transition dark:bg-[rgb(33,41,70)] bg-white h-[600px] rounded-md p-[24px] relative'>
<div className='flex justify-between'>
<span className='dark:text-[rgb(215,220,236)] text-[18px] text-[rgb(18,25,38)]'>{t("yzUIyMhr" /* 门店销售额 */)}</span>
<Dropdown menu={{
items: [
{
label: t("aSPCUBcK" /* 今日 */),
key: 'today'
},
{
label: t("EhTpnarX" /* 本月 */),
key: 'mouth'
},
{
label: t("AGGPEAdX" /* 本年 */),
key: 'year'
},
]
}}>
<span className='text-[rgb(144,202,249)] cursor-pointer'>
<DashOutlined />
</span>
</Dropdown>
</div>
<div className='dark:bg-[rgb(209,196,233)] bg-[rgb(237,231,246)] px-[16px] pt-[16px] mt-[24px] rounded-t-lg'>
<div className='flex justify-between items-center'>
<span className='text-[rgb(101,31,255)] text-[16px]'>{t("jTSvVuJx" /* 上海分店 */)}</span>
<span className='text-[rgb(66,66,66)]'>¥12,423</span>
</div>
<div className='mt-[10px] text-[rgb(66,66,66)]'>
{t("SwsawJhB" /* 20% 利润 */)}
</div>
</div>
<div className='dark:bg-[rgb(209,196,233)] bg-[rgb(237,231,246)] rounded-b-lg'>
<DemoTinyArea />
</div>
<div className='py-[24px]'>
<div>
<div className='flex justify-between items-center'>
<span className='dark:text-[rgb(189,200,240)] text-[rgb(54,65,82)] font-medium'>{t("JYSgIJHD" /* 上海分店 */)}</span>
<div>
<span className='mr-[8px] dark:text-[rgb(189,200,240)] text-[rgb(54,65,82)] font-medium'>¥12,423</span>
<CaretUpOutlined className='text-[rgb(0,200,83)]' />
</div>
</div>
<div className='text-[rgb(0,200,83)] text-[12px] mt-[6px]'>
{t("yELACPnu" /* 20% 利润 */)}
</div>
</div>
<Divider className='dark:bg-[rgb(189,200,240)] bg-[rgb(227,232,239)] dark:opacity-[0.2] my-[16px]' />
<div>
<div className='flex justify-between items-center'>
<span className='dark:text-[rgb(189,200,240)] text-[rgb(54,65,82)]font-medium'>{t("WAiyAuwV" /* 合肥分店 */)}</span>
<div>
<span className='mr-[8px] dark:text-[rgb(189,200,240)] text-[rgb(54,65,82)] font-medium'>¥10,000</span>
<CaretUpOutlined className='text-[rgb(0,200,83)]' />
</div>
</div>
<div className='text-[rgb(0,200,83)] text-[12px] mt-[6px]'>
{t("HpNzGyBz" /* 6% 利润 */)}
</div>
</div>
<Divider className='dark:bg-[rgb(189,200,240)] bg-[rgb(227,232,239)] dark:opacity-[0.2] my-[16px]' />
<div>
<div className='flex justify-between items-center'>
<span className='dark:text-[rgb(189,200,240)] text-[rgb(54,65,82)] font-medium'>{t("nGvTAQld" /* 北京分店 */)}</span>
<div>
<span className='mr-[8px] dark:text-[rgb(189,200,240)] text-[rgb(54,65,82)] font-medium'>¥8,000</span>
<CaretDownOutlined className='text-[rgb(216,67,21)] ' />
</div>
</div>
<div className='text-[rgb(216,67,21)] text-[12px] mt-[6px]'>
{t("EeunYupT" /* 8% 亏损 */)}
</div>
</div>
<Divider className='dark:bg-[rgb(189,200,240)] bg-[rgb(227,232,239)] dark:opacity-[0.2] my-[16px]' />
<div>
<div className='flex justify-between items-center'>
<span className='dark:text-[rgb(189,200,240)] text-[rgb(54,65,82)] font-medium'>{t("usCBUdwp" /* 苏州分店 */)}</span>
<div>
<span className='mr-[8px] dark:text-[rgb(189,200,240)] text-[rgb(54,65,82)] font-medium'>¥9,423</span>
<CaretUpOutlined className='text-[rgb(0,200,83)]' />
</div>
</div>
<div className='text-[rgb(0,200,83)] text-[12px] mt-[6px]'>
{t("TacOGPiP" /* 14% 利润 */)}
</div>
</div>
<Divider className='dark:bg-[rgb(189,200,240)] bg-[rgb(227,232,239)] dark:opacity-[0.2] my-[16px]' />
<div>
<div className='flex justify-between items-center'>
<span className='dark:text-[rgb(189,200,240)] text-[rgb(54,65,82)] font-medium'>{t("Imkllizi" /* 南京分店 */)}</span>
<div>
<span className='mr-[8px] dark:text-[rgb(189,200,240)] text-[rgb(54,65,82)] font-medium'>¥7,423</span>
<CaretDownOutlined className='text-[rgb(216,67,21)]' />
</div>
</div>
<div className='text-[rgb(0,200,83)] text-[12px] mt-[6px] text-[rgb(216,67,21)]'>
{t("MzCxBxLH" /* 6% 亏损 */)}
</div>
</div>
</div>
</div>
</Col>
</Row>
</>
)
}

export default Dashboard;

+ 84
- 0
src/pages/dashboard/theme/dark-column-theme.json Vedi File

@@ -0,0 +1,84 @@
{
"background": "rgba(20, 20, 20, 0)",
"components": {
"axis": {
"common": {
"grid": {
"line": {
"type": "line",
"style": {
"stroke": "rgba(189, 200, 240, 0.125)",
"lineWidth": 1,
"lineDash": null
}
},
"alignTick": true,
"animate": true
}
}
},
"tooltip": {
"domStyles": {
"g2-tooltip": {
"position": "absolute",
"visibility": "hidden",
"zIndex": 8,
"transition": "left 0.4s cubic-bezier(0.23, 1, 0.32, 1) 0s, top 0.4s cubic-bezier(0.23, 1, 0.32, 1) 0s",
"backgroundColor": "#1f1f1f",
"opacity": 0.95,
"boxShadow": "0px 2px 4px rgba(0,0,0,.5)",
"borderRadius": "3px",
"color": "#A6A6A6",
"fontSize": "12px",
"fontFamily": "\"Segoe UI\", Roboto, \"Helvetica Neue\", Arial,\n \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\",\n \"Noto Color Emoji\"",
"lineHeight": "12px",
"padding": "0 12px 0 12px"
},
"g2-tooltip-title": {
"marginBottom": "12px",
"marginTop": "12px"
},
"g2-tooltip-list": {
"margin": 0,
"listStyleType": "none",
"padding": 0
},
"g2-tooltip-list-item": {
"listStyleType": "none",
"padding": 0,
"marginBottom": "12px",
"marginTop": "12px",
"marginLeft": 0,
"marginRight": 0
},
"g2-tooltip-marker": {
"width": "8px",
"height": "8px",
"borderRadius": "50%",
"display": "inline-block",
"marginRight": "8px"
},
"g2-tooltip-value": {
"display": "inline-block",
"float": "right",
"marginLeft": "30px"
}
}
}
},
"styleSheet": {
"brandColor": "rgba(124, 77, 255, 0.85)",
"paletteQualitative10": [
"rgba(124, 77, 255, 0.85)",
"rgba(144, 202, 249, 0.85)",
"#65789B",
"#F6BD16",
"#7262fd",
"#78D3F8",
"#9661BC",
"#F6903D",
"#008685",
"#F08BB4"
]
}
}

+ 84
- 0
src/pages/dashboard/theme/light-column-theme.json Vedi File

@@ -0,0 +1,84 @@
{
"background": "rgba(20, 20, 20, 0)",
"components": {
"axis": {
"common": {
"grid": {
"line": {
"type": "line",
"style": {
"stroke": "rgb(227,232,239)",
"lineWidth": 1,
"lineDash": null
}
},
"alignTick": true,
"animate": true
}
}
},
"tooltip": {
"domStyles": {
"g2-tooltip": {
"position": "absolute",
"visibility": "hidden",
"zIndex": 8,
"transition": "left 0.4s cubic-bezier(0.23, 1, 0.32, 1) 0s, top 0.4s cubic-bezier(0.23, 1, 0.32, 1) 0s",
"backgroundColor": "#ffffff",
"opacity": 0.95,
"boxShadow": "rgb(174, 174, 174) 0px 0px 10px",
"borderRadius": "3px",
"color": "#A6A6A6",
"fontSize": "12px",
"fontFamily": "\"Segoe UI\", Roboto, \"Helvetica Neue\", Arial,\n \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\",\n \"Noto Color Emoji\"",
"lineHeight": "12px",
"padding": "0 12px 0 12px"
},
"g2-tooltip-title": {
"marginBottom": "12px",
"marginTop": "12px"
},
"g2-tooltip-list": {
"margin": 0,
"listStyleType": "none",
"padding": 0
},
"g2-tooltip-list-item": {
"listStyleType": "none",
"padding": 0,
"marginBottom": "12px",
"marginTop": "12px",
"marginLeft": 0,
"marginRight": 0
},
"g2-tooltip-marker": {
"width": "8px",
"height": "8px",
"borderRadius": "50%",
"display": "inline-block",
"marginRight": "8px"
},
"g2-tooltip-value": {
"display": "inline-block",
"float": "right",
"marginLeft": "30px"
}
}
}
},
"styleSheet": {
"brandColor": "rgba(124, 77, 255, 0.85)",
"paletteQualitative10": [
"rgba(124, 77, 255, 0.85)",
"rgba(144, 202, 249, 0.85)",
"#65789B",
"#F6BD16",
"#7262fd",
"#78D3F8",
"#9661BC",
"#F6903D",
"#008685",
"#F08BB4"
]
}
}

+ 16
- 0
src/pages/dashboard/tiny-area.tsx Vedi File

@@ -0,0 +1,16 @@
import { TinyArea } from '@ant-design/plots';

const DemoTinyArea = () => {
const data = [
0, 300, 438, 287, 309, 600, 900, 575, 563, 300, 200
];
const config = {
height: 95,
data,
smooth: true,
areaStyle: { fill: 'l(360) 1:rgba(98,0,234,0.65) 0.5:rgba(177,128,245,0.5) 0.5:rgba(177,128,245,0.5)' },
};
return <TinyArea {...config} renderer="svg" line={{ color: '#6200ea' }} appendPadding={[0, -24, 0, -16]} />;
};

export default DemoTinyArea;

+ 35
- 0
src/pages/dashboard/tiny-column.tsx Vedi File

@@ -0,0 +1,35 @@
import { TinyColumn } from '@antv/g2plot';
import { useLayoutEffect, useRef } from 'react';

const DemoTinyColumn = () => {

const container = useRef(null);

useLayoutEffect(() => {
const data = [50, 40, 81, 400, 300, 219, 269];

const tinyColumn = new TinyColumn(container.current!, {
height: 50,
autoFit: true,
data,
tooltip: {
customContent: function (x, data) {
return `NO.${x}: ${data[0]?.data?.y.toFixed(2)}`;
},
},
});

tinyColumn.render();

return () => {
tinyColumn.destroy();
}
}, []);


return (
<div ref={container} className='w-[100%]' />
);
};

export default DemoTinyColumn;

+ 33
- 0
src/pages/dashboard/tiny-line.tsx Vedi File

@@ -0,0 +1,33 @@
import { TinyLine } from '@antv/g2plot';
import { useLayoutEffect, useRef } from 'react';

const DemoTinyLine = () => {

const container = useRef(null);

useLayoutEffect(() => {
const data = [
264, 417, 438, 887, 309, 397, 550, 575, 563, 430, 525, 592, 492, 467, 513, 546, 983, 340, 539, 243, 226, 192,
];
const tinyLine = new TinyLine(container.current!, {
height: 50,
autoFit: true,
data,
smooth: true,
color: '#ffffff',
});

tinyLine.render();

return () => {
tinyLine.destroy();
}
}, []);


return (
<div ref={container} className='w-[100%]' />
);
};

export default DemoTinyLine;

+ 13
- 2
src/pages/login/index.tsx Vedi File

@@ -5,22 +5,33 @@ import { Button, Form, Input, Carousel } from 'antd';
import { useNavigate } from 'react-router-dom';
import { useRequest } from '@/hooks/use-request';
import { useUserStore } from '@/store/global/user';
import { useGlobalStore } from '@/store/global';
import loginService from '@/request/service/login';
import userService from '@/request/service/user';
import homeService from '@/request/service/home';
import { LoginDTO } from '@/models';
import './index.css'

const Login = () => {
const navigate = useNavigate();

const { runAsync: getUserInfo } = useRequest(userService.getUserInfo, { manual: true });
const { runAsync: getAnnouncement } = useRequest(homeService.getAnnouncement, { manual: true });
const { runAsync: login, loading } = useRequest(loginService.login, { manual: true });
const { setCurrentUser } = useUserStore();
const { setCookie } = useGlobalStore();
const onFinish = async (values: LoginDTO) => {
const [loginError, data] = await login(values);
if (loginError) {
return;
}
data.data.avatarUrl = 'https://test.vogocm.com:9010/eshop/eshop_img/2023/5/24/43853633d16749bfb291f81bebb73451_20230524150631A001.jpg'
data.data.avatarUrl = 'https://test.vogocm.com:9010/eshop/eshop_img/2023/5/24/43853633d16749bfb291f81bebb73451_20230524150631A001.jpg';
setCookie(data.msg);
setCurrentUser(data.data);
const [e, userInfo] = await getUserInfo();
const [ _, announcementData ] = await getAnnouncement();
console.log(announcementData)
console.log(userInfo)
navigate('/');
};



+ 6
- 26
src/request/index.ts Vedi File

@@ -5,10 +5,9 @@ import axios, {
CreateAxiosDefaults,
InternalAxiosRequestConfig,
} from 'axios';
import {useGlobalStore} from '@/store/global';
import {antdUtils} from '@/utils/antd';

const refreshTokenUrl = '/api/auth/refresh/token';
// axios.defaults.withCredentials = true;

export type Response<T> = Promise<[boolean, T, AxiosResponse<T>]>;

@@ -29,7 +28,6 @@ class Request {

private axiosInstance: AxiosInstance;

private refreshTokenFlag = false;
private requestQueue: {
resolve: any;
config: any;
@@ -46,11 +44,7 @@ class Request {
private async requestInterceptor(
axiosConfig: InternalAxiosRequestConfig
): Promise<any> {
if ([refreshTokenUrl].includes(axiosConfig.url || '')) {
return Promise.resolve(axiosConfig);
}

if (this.refreshTokenFlag || this.requestingCount >= this.limit) {
if (this.requestingCount >= this.limit) {
return new Promise((resolve) => {
this.requestQueue.push({
resolve,
@@ -61,12 +55,6 @@ class Request {
}

this.requestingCount += 1;

const {token} = useGlobalStore.getState();

if (token) {
axiosConfig.headers.Authorization = `Bearer ${token}`;
}
return Promise.resolve(axiosConfig);
}

@@ -91,8 +79,7 @@ class Request {
resolve(await this.request(config));
} else if (type === 'reuqest') {
this.requestingCount += 1;
const {token} = useGlobalStore.getState();
config.headers.Authorization = `Bearer ${token}`;
//TODO: cookie
resolve(config);
}
}
@@ -102,13 +89,10 @@ class Request {
private async responseSuccessInterceptor(
response: AxiosResponse<any, any>
): Promise<any> {
if (response.config.url !== refreshTokenUrl) {
this.requestingCount -= 1;
if (this.requestQueue.length) {
this.requestByQueue();
}
this.requestingCount -= 1;
if (this.requestQueue.length) {
this.requestByQueue();
}

return Promise.resolve([false, response.data, response]);
}

@@ -119,9 +103,6 @@ class Request {
if (status === 401) {
return new Promise((resolve) => {
this.requestQueue.unshift({resolve, config, type: 'response'});
if (this.refreshTokenFlag) return;

this.refreshTokenFlag = true;
});
} else {
antdUtils.notification?.error({
@@ -134,7 +115,6 @@ class Request {

private reset() {
this.requestQueue = [];
this.refreshTokenFlag = false;
this.requestingCount = 0;
}



+ 1
- 1
src/request/service/login.ts Vedi File

@@ -4,7 +4,7 @@ import { LoginDTO, LoginRespDTO } from '@/models'
const loginService = {
// 登录
login: (loginDTO: LoginDTO) => {
return request.post<LoginRespDTO>('/api/login', loginDTO);
return request.post<LoginRespDTO>('/api/login', loginDTO, { withCredentials: false });
},

logout: () => {


+ 6
- 3
src/request/service/user.ts Vedi File

@@ -1,5 +1,8 @@
const userService = {
import request from '@/request';
import { LoginRespDTO } from '@/models';

export default {
getUserInfo: () => {
return request.get<LoginRespDTO>('/api/userinfo/getUserInfo');
}
};

export default userService;

+ 33
- 0
src/router/config.tsx Vedi File

@@ -0,0 +1,33 @@
export const modules = import.meta.glob('../pages/**/index.tsx');

export const componentPaths = Object.keys(modules).map((path: string) => path.replace('../pages', ''));

let manifest: any;

export const components = Object.keys(modules).reduce<Record<string, () => Promise<any>>>((prev, path: string) => {
const formatPath = path.replace('../pages', '');
prev[formatPath] = async () => {
try {
// 这里其实就是动态加载js,如果报错了说明js资源不存在
return await modules[path]() as any;
} catch {
// 如果manifest已经存在了,就不用再请求了
if (manifest) {
try {
// 有可能manifest是过期的,所以可能还会加载失败
return await import('/' + manifest[`src/pages${formatPath}`]?.file);
} catch {
// 如果失败,重新获取一下manifest.json,拿到最新的路径
manifest = await (await fetch('/manifest.json')).json() as any;
return await import('/' + manifest[`src/pages${formatPath}`]?.file);
}
} else {
// 如果manifest.json为空,请求manifest.json,并根据最新的路径加载对应js
manifest = await (await fetch('/manifest.json')).json() as any;
return await import('/' + manifest[`src/pages${formatPath}`]?.file);
}
}
}
return prev;
}, {});


+ 2
- 1
src/router/router.tsx Vedi File

@@ -2,6 +2,7 @@ import { RouteObject, RouterProvider, createBrowserRouter, Navigate } from 'reac

import Login from '@/pages/login';
import BasicLayout from '@/layout';
import Dashboard from '@/pages/dashboard';
import { App } from 'antd';
import { useEffect } from 'react';
import { antdUtils } from '@/utils/antd';
@@ -18,6 +19,7 @@ export const router = createBrowserRouter(
element: (
<Navigate to="/dashboard" />
),
children: [],
},
{
path: '*',
@@ -62,7 +64,6 @@ export const replaceRoutes = (parentPath: string, routes: RouteObject[]) => {
router.routes.push(...routes as any);
return;
}

const curNode = findNodeByPath(router.routes, parentPath);

if (curNode) {


+ 6
- 2
src/store/global/index.ts Vedi File

@@ -5,12 +5,14 @@ interface State {
darkMode: boolean;
collapsed: boolean;
lang: string;
cookie: string;
}

interface Action {
setDarkMode: (darkMode: State['darkMode']) => void;
setCollapsed: (collapsed: State['collapsed']) => void;
setLang: (lang: State['lang']) => void;
setCookie: (cookie: State['cookie']) => void;
}

export const useGlobalStore = create<State & Action>()(
@@ -20,8 +22,7 @@ export const useGlobalStore = create<State & Action>()(
darkMode: false,
collapsed: false,
lang: 'zh',
token: '',
refreshToken: '',
cookie: '',
setDarkMode: (darkMode: State['darkMode']) => set({
darkMode,
}),
@@ -31,6 +32,9 @@ export const useGlobalStore = create<State & Action>()(
setLang: (lang: State['lang']) => set({
lang,
}),
setCookie: (cookie: State['cookie']) => set({
cookie,
}),
};
},
{


+ 24
- 0
src/store/global/menu.ts Vedi File

@@ -0,0 +1,24 @@
import { Menu } from '@/models';
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';

interface State {
menus: Menu[] | null;
}

interface Action {
setMenus: (menus: State['menus']) => void;
}

export const useMenusStore = create<State & Action>()(
devtools(
(set) => {
return {
menus: null,
setMenus: (menus: State['menus']) =>
set({menus}),
};
},
{name: 'globalMenuStore'}
)
);

Loading…
Annulla
Salva