feat: crud

This commit is contained in:
luchang 2024-05-22 19:14:37 +08:00
parent 4e1cd06e6e
commit 20293b6283
21 changed files with 1381 additions and 1065 deletions

10
config/config.dev.ts Normal file
View File

@ -0,0 +1,10 @@
import { defineConfig } from "@umijs/max";
export default defineConfig({
define: {
'process.env': {
API_HOST_URL: 'http://localhost:3000'
}
}
})

View File

@ -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 修改样式

View File

@ -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: { '^': '' },
},
},
};

View File

@ -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: '*',

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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: [
{

View 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;
}
}
}

View 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
View File

@ -0,0 +1,22 @@
export const bannerTypes = [
{
type: 1,
name: '首页',
},
{
type: 2,
name: '签证咨询',
},
{
type: 3,
name: '银行特惠',
},
{
type: 4,
name: '定制团建',
},
{
type: 5,
name: '游学研学',
},
];

View File

@ -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',

View File

@ -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': '搜索列表',

View File

@ -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
View 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;

View File

@ -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
View 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;

View File

@ -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 umiAnt 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
View File

@ -0,0 +1,3 @@
.bannerBox {
margin-bottom: 40px;
}

View File

@ -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 };
},
],

View File

@ -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
View 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 };