Commit 381d714f authored by wuhao's avatar wuhao 🎯

asder

parents
module.exports = {
extends: require.resolve('@umijs/max/eslint'),
};
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
**/node_modules
# roadhog-api-doc ignore
/src/utils/request-temp.js
_roadhog-api-doc
# production
/dist
# misc
.DS_Store
npm-debug.log*
yarn-error.log
/coverage
.idea
yarn.lock
package-lock.json
*bak
.vscode
# visual studio code
.history
*.log
functions/*
.temp/**
# umi
.umi
.umi-production
.umi-test
# screenshot
screenshot
.firebase
.eslintcache
build
{
"*.{md,json}": [
"prettier --cache --write"
],
"*.{js,jsx}": [
"max lint --fix --eslint-only",
"prettier --cache --write"
],
"*.{css,less}": [
"max lint --fix --stylelint-only",
"prettier --cache --write"
],
"*.ts?(x)": [
"max lint --fix --eslint-only",
"prettier --cache --parser=typescript --write"
]
}
registry=https://registry.npmmirror.com
node_modules
.umi
.umi-production
{
"printWidth": 80,
"singleQuote": true,
"trailingComma": "all",
"proseWrap": "never",
"overrides": [{ "files": ".prettierrc", "options": { "parser": "json" } }],
"plugins": ["prettier-plugin-organize-imports", "prettier-plugin-packagejson"]
}
module.exports = {
extends: require.resolve('@umijs/max/stylelint'),
};
# README
`@umijs/max` 模板项目,更多功能参考 [Umi Max 简介](https://umijs.org/docs/max/introduce)
export default [
{
path: '/login',
layout: false,
component: './login',
},
{
path: '/',
component: '@/layouts/BasicLayout',
routes: [
{
path: '/',
redirect: '/home',
},
{
name: '首页',
path: '/home',
component: './home',
icon: 'HomeOutlined',
},
],
},
{
name: '权限演示',
path: '/access',
component: './access',
routes: [
{
name: '测试',
path: '/access/jiagou',
component: './access',
},
],
},
];
import { defineConfig } from '@umijs/max';
import proxy from './proxy';
import authRoutes from './authRoutes';
import { theme } from 'antd';
const { REACT_APP_ENV } = process.env;
export default defineConfig({
antd: {
theme: {
algorithm: theme.darkAlgorithm,
token: {
colorPrimary: '#4ddaec',
},
},
},
access: {},
model: {},
initialState: {},
request: {},
// layout: {
// title: '@umijs/max',
// },
layout: false,
routes: authRoutes,
publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
npmClient: 'yarn',
proxy: proxy[REACT_APP_ENV || 'dev'],
});
const Settings = {
name: 'BASEPRO',
logo: './logo.png',
proxypath: '/sfx',
put_on_record:
'江苏南高智能装备创新中心有限公司 版权所有 备案号: 苏ICP备18015471号',
output_path: 'prods',
dev_env: 'http://192.168.40.18/wms/',
test_env: 'http://192.168.40.18/wms/',
pro_env: 'http://192.168.40.18/wms/',
};
export default Settings;
/**
* 在生产环境 代理是无法生效的,所以这里没有生产环境的配置
* -------------------------------
* The agent cannot take effect in the production environment
* so there is no configuration of the production environment
* For details, please see
* https://pro.ant.design/docs/deploy
*/
import defaultSetting from './defaultSettings';
let rewrite = `^${defaultSetting.proxypath}`;
export default {
dev: {
[defaultSetting.proxypath]: {
// 要代理的地址
target: defaultSetting?.dev_env,
changeOrigin: true,
pathRewrite: {
[rewrite]: '',
},
},
'/staticfile/': {
target: 'http://192.168.40.2/',
changeOrigin: true,
pathRewrite: {
'^/staticfile': '',
},
},
},
test: {
[defaultSetting.proxypath]: {
// 要代理的地址
target: defaultSetting?.test_env,
changeOrigin: true,
pathRewrite: {
[rewrite]: '',
},
},
},
pre: {
[defaultSetting.proxypath]: {
// 要代理的地址
target: defaultSetting?.pro_env,
changeOrigin: true,
pathRewrite: {
[rewrite]: '',
},
},
},
};
\ No newline at end of file
const users = [
{ id: 0, name: 'Umi', nickName: 'U', gender: 'MALE' },
{ id: 1, name: 'Fish', nickName: 'B', gender: 'FEMALE' },
];
export default {
'GET /api/v1/queryUserList': (req: any, res: any) => {
res.json({
success: true,
data: { list: users },
errorCode: 0,
});
},
'PUT /api/v1/user/': (req: any, res: any) => {
res.json({
success: true,
errorCode: 0,
});
},
};
{
"private": true,
"author": "wuhao930406 <1148547900@qq.com>",
"scripts": {
"build": "max build",
"dev": "max dev",
"format": "prettier --cache --write .",
"postinstall": "max setup",
"prepare": "husky install",
"setup": "max setup",
"start": "npm run dev"
},
"dependencies": {
"@ant-design/icons": "^5.0.0",
"@ant-design/pro-components": "^2.3.52",
"@umijs/max": "^4.0.42",
"ahooks": "^3.7.4",
"antd": "^5.1.6",
"antd-img-crop": "^4.5.2",
"antd-mobile": "^5.1.4",
"crypto-js": "^4.1.1",
"umi-request": "^1.4.0"
},
"devDependencies": {
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"husky": "^8.0.1",
"lint-staged": "^13.0.3",
"prettier": "^2.7.1",
"prettier-plugin-organize-imports": "^2",
"prettier-plugin-packagejson": "^2",
"typescript": "^4.1.2"
}
}
File added
export default (initialState) => {
// 在这里按照初始化数据定义项目中的权限,统一管理
// 参考文档 https://next.umijs.org/docs/max/access
const canSeeAdmin = !!(
initialState && initialState.name !== 'dontHaveAccess'
);
return {
canSeeAdmin,
};
};
// 运行时配置
// 全局初始化数据配置,用于 Layout 用户信息和权限初始化
// 更多信息见文档:https://next.umijs.org/docs/api/runtime-config#getinitialstate
import Settings from '../config/defaultSettings.js';
import { Input, Space, Button } from 'antd';
import {
MenuFoldOutlined,
SearchOutlined,
MenuUnfoldOutlined,
} from '@ant-design/icons';
import { RuntimeAntdConfig } from 'umi';
import { theme } from 'antd';
export const antd = (memo) => {
memo.theme ||= {};
memo.theme.algorithm = theme.darkAlgorithm;
return memo;
};
export async function getInitialState() {
return {
name: '@umijs/max',
collapsed: false,
};
}
// const filterByMenuData = (data, keyWord) =>
// data
// .map((item) => {
// if (
// (item.name && item.name.includes(keyWord)) ||
// filterByMenuData(item.children || [], keyWord).length > 0
// ) {
// return Object.assign(Object.assign({}, item), {
// children: filterByMenuData(item.children || [], keyWord),
// });
// }
// return undefined;
// })
// .filter((item) => item);
// export const layout = ({ initialState, setInitialState }) => {
// return {
// logo: Settings?.logo,
// collapsed: initialState?.collapsed,
// menuProps: {},
// fixedHeader: true,
// layout: 'mix',
// siderMenuType: 'group',
// onCollapse: (collapsed) => {
// setInitialState((s) => ({
// ...s,
// collapsed,
// }));
// },
// onPageChange: () => {
// setInitialState((s) => ({
// ...s,
// collapsed: true,
// }));
// },
// menu: {
// locale: false,
// maskClosable: true,
// },
// collapsedButtonRender: false,
// menuExtraRender: (ps) => (
// <div className="center inmargin">
// {ps?.collapsed ? (
// <Button
// style={{ width: 32, height: 32, flexShrink: 0 }}
// onClick={() => {
// setInitialState((s) => ({
// ...s,
// collapsed: false,
// }));
// }}
// icon={<MenuUnfoldOutlined />}
// />
// ) : (
// <>
// <Input
// allowClear
// style={{
// borderRadius: 4,
// width: '100%',
// boxShadow: '0 0 12px #d0d0d0',
// }}
// prefix={
// <SearchOutlined
// style={{
// color: 'rgba(0, 0, 0, 0.15)',
// }}
// />
// }
// placeholder="搜索方案"
// bordered={false}
// onChange={(e) => {
// setInitialState((s) => ({
// ...s,
// keyWord: e.target.value,
// }));
// }}
// />
// <Button
// style={{ width: 32, height: 32, marginLeft: 8, flexShrink: 0 }}
// onClick={() => {
// setInitialState((s) => ({
// ...s,
// collapsed: true,
// }));
// }}
// icon={<MenuFoldOutlined />}
// shape="default"
// />
// </>
// )}
// </div>
// ),
// postMenuData: (menus) => {
// return filterByMenuData(menus || [], initialState?.keyWord ?? '');
// },
// };
// };
import React, { useEffect, useState, useRef } from 'react';
import {
Button,
InfiniteScroll,
List,
DotLoading,
SearchBar,
PullToRefresh,
Empty,
} from 'antd-mobile';
import styles from './index.less';
import { doFetch } from '@/utils/doFetch';
import { FilterFilled } from '@ant-design/icons';
import { useAsyncEffect } from 'ahooks';
import { cloneElement } from 'react';
function useDidUpdateEffect(fn, inputs) {
const didMountRef = useRef(false);
useAsyncEffect(async () => {
if (didMountRef.current) await fn();
else didMountRef.current = true;
}, inputs);
}
export default (props) => {
const {
actionRef, //表格动作
formRef, //表单Ref
rowKey, // key
columns = [], //columns
style, //style
path, //接口地址
extraparams, //额外参数
pageSize, //修改默认pageSize
pagination, //分页设置
activeTabKey, //激活的tabKey 拖拽表格唯一标识使用 其他情况用不到
refreshDep, //依赖刷新 (已废弃)
resizeable = true,
iscurrent = true,
children,
} = props;
const [data, setData] = useState([]);
const [hasMore, setHasMore] = useState(true);
const [pageinfo, setpageinfo] = useState({
pageIndex: 1,
pageSize: 8,
});
const [searchparams, setsearchparams] = useState({});
const [open, setopen] = useState(false);
async function getdata(pageinfo) {
const append = await doFetch({
url: path,
params: {
...pageinfo,
...extraparams,
...searchparams,
},
});
const dataes = append?.data?.page?.list ?? append?.data?.dataList ?? [];
setData((val) => [...val, ...dataes]);
return append?.data?.page ?? { hasNextPage: false };
}
//加载更多数据
async function loadMore() {
await setpageinfo((s) => ({
...s,
pageIndex: s?.pageIndex + 1,
}));
}
//刷新
async function reFresh() {
//页码重置 数据清空
await setpageinfo((s) => ({
...s,
pageIndex: 1,
}));
await setData([]);
}
//额外参数改变后重置
useAsyncEffect(async () => {
await reFresh();
}, [extraparams]);
//分页信息改变后调用数据接口
useDidUpdateEffect(async () => {
let page = await getdata(pageinfo);
setHasMore(page?.hasNextPage);
}, [pageinfo]);
return (
<>
<div className={styles.header}>
<div className="center">
<div className={styles.left}>
<div
className="center"
style={{ width: 30, height: 30, marginRight: 8 }}
onClick={() => {
setopen(!open);
}}
>
<FilterFilled />
</div>
<SearchBar
style={{ flex: 1 }}
onChange={(val) => {
setsearchparams((s) => ({
...s,
userName: val,
}));
}}
/>
</div>
<div className={styles.right}>
<Button size="small" color="primary" onClick={reFresh}>
搜索
</Button>
</div>
</div>
<div className={styles.searchbar} style={{ height: open ? '40vh' : 0 }}>
<div></div>
</div>
</div>
{data.length > 0 ? (
<PullToRefresh
onRefresh={async () => {
await reFresh();
}}
>
{data.map((item, index) => cloneElement(children, { item, columns }))}
<InfiniteScroll loadMore={loadMore} hasMore={hasMore} />
</PullToRefresh>
) : (
<PullToRefresh
onRefresh={async () => {
await reFresh();
}}
>
<Empty
style={{ padding: '64px 0' }}
imageStyle={{ width: '30vw' }}
description="暂无数据"
/>
</PullToRefresh>
)}
</>
);
};
/* stylelint-disable rule-empty-line-before */
.header {
background-color: rgb(18 19 33 / 50%);
display: flex;
align-items: center;
position: sticky;
z-index: 122;
top: 45px;
left: 0;
> div {
width: 100%;
padding: 12px;
backdrop-filter: blur(2px);
}
.left {
flex: auto;
display: flex;
justify-content: space-between;
align-items: center;
}
.right {
flex: none;
padding-left: 12px;
}
.searchbar {
position: absolute;
width: 100%;
height: 40vh;
background: rgb(0 0 0 / 10%);
backdrop-filter: blur(2px) !important;
top: 56px;
z-index: 99999;
padding: 0;
transition: all 0.4s;
overflow: hidden;
> div {
margin: 12px;
height: calc(100% - 24px);
background: rgb(255 255 255 / 60%);
border-radius: 8px;
}
}
}
.placeholder {
padding-top: 30vh;
text-align: center;
color: var(--adm-color-weak);
.loadingWrapper {
font-size: 24px;
margin-bottom: 24px;
}
}
/* eslint-disable react-hooks/exhaustive-deps */
import { ProDescriptions } from '@ant-design/pro-components';
import React, { useMemo, useState } from 'react';
import { doFetch } from '@/utils/doFetch';
import PropTypes from 'prop-types';
import styles from './index.less';
import { Col } from 'antd';
const { Item } = ProDescriptions;
function DetailPro(props) {
let { fields } = props;
const [curitem, setcuritem] = useState(props.detailData ?? {}); //全部数据
const dataProps = useMemo(() => {
if (props.detailData) {
setcuritem(props.detailData);
return { dataSource: props.detailData };
} else {
return {
request: async () => {
let res = await doFetch({
url: props.detailpath,
params: props.params,
});
setcuritem(res?.data?.data ?? {});
return {
success: res?.code === '0000',
data: res?.data?.data ?? {},
};
},
};
}
}, [props.detailData]);
return (
<ProDescriptions column={1} {...dataProps} title={null}>
{fields
?.filter((it) => it.valueType !== 'option')
?.map((it, i) => {
const dataIndexs =
!it?.render || it?.valueType === 'option'
? { dataIndex: it.dataIndex, valueType: it.valueType }
: {};
if (it.valueType === 'split') {
return (
<>
<Col span={24}>
<div
className={styles.title}
style={{ borderWidth: i === 0 ? 0 : 1 }}
>
{it.title}
</div>
</Col>
</>
);
}
return (
<Item
span={
it?.span
? it?.span
: fields[i + 1]?.valueType === 'split'
? 3
: 1
}
key={it.dataIndex}
label={it.title}
{...dataIndexs}
>
{it?.render ? it?.render?.(curitem[it.dataIndex], curitem) : ''}
</Item>
);
})}
</ProDescriptions>
);
}
DetailPro.propTypes = {
detailpath: PropTypes.string,
params: PropTypes.object,
detailData: PropTypes.object,
fields: PropTypes.array,
};
export default DetailPro;
.title {
position: relative;
width: 100%;
padding-top: 8px;
padding-left: 12px;
font-weight: bolder;
&::before {
position: absolute;
top: 11px;
left: 0;
width: 3px;
height: 16px;
background-color: @primary-color;
border-radius: 4px;
content: '';
}
&::after {
position: absolute;
top: 18px;
right: 0;
width: calc(100% - 160px);
height: 1px;
border-bottom: 1px dotted rgb(0 0 0 / 10%);
border-radius: 4px;
content: '';
}
}
import InitForm from '../InitForm';
import { Drawer } from 'antd';
import React, { useState, memo } from 'react';
import DetailPro from '../DetailPro';
import PropTypes from 'prop-types';
function DrawerPro(props) {
let newProps = { ...props };
delete newProps.children;
const detailprops = {
title: props.title,
detailpath: props.detailpath,
fields: props.fields,
detailData: props.detailData,
params: props.params,
};
const fields = newProps?.fields?.filter?.((it, i) => {
return it?.valueType != 'option';
});
return (
<Drawer
maskClosable={false}
placement="right"
closable={true}
width={'100%'}
destroyOnClose={true}
{...props}
>
{props.val === 'only' && props.children}
{props.val === 'only' ? null : props.val === 'detail' ? (
<DetailPro {...detailprops} />
) : (
<InitForm {...newProps} fields={fields} />
)}
</Drawer>
);
}
DrawerPro.propTypes = {
detailpath: PropTypes.string, //详情
params: PropTypes.object, //详情|表单编辑 参数
fields: PropTypes.array, //详情列表
detailData: PropTypes.object, //详情数据
};
export default DrawerPro;
.title {
margin: 0 auto;
font-weight: 200;
}
import { Layout, Row, Typography } from 'antd';
import React from 'react';
import styles from './Guide.less';
interface Props {
name: string;
}
// 脚手架示例组件
const Guide: React.FC<Props> = (props) => {
const { name } = props;
return (
<Layout>
<Row>
<Typography.Title level={3} className={styles.title}>
欢迎使用 <strong>{name}</strong>
</Typography.Title>
</Row>
</Layout>
);
};
export default Guide;
import Guide from './Guide';
export default Guide;
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable react-hooks/rules-of-hooks */
import React, { useEffect, useRef, useState, memo, useMemo } from 'react';
import { EditableProTable } from '@ant-design/pro-components';
import { Tooltip } from 'antd';
import { doFetch } from '@/utils/doFetch';
const EditTable = (props) => {
const {
actionRef, //表格动作
formRef, //表单Ref
rowKey, // key
columns = [], //columns
style, //style
path, //接口地址
extraparams, //额外参数
pageSize, //修改默认pageSize
pagination, //分页设置
x, //横向滚动
refreshDep, //依赖刷新 (已废弃)
} = props;
const actionRefs = actionRef ?? useRef(),
formRefs = formRef ?? useRef(),
ifspagination = pagination == 'false' || pagination === false,
[size, setsize] = useState('small');
//调用接口
const request = async (params, sort, filter) => {
if (!path) return;
let newparams = {
...params,
...extraparams, //父组件传参
pageIndex: params.current,
pageSize: params.pageSize || pageSize,
};
delete newparams.current;
if (ifspagination) {
delete newparams.pageIndex;
delete newparams.pageSize;
}
const result = await doFetch({ url: path, params: newparams });
//分页结果
let data = result?.data?.page?.list,
success = true,
total = result?.data?.page?.total;
//不带分页获取结果
if (ifspagination || !data) {
data = result?.data?.dataList;
total = result?.data?.dataList?.length;
}
//存在默认选中向上返回选中值
return {
data,
success,
total,
};
};
let columncs = useMemo(() => {
return columns.map((item, index) => {
let it = { ...item };
let itemwidth = it.width ? it.width : 'auto';
let options = {};
if (it.valueType == 'select' || it.valueType == 'checkbox') {
if (Array.isArray(it.options)) {
options = {
fieldProps: {
options: [...it.options],
},
};
} else if (it.options) {
options = {
request: async (params) => {
let list = await doFetch({ url: it?.options?.path, params: it?.options?.params });
return list.data.dataList;
},
};
}
}
if (it.valueType == 'option') {
options = {
key: 'option',
dataIndex: 'option',
fixed: 'right',
};
}
if (!it.render) {
options = {
...options,
render: (text, row) => {
return (
<Tooltip title={row[it.dataIndex]} placement="topLeft">
<span className="table-cell">{row[it.dataIndex] ?? '-'}</span>
</Tooltip>
);
},
};
}
options = {
...options,
width: itemwidth,
};
delete it.formItemProps;
return {
...it,
...options,
};
});
}, [columns]);
return (
<EditableProTable
{...props}
recordCreatorProps={false}
size={size}
onSubmit={(params) => {
console.log(params, 'onSubmit');
}}
onSizeChange={(size) => {
localStorage.setItem('size', size); //设置全局表格规格缓存
setsize(size);
}}
columns={columncs ?? []}
style={style || {}}
actionRef={actionRefs}
formRef={formRefs}
rowKey={rowKey ?? 'id'} //表格每行数据的key
dateFormatter="string"
request={request}
scroll={
x
? {
x: x,
}
: {}
}
pagination={
ifspagination
? false
: {
showTotal: (total, range) => <span>{total}</span>,
showQuickJumper: true,
showSizeChanger: true,
pageSizeOptions: [5, 10, 15, 30, 50, 100, 200],
defaultPageSize: pageSize || 15,
}
}
editable={{
type: 'multiple',
editableKeys: props?.rowSelection?.selectedRowKeys ?? [],
...props?.editable,
}}
search={{
filterType: 'light', //轻量模式
}}
/>
);
};
export default memo(EditTable);
This diff is collapsed.
import React, { createElement, memo, useRef, useState } from 'react';
import {
ProForm,
ProFormDependency,
ProFormSelect,
ProFormText,
ProFormGroup,
ProFormList,
ProCard,
} from '@ant-design/pro-components';
import moment from 'moment';
import { doFetch } from '@/utils/doFetch';
import styles from './index.less';
import FormItems from './FormItems';
function upperCase(str) {
const newStr = str.slice(0, 1).toUpperCase() + str.slice(1);
return newStr;
}
let FormRender = memo(({ fields = [], colProps, proformRef }) => {
return (
<>
{fields
.filter((it) => it.hideInForm !== true)
.map((item, index) => {
let key = item?.valueType ? upperCase(item?.valueType) : 'Input';
let { hideInForm } = item;
item.formItemProps = item.formItemProps ?? { rules: [] };
if (item.valueType == 'split') {
return (
<div className={styles.title} style={{ borderWidth: index == 0 ? 0 : 1 }}>
{item.title}
</div>
);
}
if (hideInForm && Object.keys(hideInForm)) {
return (
<ProFormDependency name={Object.keys(hideInForm)}>
{(params) => {
let ifs = true;
let res = Object.keys(hideInForm).map((its) => {
if (Array.isArray(hideInForm[its])) {
return !hideInForm[its].includes(params[its]);
} else {
let vals = hideInForm[its].reverse; //取反 即不存在当前数组中的
return vals.indexOf(params[its]) != -1;
}
});
ifs = res.includes(false);
if (ifs) {
return;
} else {
return (
<>
{createElement(FormItems[key], {
item: item,
colProps: colProps,
key: item.dataIndex,
formRef: proformRef,
})}
</>
);
}
}}
</ProFormDependency>
);
} else {
return (
<>
{createElement(FormItems[key], {
item: item,
colProps: colProps,
key: item.dataIndex,
formRef: proformRef,
})}
</>
);
}
})}
</>
);
});
function InitForm({
formRef,
onFinish = (vals) => {
console.log(vals);
},
formKey,
params = {},
detailpath = '',
defaultFormValue = {},
submitter,
fields,
colProps = { xs: 24, sm: 24, md: 12, lg: 12, xl: 12, xxl: 12 },
onValuesChange = (changedValues, allValues) => {
console.log(changedValues, allValues);
},
}) {
let proformRef = useRef();
proformRef = formRef ?? proformRef;
return (
<ProForm
style={{ overflow: 'hidden' }}
formRef={proformRef}
onFinish={onFinish}
formKey={formKey ?? parseInt(Math.random() * 1000000)}
params={params}
submitter={submitter ?? true}
grid={true}
rowProps={{
gutter: 12,
}}
request={async (params) => {
if (detailpath) {
let res = await doFetch({ url: detailpath, params });
return {
...defaultFormValue,
...(res?.data?.data ?? {}),
};
} else {
return {
...defaultFormValue,
};
}
}}
autoFocusFirstInput
onValuesChange={(changedValues, allValues) => {
onValuesChange?.(changedValues, allValues);
}}
>
<FormRender
fields={fields.filter((it) => it.valueType != 'option')}
colProps={colProps}
proformRef={proformRef}
/>
</ProForm>
);
}
export default InitForm;
/* stylelint-disable rule-empty-line-before */
.title {
position: relative;
width: 100%;
margin-bottom: 8px;
padding-left: 16px;
color: #000;
font-weight: bolder;
font-size: 16px;
&::before {
position: absolute;
top: 4px;
left: 7px;
width: 3px;
height: 16px;
background-color: @primary-color;
border-radius: 4px;
content: '';
}
&::after {
position: absolute;
top: 14px;
right: 0;
width: calc(100% - 160px);
height: 1px;
border-bottom: 1px dotted rgb(0 0 0 / 10%);
border-radius: 4px;
content: '';
}
}
import React, { useState } from 'react';
import { Popconfirm, Button } from 'antd';
import { useModel } from '@umijs/max';
function PremButton(props) {
const { initialState, setInitialState } = useModel('@@initialState');
const { children, btn, pop, access } = props;
let accesses = access ? ['havePrem'].includes(access) : true;
//配置按钮权限接口
return pop ? (
<Popconfirm {...pop} disabled={pop?.disabled || !accesses}>
<Button {...btn} disabled={btn?.disabled || !accesses}>
{children}
</Button>
</Popconfirm>
) : (
<Button {...btn} disabled={btn?.disabled || !accesses}>
{children}
</Button>
);
}
export default PremButton;
export const DEFAULT_NAME = 'Umi Max';
/* stylelint-disable rule-empty-line-before */
html,
body,
#root {
margin: 0;
padding: 0;
height: 100%;
}
.ant-layout-content {
padding: 0 !important;
}
.ant-pro-layout-header-header {
z-index: 998 !important;
}
.center {
display: flex;
justify-content: center;
align-items: center;
}
.ant-pro-sider-extra {
margin: 0 !important;
}
.inmargin {
margin: 16px;
}
@media screen and (min-width: 768px) {
.inmargin {
margin: 16px 6px;
}
}
a {
color: #4ddaec;
}
:root:root {
--adm-color-primary: #4ddaec;
--adm-color-background: #0d0e1f;
}
.adm-list-item-content {
border: none !important;
}
.adm-list-body {
border-top: 1px solid #2c2d4a;
border-bottom: 1px solid #2c2d4a;
border-radius: 0;
}
import React, { useState, useEffect } from 'react';
import { Outlet, history, useLocation, useAppData } from '@umijs/max';
import { NavBar, TabBar } from 'antd-mobile';
import {
AppOutline,
MessageOutline,
UnorderedListOutline,
UserOutline,
} from 'antd-mobile-icons';
import './index.less';
import { useMemo } from 'react';
import { MenuOutlined, ArrowLeftOutlined } from '@ant-design/icons';
import { Drawer } from 'antd';
/*
menulist 中的表示主路由菜单上左上角是打开 drawer 否则为返回上一页
*/
const menulist = ['/home'];
function BasicLayout() {
const setRouteActive = (value) => {
history.push(value);
};
const tabs = [
{
key: '/home',
title: '首页',
icon: <AppOutline />,
},
{
key: '/todo',
title: '待办',
icon: <UnorderedListOutline />,
},
{
key: '/message',
title: '消息',
icon: <MessageOutline />,
},
{
key: '/me',
title: '我的',
icon: <UserOutline />,
},
];
const location = useLocation();
const { pathname } = location;
const { routes } = useAppData();
const currouter = useMemo(() => {
let allroutes = Object.values(routes).filter((it, i) => {
return it.path === pathname;
})?.[0];
return allroutes;
}, [pathname]);
const [drawer, setdrawer] = useState({ open: false });
return (
<div className="container">
<Drawer
onClose={() => {
setdrawer((s) => ({
...s,
open: false,
}));
}}
closable={false}
{...drawer}
width={'80%'}
placement="left"
></Drawer>
<div className="top">
<NavBar
backArrow={
menulist.includes(pathname) ? (
<MenuOutlined />
) : (
<ArrowLeftOutlined />
)
}
onBack={() => {
if (menulist.includes(pathname)) {
setdrawer((s) => ({
...s,
open: true,
}));
} else {
history.go(-1);
}
}}
>
{currouter?.name}
</NavBar>
</div>
<div style={{ height: 45 }}></div>
<Outlet></Outlet>
<div style={{ height: 66 }}></div>
<div className="bottom">
<TabBar
activeKey={pathname}
onChange={(value) => setRouteActive(value)}
>
{tabs.map((item) => (
<TabBar.Item key={item.key} icon={item.icon} title={item.title} />
))}
</TabBar>
</div>
</div>
);
}
export default BasicLayout;
.container {
background-color: #0e0e1f;
div {
color: #fff;
}
}
.top {
position: fixed;
top: 0;
z-index: 999;
left: 0;
width: 100%;
background-color: rgb(18 19 33 / 50%);
right: 0;
margin: auto;
backdrop-filter: blur(2px);
}
.bottom {
position: fixed;
bottom: 0;
z-index: 999;
left: 0;
width: 96%;
background-color: #121321;
right: 0;
margin: auto;
height: 66px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 12px 12px 0 0;
> div {
width: 100%;
}
}
// 全局共享数据示例
import { DEFAULT_NAME } from '@/constants';
import { useState } from 'react';
const useUser = () => {
const [name, setName] = useState<string>(DEFAULT_NAME);
return {
name,
setName,
};
};
export default useUser;
import { PageContainer } from '@ant-design/pro-components';
import { Access, useAccess } from '@umijs/max';
import { Button } from 'antd';
const AccessPage: React.FC = () => {
const access = useAccess();
return (
<PageContainer
ghost
header={{
title: '权限示例',
}}
>
<Access accessible={access.canSeeAdmin}>
<Button>只有 Admin 可以看到这个按钮</Button>
</Access>
</PageContainer>
);
};
export default AccessPage;
function getcolumns(setdrawer) {
return [
{
tab: '123',
key: '1',
columns: [
{
title: '基础信息',
valueType: 'split',
},
{
title: '用户名',
dataIndex: 'accountName',
formItemProps: {
rules: [
{
required: false,
message: '此项为必填项',
},
],
},
initialValue: '默认值',
hideInForm: false,
convertValue: (value) => {
return `标题:${value}`;
},
transform: (value) => {
return {
title: `${value}-转换`,
};
},
search: false,
render: (text, row) => {
return <a onClick={() => {}}>{text}</a>;
},
},
{
title: '姓名',
dataIndex: 'userName',
key: 'userId',
hideInForm: {
accountName: {
reverse: ['1', '2', '5'],
},
remark: ['3'],
},
search: false,
},
{
title: '组织',
dataIndex: 'departmentName',
key: 'departmentId',
valueType: 'Cascader',
mode: 'multiple',
fieldProps: {
placeholder: '请选择',
},
formItemProps: {
rules: [
{
required: false,
message: '此项为必填项',
},
],
},
options: {
path: '/ngic-auth/sysDepartment/query/tree',
params: {},
},
},
{
title: '工厂',
dataIndex: 'factoryName',
key: 'factoryIdList',
valueType: 'select',
fieldProps: {
placeholder: '请选择',
showSearch: true,
mode: 'multiple',
allowClear: true,
},
formItemProps: {
rules: [
{
required: false,
message: '此项为必填项',
},
],
},
options: {
path: '/ngic-auth/sysFactory/selectionBox',
params: {},
},
},
{
title: '负责仓库',
dataIndex: 'chargeStoreName',
key: 'storeIdList',
valueType: 'select',
fieldProps: {
placeholder: '请选择',
showSearch: true,
mode: 'multiple',
},
options: {
path: '/ngic-auth/sysStore/selectionBox',
linkParams: {
factoryIdList: '',
},
},
hideInTable:true,
},
{
title: '角色',
dataIndex: 'roleName',
key: 'roleIdList',
valueType: 'select',
fieldProps: {
placeholder: '请选择',
showSearch: true,
mode: 'multiple',
},
options: [{ label: '12', value: '12' }],
hideInTable:true,
},
{
title: '直属领导',
dataIndex: 'parentName',
key: 'parentId',
valueType: 'select',
fieldProps: {
placeholder: '请选择',
showSearch: true,
},
options: {
path: '/ngic-auth/sysUser/queryParentSelectionByUserId',
params: {},
},
hideInTable:true,
},
{
title: '额外信息',
valueType: 'split',
},
{
title: '联系电话',
dataIndex: 'telephone',
formItemProps: {
rules: [
{
required: false,
message: '此项为必填项',
},
],
},
search: false,
},
{
title: '邮箱',
dataIndex: 'mailNo',
formItemProps: {
rules: [
{
required: false,
message: '此项为必填项',
},
],
},
search: false,
},
{
title: '备注',
dataIndex: 'remark',
valueType: 'editor',
search: false,
colProps: { span: 24 },
initialValue: '<p>Hello <b>World!</b></p>',
hideInTable:true,
},
{
title: '上传样式-图片',
dataIndex: 'uploadImage',
key: 'uploadImage',
valueType: 'uploadImage',
fieldProps: {
limit: 2,
},
formItemProps: {
rules: [
{
required: false,
message: '此项为必填项',
},
],
},
hideInTable:true
},
{
title: '列表可选择',
valueType: 'formSelectList',
rowKey: 'id',
rowName: 'accountName',
dataIndex: 'lists',
hideInTable:true,
colProps: {
xs: 24,
sm: 24,
},
columns: [
{
title: '用户名',
dataIndex: 'accountName',
colProps: {
sm: 8,
},
editable: false,
},
{
title: '姓名',
dataIndex: 'userName',
valueType: 'digit',
colProps: {
sm: 8,
},
},
{
title: '手机号',
dataIndex: 'telephone',
colProps: {
sm: 8,
},
},
],
path: '/ngic-auth/sysUser/query/page',
linkParams: {
accountName: 'userName',
}, //params 则不联动
search: false,
rowSelection: {
type: 'checkbox',
getCheckboxProps: (record) => ({
disabled: record.accountName == 'GF',
}),
},
},
],
pathconfig: {
enableadd: true,
enableedit: true,
enabledelete: true,
enabledetail: true,
},
},
{
tab: '456',
key: '2',
columns: [
{
title: '维修单号',
dataIndex: 'repairOrderNo',
key: 'repairOrderNo',
},
{
title: '设备编号',
dataIndex: 'equipmentNo',
key: 'equipmentNo',
},
{
title: '设备名称',
dataIndex: 'equipmentName',
key: 'equipmentName',
},
{
title: '故障描述',
dataIndex: 'faultDescription',
key: 'faultDescription',
},
{
title: '报修人员',
dataIndex: 'repairUserName',
key: 'repairUserName',
},
{
title: '报修时间',
dataIndex: 'repairTime',
key: 'repairTimeList',
valueType: 'dateTimeRange',
},
{
title: '报修单号',
dataIndex: 'repairNo',
key: 'repairNo',
},
{
title: '工单状态',
dataIndex: 'statusName',
key: 'statusName',
},
],
pathconfig: {
enableadd: true,
enableedit: true,
enabledelete: true,
enabledetail: true,
},
},
];
}
export default getcolumns;
import * as React from 'react';
import { useState, useMemo, useRef } from 'react';
import DrawerPro from '@/components/DrawerPro';
import AutoTable from '@/components/AutoTable';
import PremButton from '@/components/PremButton';
import getcolumns from './columns';
import { useRequest } from 'ahooks';
import { doFetch } from '@/utils/doFetch';
import { List } from 'antd-mobile';
const Extra = ({ it, item }) => {
return (
<div>{it?.render?.(item[it?.dataIndex], item) ?? item[it?.dataIndex]}</div>
);
};
const Cards = ({ item, columns }) => {
return (
<List
mode="card"
key={item.dataIndex}
onClick={() => {
alert(0);
}}
>
{columns
?.filter((it) => !(it.valueType === 'split' || it?.hideInTable))
?.map((it) => {
return (
<List.Item
key={it?.dataIndex}
extra={<Extra it={it} item={item} />}
arrow={false}
>
{it?.title}
</List.Item>
);
})}
</List>
);
};
function Home(props) {
const actionRef = useRef(),
formRef = useRef();
const [drawer, setdrawer] = useState({
open: false,
}),
[activeTabKey, setactiveTabKey] = useState('1');
const { run, loading } = useRequest(doFetch, {
manual: true,
onSuccess: (res, params) => {
if (res?.code === '0000') {
actionRef?.current?.reload();
setdrawer((s) => ({
...s,
open: false,
}));
}
},
});
const detail = (text, row, _, action) => {
return (
<PremButton
btn={{
size: 'small',
type: 'link',
onClick: () => {
setdrawer((s) => ({
...s,
open: true,
item: row,
title: '详情',
val: 'detail',
title: '详细信息',
}));
},
}}
>
详情
</PremButton>
);
};
const edit = (text, row, _, action) => {
return (
<PremButton
btn={{
size: 'small',
onClick: () => {
setdrawer((s) => ({
...s,
open: true,
item: row,
title: '编辑',
val: 'edit',
}));
},
}}
>
编辑
</PremButton>
);
};
const remove = (text, row, _, action) => {
return (
<PremButton
pop={{
title: '是否删除?',
okText: '确认',
cancelText: '取消',
onConfirm: () => {
run({
url: pathconfig?.delete || '/delete',
params: { id: row?.id },
});
},
}}
btn={{
size: 'small',
type: 'danger',
}}
>
删除
</PremButton>
);
};
const columns = useMemo(() => {
let defcolumn = getcolumns(setdrawer).filter(
(it) => it.key === activeTabKey,
)[0]?.columns;
let defpath =
getcolumns(setdrawer).filter((it) => it.key === activeTabKey)[0]
?.pathconfig ?? {};
return defcolumn.concat({
title: '操作',
valueType: 'option',
width: 150,
render: (text, row, _, action) => [
defpath?.enabledetail && detail(text, row, _, action),
defpath?.enableedit && edit(text, row, _, action),
defpath?.enabledelete && remove(text, row, _, action),
],
});
}, [activeTabKey]);
const pathconfig = useMemo(() => {
let defpath =
getcolumns(setdrawer).filter((it) => it.key === activeTabKey)[0]
?.pathconfig ?? {};
return defpath;
}, [activeTabKey]);
return (
<div style={{ position: 'relative' }}>
<AutoTable
pagetitle="左金玲"
columns={columns}
path={pathconfig?.list || '/ngic-auth/sysUser/query/page'}
actionRef={actionRef}
pageextra={pathconfig?.enableadd ? 'add' : null}
resizeable={true}
addconfig={{
// access: 'sysDepartment_save',
btn: {
disabled: false,
onClick: () => {
setdrawer((s) => ({
...s,
open: true,
item: null,
title: '新增',
val: 'add',
}));
},
},
}}
tabList={getcolumns()}
activeTabKey={activeTabKey}
onTabChange={(key) => {
setactiveTabKey(key);
}}
>
<Cards />
</AutoTable>
<DrawerPro
fields={columns}
detailpath={pathconfig?.detail || null}
detailData={drawer?.item}
defaultFormValue={drawer?.item}
params={{ id: drawer?.item?.id }}
formRef={formRef}
placement="right"
onClose={() => {
setdrawer((s) => ({
...s,
open: false,
}));
}}
{...drawer}
onFinish={(vals) => {
if (drawer?.val === 'add') {
run({ url: pathconfig?.add || '/add', params: { ...vals } });
} else if (drawer?.val === 'edit') {
run({
url: pathconfig?.edit || '/edit',
params: { ...vals, id: drawer?.item?.id },
});
}
}}
/>
</div>
);
}
export default Home;
import {
AlipayCircleOutlined,
LockOutlined,
MobileOutlined,
TaobaoCircleOutlined,
UserOutlined,
ArrowLeftOutlined,
} from '@ant-design/icons';
import {
LoginForm,
ProFormCaptcha,
ProFormCheckbox,
ProFormText,
ProConfigProvider,
} from '@ant-design/pro-components';
import { message, Space, Tabs } from 'antd';
import { useState } from 'react';
import './index.less';
import { history } from '@umijs/max';
import { doFetch } from '@/utils/doFetch';
import AES from 'crypto-js/aes';
import ECB from 'crypto-js/mode-ecb';
import Pkcs7 from 'crypto-js/pad-pkcs7';
import Utf8 from 'crypto-js/enc-utf8';
const iconStyles = {
marginInlineStart: '16px',
color: 'rgba(0, 0, 0, 0.2)',
fontSize: '24px',
verticalAlign: 'middle',
cursor: 'pointer',
};
export default () => {
const [loginType, setLoginType] = useState('account');
return (
<div className="bg">
<ul className="circles">
{new Array(10).fill('').map((it) => (
<li key={it} />
))}
</ul>
<div style={{ position: 'absolute', width: '100%', zIndex: 99999 }}>
<LoginForm
logo="./logo.png"
subTitle={
<>
<p style={{ fontSize: 17, marginBottom: 0 }}>
<b>江苏南高智能装备创新中心有限公司</b>
</p>
<p style={{ fontSize: 12, zoom: 0.8, margin: 0 }}>
Jiangsu Nangao Intelligent Equipment Innovation Center Co., Ltd.
</p>
</>
}
onFinish={(values) => {
let timestamp = new Date().getTime() + 'acb';
let newtimestamp = AES.encrypt(
timestamp,
Utf8.parse('NANGAODEAESKEY--'),
{
mode: ECB,
padding: Pkcs7,
},
).toString();
let password = AES.encrypt(values.password, Utf8.parse(timestamp), {
mode: ECB,
padding: Pkcs7,
}).toString();
const postdata = {
accountName: values.username,
password: password,
encryptKey: newtimestamp,
};
doFetch({
url: '/ngic-auth/sysAccount/login',
params: postdata,
}).then((res) => {
if (res.code === '0000') {
let token = res?.data?.token;
localStorage.setItem('TOKENES', token);
message.success('🎉 🎉 🎉 登录成功!');
history.push('/');
return;
}
});
}}
>
{loginType === 'account' && (
<>
<ProFormText
name="username"
fieldProps={{
size: 'large',
prefix: <UserOutlined className={'prefixIcon'} />,
}}
placeholder={'用户名: admin or user'}
rules={[
{
required: true,
message: '请输入用户名!',
},
]}
/>
<ProFormText.Password
name="password"
fieldProps={{
size: 'large',
prefix: <LockOutlined className={'prefixIcon'} />,
}}
placeholder={'密码: 123'}
rules={[
{
required: true,
message: '请输入密码!',
},
]}
/>
<div
style={{
marginBlockEnd: 24,
}}
>
<ProFormCheckbox noStyle name="autoLogin">
自动登录
</ProFormCheckbox>
<a
style={{
float: 'right',
}}
onClick={() => {
setLoginType('phone');
}}
>
忘记密码
</a>
</div>
</>
)}
{loginType === 'phone' && (
<>
<ArrowLeftOutlined
style={{
fontSize: 20,
marginBottom: 12,
position: 'fixed',
left: 36,
top: 36,
zIndex: 99999,
}}
onClick={() => {
setLoginType('account');
}}
/>
<ProFormText.Password
name="confirmpassword"
fieldProps={{
size: 'large',
prefix: <LockOutlined className={'prefixIcon'} />,
}}
placeholder={'密码'}
rules={[
{
required: true,
message: '请输入密码!',
},
]}
/>
<ProFormText.Password
name="password"
fieldProps={{
size: 'large',
prefix: <LockOutlined className={'prefixIcon'} />,
}}
placeholder={'确认密码'}
rules={[
{
required: true,
message: '请输入确认密码!',
},
]}
/>
</>
)}
</LoginForm>
</div>
</div>
);
};
.ant-pro-form-login-main {
min-width: 100px !important;
width: 100% !important;
}
.ant-pro-form-login-container {
padding-top: 88px !important;
}
.bg {
height: 100%;
overflow: hidden;
position: relative;
background-image: linear-gradient(
145deg,
#1e1373 0%,
#2a1448 50%,
#1e1373 100%
);
background-size: 400% 400%;
animation: bgmove 20s linear 0s infinite alternate forwards;
.ant-pro-form-login-header {
height: auto !important;
}
.ant-pro-form-login-logo {
width: 88px;
height: 88px;
}
p {
color: #e9e9e9;
}
}
.bglight {
height: 100%;
overflow: hidden;
position: relative;
background-image: linear-gradient(
145deg,
#bee9ff 0%,
#99dbff 25%,
#ffc4f1 50%,
#99dbff 75%,
#bee9ff 100%
);
background-size: 400% 400%;
background-position: 0% 50%;
}
@keyframes bgmove {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
.circles {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.circles li {
position: absolute;
display: block;
list-style: none;
width: 20px;
height: 20px;
background: rgb(255 255 255 / 2%);
animation: animate 25s linear infinite;
bottom: -150px;
}
.circles li:nth-child(1) {
left: 25%;
width: 80px;
height: 80px;
animation-delay: 0s;
}
.circles li:nth-child(2) {
left: 10%;
width: 20px;
height: 20px;
animation-delay: 2s;
animation-duration: 12s;
}
.circles li:nth-child(3) {
left: 70%;
width: 20px;
height: 20px;
animation-delay: 4s;
}
.circles li:nth-child(4) {
left: 40%;
width: 60px;
height: 60px;
animation-delay: 0s;
animation-duration: 18s;
}
.circles li:nth-child(5) {
left: 65%;
width: 20px;
height: 20px;
animation-delay: 0s;
}
.circles li:nth-child(6) {
left: 75%;
width: 110px;
height: 110px;
animation-delay: 3s;
}
.circles li:nth-child(7) {
left: 35%;
width: 88px;
height: 88px;
animation-delay: 7s;
}
.circles li:nth-child(8) {
left: 50%;
width: 25px;
height: 25px;
animation-delay: 15s;
animation-duration: 45s;
}
.circles li:nth-child(9) {
left: 20%;
width: 15px;
height: 15px;
animation-delay: 2s;
animation-duration: 35s;
}
.circles li:nth-child(10) {
left: 85%;
width: 88px;
height: 88px;
animation-delay: 0s;
animation-duration: 11s;
}
@keyframes animate {
0% {
transform: translateY(0) rotate(0deg);
opacity: 1;
border-radius: 0;
}
100% {
transform: translateY(-1000px) rotate(720deg);
opacity: 0;
border-radius: 50%;
}
}
import request from './request';
export async function doFetch({ url, params }) {
if (!url) {
return;
}
return request('' + url, {
method: 'post',
data: params,
});
}
export async function postFetch({ url, params }) {
return request(url, {
method: 'post',
data: params,
});
}
export async function getFetch({ url, params }) {
return request(url, {
method: 'get',
params,
});
}
export async function formFetch({ url, params }) {
return request(url, {
method: 'post',
data: params,
type: 'form',
});
}
/* eslint-disable eqeqeq */
/**
* request 网络请求工具
* 更详细的 api 文档: https://github.com/umijs/umi-request
*/
import { extend } from 'umi-request';
import { message, notification, Modal } from 'antd';
import { history } from '@umijs/max';
import qs from 'query-string';
import defaultSetting from '../../config/defaultSettings';
const codeMessage = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。',
204: '删除数据成功。',
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401: '用户没有权限(令牌、用户名、密码错误)。',
403: '用户得到授权,但是访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。',
};
/**
* 异常处理程序
*/
const errorHandler = (error) => {
const { response } = error;
if (response && response.status) {
const errorText = codeMessage[response.status] || response.statusText;
const { status, url } = response;
if (response?.url.indexOf('api/user_token') == -1) {
notification.error({
message: `请求错误 ${status}: ${url}`,
description: errorText,
});
}
} else if (!response) {
if (response?.url.indexOf('api/user_token') == -1) {
notification.error({
description: '您的网络发生异常,无法连接服务器',
message: '网络异常',
});
}
}
return response ? response : {};
};
/**
* 配置request请求时的默认参数
*/
const request = extend({
prefix: defaultSetting.proxypath, //前缀代理
errorHandler,
// 默认错误处理
credentials: 'include', // 默认请求是否带上cookie
});
// request拦截器, 改变url 或 options.
request.interceptors.request.use(async (url, options) => {
let token = localStorage.getItem('TOKENES');
if (token) {
const headers =
options.type == 'form'
? {
token: token,
}
: {
'Content-Type': 'application/json',
Accept: 'application/json',
token: token,
};
return {
url: url,
options: { ...options, headers: headers, useCache: true, ttl: 2000 },
};
}
});
// response拦截器, 处理response
request.interceptors.response.use(async (response, options) => {
if (options.responseType === 'blob') {
const data = await response.clone().blob();
let blobUrl = window.URL.createObjectURL(data);
const a = document.createElement('a');
a.style.display = 'none';
let temp =
response.headers.get('Content-Disposition').split(';')[1].split('=')[1] ||
'';
let fileDefaultName = decodeURI(temp);
let pathname = '表格';
a.download = fileDefaultName || pathname + '.xls';
a.href = blobUrl;
a.click();
a.remove();
} else {
const data = await response.clone().json();
// 详情返回的response处理
if (data?.code !== '0000') {
message.destroy();
message.error(data?.msg);
if (
data?.code === '0001' &&
window.location.href.indexOf('login') === -1
) {
localStorage.clear();
history.replace('/login');
}
}
}
return response;
});
export default request;
{
"extends": "./src/.umi/tsconfig.json"
}
import '@umijs/max/typings';
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment