feat: crud
This commit is contained in:
parent
4e1cd06e6e
commit
20293b6283
10
config/config.dev.ts
Normal file
10
config/config.dev.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { defineConfig } from "@umijs/max";
|
||||
|
||||
|
||||
export default defineConfig({
|
||||
define: {
|
||||
'process.env': {
|
||||
API_HOST_URL: 'http://localhost:3000'
|
||||
}
|
||||
}
|
||||
})
|
@ -15,9 +15,9 @@ const Settings: ProLayoutProps & {
|
||||
fixedHeader: false,
|
||||
fixSiderbar: true,
|
||||
colorWeak: false,
|
||||
title: 'Ant Design Pro',
|
||||
title: '依图',
|
||||
pwa: true,
|
||||
logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
|
||||
logo: 'https://polaris-frontend.oss-cn-shanghai.aliyuncs.com/yitu_image/logo.png',
|
||||
iconfontUrl: '',
|
||||
token: {
|
||||
// 参见ts声明,demo 见文档,通过token 修改样式
|
||||
|
@ -9,36 +9,19 @@
|
||||
*
|
||||
* @doc https://umijs.org/docs/guides/proxy
|
||||
*/
|
||||
const { API_HOST_URL = 'localhost:3000' } = process.env;
|
||||
export default {
|
||||
// 如果需要自定义本地开发服务器 请取消注释按需调整
|
||||
// dev: {
|
||||
// // localhost:8000/api/** -> https://preview.pro.ant.design/api/**
|
||||
// '/api/': {
|
||||
// // 要代理的地址
|
||||
// target: 'https://preview.pro.ant.design',
|
||||
// // 配置了这个可以从 http 代理到 https
|
||||
// // 依赖 origin 的功能可能需要这个,比如 cookie
|
||||
// changeOrigin: true,
|
||||
// },
|
||||
// },
|
||||
|
||||
/**
|
||||
* @name 详细的代理配置
|
||||
* @doc https://github.com/chimurai/http-proxy-middleware
|
||||
*/
|
||||
test: {
|
||||
dev: {
|
||||
// localhost:8000/api/** -> https://preview.pro.ant.design/api/**
|
||||
'/api/': {
|
||||
target: 'https://proapi.azurewebsites.net',
|
||||
changeOrigin: true,
|
||||
pathRewrite: { '^': '' },
|
||||
},
|
||||
},
|
||||
pre: {
|
||||
'/api/': {
|
||||
target: 'your pre url',
|
||||
// 要代理的地址
|
||||
target: API_HOST_URL,
|
||||
// 配置了这个可以从 http 代理到 https
|
||||
// 依赖 origin 的功能可能需要这个,比如 cookie
|
||||
changeOrigin: true,
|
||||
pathRewrite: { '^': '' },
|
||||
},
|
||||
},
|
||||
|
||||
};
|
||||
|
@ -23,37 +23,37 @@ export default [
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/welcome',
|
||||
path: '/userList',
|
||||
name: 'welcome',
|
||||
icon: 'smile',
|
||||
component: './Welcome',
|
||||
},
|
||||
{
|
||||
path: '/admin',
|
||||
name: 'admin',
|
||||
icon: 'crown',
|
||||
access: 'canAdmin',
|
||||
routes: [
|
||||
{
|
||||
path: '/admin',
|
||||
redirect: '/admin/sub-page',
|
||||
},
|
||||
{
|
||||
path: '/admin/sub-page',
|
||||
name: 'sub-page',
|
||||
component: './Admin',
|
||||
},
|
||||
],
|
||||
component: './UserList',
|
||||
},
|
||||
{
|
||||
name: 'list.table-list',
|
||||
icon: 'table',
|
||||
path: '/list',
|
||||
component: './TableList',
|
||||
path: '/banner',
|
||||
component: './Banner',
|
||||
routers: [
|
||||
{
|
||||
name: 'login',
|
||||
path: '/banner/test',
|
||||
component: './Banner',
|
||||
},
|
||||
{
|
||||
name: 'login',
|
||||
path: '/banner/2',
|
||||
component: './Banner',
|
||||
},
|
||||
{
|
||||
name: 'login',
|
||||
path: '/banner/3',
|
||||
component: './Banner',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/welcome',
|
||||
redirect: '/userList',
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
|
15
package.json
15
package.json
@ -36,9 +36,15 @@
|
||||
},
|
||||
"lint-staged": {
|
||||
"**/*.{js,jsx,ts,tsx}": "npm run lint-staged:js",
|
||||
"**/*.{js,jsx,tsx,ts,less,md,json}": ["prettier --write"]
|
||||
"**/*.{js,jsx,tsx,ts,less,md,json}": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"browserslist": ["> 1%", "last 2 versions", "not ie <= 10"],
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 10"
|
||||
],
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^4.8.1",
|
||||
"@ant-design/pro-components": "^2.6.48",
|
||||
@ -46,6 +52,7 @@
|
||||
"antd": "^5.13.2",
|
||||
"antd-style": "^3.6.1",
|
||||
"classnames": "^2.5.1",
|
||||
"less": "^4.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.30.1",
|
||||
"omit.js": "^2.0.2",
|
||||
@ -87,5 +94,7 @@
|
||||
"umi-presets-pro": "^2.0.3",
|
||||
"umi-serve": "^1.9.11"
|
||||
},
|
||||
"engines": { "node": ">=12.0.0" }
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
}
|
||||
|
1608
pnpm-lock.yaml
generated
1608
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
29
src/app.tsx
29
src/app.tsx
@ -27,20 +27,20 @@ export async function getInitialState(): Promise<{
|
||||
});
|
||||
return msg.data;
|
||||
} catch (error) {
|
||||
history.push(loginPath);
|
||||
// history.push(loginPath);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
// 如果不是登录页面,执行
|
||||
const { location } = history;
|
||||
if (location.pathname !== loginPath) {
|
||||
const currentUser = await fetchUserInfo();
|
||||
return {
|
||||
fetchUserInfo,
|
||||
currentUser,
|
||||
settings: defaultSettings as Partial<LayoutSettings>,
|
||||
};
|
||||
}
|
||||
// if (location.pathname !== loginPath) {
|
||||
// const currentUser = await fetchUserInfo();
|
||||
// return {
|
||||
// fetchUserInfo,
|
||||
// currentUser,
|
||||
// settings: defaultSettings as Partial<LayoutSettings>,
|
||||
// };
|
||||
// }
|
||||
return {
|
||||
fetchUserInfo,
|
||||
settings: defaultSettings as Partial<LayoutSettings>,
|
||||
@ -55,19 +55,20 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
|
||||
src: initialState?.currentUser?.avatar,
|
||||
title: <AvatarName />,
|
||||
render: (_, avatarChildren) => {
|
||||
return <AvatarDropdown>{avatarChildren}</AvatarDropdown>;
|
||||
return <>admin</>
|
||||
// return <AvatarDropdown>{avatarChildren}</AvatarDropdown>;
|
||||
},
|
||||
},
|
||||
waterMarkProps: {
|
||||
content: initialState?.currentUser?.name,
|
||||
},
|
||||
footerRender: () => <Footer />,
|
||||
// footerRender: () => <Footer />,
|
||||
onPageChange: () => {
|
||||
const { location } = history;
|
||||
// 如果没有登录,重定向到 login
|
||||
if (!initialState?.currentUser && location.pathname !== loginPath) {
|
||||
history.push(loginPath);
|
||||
}
|
||||
// if (!initialState?.currentUser && location.pathname !== loginPath) {
|
||||
// history.push(loginPath);
|
||||
// }
|
||||
},
|
||||
bgLayoutImgList: [
|
||||
{
|
||||
|
42
src/components/Banner/index.less
Normal file
42
src/components/Banner/index.less
Normal file
@ -0,0 +1,42 @@
|
||||
.bannerWrapper {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
.bannerItem {
|
||||
border-radius: 10px;
|
||||
// overflow: hidden;
|
||||
max-width: 400px;
|
||||
position: relative;
|
||||
.operation {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: red;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
color: white;
|
||||
top: -10px;
|
||||
right: -10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
&:hover {
|
||||
.operation {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.uploadWrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
:global {
|
||||
.ant-upload-wrapper {
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
}
|
82
src/components/Banner/index.tsx
Normal file
82
src/components/Banner/index.tsx
Normal file
@ -0,0 +1,82 @@
|
||||
import React, { useState, useEffect, memo } from 'react';
|
||||
import { LoadingOutlined, PlusOutlined, CloseOutlined } from '@ant-design/icons';
|
||||
import { Image, Upload } from 'antd';
|
||||
import type { UploadProps } from 'antd';
|
||||
import styles from './index.less';
|
||||
|
||||
interface IItemImage {
|
||||
id: number;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface IBanner {
|
||||
type: number;
|
||||
dataSource: IItemImage[];
|
||||
onChange: (info: any, type: number) => void;
|
||||
onDelFile: (id: number) => void;
|
||||
}
|
||||
|
||||
const Banner: React.FC<IBanner> = ({ type, dataSource, onChange, onDelFile }) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [dataList, setDataList] = useState<IItemImage[]>(dataSource);
|
||||
|
||||
const handleChange: UploadProps['onChange'] = async (info) => {
|
||||
if (info.file.status === 'uploading') {
|
||||
setLoading(true);
|
||||
return;
|
||||
}
|
||||
if (info.file.status === 'done') {
|
||||
onChange?.(info, type);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setDataList(dataSource);
|
||||
}, [dataSource]);
|
||||
|
||||
const uploadButton = (
|
||||
<button style={{ border: 0, background: 'none' }} type="button">
|
||||
{loading ? <LoadingOutlined /> : <PlusOutlined />}
|
||||
<div style={{ marginTop: 8 }}>上传图片</div>
|
||||
</button>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Image.PreviewGroup
|
||||
preview={{
|
||||
onChange: (current, prev) =>
|
||||
console.log(`current index: ${current}, prev index: ${prev}`),
|
||||
}}
|
||||
>
|
||||
<div className={styles.bannerWrapper}>
|
||||
{dataList.map((item) => {
|
||||
return (
|
||||
<div key={item.id} className={styles.bannerItem}>
|
||||
<Image width="100%" height={250} src={item.url} />
|
||||
<div className={styles.operation} onClick={() => onDelFile(item.id)}>
|
||||
<CloseOutlined />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Image.PreviewGroup>
|
||||
<div className={styles.uploadWrapper}>
|
||||
<Upload
|
||||
accept=".png,.jpg,.jpeg,.gif,.webp,.svg"
|
||||
name="file"
|
||||
listType="picture-circle"
|
||||
className={`avatar-uploader uploadBtn`}
|
||||
showUploadList={false}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{uploadButton}
|
||||
</Upload>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Banner);
|
22
src/constant/index.ts
Normal file
22
src/constant/index.ts
Normal file
@ -0,0 +1,22 @@
|
||||
export const bannerTypes = [
|
||||
{
|
||||
type: 1,
|
||||
name: '首页',
|
||||
},
|
||||
{
|
||||
type: 2,
|
||||
name: '签证咨询',
|
||||
},
|
||||
{
|
||||
type: 3,
|
||||
name: '银行特惠',
|
||||
},
|
||||
{
|
||||
type: 4,
|
||||
name: '定制团建',
|
||||
},
|
||||
{
|
||||
type: 5,
|
||||
name: '游学研学',
|
||||
},
|
||||
];
|
@ -1,5 +1,5 @@
|
||||
export default {
|
||||
'menu.welcome': 'Welcome',
|
||||
'menu.welcome': 'UserList',
|
||||
'menu.more-blocks': 'More Blocks',
|
||||
'menu.home': 'Home',
|
||||
'menu.admin': 'Admin',
|
||||
@ -22,7 +22,7 @@ export default {
|
||||
'menu.form.step-form.result': 'Step Form(finished)',
|
||||
'menu.form.advanced-form': 'Advanced Form',
|
||||
'menu.list': 'List',
|
||||
'menu.list.table-list': 'Search Table',
|
||||
'menu.list.table-list': 'Carousel map',
|
||||
'menu.list.basic-list': 'Basic List',
|
||||
'menu.list.card-list': 'Card List',
|
||||
'menu.list.search-list': 'Search List',
|
||||
|
@ -1,5 +1,5 @@
|
||||
export default {
|
||||
'menu.welcome': '欢迎',
|
||||
'menu.welcome': '用户列表',
|
||||
'menu.more-blocks': '更多区块',
|
||||
'menu.home': '首页',
|
||||
'menu.admin': '管理页',
|
||||
@ -22,7 +22,7 @@ export default {
|
||||
'menu.form.step-form.result': '分步表单(完成)',
|
||||
'menu.form.advanced-form': '高级表单',
|
||||
'menu.list': '列表页',
|
||||
'menu.list.table-list': '查询表格',
|
||||
'menu.list.table-list': '轮播图',
|
||||
'menu.list.basic-list': '标准列表',
|
||||
'menu.list.card-list': '卡片列表',
|
||||
'menu.list.search-list': '搜索列表',
|
||||
|
@ -22,7 +22,7 @@ export default {
|
||||
'menu.form.step-form.result': '分步表單(完成)',
|
||||
'menu.form.advanced-form': '高級表單',
|
||||
'menu.list': '列表頁',
|
||||
'menu.list.table-list': '查詢表格',
|
||||
'menu.list.table-list': '轮播图',
|
||||
'menu.list.basic-list': '標淮列表',
|
||||
'menu.list.card-list': '卡片列表',
|
||||
'menu.list.search-list': '搜索列表',
|
||||
|
87
src/pages/Banner.tsx
Normal file
87
src/pages/Banner.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { Card } from 'antd';
|
||||
import { useModel } from '@umijs/max';
|
||||
import Banner from '@/components/Banner';
|
||||
import { getBanners, deleteBanner, uploadBanner } from '@/services/ant-design-pro/api';
|
||||
import styles from './index.less';
|
||||
import { requestUpload } from '@/utils/upload';
|
||||
import { bannerTypes } from '@/constant';
|
||||
|
||||
interface IItemImage {
|
||||
id: number;
|
||||
type: number;
|
||||
url: string;
|
||||
}
|
||||
|
||||
const Page = () => {
|
||||
const { initialState } = useModel('@@initialState');
|
||||
const [dataList, setDataList] = useState<IItemImage[]>([]);
|
||||
|
||||
const getBannerList = async () => {
|
||||
const result = await getBanners();
|
||||
setDataList(result.data);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getBannerList();
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* 上传图片
|
||||
* @param info 文件内容
|
||||
* @param type 轮播图类型
|
||||
*/
|
||||
const handleFileUpload = async (info: any, type: number) => {
|
||||
const file = info.file.originFileObj;
|
||||
const fileName = info.file.name;
|
||||
const url = await requestUpload(file as Blob, fileName);
|
||||
await uploadBanner({ url, type });
|
||||
await getBanners();
|
||||
getBannerList();
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除图片
|
||||
* @param id 文件id
|
||||
*/
|
||||
const handleDelImage = async (id: number) => {
|
||||
await deleteBanner(id);
|
||||
getBannerList();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageContainer>
|
||||
<Card
|
||||
style={{
|
||||
borderRadius: 8,
|
||||
}}
|
||||
bodyStyle={{
|
||||
backgroundImage:
|
||||
initialState?.settings?.navTheme === 'realDark'
|
||||
? 'background-image: linear-gradient(75deg, #1A1B1F 0%, #191C1F 100%)'
|
||||
: 'background-image: linear-gradient(75deg, #FBFDFF 0%, #F5F7FF 100%)',
|
||||
}}
|
||||
>
|
||||
{bannerTypes.map((item) => {
|
||||
const dataSource = dataList.filter((_item) => item.type === _item.type);
|
||||
return (
|
||||
<div className={styles.bannerBox} key={item.type}>
|
||||
<h3>{item.name}</h3>
|
||||
<Banner
|
||||
dataSource={dataSource}
|
||||
type={item.type}
|
||||
onDelFile={handleDelImage}
|
||||
onChange={handleFileUpload}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Card>
|
||||
</PageContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
@ -124,23 +124,22 @@ const Login: React.FC = () => {
|
||||
defaultMessage: '登录成功!',
|
||||
});
|
||||
message.success(defaultLoginSuccessMessage);
|
||||
await fetchUserInfo();
|
||||
const urlParams = new URL(window.location.href).searchParams;
|
||||
history.push(urlParams.get('redirect') || '/');
|
||||
// await fetchUserInfo();
|
||||
history.push('/userList');
|
||||
return;
|
||||
}
|
||||
console.log(msg);
|
||||
// 如果失败去设置用户错误信息
|
||||
setUserLoginState(msg);
|
||||
} catch (error) {
|
||||
// const defaultLoginFailureMessage = intl.formatMessage({
|
||||
// id: 'pages.login.failure',
|
||||
// defaultMessage: '登录失败,请重试!',
|
||||
// });
|
||||
const defaultLoginFailureMessage = intl.formatMessage({
|
||||
id: 'pages.login.failure',
|
||||
defaultMessage: '登录失败,请重试!',
|
||||
});
|
||||
const urlParams = new URL(window.location.href).searchParams;
|
||||
history.push(urlParams.get('redirect') || '/');
|
||||
console.log(error);
|
||||
// message.error(defaultLoginFailureMessage);
|
||||
message.error(defaultLoginFailureMessage);
|
||||
}
|
||||
};
|
||||
const { status, type: loginType } = userLoginState;
|
||||
@ -168,25 +167,17 @@ const Login: React.FC = () => {
|
||||
minWidth: 280,
|
||||
maxWidth: '75vw',
|
||||
}}
|
||||
logo={<img alt="logo" src="/logo.svg" />}
|
||||
title="Ant Design"
|
||||
subTitle={intl.formatMessage({ id: 'pages.layouts.userLayout.title' })}
|
||||
logo={<img alt="logo" src={Settings.logo} />}
|
||||
title={Settings.title}
|
||||
subTitle={' '}
|
||||
initialValues={{
|
||||
autoLogin: true,
|
||||
}}
|
||||
actions={[
|
||||
<FormattedMessage
|
||||
key="loginWith"
|
||||
id="pages.login.loginWith"
|
||||
defaultMessage="其他登录方式"
|
||||
/>,
|
||||
<ActionIcons key="icons" />,
|
||||
]}
|
||||
onFinish={async (values) => {
|
||||
await handleSubmit(values as API.LoginParams);
|
||||
}}
|
||||
>
|
||||
<Tabs
|
||||
{/* <Tabs
|
||||
activeKey={type}
|
||||
onChange={setType}
|
||||
centered
|
||||
@ -206,7 +197,7 @@ const Login: React.FC = () => {
|
||||
}),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
{status === 'error' && loginType === 'account' && (
|
||||
<LoginMessage
|
||||
@ -265,7 +256,7 @@ const Login: React.FC = () => {
|
||||
</>
|
||||
)}
|
||||
|
||||
{status === 'error' && loginType === 'mobile' && <LoginMessage content="验证码错误" />}
|
||||
{/* {status === 'error' && loginType === 'mobile' && <LoginMessage content="验证码错误" />}
|
||||
{type === 'mobile' && (
|
||||
<>
|
||||
<ProFormText
|
||||
@ -346,8 +337,8 @@ const Login: React.FC = () => {
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
)} */}
|
||||
{/* <div
|
||||
style={{
|
||||
marginBottom: 24,
|
||||
}}
|
||||
@ -362,10 +353,10 @@ const Login: React.FC = () => {
|
||||
>
|
||||
<FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" />
|
||||
</a>
|
||||
</div>
|
||||
</div> */}
|
||||
</LoginForm>
|
||||
</div>
|
||||
<Footer />
|
||||
{/* <Footer /> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
133
src/pages/UserList.tsx
Normal file
133
src/pages/UserList.tsx
Normal file
@ -0,0 +1,133 @@
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { useModel } from '@umijs/max';
|
||||
import { Card } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { getUserList, updateUser, deleteUser } from '@/services/ant-design-pro/api';
|
||||
import { EditableProTable } from '@ant-design/pro-components';
|
||||
import type { ProColumns } from '@ant-design/pro-components';
|
||||
|
||||
interface DataSourceType {
|
||||
id: string;
|
||||
phone: string;
|
||||
email: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
const Welcome: React.FC = () => {
|
||||
const { initialState } = useModel('@@initialState');
|
||||
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
|
||||
const [dataSource, setDataSource] = useState<readonly DataSourceType[]>([]);
|
||||
|
||||
const request = async () => {
|
||||
const result = await getUserList();
|
||||
let data = [];
|
||||
if (result.code === 0) {
|
||||
data = result.data;
|
||||
} else {
|
||||
data = [];
|
||||
}
|
||||
return {
|
||||
data,
|
||||
total: 3,
|
||||
success: true,
|
||||
};
|
||||
};
|
||||
|
||||
const columns: ProColumns[] = [
|
||||
{
|
||||
title: '用户名',
|
||||
dataIndex: 'username',
|
||||
key: 'username',
|
||||
// 第一行不允许编辑
|
||||
editable: (text, record, index) => {
|
||||
return index !== 0;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '电话',
|
||||
dataIndex: 'phone',
|
||||
key: 'phone',
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
dataIndex: 'email',
|
||||
key: 'email',
|
||||
},
|
||||
{
|
||||
title: '银行',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
editable: (text, record, index) => {
|
||||
return index !== 0;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
width: 200,
|
||||
render: (text, record, _, action) => [
|
||||
<a
|
||||
key="editable"
|
||||
onClick={() => {
|
||||
action?.startEditable?.(record.id);
|
||||
}}
|
||||
>
|
||||
编辑
|
||||
</a>,
|
||||
<a
|
||||
key="delete"
|
||||
onClick={() => {
|
||||
deleteUser(record);
|
||||
setDataSource(dataSource.filter((item) => item.id !== record.id));
|
||||
}}
|
||||
>
|
||||
删除
|
||||
</a>,
|
||||
],
|
||||
},
|
||||
];
|
||||
return (
|
||||
<PageContainer>
|
||||
<Card
|
||||
style={{
|
||||
borderRadius: 8,
|
||||
}}
|
||||
bodyStyle={{
|
||||
backgroundImage:
|
||||
initialState?.settings?.navTheme === 'realDark'
|
||||
? 'background-image: linear-gradient(75deg, #1A1B1F 0%, #191C1F 100%)'
|
||||
: 'background-image: linear-gradient(75deg, #FBFDFF 0%, #F5F7FF 100%)',
|
||||
}}
|
||||
>
|
||||
<EditableProTable<DataSourceType>
|
||||
rowKey="id"
|
||||
maxLength={5}
|
||||
scroll={{
|
||||
x: 960,
|
||||
}}
|
||||
loading={false}
|
||||
columns={columns}
|
||||
request={request}
|
||||
value={dataSource}
|
||||
onChange={setDataSource}
|
||||
editable={{
|
||||
type: 'multiple',
|
||||
editableKeys,
|
||||
onSave: async (rowKey, data, row) => {
|
||||
console.log(rowKey, data, row);
|
||||
const newData = {
|
||||
id: data.id,
|
||||
email: data.email,
|
||||
phone: data.phone,
|
||||
};
|
||||
updateUser(newData);
|
||||
},
|
||||
onChange: setEditableRowKeys,
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Welcome;
|
@ -1,164 +0,0 @@
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { useModel } from '@umijs/max';
|
||||
import { Card, theme } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
* 每个单独的卡片,为了复用样式抽成了组件
|
||||
* @param param0
|
||||
* @returns
|
||||
*/
|
||||
const InfoCard: React.FC<{
|
||||
title: string;
|
||||
index: number;
|
||||
desc: string;
|
||||
href: string;
|
||||
}> = ({ title, href, index, desc }) => {
|
||||
const { useToken } = theme;
|
||||
|
||||
const { token } = useToken();
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: token.colorBgContainer,
|
||||
boxShadow: token.boxShadow,
|
||||
borderRadius: '8px',
|
||||
fontSize: '14px',
|
||||
color: token.colorTextSecondary,
|
||||
lineHeight: '22px',
|
||||
padding: '16px 19px',
|
||||
minWidth: '220px',
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: '4px',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 48,
|
||||
height: 48,
|
||||
lineHeight: '22px',
|
||||
backgroundSize: '100%',
|
||||
textAlign: 'center',
|
||||
padding: '8px 16px 16px 12px',
|
||||
color: '#FFF',
|
||||
fontWeight: 'bold',
|
||||
backgroundImage:
|
||||
"url('https://gw.alipayobjects.com/zos/bmw-prod/daaf8d50-8e6d-4251-905d-676a24ddfa12.svg')",
|
||||
}}
|
||||
>
|
||||
{index}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '16px',
|
||||
color: token.colorText,
|
||||
paddingBottom: 8,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '14px',
|
||||
color: token.colorTextSecondary,
|
||||
textAlign: 'justify',
|
||||
lineHeight: '22px',
|
||||
marginBottom: 8,
|
||||
}}
|
||||
>
|
||||
{desc}
|
||||
</div>
|
||||
<a href={href} target="_blank" rel="noreferrer">
|
||||
了解更多 {'>'}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Welcome: React.FC = () => {
|
||||
const { token } = theme.useToken();
|
||||
const { initialState } = useModel('@@initialState');
|
||||
return (
|
||||
<PageContainer>
|
||||
<Card
|
||||
style={{
|
||||
borderRadius: 8,
|
||||
}}
|
||||
bodyStyle={{
|
||||
backgroundImage:
|
||||
initialState?.settings?.navTheme === 'realDark'
|
||||
? 'background-image: linear-gradient(75deg, #1A1B1F 0%, #191C1F 100%)'
|
||||
: 'background-image: linear-gradient(75deg, #FBFDFF 0%, #F5F7FF 100%)',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
backgroundPosition: '100% -30%',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundSize: '274px auto',
|
||||
backgroundImage:
|
||||
"url('https://gw.alipayobjects.com/mdn/rms_a9745b/afts/img/A*BuFmQqsB2iAAAAAAAAAAAAAAARQnAQ')",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '20px',
|
||||
color: token.colorTextHeading,
|
||||
}}
|
||||
>
|
||||
欢迎使用 Ant Design Pro
|
||||
</div>
|
||||
<p
|
||||
style={{
|
||||
fontSize: '14px',
|
||||
color: token.colorTextSecondary,
|
||||
lineHeight: '22px',
|
||||
marginTop: 16,
|
||||
marginBottom: 32,
|
||||
width: '65%',
|
||||
}}
|
||||
>
|
||||
Ant Design Pro 是一个整合了 umi,Ant Design 和 ProComponents
|
||||
的脚手架方案。致力于在设计规范和基础组件的基础上,继续向上构建,提炼出典型模板/业务组件/配套设计资源,进一步提升企业级中后台产品设计研发过程中的『用户』和『设计者』的体验。
|
||||
</p>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: 16,
|
||||
}}
|
||||
>
|
||||
<InfoCard
|
||||
index={1}
|
||||
href="https://umijs.org/docs/introduce/introduce"
|
||||
title="了解 umi"
|
||||
desc="umi 是一个可扩展的企业级前端应用框架,umi 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。"
|
||||
/>
|
||||
<InfoCard
|
||||
index={2}
|
||||
title="了解 ant design"
|
||||
href="https://ant.design"
|
||||
desc="antd 是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品。"
|
||||
/>
|
||||
<InfoCard
|
||||
index={3}
|
||||
title="了解 Pro Components"
|
||||
href="https://procomponents.ant.design"
|
||||
desc="ProComponents 是一个基于 Ant Design 做了更高抽象的模板组件,以 一个组件就是一个页面为开发理念,为中后台开发带来更好的体验。"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Welcome;
|
3
src/pages/index.less
Normal file
3
src/pages/index.less
Normal file
@ -0,0 +1,3 @@
|
||||
.bannerBox {
|
||||
margin-bottom: 40px;
|
||||
}
|
@ -26,6 +26,7 @@ interface ResponseStructure {
|
||||
*/
|
||||
export const errorConfig: RequestConfig = {
|
||||
// 错误处理: umi@3 的错误处理方案。
|
||||
baseURL: process.env.API_HOST_URL,
|
||||
errorConfig: {
|
||||
// 错误抛出
|
||||
errorThrower: (res) => {
|
||||
@ -89,7 +90,7 @@ export const errorConfig: RequestConfig = {
|
||||
requestInterceptors: [
|
||||
(config: RequestOptions) => {
|
||||
// 拦截请求配置,进行个性化处理。
|
||||
const url = config?.url?.concat('?token = 123');
|
||||
const url = config?.url?.concat();
|
||||
return { ...config, url };
|
||||
},
|
||||
],
|
||||
|
@ -22,7 +22,7 @@ export async function outLogin(options?: { [key: string]: any }) {
|
||||
|
||||
/** 登录接口 POST /api/login/account */
|
||||
export async function login(body: API.LoginParams, options?: { [key: string]: any }) {
|
||||
return request<API.LoginResult>('/api/login/account', {
|
||||
return request<API.LoginResult>('/api/user/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -32,6 +32,77 @@ export async function login(body: API.LoginParams, options?: { [key: string]: an
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取用户列表 */
|
||||
export async function getUserList(options?: { [key: string]: any }) {
|
||||
return request<any>('/api/user', {
|
||||
method: 'GET',
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 编辑用户 */
|
||||
export async function updateUser(body: any, options?: { [key: string]: any }) {
|
||||
return request<any>('/api/user/' + body?.id, {
|
||||
method: 'PATCH',
|
||||
data: body,
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 删除用户 */
|
||||
export async function deleteUser(options?: { [key: string]: any }) {
|
||||
return request<any>('/api/user/' + options?.id, {
|
||||
method: 'DELETE',
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取oss签名 */
|
||||
export async function getSignature(options?: { [key: string]: any }) {
|
||||
return request<any>('/api/signature', {
|
||||
method: 'GET',
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取oss签名 */
|
||||
export async function upload(url: string, formData: any) {
|
||||
return request<any>(url, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
}
|
||||
|
||||
/** 上传banner图 */
|
||||
export async function uploadBanner(body: any, options?: { [key: string]: any }) {
|
||||
return request<any>('/api/banner', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: body,
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
export async function getBanners(options?: { [key: string]: any }) {
|
||||
return request<any>('/api/banner', {
|
||||
method: 'GET',
|
||||
...(options || {}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
export async function deleteBanner(id: number) {
|
||||
return request<any>('/api/banner/' + id, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** 此处后端没有提供注释 GET /api/notices */
|
||||
export async function getNotices(options?: { [key: string]: any }) {
|
||||
return request<API.NoticeIconList>('/api/notices', {
|
||||
@ -64,10 +135,10 @@ export async function rule(
|
||||
export async function updateRule(options?: { [key: string]: any }) {
|
||||
return request<API.RuleListItem>('/api/rule', {
|
||||
method: 'POST',
|
||||
data:{
|
||||
data: {
|
||||
method: 'update',
|
||||
...(options || {}),
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -75,10 +146,10 @@ export async function updateRule(options?: { [key: string]: any }) {
|
||||
export async function addRule(options?: { [key: string]: any }) {
|
||||
return request<API.RuleListItem>('/api/rule', {
|
||||
method: 'POST',
|
||||
data:{
|
||||
data: {
|
||||
method: 'post',
|
||||
...(options || {}),
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -86,9 +157,9 @@ export async function addRule(options?: { [key: string]: any }) {
|
||||
export async function removeRule(options?: { [key: string]: any }) {
|
||||
return request<Record<string, any>>('/api/rule', {
|
||||
method: 'POST',
|
||||
data:{
|
||||
data: {
|
||||
method: 'delete',
|
||||
...(options || {}),
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
31
src/utils/upload.ts
Normal file
31
src/utils/upload.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { getSignature } from '@/services/ant-design-pro/api';
|
||||
|
||||
// 生成文件名,作为 key 使用
|
||||
const generateFileName = (ossData: any, fileName: any) => {
|
||||
const filename = Date.now() + fileName;
|
||||
return ossData.dir + filename;
|
||||
};
|
||||
|
||||
const requestUpload = async (file: Blob, fileName: string) => {
|
||||
const result = await getSignature();
|
||||
const ossData = result.data;
|
||||
const key = generateFileName(ossData, fileName);
|
||||
const formData = new FormData(); // 注意参数的顺序,key 必须是第一位,表示OSS存储文件的路径
|
||||
|
||||
formData.append('key', key);
|
||||
formData.append('OSSAccessKeyId', ossData.accessId);
|
||||
formData.append('policy', ossData.policy);
|
||||
formData.append('signature', ossData.signature); // 文件上传成功默认返回 204 状态码,可根据需要修改为 200
|
||||
formData.append('success_action_status', '200'); // file 必须放在最后一位
|
||||
formData.append('file', file as Blob);
|
||||
// const res = await upload(ossData.host, formData);
|
||||
const params = { method: 'POST', body: formData };
|
||||
const res = await fetch(ossData.host, params)
|
||||
.then((response) => response.json())
|
||||
.catch((error) => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
return ossData.host + '/' + key;
|
||||
};
|
||||
|
||||
export { requestUpload };
|
Loading…
x
Reference in New Issue
Block a user