= (props) => {
22 | const { initApi, title, body, data, callback, style } = {
23 | ...defaultProps,
24 | ...props,
25 | };
26 | const location = useLocation();
27 | const query = qs.parse(location.search);
28 | const [innerData, setInnerData] = useState(data);
29 |
30 | useEffect(() => {
31 | if (initApi) {
32 | getData(initApi);
33 | }
34 | }, [initApi]);
35 |
36 | const getData = async (initApi: string) => {
37 | const result = await get({
38 | url: initApi,
39 | data: query,
40 | });
41 | setInnerData(result.data);
42 | };
43 |
44 | return (
45 |
46 |
47 |
48 | {title}
49 |
50 |
51 |
52 | );
53 | };
54 |
55 | export default Page;
56 |
--------------------------------------------------------------------------------
/src/components/PageContainer/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { PageContainer as AntPageContainer, PageHeader } from '@ant-design/pro-components';
3 | import { useLocation, history } from '@umijs/max';
4 | import qs from 'query-string';
5 | import Render from '@/components/Render';
6 | import { get } from '@/services/action';
7 | import { setObjToUrlParams } from '@/utils/url';
8 |
9 | const PageContainer: React.FC = (props: any) => {
10 | const location = useLocation();
11 | const query = qs.parse(location.search);
12 | const [pageContainerProps, setPageContainer] = useState(props);
13 |
14 | const findComponent: any = (data: any, key: string) => {
15 | let component: any = [];
16 | if (data.key === key) {
17 | return data;
18 | }
19 | // tab做特殊处理
20 | if (data.hasOwnProperty('tabPanes')) {
21 | return findComponent(data.tabPanes, key);
22 | }
23 | if (data.hasOwnProperty('body')) {
24 | return findComponent(data.body, key);
25 | }
26 | if (data.hasOwnProperty(0)) {
27 | data.forEach((item: any) => {
28 | let getComponent = findComponent(item, key);
29 | let getComponentKeys = Object.keys(getComponent);
30 | if (getComponentKeys.length > 0) {
31 | component = getComponent;
32 | }
33 | });
34 | }
35 | return component;
36 | };
37 |
38 | const getPageContainer: any = async (key: string) => {
39 | const result = await get({
40 | url: query.api,
41 | data: query,
42 | });
43 | const pageContainer = findComponent(result, key);
44 | setPageContainer(pageContainer);
45 | };
46 |
47 | const onTabChange = (key: any) => {
48 | let getQuery: any = {};
49 | getQuery['api'] = query.api;
50 | getQuery['tabKey'] = key;
51 | history.push({ pathname: history.location.pathname, search: setObjToUrlParams('', getQuery) });
52 | getPageContainer(props.componentkey);
53 | };
54 |
55 | return (
56 | {
63 | history.go(-1);
64 | },
65 | children: props.header.body && (
66 |
71 | ),
72 | extra: props.header.extra && (
73 |
78 | ),
79 | }
80 | )
81 | }
82 | content={
83 | props.content ? (
84 |
89 | ) : undefined
90 | }
91 | extraContent={
92 | props.extraContent ? (
93 |
98 | ) : undefined
99 | }
100 | tabActiveKey={query?.tabKey ?? props.tabActiveKey}
101 | onTabChange={(key) => {
102 | onTabChange(key);
103 | }}
104 | >
105 |
110 |
111 | );
112 | };
113 |
114 | export default PageContainer;
115 |
--------------------------------------------------------------------------------
/src/components/Paragraph/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Render from '@/components/Render';
3 | import { Typography } from 'antd';
4 |
5 | const Paragraph: React.FC = (props: any) => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
13 | export default Paragraph;
14 |
--------------------------------------------------------------------------------
/src/components/Render/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Space } from 'antd';
3 | import { ProCard } from '@ant-design/pro-components';
4 | import components from '@/components';
5 | import tplEngine from '@/utils/template';
6 |
7 | export interface RenderProps {
8 | body?: any;
9 | data?: any;
10 | callback?: any;
11 | }
12 |
13 | const defaultProps = {
14 | body: undefined,
15 | data: undefined,
16 | callback: undefined,
17 | } as RenderProps;
18 |
19 | // 获取组件
20 | const getComponent = (componentName: string) => {
21 | let component: any = null;
22 | components.forEach((item: any) => {
23 | if (item.name.indexOf(componentName + '|') !== -1) {
24 | component = item.component;
25 | } else {
26 | if (item.name === componentName) {
27 | component = item.component;
28 | }
29 | }
30 | });
31 |
32 | return component;
33 | };
34 |
35 | // 解析组件
36 | const parserComponent = (
37 | index: number,
38 | componentName: string,
39 | componentProps: any,
40 | data: any,
41 | callback: any,
42 | ) => {
43 | // Tpl组件特殊处理
44 | if (componentName === 'tpl') {
45 | return (
46 |
52 | );
53 | }
54 |
55 | // Space组件特殊处理
56 | if (componentName === 'space') {
57 | return (
58 |
59 | {componentRender(componentProps.body, data, callback)}
60 |
61 | );
62 | }
63 |
64 | // Card组件特殊处理
65 | if (componentName === 'card') {
66 | return (
67 |
74 | {componentRender(componentProps.body, data, callback)}
75 |
76 | );
77 | }
78 |
79 | let component = getComponent(componentName);
80 | if (component) {
81 | return React.cloneElement(component, {
82 | key: index,
83 | ...componentProps,
84 | data: data,
85 | callback: callback,
86 | });
87 | }
88 |
89 | return <>No {componentName} component found>;
90 | };
91 |
92 | // 渲染组件
93 | const componentRender = (body: any, data: any, callback: any) => {
94 | let component: any = null;
95 | if (body === null || body === undefined) {
96 | return null;
97 | }
98 | if (typeof body === 'string' || typeof body === 'number') {
99 | if (data) {
100 | body = tplEngine(body, data);
101 | }
102 | return body;
103 | }
104 | if (body.hasOwnProperty('component')) {
105 | component = parserComponent(0, body.component, body, data, callback);
106 | } else {
107 | component = body?.map?.((item: any, index: number) => {
108 | if (item.hasOwnProperty(0)) {
109 | return componentRender(item, data, callback);
110 | }
111 | return parserComponent(index, item.component, item, data, callback);
112 | });
113 | }
114 |
115 | return component;
116 | };
117 |
118 | const Render: React.FC = (props) => {
119 | const { body, data, callback } = { ...defaultProps, ...props };
120 |
121 | // 组件渲染
122 | const component = componentRender(body, data, callback);
123 |
124 | // 字符串
125 | if (typeof component === 'string') {
126 | return ;
127 | }
128 |
129 | // 返回组件
130 | return component;
131 | };
132 |
133 | export default Render;
134 |
--------------------------------------------------------------------------------
/src/components/RightContent/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import type { MenuProps } from 'antd';
3 | import { Space, Dropdown, Avatar } from 'antd';
4 | import { useEmotionCss } from '@ant-design/use-emotion-css';
5 | import { setAlpha } from '@ant-design/pro-components';
6 |
7 | export interface RightContentProps {
8 | avatar?: string | React.ReactNode;
9 | name?: string;
10 | menu?: MenuProps | undefined;
11 | style?: React.CSSProperties | undefined;
12 | }
13 |
14 | const RightContent: React.FC = (props) => {
15 | const { avatar, name, menu } = { ...props };
16 | const className = useEmotionCss(() => {
17 | return {
18 | display: 'flex',
19 | height: '48px',
20 | overflow: 'hidden',
21 | marginLeft: '0px',
22 | gap: 8,
23 | };
24 | });
25 | const nameClassName = useEmotionCss(({ token }) => {
26 | return {
27 | marginLeft: '8px',
28 | width: '70px',
29 | height: '48px',
30 | overflow: 'hidden',
31 | lineHeight: '48px',
32 | whiteSpace: 'nowrap',
33 | textOverflow: 'ellipsis',
34 | [`@media only screen and (max-width: ${token.screenMD}px)`]: {
35 | display: 'none',
36 | },
37 | };
38 | });
39 | const avatarClassName = useEmotionCss(({ token }) => {
40 | return {
41 | color: token.colorPrimary,
42 | verticalAlign: 'top',
43 | background: setAlpha(token.colorBgContainer, 0.85),
44 | [`@media only screen and (max-width: ${token.screenMD}px)`]: {
45 | margin: 0,
46 | },
47 | };
48 | });
49 | const actionClassName = useEmotionCss(({ token }) => {
50 | return {
51 | display: 'flex',
52 | height: '48px',
53 | marginLeft: 'auto',
54 | overflow: 'hidden',
55 | alignItems: 'center',
56 | padding: '0 8px',
57 | cursor: 'pointer',
58 | borderRadius: token.borderRadius,
59 | '&:hover': {
60 | backgroundColor: token.colorBgTextHover,
61 | },
62 | };
63 | });
64 |
65 | return (
66 |
67 | {(avatar || name) && (
68 |
69 |
70 | {avatar && (
71 |
72 | )}
73 | {name && {name}}
74 |
75 |
76 | )}
77 |
78 | );
79 | };
80 |
81 | export default RightContent;
82 |
--------------------------------------------------------------------------------
/src/components/Row/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Row as AntdRow, RowProps as AntdRowProps } from 'antd';
3 | import Render from '@/components/Render';
4 |
5 | export interface RowProps {
6 | body?: any;
7 | data?: any;
8 | callback?: any;
9 | }
10 |
11 | const Row: React.FC<
12 | RowProps & AntdRowProps & React.RefAttributes
13 | > = (props) => {
14 | const { body, data, callback } = { ...props };
15 | return (
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | export default Row;
23 |
--------------------------------------------------------------------------------
/src/components/Statistic/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Statistic as AntStatistic, StatisticProps } from 'antd';
3 |
4 | const Statistic: React.FC = (props) => {
5 | return ;
6 | };
7 |
8 | export default Statistic;
9 |
--------------------------------------------------------------------------------
/src/components/StatisticCard/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Render from '@/components/Render';
3 | import { StatisticCard as AntStatisticCard } from '@ant-design/pro-components';
4 |
5 | const StatisticCard: React.FC = (props: any) => {
6 | return (
7 | }
10 | />
11 | );
12 | };
13 |
14 | export default StatisticCard;
15 |
--------------------------------------------------------------------------------
/src/components/Table/Editable.less:
--------------------------------------------------------------------------------
1 | .editableCellValueWrap {
2 | padding: 5px 12px;
3 | cursor: pointer;
4 | }
5 |
6 | .editableCellValueWrap:hover {
7 | border: 1px solid #d9d9d9;
8 | border-radius: 2px;
9 | padding: 4px 11px;
10 | min-height: 32px;
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/Table/index.less:
--------------------------------------------------------------------------------
1 | .oddTr {
2 | background: #fafafa;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/Tabs/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import type { TabsProps } from 'antd';
3 | import { ProCard, ProCardTabsProps } from '@ant-design/pro-components';
4 | import Render from '@/components/Render';
5 | import { useModel } from '@umijs/max';
6 |
7 | export interface TabsExtendProps {
8 | component?: string;
9 | componentkey?: string;
10 | tabPanes?: any;
11 | data?: any;
12 | callback?: any;
13 | }
14 |
15 | const Tabs: React.FC = (props) => {
16 | const {
17 | centered,
18 | defaultActiveKey,
19 | size,
20 | style,
21 | tabBarGutter,
22 | tabBarStyle,
23 | tabPosition,
24 | type,
25 | tabBarExtraContent,
26 | tabPanes,
27 | data,
28 | callback,
29 | } = { ...props };
30 |
31 | const { tabs, setTabs } = useModel('tabs');
32 |
33 | useEffect(() => {
34 | setTabs({
35 | itemNum: tabPanes.length,
36 | activeKey: 0,
37 | });
38 | }, []);
39 |
40 | const items: TabsProps['items'] = tabPanes.map((tab: any, index: number) => {
41 | return {
42 | key: index,
43 | label: tab.title,
44 | children: ,
45 | };
46 | });
47 |
48 | return (
49 |
62 | ),
63 | items: items,
64 | onChange: (key) => {
65 | setTabs({
66 | itemNum: tabPanes.length,
67 | activeKey: key,
68 | });
69 | },
70 | }}
71 | />
72 | );
73 | };
74 |
75 | export default Tabs;
76 |
--------------------------------------------------------------------------------
/src/components/Text/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Render from '@/components/Render';
3 | import { Typography } from 'antd';
4 |
5 | const Text: React.FC = (props: any) => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
13 | export default Text;
14 |
--------------------------------------------------------------------------------
/src/components/Title/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Render from '@/components/Render';
3 | import { Typography } from 'antd';
4 |
5 | const Title: React.FC = (props: any) => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
13 | export default Title;
14 |
--------------------------------------------------------------------------------
/src/components/Typography/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Render from '@/components/Render';
3 | import { Typography as AntTypography } from 'antd';
4 |
5 | const Typography: React.FC = (props: any) => {
6 | return (
7 |
8 |
9 |
10 | );
11 | };
12 |
13 | export default Typography;
14 |
--------------------------------------------------------------------------------
/src/components/View/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Render from '@/components/Render';
3 |
4 | const View: React.FC = (props: any) => {
5 | return (
6 |
7 |
8 |
9 | );
10 | };
11 |
12 | export default View;
13 |
--------------------------------------------------------------------------------
/src/components/When/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Render from '@/components/Render';
3 | import tplEngine from '@/utils/template';
4 |
5 | const When: React.FC = (props: any) => {
6 | const componentRender = () => {
7 | return props?.items?.map((item: any, index: number) => {
8 | if (tplEngine(item.condition, props.data) === 'true') {
9 | return (
10 |
16 | );
17 | }
18 | return null;
19 | });
20 | };
21 |
22 | return componentRender();
23 | };
24 |
25 | export default When;
26 |
--------------------------------------------------------------------------------
/src/components/index.tsx:
--------------------------------------------------------------------------------
1 | import Action from '@/components/Action';
2 | import { Line } from '@/components/Chart';
3 | import Col from '@/components/Col';
4 | import Container from '@/components/Container';
5 | import Descriptions from '@/components/Descriptions';
6 | import Divider from '@/components/Divider';
7 | import Drawer from '@/components/Drawer';
8 | import Dropdown from '@/components/Dropdown';
9 | import { Form, Field } from '@/components/Form';
10 | import Layout from '@/components/Layout';
11 | import List from '@/components/List';
12 | import Login from '@/components/Login';
13 | import Menu from '@/components/Menu';
14 | import Modal from '@/components/Modal';
15 | import Page from '@/components/Page';
16 | import PageContainer from '@/components/PageContainer';
17 | import Paragraph from '@/components/Paragraph';
18 | import Row from '@/components/Row';
19 | import Statistic from '@/components/Statistic';
20 | import StatisticCard from '@/components/StatisticCard';
21 | import Table from '@/components/Table';
22 | import Tabs from '@/components/Tabs';
23 | import Text from '@/components/Text';
24 | import Title from '@/components/Title';
25 | import Typography from '@/components/Typography';
26 | import View from '@/components/View';
27 | import When from '@/components/When';
28 | import { DefaultFooter } from '@ant-design/pro-components';
29 | import Icon from '@/components/Icon';
30 | import Order from '@/components/Order';
31 |
32 | const fieldName =
33 | 'textField|passwordField|textAreaField|inputNumberField|\
34 | iconField|idField|hiddenField|checkboxField|radioField|imageField|\
35 | fileField|switchField|selectField|treeField|cascaderField|\
36 | dateField|weekField|monthField|quarterField|yearField|datetimeField|\
37 | dateRangeField|datetimeRangeField|timeField|timeRangeField|displayField|\
38 | editorField|searchField|mapField|geofenceField|listField|groupField|selects|\
39 | treeSelectField|spaceField|compactField|fieldsetField|dependencyField|transferField|\
40 | imageCaptchaField|smsCaptchaField|imagePickerField|skuField|';
41 |
42 | const components = [
43 | { name: fieldName, component: },
44 | { name: 'page', component: },
45 | { name: 'login', component: },
46 | { name: 'layout', component: },
47 | { name: 'pageContainer', component: },
48 | { name: 'row', component:
},
49 | { name: 'col', component: },
50 | { name: 'container', component: },
51 | { name: 'statistic', component: },
52 | { name: 'descriptions', component: },
53 | { name: 'divider', component: },
54 | { name: 'typography', component: },
55 | { name: 'paragraph', component: },
56 | { name: 'title', component: },
57 | { name: 'text', component: },
58 | { name: 'action', component: },
59 | { name: 'drawer', component: },
60 | { name: 'modal', component: },
61 | { name: 'line', component: },
62 | { name: 'footer', component: },
63 | { name: 'dropdown', component: },
64 | { name: 'form', component: },
65 | { name: 'list', component:
},
66 | { name: 'menu', component: },
67 | { name: 'statisticCard', component: },
68 | { name: 'table', component: },
69 | { name: 'tabs', component: },
70 | { name: 'view', component: },
71 | { name: 'when', component: },
72 | { name: 'icon', component: },
73 | { name: 'order', component: },
74 | ];
75 |
76 | export default components;
77 |
--------------------------------------------------------------------------------
/src/global.less:
--------------------------------------------------------------------------------
1 | html,
2 | body,
3 | #root {
4 | height: 100%;
5 | margin: 0;
6 | padding: 0;
7 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
8 | 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji',
9 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
10 | }
11 |
12 | .colorWeak {
13 | filter: invert(80%);
14 | }
15 |
16 | .ant-layout {
17 | min-height: 100vh;
18 | }
19 |
20 | .ant-pro-sider.ant-layout-sider.ant-pro-sider-fixed {
21 | left: unset;
22 | }
23 |
24 | canvas {
25 | display: block;
26 | }
27 |
28 | body {
29 | text-rendering: optimizelegibility;
30 | -webkit-font-smoothing: antialiased;
31 | -moz-osx-font-smoothing: grayscale;
32 | }
33 |
34 | ul,
35 | ol {
36 | list-style: none;
37 | }
38 |
39 | @media (max-width: 768px) {
40 | .ant-table {
41 | width: 100%;
42 | overflow-x: auto;
43 |
44 | &-thead > tr,
45 | &-tbody > tr {
46 | > th,
47 | > td {
48 | white-space: pre;
49 |
50 | > span {
51 | display: block;
52 | }
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/global.tsx:
--------------------------------------------------------------------------------
1 | // 全局方法,可以放数据统计等脚本
2 | console.log('welcome to use quark ui');
3 |
--------------------------------------------------------------------------------
/src/models/buttonLoading.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | const useButtonLoading = () => {
4 | const [buttonLoadings, setButtonLoadings] = useState([]);
5 |
6 | return {
7 | buttonLoadings,
8 | setButtonLoadings,
9 | };
10 | };
11 |
12 | export default useButtonLoading;
13 |
--------------------------------------------------------------------------------
/src/models/component.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | const useComponent = () => {
4 | const [components, setComponents] = useState({});
5 |
6 | return {
7 | components,
8 | setComponents,
9 | };
10 | };
11 |
12 | export default useComponent;
13 |
--------------------------------------------------------------------------------
/src/models/formFields.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | const useFormFields = () => {
4 | const [fields, setFields] = useState({});
5 |
6 | return {
7 | fields,
8 | setFields,
9 | };
10 | };
11 |
12 | export default useFormFields;
13 |
--------------------------------------------------------------------------------
/src/models/object.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | const useObject = () => {
4 | const [object, setObject] = useState({});
5 |
6 | return {
7 | object,
8 | setObject,
9 | };
10 | };
11 |
12 | export default useObject;
13 |
--------------------------------------------------------------------------------
/src/models/pageLoading.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | const usePageLoading = () => {
4 | const [pageLoading, setPageLoading] = useState(false);
5 |
6 | return {
7 | pageLoading,
8 | setPageLoading,
9 | };
10 | };
11 |
12 | export default usePageLoading;
13 |
--------------------------------------------------------------------------------
/src/models/submit.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | const useSubmit = () => {
4 | const [submit, setSubmit] = useState({});
5 |
6 | return {
7 | submit,
8 | setSubmit,
9 | };
10 | };
11 |
12 | export default useSubmit;
13 |
--------------------------------------------------------------------------------
/src/models/tabs.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | const useTabs = () => {
4 | const [tabs, setTabs] = useState({
5 | itemNum: 0,
6 | activeKey: 0,
7 | });
8 |
9 | return {
10 | tabs,
11 | setTabs,
12 | };
13 | };
14 |
15 | export default useTabs;
16 |
--------------------------------------------------------------------------------
/src/pages/Index/index.less:
--------------------------------------------------------------------------------
1 | .loading {
2 | margin: 20px 0;
3 | margin-bottom: 20px;
4 | padding: 30px 50px;
5 | text-align: center;
6 | }
7 |
--------------------------------------------------------------------------------
/src/pages/Index/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { useLocation, request } from '@umijs/max';
3 | import { ConfigProvider, App, Spin } from 'antd';
4 | import Engine from '@/components/Engine';
5 | import locale from 'antd/locale/zh_CN';
6 | import dayjs from 'dayjs';
7 | import 'dayjs/locale/zh-cn';
8 | import qs from 'query-string';
9 | import styles from './index.less';
10 |
11 | const Index: React.FC = () => {
12 | const location = useLocation();
13 | const query = qs.parse(location.search);
14 | const [api, setApi] = useState(String);
15 | dayjs.locale('zh-cn');
16 | const getApi = async () => {
17 | let api: any = process.env.UMI_APP_DEFAULT_URL;
18 | if (query?.api) {
19 | api = query.api;
20 | }
21 |
22 | setApi(api);
23 | };
24 |
25 | useEffect(() => {
26 | getApi();
27 | }, [location.search]);
28 |
29 | return (
30 |
31 |
32 | {api ? (
33 |
34 | ) : (
35 |
36 |
37 |
38 | )}
39 |
40 |
41 | );
42 | };
43 |
44 | export default Index;
45 |
--------------------------------------------------------------------------------
/src/pages/Test/index.less:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/quarkcloudio/quark-ui/8dc37e388c16feadfb71d9727fcd219f4fc87194/src/pages/Test/index.less
--------------------------------------------------------------------------------
/src/pages/Test/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Form } from 'antd';
3 | import ImagePicker from '@/components/Form/Field/ImagePicker';
4 | import {
5 | ProForm,
6 | ProCard,
7 | PageContainer,
8 | ProFormItem,
9 | } from '@ant-design/pro-components';
10 |
11 | const Index: React.FC = () => {
12 | return (
13 |
14 |
15 | {
17 | console.log(values);
18 | }}
19 | layout="horizontal"
20 | >
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | };
29 |
30 | export default Index;
31 |
--------------------------------------------------------------------------------
/src/requestConfig.ts:
--------------------------------------------------------------------------------
1 | import type { RequestOptions } from '@@/plugin-request/request';
2 | import type { RequestConfig } from '@umijs/max';
3 | import { history } from '@umijs/max';
4 | import { message, notification } from 'antd';
5 |
6 | // 错误处理方案: 错误类型
7 | enum ErrorShowType {
8 | SILENT = 0,
9 | WARN_MESSAGE = 1,
10 | ERROR_MESSAGE = 2,
11 | NOTIFICATION = 3,
12 | REDIRECT = 9,
13 | }
14 | // 与后端约定的响应数据格式
15 | interface ResponseStructure {
16 | success: boolean;
17 | data: any;
18 | errorCode?: number;
19 | errorMessage?: string;
20 | showType?: ErrorShowType;
21 | }
22 |
23 | /**
24 | * @name 错误处理
25 | * pro 自带的错误处理, 可以在这里做自己的改动
26 | * @doc https://umijs.org/docs/max/request#配置
27 | */
28 | export const requestConfig: RequestConfig = {
29 | // 错误处理: umi@3 的错误处理方案。
30 | errorConfig: {
31 | // 错误抛出
32 | errorThrower: (res) => {
33 | const { success, data, errorCode, errorMessage, showType } =
34 | res as unknown as ResponseStructure;
35 | if (!success) {
36 | const error: any = new Error(errorMessage);
37 | error.name = 'BizError';
38 | error.info = { errorCode, errorMessage, showType, data };
39 | throw error; // 抛出自制的错误
40 | }
41 | },
42 | // 错误接收及处理
43 | errorHandler: (error: any, opts: any) => {
44 | if (opts?.skipErrorHandler) throw error;
45 | // 我们的 errorThrower 抛出的错误。
46 | if (error.name === 'BizError') {
47 | const errorInfo: ResponseStructure | undefined = error.info;
48 | if (errorInfo) {
49 | const { errorMessage, errorCode } = errorInfo;
50 | switch (errorInfo.showType) {
51 | case ErrorShowType.SILENT:
52 | // do nothing
53 | break;
54 | case ErrorShowType.WARN_MESSAGE:
55 | message.warning(errorMessage);
56 | break;
57 | case ErrorShowType.ERROR_MESSAGE:
58 | message.error(errorMessage);
59 | break;
60 | case ErrorShowType.NOTIFICATION:
61 | notification.open({
62 | description: errorMessage,
63 | message: errorCode,
64 | });
65 | break;
66 | case ErrorShowType.REDIRECT:
67 | // TODO: redirect
68 | break;
69 | default:
70 | message.error(errorMessage);
71 | }
72 | }
73 | } else if (error.response) {
74 | // token过期后,跳转到登录页
75 | if (error.response.status === 401) {
76 | history.push('/');
77 | return;
78 | }
79 |
80 | // Axios 的错误
81 | // 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
82 | message.error(`Response status:${error.response.status}`);
83 | } else if (error.request) {
84 | // 请求已经成功发起,但没有收到响应
85 | // \`error.request\` 在浏览器中是 XMLHttpRequest 的实例,
86 | // 而在node.js中是 http.ClientRequest 的实例
87 | message.error('None response! Please retry.');
88 | } else {
89 | // 发送请求时出了点问题
90 | message.error('Request error, please retry.');
91 | }
92 | },
93 | },
94 |
95 | // 请求拦截器
96 | requestInterceptors: [
97 | (config: RequestOptions) => {
98 | config.headers = {
99 | Accept: 'application/json',
100 | Authorization: `Bearer ${
101 | localStorage.getItem(process.env.UMI_APP_TOKEN ?? 'token') ?? ''
102 | }`,
103 | };
104 | const url = config?.url;
105 | return { ...config, url };
106 | },
107 | ],
108 |
109 | // 响应拦截器
110 | responseInterceptors: [
111 | (response) => {
112 | // 拦截响应数据,进行个性化处理
113 | const { data } = response as unknown as ResponseStructure;
114 |
115 | if (data?.success === false) {
116 | message.error('请求失败!');
117 | }
118 | return response;
119 | },
120 | ],
121 | };
122 |
--------------------------------------------------------------------------------
/src/services/action.ts:
--------------------------------------------------------------------------------
1 | import { request } from '@umijs/max';
2 | import qs from 'query-string';
3 |
4 | export async function get(params: any) {
5 | let url = params.url;
6 | let data = params.data;
7 |
8 | if (url.indexOf('?') !== -1) {
9 | url = `${url}&${qs.stringify(data)}`;
10 | } else {
11 | url = `${url}?${qs.stringify(data)}`;
12 | }
13 |
14 | return request(url);
15 | }
16 |
17 | export async function post(params: any) {
18 | let url = params.url;
19 | let data = params.data;
20 |
21 | return request(url, {
22 | method: 'post',
23 | data: data,
24 | });
25 | }
26 |
--------------------------------------------------------------------------------
/src/utils/component.ts:
--------------------------------------------------------------------------------
1 | // 根据key查找组件
2 | const getComponent: any = (data: any, key: string) => {
3 | let component: any = {};
4 | if (data === undefined || data === null) {
5 | return component;
6 | }
7 | if (typeof data === 'string' || typeof data === 'number') {
8 | return component;
9 | }
10 | if (data.componentkey === key) {
11 | return data;
12 | }
13 | if (data.hasOwnProperty('tabPanes')) {
14 | return getComponent(data.tabPanes, key); // tab做特殊处理
15 | }
16 | if (data.hasOwnProperty('body')) {
17 | return getComponent(data.body, key);
18 | }
19 | if (data.hasOwnProperty(0)) {
20 | data.forEach((item: any) => {
21 | let subComponent = getComponent(item, key);
22 | let componentKeys = Object.keys(subComponent);
23 | if (componentKeys.length > 0) {
24 | component = subComponent;
25 | }
26 | });
27 | }
28 |
29 | return component;
30 | };
31 |
32 | export { getComponent };
33 |
--------------------------------------------------------------------------------
/src/utils/reload.ts:
--------------------------------------------------------------------------------
1 | import { history } from '@umijs/max';
2 |
3 | const useReload = () => {
4 | let { search }: any = history.location;
5 | const timestamp = new Date().getTime().toString();
6 |
7 | if (search.indexOf('timestamp') > -1) {
8 | search = search.replace(/timestamp=(\d*)/, `timestamp=${timestamp}`);
9 | } else {
10 | search += '×tamp=' + timestamp;
11 | }
12 |
13 | // @ts-ignore
14 | history.push({
15 | pathname: history.location.pathname,
16 | search: search,
17 | });
18 | };
19 |
20 | export default useReload;
21 |
--------------------------------------------------------------------------------
/src/utils/template.ts:
--------------------------------------------------------------------------------
1 | import template from 'lodash.template';
2 |
3 | // 模板引擎
4 | const tplEngine = (tpl: any, data: any) => {
5 | let result = tpl;
6 | if (!tpl || !data) {
7 | return result;
8 | }
9 |
10 | let keys = Object.keys(data);
11 | if (keys.length === 0) {
12 | return result;
13 | }
14 |
15 | try {
16 | const compiled = template(tpl);
17 | result = compiled(data);
18 | } catch (error) {
19 | console.error('引擎渲染失败:', error);
20 | return '';
21 | }
22 |
23 | return result;
24 | };
25 |
26 | export default tplEngine;
27 |
--------------------------------------------------------------------------------
/src/utils/trim.ts:
--------------------------------------------------------------------------------
1 | const trim = (
2 | trimString: string,
3 | trimChar: string = ' ',
4 | type: string = '',
5 | ) => {
6 | if (trimChar) {
7 | if (type === 'left') {
8 | return trimString.replace(new RegExp('^\\' + trimChar + '+', 'g'), '');
9 | } else if (type === 'right') {
10 | return trimString.replace(new RegExp('\\' + trimChar + '+$', 'g'), '');
11 | }
12 | return trimString.replace(
13 | new RegExp('^\\' + trimChar + '+|\\' + trimChar + '+$', 'g'),
14 | '',
15 | );
16 | }
17 |
18 | return trimString.replace(/^\s+|\s+$/g, '');
19 | };
20 |
21 | export default trim;
22 |
--------------------------------------------------------------------------------
/src/utils/url.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 将对象添加当作参数拼接到URL上面
3 | * @param baseUrl 需要拼接的url
4 | * @param obj 参数对象
5 | * @returns {string} 拼接后的对象
6 | * 例子:
7 | * let obj = {a: '3', b: '4'}
8 | * setObjToUrlParams('www.baidu.com', obj)
9 | * ==>www.baidu.com?a=3&b=4
10 | */
11 | export const setObjToUrlParams = (baseUrl: string, obj: Record): string => {
12 | let parameters = ''
13 | let url = ''
14 | for (const key in obj) {
15 | parameters += `${key}=${obj[key]}&`
16 | }
17 | parameters = parameters.replace(/&$/, '')
18 | if (/\?$/.test(baseUrl)) {
19 | url = baseUrl + parameters
20 | } else {
21 | url = baseUrl.replace(/\/?$/, '?') + parameters
22 | }
23 | return url
24 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./src/.umi/tsconfig.json"
3 | }
4 |
--------------------------------------------------------------------------------
/typings.d.ts:
--------------------------------------------------------------------------------
1 | import '@umijs/max/typings';
2 |
--------------------------------------------------------------------------------
/unocss.config.ts:
--------------------------------------------------------------------------------
1 | // unocss.config.ts
2 |
3 | import {defineConfig, presetAttributify, presetUno} from 'unocss';
4 |
5 | export function createConfig({strict = true, dev = true} = {}) {
6 | return defineConfig({
7 | rules: [
8 | [/^line-(\d+)$/, ([, d]) => ({
9 | 'overflow': 'hidden', //溢出内容隐藏
10 | 'text-overflow': 'ellipsis', //文本溢出部分用省略号表示
11 | 'display': '-webkit-box', //特别显示模式
12 | '-webkit-line-clamp': d, //行数
13 | 'line-clamp': d,
14 | '-webkit-box-orient': 'vertical', //盒子中内容竖直排列
15 | })],
16 | ],
17 | envMode: dev ? 'dev' : 'build', presets: [presetAttributify({strict}), presetUno()],
18 | });
19 | }
20 |
21 | export default createConfig();
--------------------------------------------------------------------------------