feat:初始化

This commit is contained in:
zsqtony 2025-06-27 16:42:11 +08:00
parent dbbfe346b3
commit 67fc8f005e
112 changed files with 50319 additions and 0 deletions

2
.env Normal file
View File

@ -0,0 +1,2 @@
TOKEN_NAME=ADMIN_TOKEN
GUARD_NAME=Admin

3
.eslintrc.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
extends: require.resolve('@umijs/max/eslint'),
};

13
.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
/node_modules
/.env.local
/.umirc.local.ts
/config/config.local.ts
/src/.umi
/src/.umi-production
/src/.umi-test
/.umi
/.umi-production
/.umi-test
/dist
/.mfsu
.swc

17
.lintstagedrc Normal file
View File

@ -0,0 +1,17 @@
{
"*.{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"
]
}

2
.npmrc Normal file
View File

@ -0,0 +1,2 @@
registry=https://registry.npmjs.com/

3
.prettierignore Normal file
View File

@ -0,0 +1,3 @@
node_modules
.umi
.umi-production

8
.prettierrc Normal file
View File

@ -0,0 +1,8 @@
{
"printWidth": 80,
"singleQuote": true,
"trailingComma": "all",
"proseWrap": "never",
"overrides": [{ "files": ".prettierrc", "options": { "parser": "json" } }],
"plugins": ["prettier-plugin-organize-imports", "prettier-plugin-packagejson"]
}

3
.stylelintrc.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
extends: require.resolve('@umijs/max/stylelint'),
};

23
.umirc.ts Normal file
View File

@ -0,0 +1,23 @@
import { defineConfig } from '@umijs/max';
export default defineConfig({
antd: {},
access: {},
model: {},
initialState: {},
request: {},
layout: {},
npmClient: 'npm',
define: {
'process.env.TOKEN_NAME': process.env.TOKEN_NAME,
'process.env.GUARD_NAME': process.env.GUARD_NAME,
},
proxy: {
'/api/': {
target: 'http://0.0.0.0:8000',
// target: 'https://loanos-test.nchl.net/',
changeOrigin: true,
pathRewrite: { '^': '' },
},
},
});

3
Dockerfile Normal file
View File

@ -0,0 +1,3 @@
FROM nexus.zzwb.cc:18444/nginx:1.21-alpine
COPY dist /usr/share/nginx/html
COPY docker/nginx.conf /etc/nginx/nginx.conf

56
docker/nginx.conf Executable file
View File

@ -0,0 +1,56 @@
# Generated by nginxconfig.io
# See nginxconfig.txt for the configuration share link
user nginx;
pid /var/run/nginx.pid;
worker_processes auto;
worker_rlimit_nofile 65535;
# Load modules
include /etc/nginx/modules-enabled/*.conf;
events {
multi_accept on;
worker_connections 65535;
}
http {
charset utf-8;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
server_tokens off;
log_not_found off;
types_hash_max_size 2048;
types_hash_bucket_size 64;
client_max_body_size 16M;
# MIME
include mime.types;
default_type application/octet-stream;
# Logging
access_log /dev/stdout;
error_log /dev/stderr warn;
# example.com
server {
listen 80;
listen [::]:80;
server_name _;
root /usr/share/nginx/html;
index index.html index.htm;
# index.html fallback
location / {
try_files $uri $uri/ /index.html;
}
# gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
}
}

4
gencode.json Normal file
View File

@ -0,0 +1,4 @@
{
"url": "http://0.0.0.0:8000/api/docs/openapi",
"module": "Admin"
}

19173
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

35
package.json Normal file
View File

@ -0,0 +1,35 @@
{
"private": true,
"author": "helloworld <hello@world.com>",
"scripts": {
"build": "max build",
"dev": "max dev",
"format": "prettier --cache --write .",
"postinstall": "max setup",
"setup": "max setup",
"start": "npm run dev"
},
"dependencies": {
"@ant-design/icons": "^5.0.1",
"@ant-design/pro-components": "^2.4.4",
"@antv/s2": "^2.0.0-next.25",
"@antv/s2-react": "^2.0.0-next.24",
"@umijs/max": "^4.3.10",
"antd": "^5.4.0",
"axios": "^1.7.2",
"dayjs": "^1.11.12",
"lodash": "^4.17.21",
"react-use": "^17.5.1",
"valtio": "^1.13.2"
},
"devDependencies": {
"@types/react": "^18.0.33",
"@types/react-dom": "^18.0.11",
"gencode-ts-cli": "^0.0.1",
"lint-staged": "^13.2.0",
"prettier": "^2.8.7",
"prettier-plugin-organize-imports": "^3.2.2",
"prettier-plugin-packagejson": "^2.4.3",
"typescript": "^5.0.3"
}
}

14149
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

10
src/access.ts Normal file
View File

@ -0,0 +1,10 @@
export default (initialState: API.UserInfo) => {
// 在这里按照初始化数据定义项目中的权限,统一管理
// 参考文档 https://umijs.org/docs/max/access
const canSeeAdmin = !!(
initialState && initialState.name !== 'dontHaveAccess'
);
return {
canSeeAdmin,
};
};

21
src/app.tsx Normal file
View File

@ -0,0 +1,21 @@
// 运行时配置
import React from 'react';
import {
LayoutConfig,
MyRootContainer,
requestConfig,
stateActions,
} from './common';
export const request = requestConfig;
export async function getInitialState(): Promise<any> {
return await stateActions.me();
}
export const layout = LayoutConfig;
export function rootContainer(container: React.ReactNode) {
return React.createElement(MyRootContainer, null, container);
}

0
src/assets/.gitkeep Normal file
View File

BIN
src/assets/bitcoin.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,13 @@
import { Access, useAccess } from '@umijs/max';
import { ReactNode } from 'react';
export default function MyAccess({
children,
theKey,
}: {
children: ReactNode;
theKey: string;
}) {
const access = useAccess();
return <Access accessible={access.canAccess(theKey)}>{children}</Access>;
}

View File

@ -0,0 +1,138 @@
import {
DeleteOutlined,
EditOutlined,
EyeOutlined,
PlusOutlined,
RollbackOutlined,
SaveFilled,
} from '@ant-design/icons';
import { Button, ButtonProps, Dropdown, Popconfirm } from 'antd';
import { MyResponseType } from '..';
type MyButtonsType = { title?: string } & ButtonProps;
export const MyButtons = {
Create({ title, ...rest }: MyButtonsType): JSX.Element {
return (
<Button type="primary" icon={<PlusOutlined />} {...rest}>
{title}
</Button>
);
},
Default({
onConfirm,
isConfirm,
title,
description = '确定要取消?',
...rest
}: {
onConfirm?: () => void;
isConfirm?: boolean;
description?: string;
} & MyButtonsType): JSX.Element {
return isConfirm ? (
<Popconfirm
title="提示"
description={description}
okText="是"
cancelText="否"
onConfirm={onConfirm}
>
<Button size="small" {...rest}>
{title}
</Button>
</Popconfirm>
) : (
<Button size="small" {...rest}>
{title}
</Button>
);
},
View({ title, ...rest }: MyButtonsType): JSX.Element {
return (
<Button type="link" size="small" icon={<EyeOutlined />} {...rest}>
{title ?? '查看'}
</Button>
);
},
Edit({ title = '编辑', ...rest }: MyButtonsType): JSX.Element {
return (
<Button type="link" size="small" icon={<EditOutlined />} {...rest}>
{title}
</Button>
);
},
Save({ title = '保存', ...rest }: MyButtonsType): JSX.Element {
return (
<Button type="primary" icon={<SaveFilled />} {...rest}>
{title}
</Button>
);
},
Delete({
onConfirm,
title = '删除',
...rest
}: { onConfirm: () => void } & MyButtonsType): JSX.Element {
return (
<Popconfirm
title="删除提示"
description="确定要删除,将不可恢复?"
okText="是"
cancelText="否"
onConfirm={onConfirm}
>
<Button
type="text"
size="small"
danger
icon={<DeleteOutlined />}
{...rest}
>
{title}
</Button>
</Popconfirm>
);
},
Export({
api,
params,
title,
...rest
}: {
api: (data: any) => Promise<MyResponseType>;
params?: Record<string, any>;
} & MyButtonsType): JSX.Element {
return (
<Dropdown
menu={{
onClick: ({ item, key }: any) => {
console.log(item, key);
api?.({ ...params, ...{ download_type: key } });
},
items: [
// {
// key: 'page',
// label: '导出当前页',
// },
{
key: 'query',
label: '按条件导出',
},
// {
// key: 'all',
// label: '导出全部',
// },
],
}}
placement="bottomLeft"
arrow
>
<Button key="MyExportButton" icon={<RollbackOutlined />} {...rest}>
{title || '导出'}
</Button>
</Dropdown>
);
},
SoftDelete() {},
};

View File

@ -0,0 +1,37 @@
import {
AuditOutlined,
BankOutlined,
BarChartOutlined,
BarcodeOutlined,
ClusterOutlined,
ControlOutlined,
CreditCardOutlined,
SettingOutlined,
ShopOutlined,
UserOutlined,
} from '@ant-design/icons';
export type MyIconsType =
| 'BarcodeOutlined'
| 'AuditOutlined'
| 'ShopOutlined'
| 'BarChartOutlined'
| 'SettingOutlined'
| 'ControlOutlined'
| 'ClusterOutlined'
| 'BankOutlined'
| 'UserOutlined'
| 'CreditCardOutlined';
export const MyIcons = {
BarcodeOutlined: <BarcodeOutlined />,
AuditOutlined: <AuditOutlined />,
ShopOutlined: <ShopOutlined />,
BarChartOutlined: <BarChartOutlined />,
SettingOutlined: <SettingOutlined />,
ControlOutlined: <ControlOutlined />,
ClusterOutlined: <ClusterOutlined />,
BankOutlined: <BankOutlined />,
UserOutlined: <UserOutlined />,
CreditCardOutlined: <CreditCardOutlined />,
};

View File

@ -0,0 +1,18 @@
import { Flex, Statistic } from 'antd';
export function MyStatistics({ items = {} }: { items?: Record<string, any> }) {
return (
<Flex style={{ padding: '0px 20px 20px 20px' }}>
{Object.keys(items).map((key) => {
return (
<Statistic
key={key}
title={key}
value={items[key]}
style={{ marginRight: 50 }}
/>
);
})}
</Flex>
);
}

View File

@ -0,0 +1,15 @@
import { Tag } from 'antd';
import { MyProEnumItemProps } from '../typings';
export default function MyTag({
enums,
value,
}: {
enums: MyProEnumItemProps;
value: string;
}) {
const item = enums[value] ?? undefined;
if (!item) return <>-</>;
console.log('item', value, item);
return <Tag color={item.color}>{item.text}</Tag>;
}

View File

@ -0,0 +1,58 @@
import { ColorPicker } from 'antd';
export function MyColorPicker(props: any) {
return (
<ColorPicker
allowClear
arrow
showText
format="hex"
defaultValue={null}
presets={[
{
label: 'Recommended',
colors: [
'#000000',
'#000000E0',
'#000000A6',
'#00000073',
'#00000040',
'#00000026',
'#0000001A',
'#00000012',
'#0000000A',
'#00000005',
'#F5222D',
'#FA8C16',
'#FADB14',
'#8BBB11',
'#52C41A',
'#13A8A8',
'#1677FF',
'#2F54EB',
'#722ED1',
'#EB2F96',
'#F5222D4D',
'#FA8C164D',
'#FADB144D',
'#8BBB114D',
'#52C41A4D',
'#13A8A84D',
'#1677FF4D',
'#2F54EB4D',
'#722ED14D',
'#EB2F964D',
],
},
{
label: 'Recent',
colors: [],
},
]}
{...props}
onChange={(color) => {
props.onChange?.(color.toHexString());
}}
/>
);
}

View File

@ -0,0 +1,21 @@
import { MyProEnumItemProps } from '@/common/typings';
import { Radio } from 'antd';
export default function MyEnumRadioGroup(props: {
enums: MyProEnumItemProps;
value: string | number | undefined;
onChange: (value: string | number | undefined) => void;
}) {
const options = Object.entries(props.enums).map(([, value]) => {
return { label: value.text, value: value.value };
});
return (
<Radio.Group
options={options}
onChange={(e) => props?.onChange(e.target.value)}
value={props.value || options[0].value}
optionType="button"
buttonStyle="solid"
/>
);
}

View File

@ -0,0 +1,19 @@
import { Select, SelectProps, Space } from 'antd';
import { MyIcons } from '../MyIcons';
export function MyIconSelect(props: SelectProps) {
return (
<Select allowClear {...props}>
{Object?.entries(MyIcons).map(([key, Icon]) => {
return (
<Select.Option value={key} label={key} key={key}>
<Space>
{Icon}
{key}
</Space>
</Select.Option>
);
})}
</Select>
);
}

View File

@ -0,0 +1,12 @@
import { InputNumber } from 'antd';
export function MyMoneyInput(props: any) {
return (
<InputNumber
addonBefore="¥"
precision={2}
formatter={(value) => `${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
{...props}
/>
);
}

View File

@ -0,0 +1,5 @@
import { InputNumber } from 'antd';
export function MyPercentInput(props: any) {
return <InputNumber addonAfter="%" precision={2} {...props} />;
}

View File

@ -0,0 +1,54 @@
import { Tree, TreeProps } from 'antd';
import { DataNode } from 'antd/es/tree';
import { Key, useEffect, useState } from 'react';
import { MyProFormFieldProps, MyResponseType } from '../../typings';
export function MyTreeCheckable(
props: {
api: (data?: any) => Promise<MyResponseType>;
} & TreeProps<DataNode> &
MyProFormFieldProps<Key[]>,
) {
const [treeData, setTreeData] = useState<DataNode[]>([]);
const processTree = (item: any): DataNode => {
return {
...item,
key: item.id,
title: item.id + '_' + item.name,
children: item.children?.map(processTree),
};
};
useEffect(() => {
props.api?.({ guard_name: process.env.GUARD_NAME }).then((res: any) => {
const data = res.data?.map(processTree);
setTreeData(data);
});
}, []);
const onCheck: TreeProps['onCheck'] = (checkedKeys, info) => {
console.log('onCheck', checkedKeys, info);
const ids: Key[] = [];
info.checkedNodes?.forEach((item) => {
if (item.children?.length === 0) {
ids.push(item.key);
}
});
console.log('ids', ids);
props.onChange?.(ids);
};
return (treeData?.length ?? 0) > 0 ? (
<Tree
checkable
defaultExpandAll
treeData={treeData}
onCheck={onCheck}
checkedKeys={props.value ?? []}
{...props}
/>
) : (
<></>
);
}

View File

@ -0,0 +1,137 @@
import { MyProFormFieldProps } from '@/common';
import { Apis } from '@/gen/Apis';
import { PlusOutlined, UploadOutlined } from '@ant-design/icons';
import { Button, Modal, Upload, UploadFile, UploadProps } from 'antd';
import { RcFile } from 'antd/es/upload';
import axios from 'axios';
import { useSetState } from 'react-use';
type MyType = {
uploadType?: 'image' | 'video' | 'audio' | 'file';
max?: number;
} & UploadProps<any> &
MyProFormFieldProps<UploadFile[]>;
const getBase64 = (file: RcFile): Promise<string> =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result as string);
reader.onerror = (error) => reject(error);
});
export function MyUploadImages({
value,
onChange,
uploadType = 'image',
max = 1,
...rest
}: MyType) {
const [preview, setPreview] = useSetState<{
open: boolean;
image: string;
title: string;
}>({
open: false,
image: '',
title: '',
});
console.log('MyUploadImages');
const handleCancel = () => setPreview({ open: false });
const handlePreview = async (file: UploadFile) => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj as RcFile);
}
setPreview({
open: true,
image: file.url || file.preview,
title: file.name || file.url!.substring(file.url!.lastIndexOf('/') + 1),
});
};
const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => {
console.log('newFileList', newFileList);
const _newFileList: UploadFile<any>[] = newFileList.map((file) => {
if (!file.percent) return file;
return {
name: file.name,
status: file.status,
uid: file.uid,
url: file.response,
size: file.size ?? 0,
type: file.type ?? '',
};
});
onChange?.(_newFileList);
};
const uploadButton =
uploadType === 'image' ? (
<div>
<PlusOutlined />
<div style={{ marginTop: 8 }}>Upload</div>
</div>
) : (
<Button icon={<UploadOutlined />}>Click to Upload</Button>
);
const customRequest = ({ file, onError, onProgress, onSuccess }: any) => {
Apis.Auth.PreUpload({
filename: file.name,
}).then(async (res) => {
axios
.put(res.data.url, file, {
headers: res.data.headers,
onUploadProgress: ({ total, loaded }) => {
if (total)
onProgress(
{ percent: Math.round((loaded / total) * 100).toFixed(2) },
file,
);
},
})
.then(({ data: response }) => {
console.log('response', response);
if (response.errorMessage) {
onError(response, file);
} else {
onSuccess(res.data.url.split('?')[0], file);
}
});
});
return {
abort() {
console.log('upload progress is aborted.');
},
};
};
return (
<>
<Upload
accept={uploadType === 'image' ? 'image/*' : '*'}
fileList={value}
listType={uploadType === 'image' ? 'picture-card' : 'text'}
onPreview={handlePreview}
onChange={handleChange}
customRequest={customRequest}
{...rest}
>
{!value?.length || (value && value.length < max) ? uploadButton : null}
</Upload>
<Modal
open={preview.open}
title={preview.title}
footer={null}
onCancel={handleCancel}
>
<img alt="preview" style={{ width: '100%' }} src={preview.image} />
</Modal>
</>
);
}

View File

@ -0,0 +1,94 @@
import { MyButtons } from '@/common';
import { Apis } from '@/gen/Apis';
import {
LogoutOutlined,
MenuFoldOutlined,
UnlockOutlined,
UserOutlined,
} from '@ant-design/icons';
import { history } from '@umijs/max';
import { Avatar, Drawer, Dropdown, MenuProps, Space } from 'antd';
import { useState } from 'react';
import { stateActions } from '../../libs/valtio/actions';
import ChangePassword from './ChangePassword';
import MyTasks from './Tasks';
export default function AvatarProps({ user }: { user: any }) {
const [open, setOpen] = useState(false);
const [openDrawer, setOpenDrawer] = useState(false);
const showDrawer = () => {
setOpenDrawer(true);
};
const onClose = () => {
setOpenDrawer(false);
};
const items: MenuProps['items'] = [
{
key: 'changePassword',
label: (
<Space
onClick={() => {
setOpen(true);
}}
>
<UnlockOutlined />
</Space>
),
},
{
key: 'logout',
label: (
<Space
onClick={() => {
Apis.Auth.Logout().then(() => {
stateActions.setLogout();
history.push('/login');
});
}}
>
<LogoutOutlined />
退
</Space>
),
},
];
return (
<>
<Space>
<MyButtons.Default
type="link"
icon={<MenuFoldOutlined />}
size="middle"
onClick={() => {
showDrawer();
}}
title="任务"
/>
<Dropdown menu={{ items }} placement="bottomRight">
<Space>
<Avatar
style={{ backgroundColor: '#87d068' }}
icon={<UserOutlined />}
size={28}
/>
<span>{user?.username}</span>
</Space>
</Dropdown>
</Space>
<ChangePassword open={open} setOpen={setOpen} />
<Drawer
title="任务"
placement="right"
closable={false}
onClose={onClose}
open={openDrawer}
key="right"
width={500}
>
<MyTasks item={openDrawer} />
</Drawer>
</>
);
}

View File

@ -0,0 +1,39 @@
import { Apis } from '@/gen/Apis';
import { ModalForm, ProFormText } from '@ant-design/pro-components';
import { message } from 'antd';
export default function ChangePassword({
open,
setOpen,
}: {
open: boolean;
setOpen: (open: boolean) => void;
}) {
return (
<ModalForm<ApiTypes.Auth.ChangePassword>
open={open}
wrapperCol={{ span: 24 }}
width="500px"
title="修改密码"
onFinish={async (values) => {
return Apis.Auth.ChangePassword(values)
.then(() => {
message.success('修改密码成功');
setOpen(false);
})
.catch(() => false);
}}
modalProps={{
onCancel: () => setOpen(false),
}}
>
<ProFormText.Password name="old_password" label="原密码" required />
<ProFormText.Password name="new_password" label="新密码" required />
<ProFormText.Password
name="re_new_password"
label="重复新密码"
required
/>
</ModalForm>
);
}

View File

@ -0,0 +1,40 @@
import { Button, Modal } from 'antd';
import { ReactNode, useState } from 'react';
export function MyCommonModal(props: {
title: string;
width: number;
button_text: string;
children: ReactNode;
}) {
const [isModalOpen, setIsModalOpen] = useState(false);
const showModal = () => {
setIsModalOpen(true);
};
const handleOk = () => {
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
return (
<>
<Button onClick={showModal} size="small">
{props.button_text}
</Button>
<Modal
title={props.title}
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
width={props.width || 800}
destroyOnClose
>
{props.children}
</Modal>
</>
);
}

View File

@ -0,0 +1,104 @@
import { MyResponseType } from '@/common';
import { ImportOutlined, InboxOutlined } from '@ant-design/icons';
import { Button, Flex, Modal, Upload, message } from 'antd';
import { useState } from 'react';
type MyImportModalType = {
title?: string;
type?: any;
params?: Record<string, any>;
templateApi?: () => Promise<MyResponseType>;
importApi: (data: any) => Promise<MyResponseType>;
reload?: () => void;
};
export function MyImportModal(props: MyImportModalType) {
const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState<FormData | undefined>(undefined);
const { title = '批量导入', params = {}, type = 'primary' } = props;
return (
<>
{type === 'danger' ? (
<Button onClick={() => setOpen(true)} type="primary" danger>
<ImportOutlined />
{title}
</Button>
) : (
<Button onClick={() => setOpen(true)} type="primary">
<ImportOutlined />
{title}
</Button>
)}
<Modal
title={title}
open={open}
onCancel={() => setOpen(false)}
onOk={() => {
if (!formData) return;
setLoading(true);
props
?.importApi(formData)
.then(() => {
message.success('导入操作成功');
setLoading(false);
setOpen(false);
props.reload?.();
return true;
})
.catch(() => {
setLoading(false);
});
}}
confirmLoading={loading}
destroyOnClose={true}
maskClosable={false}
footer={(dom) => {
return (
<Flex style={{ width: '100%' }} justify="space-between">
<Button onClick={() => props?.templateApi?.()}></Button>
<div>{dom}</div>
</Flex>
);
}}
>
<Upload.Dragger
accept=".xls,.xlsx,text/csv,.csv,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
maxCount={1}
beforeUpload={(file) => {
const isExcelFile =
file.type === 'application/vnd.ms-excel' ||
file.type ===
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
const isCSVFile = file.type === 'text/csv';
if (!isExcelFile && !isCSVFile) {
message.error(`${file.name} is not an Excel or CSV file`);
return Upload.LIST_IGNORE;
}
return true;
}}
customRequest={({ file }) => {
const formData = new FormData();
formData.append('upload_file', file);
Object.entries(params).forEach(([key, value]) => {
formData.append(key, value);
});
setFormData(formData);
console.log('formData', formData, file);
}}
>
<p className="ant-upload-drag-icon">
<InboxOutlined />
</p>
<p className="ant-upload-text"></p>
<p className="ant-upload-hint">
</p>
</Upload.Dragger>
</Modal>
</>
);
}

View File

@ -0,0 +1,31 @@
import { Spin } from 'antd';
import { useMyState } from '../..';
export function MyLoading() {
const { snap } = useMyState();
if (snap.session.loading === 0) return null;
return (
<div
style={{
top: 0,
left: 0,
bottom: 0,
right: 0,
width: '100vw',
height: '100vh',
position: 'fixed',
zIndex: 9999,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(0, 0, 0, 0)',
}}
>
<Spin size="large">
<div style={{ paddingTop: '70px' }} />
Loading...
</Spin>
</div>
);
}

View File

@ -0,0 +1,27 @@
import { PageContainer, PageContainerProps } from '@ant-design/pro-components';
import { Space } from 'antd';
export function MyPageContainer({
title,
children,
...rest
}: PageContainerProps) {
return (
<PageContainer
fixedHeader
header={{
title: title,
style: { backgroundColor: '#FFF' },
}}
token={{
paddingBlockPageContainerContent: 0,
paddingInlinePageContainerContent: 0,
}}
{...rest}
>
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
{children}
</Space>
</PageContainer>
);
}

View File

@ -0,0 +1,21 @@
import { ConfigProvider } from 'antd';
import { ReactNode } from 'react';
import { MyLoading } from './MyLoading';
export function MyRootContainer({ children }: { children: ReactNode }) {
return (
<ConfigProvider
theme={{
token: {
fontSize: 14,
borderRadius: 1,
// colorPrimary: '#00928a',
// colorInfo: '#00928a',
},
}}
>
<MyLoading />
{children}
</ConfigProvider>
);
}

View File

@ -0,0 +1,106 @@
import { MyButtons, renderTextHelper } from '@/common';
import { Apis } from '@/gen/Apis';
import { SysTasksStatusEnum } from '@/gen/Enums';
import { UndoOutlined } from '@ant-design/icons';
import { Card, Descriptions, Pagination, Space } from 'antd';
import { useEffect, useState } from 'react';
export default function Tasks(props: any) {
const [option, setOption] = useState<any>({});
const getList = (page: number) => {
Apis.SysTasks.List({ perPage: 10, page: page })
.then((res) => {
setOption(res);
})
.catch(() => false);
};
useEffect(() => {
if (props?.item) {
getList(1);
}
}, [props?.item]);
return (
<>
<Space direction="vertical" style={{ width: '100%' }}>
{!option?.data?.length ? (
<div
style={{ color: '#999', textAlign: 'center', padding: '20px 0' }}
>
</div>
) : (
''
)}
{option?.data?.map((res: any) => {
return (
<Card
title={res?.name}
key={res?.id}
extra={
<Space>
<MyButtons.Delete
size="middle"
type="default"
onConfirm={() =>
Apis.SysTasks.Delete({
id: res.id,
}).then(() => getList(1))
}
/>
<MyButtons.Default
icon={<UndoOutlined />}
size="middle"
onClick={() => getList(1)}
/>
</Space>
}
style={{ width: '100%' }}
>
<Descriptions column={1}>
<Descriptions.Item label="开始时间:">
{res?.start_at || '-'}
</Descriptions.Item>
<Descriptions.Item label="成功数量:">
{res?.success_count || '-'}
</Descriptions.Item>
<Descriptions.Item label="失败数量:">
{res?.fail_count || '-'}
</Descriptions.Item>
<Descriptions.Item label="状态:">
<renderTextHelper.Tag
Enums={SysTasksStatusEnum}
value={res?.status}
key="status"
/>
</Descriptions.Item>
<Descriptions.Item label="结束时间:">
{res?.end_at || '-'}
</Descriptions.Item>
<Descriptions.Item label="错误信息:">
{res?.error_message || '-'}
</Descriptions.Item>
<Descriptions.Item label="失败的策路:">
<Space>
{res?.is_error_stop ? '失败后暂停' : '失败后续集执行'}
{res?.is_error_rollback ? '失败后全部回滚' : '失败后不回滚'}
</Space>
</Descriptions.Item>
</Descriptions>
</Card>
);
})}
<div style={{ display: 'flex', justifyContent: 'end' }}>
<Pagination
defaultCurrent={option?.meta?.current_page}
total={option?.meta?.total}
onChange={(page) => {
getList(page);
}}
/>
</div>
</Space>
</>
);
}

View File

@ -0,0 +1,11 @@
import { DrawerProps } from 'antd';
export const MyDrawerProps = {
props: {
footer: null,
maskClosable: false,
placement: 'bottom',
width: '80%',
height: 'calc(100% - 50px)',
} as DrawerProps,
};

View File

@ -0,0 +1,13 @@
export const MyModalFormProps = {
props: {
layout: 'vertical',
labelAlign: 'left',
wrapperCol: { span: 20 },
style: { padding: '15px' },
layoutType: 'ModalForm',
grid: true,
modalProps: {
maskClosable: false,
},
} as any,
};

View File

@ -0,0 +1,52 @@
import { ParamsType, ProTableProps } from '@ant-design/pro-components';
import { SortOrder } from 'antd/es/table/interface';
export const MyProTableProps = {
props: {
scroll: { x: 'max-content', scrollToFirstRowOnChange: true },
bordered: true,
size: 'small',
rowKey: 'id',
pagination: {
showTotal: (total) => `总共${total}`,
showSizeChanger: true,
showQuickJumper: true,
},
search: {
defaultCollapsed: false,
},
} as ProTableProps<Record<string, any>, ParamsType, 'text'>,
request: async (
params: Record<string, any> & {
pageSize?: number;
current?: number;
keyword?: string;
},
sort: Record<string, SortOrder>,
api: (data?: any) => any,
setParams?: (params: any) => void,
setRes?: (res: any) => void,
defaultParams?: Record<string, any>,
) => {
const sortKeys = Object.keys(sort);
const sorter =
sortKeys.length > 0 ? [sortKeys[0], sort[sortKeys[0]]] : undefined;
const { current, pageSize, ...rest } = params;
const body = {
page: current,
perPage: pageSize,
...rest,
sorter: sorter,
...defaultParams,
};
setParams?.(body);
const data = await api(body);
setRes?.(data);
return {
data: data.data,
success: data.success,
total: data.meta?.total,
};
},
};

View File

@ -0,0 +1,214 @@
import { MyResponseType, renderTextHelper } from '@/common';
import { Apis } from '@/gen/Apis';
import { ProColumns } from '@ant-design/pro-components';
import { Popconfirm, Space, Tag } from 'antd';
type ReturnType = ProColumns<Record<string, any>, 'text'>;
export const MyColumns = {
ID(props?: ReturnType): ReturnType {
return { title: 'ID', dataIndex: 'id', hideInSearch: true, ...props };
},
DayStatus: (start: string, end: string) => {
const now = new Date();
const startDate = new Date(start);
const endDate = new Date(end);
// 判断当前时间与开始时间和结束时间的关系
if (now < startDate) {
return (
<Tag color="green" style={{ cursor: 'pointer' }}>
</Tag>
);
} else if (now > endDate) {
return (
<Tag color="default" style={{ cursor: 'pointer' }}>
</Tag>
);
} else {
return (
<Tag color="#f50" style={{ cursor: 'pointer' }}>
</Tag>
);
}
},
Images({ ...rest }: ReturnType): ReturnType {
return {
hideInSearch: true,
renderText: renderTextHelper.Images,
...rest,
};
},
SoftDelete({
onRestore,
onSoftDelete,
...rest
}: {
onRestore: (data: { id: number }) => Promise<MyResponseType>;
onSoftDelete: (data: { id: number }) => Promise<MyResponseType>;
} & ReturnType): ReturnType {
return {
title: '启/禁用',
render: (_, item, index, action) =>
item?.deleted_at ? (
<Popconfirm
title="启用"
description="您确认启用吗?"
onConfirm={() => {
onRestore?.({ id: item.id }).then(() => action?.reload());
}}
okText="Yes"
cancelText="No"
>
<Tag color="gray" style={{ cursor: 'pointer' }}>
</Tag>
</Popconfirm>
) : (
<Popconfirm
title="禁用"
description="您确认禁用吗?"
onConfirm={() => {
onSoftDelete?.({ id: item.id }).then(() => action?.reload());
}}
okText="Yes"
cancelText="No"
>
<Tag color="green" style={{ cursor: 'pointer' }}>
</Tag>
</Popconfirm>
),
search: false,
...rest,
};
},
CreatedAt(props?: ReturnType): ReturnType {
return {
title: '创建时间',
dataIndex: 'created_at',
hideInSearch: true,
valueType: 'dateTime',
sorter: true,
align: 'right',
...props,
};
},
UpdatedAt(): ReturnType {
return {
title: '最近修改',
dataIndex: 'updated_at',
hideInSearch: true,
valueType: 'dateTime',
sorter: true,
align: 'right',
};
},
FinishedAt(): ReturnType {
return {
title: '完成时间',
dataIndex: 'finished_at',
hideInSearch: true,
valueType: 'dateTime',
sorter: true,
align: 'right',
};
},
Boolean({ label, ...rest }: { label?: string[] } & ReturnType): ReturnType {
const option: { value: boolean; label: string; color: string }[] = [
{ value: false, label: label?.[0] ?? '否', color: 'gray' },
{ value: true, label: label?.[1] ?? '是', color: 'green' },
];
return {
align: 'center',
request: async () => option,
renderText(text: boolean) {
const item = option.find((item) => item.value === Boolean(text));
return <Tag color={item?.color}>{item?.label}</Tag>;
},
...rest,
};
},
YesOrNo({
yes = '已',
no = '未',
...rest
}: {
yes?: string;
no?: string;
} & ReturnType): ReturnType {
return {
align: 'center',
renderText(text) {
return (
<Tag bordered={false} color={text ? 'processing' : 'error'}>
{text ? yes : no}
</Tag>
);
},
...rest,
};
},
EnumTag({ ...rest }: ReturnType): ReturnType {
return {
align: 'center',
renderText(text: string | number) {
const _enum = rest.valueEnum ?? {};
if (!_enum) return <>-</>;
const item = _enum[text] ?? undefined;
if (!item) return <>-</>;
return <Tag color={item.color}>{item.text}</Tag>;
},
...rest,
};
},
Option({ ...rest }: ReturnType): ReturnType {
return {
title: '操作',
valueType: 'option',
align: 'right',
fixed: 'right',
...rest,
};
},
Network(props?: ReturnType): ReturnType {
return {
title: 'network',
...props,
render: (_: any, item: any) => {
const data = props?.dataIndex === 'network' ? item.network : item;
return (
<Space>
<img width={24} src={data.icon[0].url} />
<div>{data.name}</div>
</Space>
);
},
valueType: 'select',
key: 'networks_id',
fieldProps: {
fieldNames: { value: 'id', label: 'name' },
optionRender: (item: any) => {
return (
<Space>
<img width={24} src={item.data.icon[0].url} />
<div>{item.data.name}</div>
</Space>
);
},
},
request: async () => (await Apis.Select.Networks()).data,
};
},
Token({ ...rest }) {
return {
title: 'Token',
renderText(text: string) {
return <Tag color="blue">{text}</Tag>;
},
...rest,
};
},
};

View File

@ -0,0 +1,183 @@
import {
MyColorPicker,
MyIconSelect,
MyMoneyInput,
MyPercentInput,
MyUploadImages,
rulesHelper,
} from '@/common';
import { ProFormColumnsType } from '@ant-design/pro-components';
// import { MyRichText } from '../components/FormFields/MyRichText';
type ReturnType = ProFormColumnsType<any, 'text'>;
type PropsType = { required?: boolean } & ReturnType;
export const MyFormItems = {
Text(props: PropsType): ReturnType {
return {
formItemProps: {
...(props?.required ? rulesHelper.text : {}),
},
...props,
};
},
Integer(props: { min?: number } & PropsType): ReturnType {
return {
valueType: 'digit',
formItemProps: {
...(props?.required ? rulesHelper.number : {}),
},
fieldProps: {
min: props.min ?? 1,
},
...props,
};
},
Numeric(props: PropsType): ReturnType {
return {
valueType: 'digit',
formItemProps: {
...(props?.required ? rulesHelper.number : {}),
},
fieldProps: {
precision: 2,
min: 0,
},
...props,
};
},
// RichText(props: PropsType): ReturnType {
// return {
// renderFormItem: () => <MyRichText />,
// formItemProps: {
// ...(props?.required ? rulesHelper.richtext : {}),
// },
// ...props,
// };
// },
UploadImages({
max = 1,
help,
...rest
}: { max?: number; help?: string } & PropsType): ReturnType {
return {
renderFormItem() {
return <MyUploadImages max={max} />;
},
formItemProps: {
help,
...(rest.required ?? false ? rulesHelper.upload({ max }) : {}),
},
...rest,
};
},
EnumRadio(props: PropsType): ReturnType {
return {
valueType: 'radioButton',
proFieldProps: {
placeholder: `请选择${props.title}`,
},
formItemProps: {
...(props?.required ? rulesHelper.text : {}),
},
fieldProps: {
buttonStyle: 'solid',
},
...props,
};
},
EnumCheckbox(props: PropsType): ReturnType {
return {
valueType: 'checkbox',
proFieldProps: {
placeholder: `请选择${props.title}`,
},
formItemProps: {
...(props?.required ? rulesHelper.array : {}),
},
...props,
};
},
EnumSelect(props: PropsType): ReturnType {
return {
valueType: 'select',
proFieldProps: {
placeholder: `请选择${props.title}`,
},
formItemProps: {
...(props?.required ? rulesHelper.text : {}),
},
...props,
};
},
Switch(props: PropsType): ReturnType {
return {
valueType: 'switch',
initialValue: props.initialValue ?? false,
...props,
};
},
DatePicker(props: PropsType): ReturnType {
return {
valueType: 'date',
formItemProps: {
...(props?.required ? rulesHelper.text : {}),
},
...props,
};
},
Select(props: PropsType): ReturnType {
return {
valueType: 'select',
proFieldProps: {
placeholder: `请选择${props.title}`,
},
...props,
};
},
// EnumSelect(props: PropsType): ReturnType {
// const valueType = props.valueType ?? 'radioButton';
// const buttonStyle =
// valueType === 'radioButton'
// ? {
// buttonStyle: 'solid',
// }
// : {};
// },
IconSelect(props?: PropsType): ReturnType {
return {
key: 'icon',
title: '图标',
renderFormItem: () => <MyIconSelect />,
...props,
};
},
ColorPicker(props?: PropsType): ReturnType {
return {
key: 'color',
dataIndex: 'color',
title: '颜色',
renderFormItem: () => <MyColorPicker />,
...props,
};
},
Percent(props?: PropsType): ReturnType {
return {
renderFormItem: () => <MyPercentInput />,
formItemProps: {
...(props?.required ? rulesHelper.number : {}),
},
...props,
};
},
Money(props?: PropsType): ReturnType {
return {
renderFormItem: () => <MyMoneyInput />,
formItemProps: {
...(props?.required ? rulesHelper.number : {}),
},
...props,
};
},
};

41
src/common/index.tsx Normal file
View File

@ -0,0 +1,41 @@
// components
export * from './components/formFields/MyColorPicker';
export * from './components/formFields/MyIconSelect';
export * from './components/formFields/MyMoneyInput';
export * from './components/formFields/MyPercentInput';
export * from './components/formFields/MyTreeCheckable';
export * from './components/formFields/MyUploadImages';
export * from './components/layout/MyCommonModal';
export * from './components/layout/MyImportModal';
export * from './components/layout/MyPageContainer';
export * from './components/layout/MyRootContainer';
export * from './components/props/MyDrawerProps';
export * from './components/props/MyModalFormProps';
export * from './components/props/MyProTableProps';
export * from './components/schema/MyColumns';
export * from './components/schema/MyFormItems';
export * from './components/MyButtons';
export * from './components/MyIcons';
export * from './components/MyStatistics';
// libs
export * from './libs/umi/layoutConfig';
export * from './libs/umi/requestConfig';
export * from './libs/valtio/actions';
export * from './libs/valtio/state';
// pages
export * from './pages/MyLoginPage1';
export * from './pages/NewMyS2Table';
// utils
export * from './utils/renderTextHelper';
export * from './utils/rulesHelper';
// typings
export * from './typings.d';

View File

@ -0,0 +1,5 @@
import { ReactNode } from 'react';
export default function MyAccess({ children }: { children: ReactNode }) {
return <div>MyAccess</div>;
}

View File

@ -0,0 +1,77 @@
import Logo from '@/assets/bitcoin.webp';
import { MyIcons, MyIconsType, PermissionsType, useMyState } from '@/common';
import { Link, RuntimeConfig, history } from '@umijs/max';
import AvatarProps from '../../components/layout/AvatarProps';
const loopMenu = (permissions: PermissionsType[] | undefined) => {
let tree: PermissionsType[] = [];
let map: Record<number, PermissionsType> = {};
permissions?.forEach((permission) => {
map[permission.id] = {
path: permission.type === 'Button' ? 'null' : permission.path,
name: permission.name,
icon: permission.icon && MyIcons[permission.icon as MyIconsType],
children: [],
hideInMenu: permission.type === 'Button',
};
});
permissions?.forEach((permission) => {
let node = map[permission.id];
if (permission.parent_id !== null) {
map[permission.parent_id].children.push(node);
} else {
tree.push(node);
}
});
return tree?.[0]?.children;
};
export const LayoutConfig: RuntimeConfig['layout'] = () => {
const { snap } = useMyState();
return {
title: snap.session.campus?.name ?? '总后台',
logo: <img src={Logo} style={{ height: '30px' }} />,
layout: 'mix',
colorPrimary: '#1890ff',
siderWidth: 220,
pure: history.location.pathname === '/login',
avatarProps: {
render: () => <AvatarProps user={snap.session.user} />,
},
waterMarkProps: {
content: snap.session.user?.username,
},
collapsedButtonRender: false,
token: {
bgLayout: '#eef0f3',
header: {
colorBgHeader: '#001529',
colorHeaderTitle: '#FFF',
colorTextRightActionsItem: '#FFF',
heightLayoutHeader: 50,
},
sider: {
colorMenuBackground: '#FFF',
},
},
menuItemRender: (item, dom) => <Link to={item.path || '/'}>{dom}</Link>,
menu: {
params: snap.session.permissions,
request: async () => {
let objjs: any = [];
snap.session.permissions?.forEach((res: any) => {
objjs.push(res);
});
let data = objjs.sort((a: any, b: any) => {
return a._lft - b._lft;
});
const menus = loopMenu(data);
return Promise.resolve(menus);
},
},
unAccessible: <div>unAccessible</div>,
};
};

View File

@ -0,0 +1,87 @@
import type { RequestConfig } from '@umijs/max';
import { message } from 'antd';
import { history } from 'umi';
import { stateActions } from '../valtio/actions';
import { state } from '../valtio/state';
const downloadFile = (disposition: string, data: any) => {
const blob = new Blob([data]);
const start = "filename*=utf-8''";
let fileName = '';
let dis = disposition.replace('UTF-8', 'utf-8');
// console.log(dis, 'dis');
if (dis.includes(start)) {
fileName = dis.substr(dis.indexOf(start) + start.length);
fileName = decodeURI(fileName);
}
if ('download' in document.createElement('a')) {
// 非IE下载
const elink: any = document.createElement('a');
elink.download = fileName;
elink.style.display = 'none';
elink.href = URL.createObjectURL(blob);
document.body.appendChild(elink);
elink.click();
URL.revokeObjectURL(elink.href); // 释放URL 对象
document.body.removeChild(elink);
} else {
// IE10+下载
// navigator.msSaveBlob(blob, fileName);
}
};
export const requestConfig: RequestConfig = {
baseURL: '/api/',
timeout: 1000 * 60,
method: 'POST',
errorConfig: {
errorThrower: (res) => {
console.log('errorThrower', res);
},
// 错误接收及处理
errorHandler: (error: any) => {
if (error) {
message.error(error.errorMessage);
switch (error.errorCode) {
case 10000:
if (history.location.pathname !== '/login') history.push('/login');
break;
}
}
},
},
// 请求拦截器
requestInterceptors: [
(url: string, options: any) => {
stateActions.addLoading();
return {
url: url,
options: {
...options,
headers: {
authorization: 'Bearer ' + state.storage.access_token,
},
},
};
},
],
// 响应拦截器
responseInterceptors: [
(response: any) => {
stateActions.subLoading();
const { headers } = response;
// console.log(headers['content-disposition'], 'response');
if (headers['content-disposition']) {
downloadFile(headers['content-disposition'], response.data);
return false;
}
const { data = {} as any } = response;
if (!data.success && data.type !== 'application/vnd.ms-excel') {
return Promise.reject(response.data);
}
return response;
},
],
};

View File

@ -0,0 +1,50 @@
import { MyResponseType } from '@/common';
import { Apis } from '@/gen/Apis';
import { state } from './state';
export const stateActions = {
addLoading() {
state.session.loading++;
},
subLoading() {
state.session.loading--;
},
setReady(val: boolean) {
state.session.ready = val;
},
setLogin(res: MyResponseType) {
state.session.user = res.data.user;
state.session.campus = res.data.campus;
state.session.permissions = res.data.permissions;
if (res.data?.token?.access_token)
state.storage.access_token = res.data?.token?.access_token;
// 解析apis
const apiKeys: string[] = [];
res.data.permissions.forEach((permission: any) => {
if (permission.key) {
apiKeys.push(permission.key);
}
});
// console.log('apis', apis);
state.session.apiKeys = apiKeys;
},
setLogout() {
state.session.user = undefined;
state.session.campus = undefined;
state.session.permissions = undefined;
state.storage.access_token = undefined;
},
me: async () => {
const res = await Apis.Auth.Me();
if (res.success) {
stateActions.setLogin(res);
return {
name: state.session.user.username,
apiKeys: state.session.apiKeys,
// permissions: res.data.permissions,
};
} else {
return { name: '未登录', apiKeys: [] };
}
},
};

View File

@ -0,0 +1,60 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { proxy, snapshot, subscribe, useSnapshot } from 'valtio';
function proxyWithPersistant<V>(
val: V,
opts: {
key: string;
},
) {
const local = localStorage.getItem(opts.key);
const state = proxy(local ? JSON.parse(local) : val);
subscribe(state, () => {
localStorage.setItem(opts.key, JSON.stringify(snapshot(state)));
});
return state;
}
type StorageType = {
access_token: string | undefined;
};
const storage: StorageType = proxyWithPersistant(
{
access_token: undefined,
},
{
key: process.env.TOKEN_NAME as string,
},
);
type SessionType = {
ready: boolean;
user?: any;
campus?: {
id: number;
name: string;
};
permissions?: any;
apiKeys: string[];
loading: number;
};
const session: SessionType = proxy({
ready: false,
user: undefined,
campus: undefined,
permissions: undefined,
apiKeys: [],
loading: 0,
});
export const state = proxy({
storage,
session,
});
export function useMyState() {
const snap = useSnapshot(state);
return { snap };
}

View File

@ -0,0 +1,125 @@
import { Apis } from '@/gen/Apis';
import {
FieldTimeOutlined,
LockOutlined,
UserOutlined,
} from '@ant-design/icons';
import {
LoginFormPage,
ProConfigProvider,
ProFormText,
} from '@ant-design/pro-components';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { stateActions } from '..';
export function MyLoginPage1() {
const navigate = useNavigate();
const [getCaptcha, setCaptcha] = useState<any>({});
const methods = {
getCaptcha: () => {
Apis.Auth.Captcha().then(async (res) => {
setCaptcha(res?.data);
console.log(res, 'res');
});
},
};
useEffect(() => {
methods?.getCaptcha();
}, []);
return (
<ProConfigProvider>
<div
style={{
backgroundColor: '#f8f8f8',
height: '100vh',
}}
>
<LoginFormPage<ApiTypes.Auth.Login>
title="欢迎使用后台管理系统"
backgroundVideoUrl="https://gw.alipayobjects.com/v/huamei_gcee1x/afts/video/jXRBRK_VAwoAAAAAAAAAAAAAK4eUAQBr"
subTitle="Admin management system"
onFinish={async (values: any) => {
Apis.Auth.Login({
...values,
...{ captcha_key: getCaptcha?.key },
})
.then(async (res) => {
await stateActions.setLogin(res);
navigate('/');
})
.catch((err: any) => {
methods?.getCaptcha();
console.log(err, 'rr');
});
}}
>
<>
<ProFormText
name="username"
fieldProps={{
size: 'large',
prefix: <UserOutlined className={'prefixIcon'} />,
}}
placeholder={'登录账号'}
rules={[
{
required: true,
message: '请输入登录账号!',
},
]}
/>
<ProFormText.Password
name="password"
fieldProps={{
size: 'large',
prefix: <LockOutlined className={'prefixIcon'} />,
}}
placeholder={'登录密码'}
rules={[
{
required: true,
message: '请输入密码!',
},
]}
/>
<div style={{ display: 'flex' }}>
<ProFormText
name="captcha"
fieldProps={{
size: 'large',
prefix: <FieldTimeOutlined className={'prefixIcon'} />,
}}
placeholder={'验证码'}
rules={[
{
required: true,
message: '请输入验证码!',
},
]}
/>
<div
style={{
height: '40px',
border: '1px solid #eee',
padding: '0 2px',
margin: '0 10px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
borderRadius: '3px',
cursor: 'pointer',
}}
onClick={() => {
methods?.getCaptcha();
}}
>
<img height={32} width={100} src={getCaptcha?.img} />
</div>
</div>
</>
</LoginFormPage>
</div>
</ProConfigProvider>
);
}

View File

@ -0,0 +1,51 @@
import { SheetComponent } from '@antv/s2-react';
import { DataType } from '.';
export function MyS2TableComponent(props: DataType) {
return (
<SheetComponent
sheetType="pivot"
dataCfg={{
fields: props.fields,
data: props.data,
meta: props.meta,
}}
adaptive={true}
options={{
height: 888,
hierarchyType: props.config?.hierarchyType,
// style: {
// layoutWidthType: 'colAdaptive',
// },
totals: {
row: {
showGrandTotals: props.config.rowGrandTotals,
showSubTotals: props.config.rowSubTotals,
reverseGrandTotalsLayout: true,
reverseSubTotalsLayout: true,
calcGrandTotals: {
aggregation: 'SUM',
},
calcSubTotals: {
aggregation: 'SUM',
},
// grandTotalsGroupDimensions: ['day'],
// subTotalsGroupDimensions: ['day'],
},
col: {
showGrandTotals: props.config.colGrandTotals,
showSubTotals: props.config.colSubTotals,
reverseGrandTotalsLayout: true,
reverseSubTotalsLayout: true,
calcGrandTotals: {
aggregation: 'SUM',
},
calcSubTotals: {
aggregation: 'SUM',
},
},
},
}}
/>
);
}

View File

@ -0,0 +1,106 @@
import { Button, Radio, Space, Switch, Typography } from 'antd';
import { DataType } from '.';
function downloadCSV(json: any) {
if (json === undefined) return;
// 提取表头
const headers = Object.keys(json[0]);
// 提取数据行
const rows = json.map((item: any) => Object.values(item));
// 构建CSV内容
let csvContent = '';
// 添加表头
csvContent += headers.join(',') + '\r\n';
// 添加数据行
rows.forEach((row: any) => {
csvContent += row.join(',') + '\r\n';
});
// 创建一个Blob对象使用CSV内容并设置类型为text/csv
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
// 创建一个链接元素
const link = document.createElement('a');
// 设置下载的文件名,默认为'download.csv'
if (link.download !== undefined) {
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', 'download.csv');
// 触发点击事件以下载文件
document.body.appendChild(link);
link.click();
// 清理DOM
document.body.removeChild(link);
}
}
export function MyS2TableExtra({
config,
data,
setConfig,
}: {
config: DataType['config'];
data: DataType['data'];
setConfig: (config: DataType['config']) => void;
}) {
return (
<Space>
<Typography.Text style={{ marginLeft: 20 }}>:</Typography.Text>
<Switch
value={config?.rowSubTotals || false}
onChange={(e) => {
setConfig({ ...config, rowSubTotals: Boolean(e) });
}}
/>
<Typography.Text style={{ marginLeft: 20 }}>:</Typography.Text>
<Switch
value={config?.rowGrandTotals || false}
onChange={(e) => {
setConfig({ ...config, rowGrandTotals: Boolean(e) });
}}
/>
<Typography.Text style={{ marginLeft: 20 }}>:</Typography.Text>
<Switch
value={config?.colSubTotals || false}
onChange={(e) => {
setConfig({ ...config, colSubTotals: Boolean(e) });
}}
/>
<Typography.Text style={{ marginLeft: 20 }}>:</Typography.Text>
<Switch
value={config?.colGrandTotals || false}
onChange={(e) => {
setConfig({ ...config, colGrandTotals: Boolean(e) });
}}
/>
<Typography.Text style={{ marginLeft: 20 }}>:</Typography.Text>
<Radio.Group
size="small"
options={[
{ label: '树形', value: 'tree' },
{ label: '平铺', value: 'grid' },
]}
value={config?.hierarchyType || 'tree'}
onChange={(e) =>
setConfig({
...config,
hierarchyType: e.target.value,
})
}
optionType="button"
buttonStyle="solid"
style={{ marginRight: 20 }}
/>
<Button
size="small"
onClick={() => {
downloadCSV(data);
}}
>
</Button>
</Space>
);
}

View File

@ -0,0 +1,73 @@
import { Switcher } from '@antv/s2-react';
import { useSetState } from 'react-use';
import { DataType } from '.';
export function MySwitcherFields({
fields,
setFields,
}: {
fields: DataType['fields'];
setFields: (fields: DataType['fields']) => void;
}) {
const [switcherFields, setSwitcherFields] = useSetState(() => {
return {
rows: {
selectable: true,
allowEmpty: false,
items: fields.rows?.map((item) => {
return { id: item };
}),
},
columns: {
selectable: true,
allowEmpty: true,
items: fields.columns?.map((item) => {
return { id: item };
}),
},
values: {
selectable: true,
allowEmpty: false,
items: fields.values?.map((item) => {
return { id: item };
}),
},
};
});
function onSwitcherChange(result: any): void {
const fields = {
rows: result.rows.items
.filter((item: { checked?: boolean }) => item.checked ?? true)
.map((item: { id: string }) => item.id),
columns: result.columns.items
.filter((item: { checked?: boolean }) => item.checked ?? true)
.map((item: { id: string }) => item.id),
values: result.values.items
.filter((item: { checked?: boolean }) => item.checked ?? true)
.map((item: { id: string }) => item.id),
};
setSwitcherFields({
rows: {
selectable: true,
allowEmpty: false,
items: result.rows.items,
},
columns: {
selectable: true,
allowEmpty: true,
items: result.columns.items,
},
values: {
selectable: true,
allowEmpty: false,
items: result.values.items,
},
});
setFields?.(fields);
}
return <Switcher {...switcherFields} onSubmit={onSwitcherChange} />;
}

View File

@ -0,0 +1,22 @@
import { BetaSchemaForm, ProCard } from '@ant-design/pro-components';
import { PropsType } from '.';
export default function Query({
columns,
doRequest,
}: {
columns?: PropsType['columns'];
doRequest: (values: any) => void;
}) {
return (
<ProCard>
<BetaSchemaForm
layoutType="QueryFilter"
onFinish={async (values: Record<string, any>) => {
doRequest(values);
}}
columns={columns as any}
/>
</ProCard>
);
}

View File

@ -0,0 +1,116 @@
import { MyPageContainer } from '@/common';
import { MyResponseType } from '@/common/typings';
import { ProCard, ProFormColumnsType } from '@ant-design/pro-components';
import { Fields, HierarchyType, Meta, RawData } from '@antv/s2';
import { Flex, Space } from 'antd';
import { useEffect } from 'react';
import { useSetState } from 'react-use';
import { MyS2TableComponent } from './MyS2TableComponent';
import { MyS2TableExtra } from './MyS2TableExtra';
import { MySwitcherFields } from './MySwitcherFields';
import Query from './Query';
export type PropsType = {
title: string;
api: (values?: any) => Promise<MyResponseType>;
defaultParams?: Record<string, any>;
columns?: ProFormColumnsType<Record<string, any>, 'text'>[] | undefined;
};
export type DataType = {
config: {
hierarchyType: HierarchyType | undefined;
rowGrandTotals: boolean;
rowSubTotals: boolean;
colGrandTotals: boolean;
colSubTotals: boolean;
};
data: RawData[];
fields: Fields;
meta?: Meta[];
query?: Record<string, any>;
};
export function NewMyS2Table(props: PropsType) {
const [data, setData] = useSetState<DataType>(undefined);
// 请求数据
function doRequest() {
props
.api?.({ ...props.defaultParams, ...data.query, fields: data.fields })
.then((resp) => {
if (!data.config)
setData({
config: resp.data.config,
data: resp.data.data,
fields: resp.data.fields,
meta: resp.data.meta,
});
else setData({ data: resp.data.data });
});
}
// // 如果没有columns直接发起请求
// useEffect(() => {
// if (props.columns === undefined) doRequest({});
// }, []);
useEffect(() => {
console.log('useEffect query', data.query, data.fields);
if (!data.query && !data.fields) {
if (props.columns === undefined) {
doRequest();
}
} else {
doRequest();
}
}, [data.query, data.fields]);
return (
<MyPageContainer title={props.title}>
{props.columns && (
<Query
columns={props.columns}
doRequest={(values) => {
setData({ query: values });
}}
/>
)}
<ProCard>
<Space direction="vertical" style={{ width: '100%' }}>
<Flex
justify="space-between"
style={{ width: '100%', margin: '10px 0' }}
>
{data.fields && (
<MySwitcherFields
fields={data.fields}
setFields={(fields: DataType['fields']) => {
console.log('fields', fields);
setData({ fields });
}}
/>
)}
{data.config && (
<MyS2TableExtra
config={data.config}
setConfig={(config) => {
setData({ config });
}}
data={data.data}
/>
)}
</Flex>
{data.config && (
<MyS2TableComponent
config={data.config}
fields={data.fields}
meta={data.meta}
data={data.data}
/>
)}
</Space>
</ProCard>
</MyPageContainer>
);
}

68
src/common/typings.d.ts vendored Normal file
View File

@ -0,0 +1,68 @@
import { ProColumns, ProFormColumnsType } from '@ant-design/pro-components';
type MyColumnsType =
| ProFormColumnsType<any, 'text'>
| ProColumns<any, FormFieldType | 'text' | any>;
type MyResponseType = {
success?: boolean;
data?: any;
errorCode?: number;
errorMessage?: string;
showType?: ErrorShowType;
meta?: MyPaginationMetaType;
};
type MyPaginationMetaType = {
current_page: number;
last_page: number;
per_page: number;
total: number;
};
type MyParamsType<T> = {
q: T;
p?: { page: number; perPage: number };
t?: { sorter: { sort: string; order: string } };
};
type PermissionsType = MenuDataItem & {
parent_id: number;
};
type MyModalRefType = {
showModal: (data: MyModalProps) => void;
hideModal: () => void;
};
// export type MyModalFormProps = {
// refresh: () => void;
// item?: Record<string, any>;
// title: string;
// } & ModalFormProps;
type MyProFormFieldProps<T> = {
value?: T;
onChange?: (value: T) => void;
};
// type MyEnumItemProps = {
// label: string;
// value: string | number;
// color: string;
// textColor: string;
// };
export type MyBetaModalFormProps = {
title?: string;
// action: ActionType | undefined;
reload?: () => void;
item?: Record<string, any>;
};
export type MyProEnumItemProps = {
[key: string]: {
text: string;
color?: string;
};
};

View File

@ -0,0 +1,14 @@
export function error(msg: string = '系统错误', code: number = 999) {
return {
errorCode: code,
success: false,
errorMessage: msg,
};
}
export function mockSuccess(data: any) {
return {
success: true,
data: data,
};
}

View File

@ -0,0 +1,53 @@
import { Image, Space, Tag } from 'antd';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);
export const renderTextHelper = {
TagList(items: { label: string; value: string; color: string }[]) {
return (
<Space>
{items?.map((item) => (
<Tag color={item.color} key={item.value}>
{item.label}
</Tag>
))}
</Space>
);
},
Tag({
Enums,
value,
isColor = true,
}: {
Enums?: any;
value?: any;
isColor?: any;
}) {
let item: any = Object.values(Enums).find((data: any) => {
return data.value === '' + value;
});
return isColor ? (
<Tag color={item?.color}>{item?.text}</Tag>
) : (
<>{item?.text}</>
);
},
Images(images: string[]) {
return (
<Image.PreviewGroup>
{images?.map((img: any) => (
<Image
key={img.uid}
width={100}
// height={100}
src={img.url}
/>
))}
</Image.PreviewGroup>
);
},
RelativeTo(value: string) {
return dayjs().to(dayjs(value));
},
};

View File

@ -0,0 +1,109 @@
import { Rule } from 'antd/es/form';
export const rulesHelper = {
text: {
required: true,
rules: [
() => ({
validator(_, value) {
return value && value !== ''
? Promise.resolve()
: Promise.reject(new Error('不能为空'));
},
}),
] as Rule[],
},
upload(props?: { max?: number; errMsg?: string }) {
return {
required: true,
rules: [
() => ({
validator(_, value) {
if (!value) return Promise.reject(new Error('请上传'));
let doneUploads = value?.filter(
(item: { status: string }) => item.status === 'done',
);
if (!doneUploads) return Promise.reject(new Error('请上传'));
return doneUploads.length > (props?.max ?? 1)
? Promise.reject(new Error(props?.errMsg ?? '请上传'))
: Promise.resolve();
},
}),
] as Rule[],
};
},
number: {
required: true,
rules: [
() => ({
validator(_, value) {
if (value !== undefined && value !== '' && !isNaN(Number(value))) {
return Promise.resolve();
} else {
return Promise.reject(new Error('请输入数字,且不能为空'));
}
},
}),
] as Rule[],
},
boolean: {
required: true,
rules: [
() => ({
validator(_, value) {
return value !== undefined && value !== ''
? Promise.resolve()
: Promise.reject(new Error('不能为空'));
},
}),
] as Rule[],
},
richtext: {
required: true,
rules: [
() => ({
validator(_, value) {
if (value && value !== '<p><br></p>') {
return Promise.resolve();
} else {
return Promise.reject(new Error('不能为空'));
}
},
}),
] as Rule[],
},
array: {
required: true,
rules: [
() => ({
validator(_, value) {
if (value && value.length > 0) {
return Promise.resolve();
} else {
return Promise.reject(new Error('不能为空'));
}
},
}),
] as Rule[],
},
cascader: {
required: true,
rules: [
() => ({
validator(_, value) {
if (value && value.length > 0) {
value.forEach((item: any) => {
if (item === undefined) {
return Promise.reject(new Error('不能为空'));
}
});
return Promise.resolve();
} else {
return Promise.reject(new Error('不能为空'));
}
},
}),
] as Rule[],
},
};

205
src/components/Selects.tsx Normal file
View File

@ -0,0 +1,205 @@
import { rulesHelper } from '@/common';
import { Apis } from '@/gen/Apis';
import { ProColumns, ProFormColumnsType } from '@ant-design/pro-components';
type ReturnType = ProColumns<any, 'text'> & ProFormColumnsType<any, 'text'>;
type PropsType = { required?: boolean } & ReturnType;
export const Selects = {
Agents(props?: PropsType): ReturnType {
const {
title = '代理',
key = 'agents_id',
dataIndex = ['agent', 'name'],
required = false,
hideInTable = false,
...rest
} = props ?? {};
return {
title: title,
key: key,
dataIndex: dataIndex,
valueType: 'select',
hideInTable: hideInTable,
formItemProps: { ...(required ? rulesHelper.number : {}) },
fieldProps: {
showSearch: false,
fieldNames: {
label: 'name',
value: 'id',
},
},
request: async () => (await Apis.Select.Agents()).data,
...rest,
};
},
LoanCompany(props?: PropsType): ReturnType {
const {
title = '资金方',
key = 'loan_companies_id',
dataIndex = ['loan_company', 'name'],
required = false,
hideInTable = false,
...rest
} = props ?? {};
return {
title: title,
key: key,
dataIndex: dataIndex,
valueType: 'select',
hideInTable: hideInTable,
formItemProps: { ...(required ? rulesHelper.number : {}) },
fieldProps: {
showSearch: false,
fieldNames: {
label: 'name',
value: 'id',
},
},
request: async () => (await Apis.Select.LoanCompanies()).data,
...rest,
};
},
Markets(props?: PropsType): ReturnType {
const {
title = '选择上游',
key = 'markets_id',
dataIndex = ['market', 'name'],
required = false,
hideInTable = false,
...rest
} = props ?? {};
return {
title: title,
key: key,
dataIndex: dataIndex,
valueType: 'select',
hideInTable: hideInTable,
formItemProps: { ...(required ? rulesHelper.number : {}) },
fieldProps: {
showSearch: false,
fieldNames: {
label: 'name',
value: 'id',
},
},
request: async () => (await Apis.Select.Markets()).data,
...rest,
};
},
Bosses(props?: PropsType): ReturnType {
const {
title = '老板',
key = 'bosses_id',
dataIndex = ['boss', 'name'],
required = false,
hideInTable = false,
...rest
} = props ?? {};
return {
title: title,
key: key,
dataIndex: dataIndex,
valueType: 'select',
hideInTable: hideInTable,
formItemProps: { ...(required ? rulesHelper.number : {}) },
fieldProps: {
showSearch: false,
fieldNames: {
label: 'name',
value: 'id',
},
},
request: async () => (await Apis.Select.Bosses()).data,
...rest,
};
},
Factories(props?: PropsType): ReturnType {
const {
title = '下游厂家',
key = 'factories_id',
dataIndex = ['factory', 'name'],
required = false,
hideInTable = false,
...rest
} = props ?? {};
return {
title: title,
key: key,
dataIndex: dataIndex,
valueType: 'select',
hideInTable: hideInTable,
formItemProps: { ...(required ? rulesHelper.number : {}) },
fieldProps: {
showSearch: false,
fieldNames: {
label: 'name',
value: 'id',
},
},
request: async () => (await Apis.Select.Factories()).data,
...rest,
};
},
Platforms(props?: PropsType): ReturnType {
const {
title = '平台',
key = 'platforms_id',
dataIndex = ['platform', 'name'],
required = false,
hideInTable = false,
...rest
} = props ?? {};
return {
title: title,
key: key,
dataIndex: dataIndex,
valueType: 'select',
hideInTable: hideInTable,
formItemProps: { ...(required ? rulesHelper.number : {}) },
fieldProps: {
showSearch: false,
fieldNames: {
label: 'name',
value: 'id',
},
},
request: async () => (await Apis.Select.Platforms()).data,
...rest,
};
},
Merchants(props?: PropsType): ReturnType {
const {
title = '商户',
key = 'merchants_id',
dataIndex = ['merchant', 'name'],
required = false,
hideInTable = false,
...rest
} = props ?? {};
return {
title: title,
key: key,
dataIndex: dataIndex,
valueType: 'select',
hideInTable: hideInTable,
formItemProps: { ...(required ? rulesHelper.number : {}) },
fieldProps: {
showSearch: false,
fieldNames: {
label: 'name',
value: 'id',
},
},
request: async () => (await Apis.Select.Merchants()).data,
...rest,
};
},
};

View File

@ -0,0 +1,90 @@
import { rulesHelper } from '@/common';
import { Apis } from '@/gen/Apis';
import { ProColumns, ProFormColumnsType } from '@ant-design/pro-components';
type ReturnType = ProColumns<any, 'text'> & ProFormColumnsType<any, 'text'>;
type PropsType = { required?: boolean } & ReturnType;
export const SysSelects = {
Api(props?: PropsType): ReturnType {
const { required = false, ...rest } = props ?? {};
return {
title: '后端API',
dataIndex: 'backend_apis',
valueType: 'treeSelect',
hideInTable: true,
formItemProps: { ...(required ? rulesHelper.number : {}) },
fieldProps: {
showSearch: true,
allowClear: true,
treeDefaultExpandAll: true,
dropdownStyle: { maxHeight: 400, overflow: 'auto' },
multiple: true,
fieldNames: {
label: 'value',
value: 'value',
children: 'children',
},
},
request: async () =>
(await Apis.SysPermissions.SelectApi()).data?.children,
...rest,
};
},
SysPermissionsTree(props?: { guard_name: string } & PropsType): ReturnType {
const { guard_name = 'Admin', ...rest } = props ?? {};
return {
key: 'parent_id',
title: '上级菜单',
valueType: 'treeSelect',
request: async () => {
return Apis.SysPermissions.Tree({ guard_name: guard_name }).then(
(res) => res.data,
);
},
fieldProps: {
allowClear: true,
autoClearSearchValue: true,
bordered: true,
fieldNames: {
label: 'name',
value: 'id',
},
filterTreeNode: true,
showSearch: true,
treeNodeFilterProp: 'title',
treeDefaultExpandAll: true,
},
...rest,
};
},
SysRoles(props?: PropsType): ReturnType {
const {
title = '角色',
key = 'roles_id',
required = false,
hideInTable = true,
...rest
} = props ?? {};
return {
title: title,
key: key,
valueType: 'select',
hideInTable: hideInTable,
formItemProps: { ...(required ? rulesHelper.number : {}) },
fieldProps: {
mode: 'multiple',
showSearch: false,
fieldNames: {
label: 'name',
value: 'id',
},
},
request: async () => (await Apis.SysRoles.Select()).data,
...rest,
};
},
};

360
src/gen/ApiTypes.d.ts vendored Normal file
View File

@ -0,0 +1,360 @@
declare namespace ApiTypes {
namespace Admins {
type List = {
"username"?: string; // 模糊搜索:名称
};
type Store = {
"username": string; // 用户名
"password": string; // 密码
"roles_id"?: string[]; // 角色
};
type Update = {
"id": number; // id
"username": string; // 用户名/手机号
"password"?: string; // 密码,[hidden]
"roles_id"?: string[]; // 角色
};
type Delete = {
"id": number; // id
};
}
namespace Agents {
type List = {
"name"?: string; // 模糊搜索:名称
};
type Store = {
"name": string; // 简称
"full_name"?: string; // 全称
"address"?: string; // 地址
"contact"?: string; // 联系人
"phone"?: string; // 联系电话
};
type Update = {
"id": number; // id
"name": string; // 简称
"full_name"?: string; // 全称
"address"?: string; // 地址
"contact"?: string; // 联系人
"phone"?: string; // 联系电话
};
type Show = {
"id": number; // id
};
type Delete = {
"id": number; // id
};
}
namespace Auth {
type Login = {
"username": string; // 用户名
"password": string; // 密码
"captcha": string; // 验证码
"captcha_key": string; // 验证码key
};
type ChangePassword = {
"old_password": string; // 老密码
"new_password": string; // 新密码
"re_new_password": string; // 重复新密码
};
type PreUpload = {
"filename": string; // 文件名称
};
}
namespace Bosses {
type List = {
"name"?: string; // 模糊搜索:名称
};
type Store = {
"name": string; // 简称
"full_name"?: string; // 全称
"address"?: string; // 地址
"contact"?: string; // 联系人
"phone"?: string; // 联系电话
};
type Update = {
"id": number; // id
"name": string; // 简称
"full_name"?: string; // 全称
"address"?: string; // 地址
"contact"?: string; // 联系人
"phone"?: string; // 联系电话
};
type Show = {
"id": number; // id
};
type Delete = {
"id": number; // id
};
}
namespace Factories {
type List = {
"name"?: string; // 模糊搜索:名称
};
type Store = {
"name": string; // 厂家名称
"public_key": string; // 下游公钥
};
type Update = {
"id": number; // id
"name": string; // 厂家名称
"public_key": string; // 下游公钥
};
type Delete = {
"id": number; // id
};
}
namespace LoanCompanies {
type List = {
"type"?: string; // 用户类型,[enum:LoanCompaniesTypeEnum]
"name"?: string; // 模糊搜索:名称
};
type Store = {
"type"?: string; // 用户类型,[enum:LoanCompaniesTypeEnum]
"name": string; // 简称
"full_name"?: string; // 全称
"address"?: string; // 地址
"contact"?: string; // 联系人
"phone"?: string; // 联系电话
"up_merchant_no": string; // 上游商户号
};
type Update = {
"id": number; // id
"type"?: string; // 用户类型,[enum:LoanCompaniesTypeEnum]
"name": string; // 简称
"full_name"?: string; // 全称
"address"?: string; // 地址
"contact"?: string; // 联系人
"phone"?: string; // 联系电话
"up_merchant_no": string; // 上游商户号
};
type Show = {
"id": number; // id
};
type Delete = {
"id": number; // id
};
}
namespace LoanDailyDetails {
type List = {
"merchants_id"?: number; // 商户ID
"date"?: Date; // 日期
"order_status"?: string; // 订单状态:LoansOrderStatusEnum
};
type Export = {
"merchants_id"?: number; // 商户ID
"date"?: Date; // 日期
"order_status"?: string; // 订单状态:LoansOrderStatusEnum
};
}
namespace Loans {
type List = {
"type"?: string; // 算法类型,[enum:LoansTypeEnum]
"name"?: string; // 显示名称
"order_status"?: string; // 订单状态,[enum:LoansOrderStatusEnum]
};
type Store = {
"merchants_id": number; // 商户id,[ref:merchants]
"loan_companies_id": number; // 资金方id,[ref:loan_companies]
"name": string; // 显示名称
"type"?: string; // 算法类型,[enum:LoansTypeEnum]
"loan_days"?: number; // 贷款天数
"started_at": Date; // 开始时间
"total_due_principal": number; // 总应还-本金
"total_due_interest"?: number; // 总应还-利息
};
type Delete = {
"id": number; // id
};
}
namespace Markets {
type List = {
"name"?: string; // 模糊搜索:名称
};
type Store = {
"name": string; // 市场名称
"market_no": string; // 市场编号
"public_key": string; // 上游公钥
};
type Update = {
"id": number; // id
"name": string; // 市场名称
"market_no": string; // 市场编号
"public_key": string; // 上游公钥
};
type Delete = {
"id": number; // id
};
}
namespace MerchantCounters {
type List = {
"merchants_id"?: number; // 商户id,[ref:merchants]
};
type Store = {
"merchants_id": number; // 商户id,[ref:merchants]
"markets_id": number; // 上游市场id,[ref:markets]
"name": string; // 名称
"up_merchant_no": string; // 上游商家编号
"commissions": string[]; // 分账配置
};
type Update = {
"id": number; // id
"name": string; // 名称
"up_merchant_no": string; // 上游商家编号
"commissions": string[]; // 分账配置
};
type Delete = {
"id": number; // id
};
}
namespace Merchants {
type List = {
"agents_id"?: number; // 代理id,[ref:agents]
"bosses_id"?: number; // 老板id,[ref:bosses]
"factories_id"?: number; // 厂家id,[ref:factories]
"platforms_id"?: number; // 平台id,[ref:platforms]
"name"?: string; // 商户简称
"full_name"?: string; // 商户全称呼
"plat_merchant_no"?: string; // 平台商户号
};
type Store = {
"agents_id": number; // 代理id,[ref:agents]
"bosses_id": number; // 老板id,[ref:bosses]
"factories_id": number; // 厂家id,[ref:factories]
"platforms_id": number; // 平台id,[ref:platforms]
"name": string; // 商户简称
"full_name": string; // 商户全称呼
};
type Update = {
"id": number; // id
"agents_id": number; // 代理id,[ref:agents]
"bosses_id": number; // 老板id,[ref:bosses]
"factories_id": number; // 厂家id,[ref:factories]
"platforms_id": number; // 平台id,[ref:platforms]
"name": string; // 商户简称
"full_name": string; // 商户全称呼
};
type Delete = {
"id": number; // id
};
}
namespace OrgUsers {
type List = {
"type"?: string; // 机构类型,[enum:OrgUsersTypeEnum]
"org_name"?: string; // 机构名称
"org_id"?: number; // 机构id
"username"?: string; // 用户名
};
type Store = {
"type": string; // 用户类型,[enum:OrgUsersTypeEnum]
"org_name": string; // 机构名称
"username": string; // 用户名
"password": string; // 密码,[hidden]
};
type Update = {
"id": number; // id
"password": string; // 密码,[hidden]
};
type Show = {
"id": number; // id
};
type Delete = {
"id": number; // id
};
}
namespace PayOrders {
type List = {
"merchant_name"?: string; // 模糊搜索:商户简称
"type"?: string; // 交易类型,[enum:PayOrdersTypeEnum]
"order_status"?: string; // 订单状态,[enum:PayOrderStatusEnum]
"day_date"?: Date; // 日期
"down_order_no"?: string; // 下游订单号
"up_order_no"?: string; // 上游订单号
"up_tx_no"?: string; // 上游交易流水号
"amount_order"?: string; // 金额
"up_order_at_range"?: string[]; // 上游订单时间范围
};
}
namespace Platforms {
type List = {
"name"?: string; // 模糊搜索:名称
};
type Store = {
"name": string; // 平台名称
"private_key": string; // 平台私钥
"public_key": string; // 平台公钥
};
type Update = {
"id": number; // id
"name": string; // 平台名称
"private_key": string; // 平台私钥
"public_key": string; // 平台公钥
};
type Delete = {
"id": number; // id
};
}
namespace Select {
}
namespace SysPermissions {
type List = {
"parent_id"?: number; // 上级ID
"guard_name": string; //
};
type Tree = {
"parent_id"?: number; // 上级ID
"guard_name": string; //
};
type Store = {
"name": string; //
"key"?: string; //
"guard_name": string; //
"icon"?: string; // 图标
"type": string; // 类型:SysPermissionsTypeEnum
"backend_apis"?: string[]; // 后台api
"path"?: string; // 路由
"parent_id"?: number; //
};
type Update = {
"id": number; // ID
"name": string; //
"key"?: string; //
"guard_name": string; //
"icon"?: string; // 图标
"type": string; // 类型:SysPermissionsTypeEnum
"backend_apis"?: string[]; // 后台api
"path"?: string; // 路由
"parent_id"?: number; //
};
type Delete = {
"id": number; // ID
};
type Move = {
"id": number; // ID
"type": string; // 类型up 升级down 降级
};
}
namespace SysRoles {
type List = {
"name"?: string; // 模糊搜索:名称
};
type Store = {
"name": string; // 名称
"color"?: string; // 颜色
};
type Update = {
"id": number; // ID
"name": string; // 名称
"color"?: string; // 颜色
};
type Delete = {
"id": number; // ID
};
type GetPermissions = {
"id": number; // ID
};
type SetPermissions = {
"id": number; // ID
"permissions_ids": string[]; // 权限ID
};
}
}

270
src/gen/Apis.ts Normal file
View File

@ -0,0 +1,270 @@
import { MyResponseType } from '@/common';
import { request } from '@umijs/max';
export const Apis = {
Admins: {
List(data?: ApiTypes.Admins.List): Promise<MyResponseType> {
return request('admin/admins/list', { data });
},
Store(data: ApiTypes.Admins.Store): Promise<MyResponseType> {
return request('admin/admins/store', { data });
},
Update(data: ApiTypes.Admins.Update): Promise<MyResponseType> {
return request('admin/admins/update', { data });
},
Delete(data: ApiTypes.Admins.Delete): Promise<MyResponseType> {
return request('admin/admins/delete', { data });
},
},
Agents: {
List(data?: ApiTypes.Agents.List): Promise<MyResponseType> {
return request('admin/agents/list', { data });
},
Store(data: ApiTypes.Agents.Store): Promise<MyResponseType> {
return request('admin/agents/store', { data });
},
Update(data: ApiTypes.Agents.Update): Promise<MyResponseType> {
return request('admin/agents/update', { data });
},
Show(data: ApiTypes.Agents.Show): Promise<MyResponseType> {
return request('admin/agents/show', { data });
},
Delete(data: ApiTypes.Agents.Delete): Promise<MyResponseType> {
return request('admin/agents/delete', { data });
},
},
Auth: {
Captcha(): Promise<MyResponseType> {
return request('admin/auth/captcha', {});
},
Login(data: ApiTypes.Auth.Login): Promise<MyResponseType> {
return request('admin/auth/login', { data });
},
Logout(): Promise<MyResponseType> {
return request('admin/auth/logout', {});
},
Me(): Promise<MyResponseType> {
return request('admin/auth/me', {});
},
ChangePassword(data: ApiTypes.Auth.ChangePassword): Promise<MyResponseType> {
return request('admin/auth/change_password', { data });
},
PreUpload(data: ApiTypes.Auth.PreUpload): Promise<MyResponseType> {
return request('admin/auth/pre_upload', { data });
},
},
Bosses: {
List(data?: ApiTypes.Bosses.List): Promise<MyResponseType> {
return request('admin/bosses/list', { data });
},
Store(data: ApiTypes.Bosses.Store): Promise<MyResponseType> {
return request('admin/bosses/store', { data });
},
Update(data: ApiTypes.Bosses.Update): Promise<MyResponseType> {
return request('admin/bosses/update', { data });
},
Show(data: ApiTypes.Bosses.Show): Promise<MyResponseType> {
return request('admin/bosses/show', { data });
},
Delete(data: ApiTypes.Bosses.Delete): Promise<MyResponseType> {
return request('admin/bosses/delete', { data });
},
},
Factories: {
List(data?: ApiTypes.Factories.List): Promise<MyResponseType> {
return request('admin/factories/list', { data });
},
Store(data: ApiTypes.Factories.Store): Promise<MyResponseType> {
return request('admin/factories/store', { data });
},
Update(data: ApiTypes.Factories.Update): Promise<MyResponseType> {
return request('admin/factories/update', { data });
},
Delete(data: ApiTypes.Factories.Delete): Promise<MyResponseType> {
return request('admin/factories/delete', { data });
},
},
LoanCompanies: {
List(data?: ApiTypes.LoanCompanies.List): Promise<MyResponseType> {
return request('admin/loan_companies/list', { data });
},
Store(data: ApiTypes.LoanCompanies.Store): Promise<MyResponseType> {
return request('admin/loan_companies/store', { data });
},
Update(data: ApiTypes.LoanCompanies.Update): Promise<MyResponseType> {
return request('admin/loan_companies/update', { data });
},
Show(data: ApiTypes.LoanCompanies.Show): Promise<MyResponseType> {
return request('admin/loan_companies/show', { data });
},
Delete(data: ApiTypes.LoanCompanies.Delete): Promise<MyResponseType> {
return request('admin/loan_companies/delete', { data });
},
},
LoanDailyDetails: {
List(data?: ApiTypes.LoanDailyDetails.List): Promise<MyResponseType> {
return request('admin/loan_daily_details/list', { data });
},
Export(data?: ApiTypes.LoanDailyDetails.Export): Promise<MyResponseType> {
return request('admin/loan_daily_details/export', { responseType: 'blob',data });
},
},
Loans: {
List(data?: ApiTypes.Loans.List): Promise<MyResponseType> {
return request('admin/loans/list', { data });
},
Store(data: ApiTypes.Loans.Store): Promise<MyResponseType> {
return request('admin/loans/store', { data });
},
Delete(data: ApiTypes.Loans.Delete): Promise<MyResponseType> {
return request('admin/loans/delete', { data });
},
},
Markets: {
List(data?: ApiTypes.Markets.List): Promise<MyResponseType> {
return request('admin/markets/list', { data });
},
Store(data: ApiTypes.Markets.Store): Promise<MyResponseType> {
return request('admin/markets/store', { data });
},
Update(data: ApiTypes.Markets.Update): Promise<MyResponseType> {
return request('admin/markets/update', { data });
},
Delete(data: ApiTypes.Markets.Delete): Promise<MyResponseType> {
return request('admin/markets/delete', { data });
},
},
MerchantCounters: {
List(data?: ApiTypes.MerchantCounters.List): Promise<MyResponseType> {
return request('admin/merchant_counters/list', { data });
},
Store(data: ApiTypes.MerchantCounters.Store): Promise<MyResponseType> {
return request('admin/merchant_counters/store', { data });
},
Update(data: ApiTypes.MerchantCounters.Update): Promise<MyResponseType> {
return request('admin/merchant_counters/update', { data });
},
Delete(data: ApiTypes.MerchantCounters.Delete): Promise<MyResponseType> {
return request('admin/merchant_counters/delete', { data });
},
},
Merchants: {
List(data?: ApiTypes.Merchants.List): Promise<MyResponseType> {
return request('admin/merchants/list', { data });
},
Store(data: ApiTypes.Merchants.Store): Promise<MyResponseType> {
return request('admin/merchants/store', { data });
},
Update(data: ApiTypes.Merchants.Update): Promise<MyResponseType> {
return request('admin/merchants/update', { data });
},
Delete(data: ApiTypes.Merchants.Delete): Promise<MyResponseType> {
return request('admin/merchants/delete', { data });
},
},
OrgUsers: {
List(data?: ApiTypes.OrgUsers.List): Promise<MyResponseType> {
return request('admin/org_users/list', { data });
},
Store(data: ApiTypes.OrgUsers.Store): Promise<MyResponseType> {
return request('admin/org_users/store', { data });
},
Update(data: ApiTypes.OrgUsers.Update): Promise<MyResponseType> {
return request('admin/org_users/update', { data });
},
Show(data: ApiTypes.OrgUsers.Show): Promise<MyResponseType> {
return request('admin/org_users/show', { data });
},
Delete(data: ApiTypes.OrgUsers.Delete): Promise<MyResponseType> {
return request('admin/org_users/delete', { data });
},
},
PayOrders: {
List(data?: ApiTypes.PayOrders.List): Promise<MyResponseType> {
return request('admin/pay_orders/list', { data });
},
},
Platforms: {
List(data?: ApiTypes.Platforms.List): Promise<MyResponseType> {
return request('admin/platforms/list', { data });
},
Store(data: ApiTypes.Platforms.Store): Promise<MyResponseType> {
return request('admin/platforms/store', { data });
},
Update(data: ApiTypes.Platforms.Update): Promise<MyResponseType> {
return request('admin/platforms/update', { data });
},
Delete(data: ApiTypes.Platforms.Delete): Promise<MyResponseType> {
return request('admin/platforms/delete', { data });
},
},
Select: {
Agents(): Promise<MyResponseType> {
return request('admin/select/agents', {});
},
LoanCompanies(): Promise<MyResponseType> {
return request('admin/select/loan_companies', {});
},
Factories(): Promise<MyResponseType> {
return request('admin/select/factories', {});
},
Platforms(): Promise<MyResponseType> {
return request('admin/select/platforms', {});
},
Bosses(): Promise<MyResponseType> {
return request('admin/select/bosses', {});
},
Markets(): Promise<MyResponseType> {
return request('admin/select/markets', {});
},
Merchants(): Promise<MyResponseType> {
return request('admin/select/merchants', {});
},
},
SysPermissions: {
List(data: ApiTypes.SysPermissions.List): Promise<MyResponseType> {
return request('admin/sys_permissions/list', { data });
},
Tree(data: ApiTypes.SysPermissions.Tree): Promise<MyResponseType> {
return request('admin/sys_permissions/tree', { data });
},
Store(data: ApiTypes.SysPermissions.Store): Promise<MyResponseType> {
return request('admin/sys_permissions/store', { data });
},
Update(data: ApiTypes.SysPermissions.Update): Promise<MyResponseType> {
return request('admin/sys_permissions/update', { data });
},
Delete(data: ApiTypes.SysPermissions.Delete): Promise<MyResponseType> {
return request('admin/sys_permissions/delete', { data });
},
Move(data: ApiTypes.SysPermissions.Move): Promise<MyResponseType> {
return request('admin/sys_permissions/move', { data });
},
SelectApi(): Promise<MyResponseType> {
return request('admin/sys_permissions/select_api', {});
},
},
SysRoles: {
List(data?: ApiTypes.SysRoles.List): Promise<MyResponseType> {
return request('admin/sys_roles/list', { data });
},
Store(data: ApiTypes.SysRoles.Store): Promise<MyResponseType> {
return request('admin/sys_roles/store', { data });
},
Update(data: ApiTypes.SysRoles.Update): Promise<MyResponseType> {
return request('admin/sys_roles/update', { data });
},
Delete(data: ApiTypes.SysRoles.Delete): Promise<MyResponseType> {
return request('admin/sys_roles/delete', { data });
},
Select(): Promise<MyResponseType> {
return request('admin/sys_roles/select', {});
},
GetPermissions(data: ApiTypes.SysRoles.GetPermissions): Promise<MyResponseType> {
return request('admin/sys_roles/get_permissions', { data });
},
SetPermissions(data: ApiTypes.SysRoles.SetPermissions): Promise<MyResponseType> {
return request('admin/sys_roles/set_permissions', { data });
},
},
}

72
src/gen/Enums.ts Normal file
View File

@ -0,0 +1,72 @@
// CommissionTypeEnum
export const CommissionTypeEnum= {
'Platform': {"text":"平台","color":"#1890ff","value":"Platform"},
'Agent': {"text":"代理","color":"#722ed1","value":"Agent"},
'Repayment': {"text":"还款","color":"#f5222d","value":"Repayment"},
'Merchant': {"text":"商家","color":"#52c41a","value":"Merchant"},
'Other': {"text":"其他","color":"#bfbfbf","value":"Other"},
};
// LoanCompaniesTypeEnum
export const LoanCompaniesTypeEnum= {
'DeviceManufacturer': {"text":"设备商","color":"#459423","value":"DeviceManufacturer"},
'CapitalProvider': {"text":"资金方","color":"#13952d","value":"CapitalProvider"},
'SupplyChain': {"text":"供应链","color":"#033fcc","value":"SupplyChain"},
};
// LoansOrderStatusEnum
export const LoansOrderStatusEnum= {
'NotStart': {"text":"未开始","color":"#983054","value":"NotStart"},
'OnGoing': {"text":"进行中","color":"#a36451","value":"OnGoing"},
'Finished': {"text":"已完成","color":"#0ce4b6","value":"Finished"},
'Overdue': {"text":"逾期","color":"#7723f8","value":"Overdue"},
};
// LoansTypeEnum
export const LoansTypeEnum= {
'Daily': {"text":"每日还款(等本等息)","color":"#e847d5","value":"Daily"},
};
// OrgUserTypeEnum
export const OrgUsersTypeEnum= {
'Agent': {"text":"代理","color":"#bd1087","value":"Agent"},
'Bosses': {"text":"老板","color":"#5892ea","value":"Bosses"},
'LoanCompanies': {"text":"资金方","color":"#7e2070","value":"LoanCompanies"},
};
// PayOrderStatusEnum
export const PayOrderStatusEnum= {
'1': {"text":"待支付","color":"#faad14","value":"1"},
'2': {"text":"支付成功","color":"#52c41a","value":"2"},
'3': {"text":"支付失败","color":"#f5222d","value":"3"},
'9': {"text":"待轮询","color":"#1890ff","value":"9"},
'0': {"text":"其他","color":"#bfbfbf","value":"0"},
};
// PayOrdersTypeEnum
export const PayOrdersTypeEnum= {
'1': {"text":"支付","color":"#52c41a","value":"1"},
'2': {"text":"退款","color":"#faad14","value":"2"},
};
// SysModuleEnum
export const SysModuleEnum= {
'Admin': {"text":"管理员","color":"#cf1322","value":"Admin"},
'Customer': {"text":"客户","color":"#d4b106","value":"Customer"},
};
// SysPermissionsTypeEnum
export const SysPermissionsTypeEnum= {
'Directory': {"text":"目录","color":"#6d7e14","value":"Directory"},
'Page': {"text":"页面","color":"#4d9a13","value":"Page"},
'Button': {"text":"按钮","color":"#97224f","value":"Button"},
};
// SysTasksStatusEnum
export const SysTasksStatusEnum= {
'1': {"text":"未开始","color":"#bfbfbf","value":"1"},
'2': {"text":"未开始","color":"#1890ff","value":"2"},
'3': {"text":"已完成","color":"#a0d911","value":"3"},
'4': {"text":"错误","color":"#f5222d","value":"4"},
};

11
src/global.scss Normal file
View File

@ -0,0 +1,11 @@
.ant-pro-page-container {
.ant-page-header-heading-title {
line-height: 51px !important;
font-size: 16px !important;
font-weight: 700;
padding-left: 20px;
}
.ant-pro-grid-content {
padding: 20px;
}
}

View File

@ -0,0 +1,65 @@
import {
MyButtons,
MyColumns,
MyPageContainer,
MyProTableProps,
} from '@/common';
import { Apis } from '@/gen/Apis';
import { ProTable } from '@ant-design/pro-components';
import { Space } from 'antd';
import Create from './modals/Create';
import Update from './modals/Update';
export default function Index({ title = '代理商' }) {
return (
<MyPageContainer title={title}>
<ProTable
{...MyProTableProps.props}
request={async (params, sort) =>
MyProTableProps.request(params, sort, Apis.Agents.List)
}
toolBarRender={(action) => [
<Create key="Create" reload={action?.reload} title={title} />,
]}
columns={[
MyColumns.ID(),
{
title: '简称',
dataIndex: 'name',
},
{
title: '全称',
dataIndex: 'full_name',
},
{
dataIndex: 'address',
title: '地址',
},
{
dataIndex: 'contact',
title: '联系人',
},
{
dataIndex: 'phone',
title: '联系电话',
},
MyColumns.CreatedAt(),
MyColumns.Option({
render: (_, item: any, index, action) => (
<Space key={index}>
<Update item={item} reload={action?.reload} title={title} />
<MyButtons.Delete
onConfirm={() =>
Apis.Agents.Delete({ id: item.id }).then(() =>
action?.reload(),
)
}
/>
</Space>
),
}),
]}
/>
</MyPageContainer>
);
}

View File

@ -0,0 +1,54 @@
import {
MyBetaModalFormProps,
MyButtons,
MyModalFormProps,
rulesHelper,
} from '@/common';
import { Apis } from '@/gen/Apis';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { message } from 'antd';
export default function Create(props: MyBetaModalFormProps) {
return (
<BetaSchemaForm<ApiTypes.Agents.Store>
{...MyModalFormProps.props}
title={`添加${props.title}`}
wrapperCol={{ span: 24 }}
width="500px"
trigger={<MyButtons.Create title={`添加${props.title}`} />}
onFinish={async (values) =>
Apis.Agents.Store(values)
.then(() => {
props.reload?.();
message.success(props.title + '成功');
return true;
})
.catch(() => false)
}
columns={[
{
key: 'name',
title: '简称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'full_name',
title: '全称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'address',
title: '地址',
},
{
key: 'contact',
title: '联系人',
},
{
key: 'phone',
title: '联系电话',
},
]}
/>
);
}

View File

@ -0,0 +1,55 @@
import {
MyBetaModalFormProps,
MyButtons,
MyModalFormProps,
rulesHelper,
} from '@/common';
import { Apis } from '@/gen/Apis';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { message } from 'antd';
export default function Update(props: MyBetaModalFormProps) {
return (
<BetaSchemaForm<ApiTypes.Agents.Update>
{...MyModalFormProps.props}
title={`编辑${props.title}`}
trigger={<MyButtons.Edit />}
wrapperCol={{ span: 24 }}
width="500px"
request={() => Promise.resolve(props.item)}
onFinish={async (values) =>
Apis.Agents.Update({ ...values, id: props.item?.id ?? 0 })
.then(() => {
props.reload?.();
message.success(props.title + '成功');
return true;
})
.catch(() => false)
}
columns={[
{
key: 'name',
title: '简称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'full_name',
title: '全称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'address',
title: '地址',
},
{
key: 'contact',
title: '联系人',
},
{
key: 'phone',
title: '联系电话',
},
]}
/>
);
}

View File

@ -0,0 +1,65 @@
import {
MyButtons,
MyColumns,
MyPageContainer,
MyProTableProps,
} from '@/common';
import { Apis } from '@/gen/Apis';
import { ProTable } from '@ant-design/pro-components';
import { Space } from 'antd';
import Create from './modals/Create';
import Update from './modals/Update';
export default function Index({ title = '超市老板' }) {
return (
<MyPageContainer title={title}>
<ProTable
{...MyProTableProps.props}
request={async (params, sort) =>
MyProTableProps.request(params, sort, Apis.Bosses.List)
}
toolBarRender={(action) => [
<Create key="Create" reload={action?.reload} title={title} />,
]}
columns={[
MyColumns.ID(),
{
title: '简称',
dataIndex: 'name',
},
{
title: '全称',
dataIndex: 'full_name',
},
{
dataIndex: 'address',
title: '地址',
},
{
dataIndex: 'contact',
title: '联系人',
},
{
dataIndex: 'phone',
title: '联系电话',
},
MyColumns.CreatedAt(),
MyColumns.Option({
render: (_, item: any, index, action) => (
<Space key={index}>
<Update item={item} reload={action?.reload} title={title} />
<MyButtons.Delete
onConfirm={() =>
Apis.Bosses.Delete({ id: item.id }).then(() =>
action?.reload(),
)
}
/>
</Space>
),
}),
]}
/>
</MyPageContainer>
);
}

View File

@ -0,0 +1,54 @@
import {
MyBetaModalFormProps,
MyButtons,
MyModalFormProps,
rulesHelper,
} from '@/common';
import { Apis } from '@/gen/Apis';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { message } from 'antd';
export default function Create(props: MyBetaModalFormProps) {
return (
<BetaSchemaForm<ApiTypes.Bosses.Store>
{...MyModalFormProps.props}
title={`添加${props.title}`}
wrapperCol={{ span: 24 }}
width="500px"
trigger={<MyButtons.Create title={`添加${props.title}`} />}
onFinish={async (values) =>
Apis.Bosses.Store(values)
.then(() => {
props.reload?.();
message.success(props.title + '成功');
return true;
})
.catch(() => false)
}
columns={[
{
key: 'name',
title: '简称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'full_name',
title: '全称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'address',
title: '地址',
},
{
key: 'contact',
title: '联系人',
},
{
key: 'phone',
title: '联系电话',
},
]}
/>
);
}

View File

@ -0,0 +1,55 @@
import {
MyBetaModalFormProps,
MyButtons,
MyModalFormProps,
rulesHelper,
} from '@/common';
import { Apis } from '@/gen/Apis';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { message } from 'antd';
export default function Update(props: MyBetaModalFormProps) {
return (
<BetaSchemaForm<ApiTypes.Bosses.Update>
{...MyModalFormProps.props}
title={`编辑${props.title}`}
trigger={<MyButtons.Edit />}
wrapperCol={{ span: 24 }}
width="500px"
request={() => Promise.resolve(props.item)}
onFinish={async (values) =>
Apis.Bosses.Update({ ...values, id: props.item?.id ?? 0 })
.then(() => {
props.reload?.();
message.success(props.title + '成功');
return true;
})
.catch(() => false)
}
columns={[
{
key: 'name',
title: '简称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'full_name',
title: '全称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'address',
title: '地址',
},
{
key: 'contact',
title: '联系人',
},
{
key: 'phone',
title: '联系电话',
},
]}
/>
);
}

View File

@ -0,0 +1,55 @@
import {
MyButtons,
MyColumns,
MyPageContainer,
MyProTableProps,
} from '@/common';
import { Apis } from '@/gen/Apis';
import { ProTable } from '@ant-design/pro-components';
import { Space } from 'antd';
import Create from './modals/Create';
import Update from './modals/Update';
export default function Index({ title = '下游厂家' }) {
return (
<MyPageContainer title={title}>
<ProTable
{...MyProTableProps.props}
search={false}
request={async (params, sort) =>
MyProTableProps.request(params, sort, Apis.Factories.List)
}
toolBarRender={(action) => [
<Create key="Create" reload={action?.reload} title={title} />,
]}
columns={[
MyColumns.ID(),
{
title: '厂家名称',
dataIndex: 'name',
},
{
title: '下游公钥',
dataIndex: 'public_key',
},
MyColumns.CreatedAt(),
MyColumns.UpdatedAt(),
MyColumns.Option({
render: (_, item: any, index, action) => (
<Space key={index}>
<Update item={item} reload={action?.reload} title={title} />
<MyButtons.Delete
onConfirm={() =>
Apis.Factories.Delete({ id: item.id }).then(() =>
action?.reload(),
)
}
/>
</Space>
),
}),
]}
/>
</MyPageContainer>
);
}

View File

@ -0,0 +1,43 @@
import {
MyBetaModalFormProps,
MyButtons,
MyModalFormProps,
rulesHelper,
} from '@/common';
import { Apis } from '@/gen/Apis';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { message } from 'antd';
export default function Create(props: MyBetaModalFormProps) {
return (
<BetaSchemaForm<ApiTypes.Factories.Store>
{...MyModalFormProps.props}
title={`添加${props.title}`}
wrapperCol={{ span: 24 }}
width="500px"
trigger={<MyButtons.Create title={`添加${props.title}`} />}
onFinish={async (values) =>
Apis.Factories.Store(values)
.then(() => {
props.reload?.();
message.success(props.title + '成功');
return true;
})
.catch(() => false)
}
columns={[
{
key: 'name',
title: '厂家名称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'public_key',
title: '下游公钥',
valueType: 'textarea',
formItemProps: { ...rulesHelper.text },
},
]}
/>
);
}

View File

@ -0,0 +1,44 @@
import {
MyBetaModalFormProps,
MyButtons,
MyModalFormProps,
rulesHelper,
} from '@/common';
import { Apis } from '@/gen/Apis';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { message } from 'antd';
export default function Update(props: MyBetaModalFormProps) {
return (
<BetaSchemaForm<ApiTypes.Factories.Update>
{...MyModalFormProps.props}
title={`编辑${props.title}`}
trigger={<MyButtons.Edit />}
wrapperCol={{ span: 24 }}
width="500px"
request={() => Promise.resolve(props.item)}
onFinish={async (values) =>
Apis.Factories.Update({ ...values, id: props.item?.id ?? 0 })
.then(() => {
props.reload?.();
message.success(props.title + '成功');
return true;
})
.catch(() => false)
}
columns={[
{
key: 'name',
title: '厂家名称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'public_key',
title: '下游公钥',
valueType: 'textarea',
formItemProps: { ...rulesHelper.text },
},
]}
/>
);
}

3
src/pages/index.tsx Normal file
View File

@ -0,0 +1,3 @@
export default function index() {
return <div>index</div>;
}

View File

@ -0,0 +1,75 @@
import {
MyButtons,
MyColumns,
MyPageContainer,
MyProTableProps,
} from '@/common';
import { Apis } from '@/gen/Apis';
import { LoanCompaniesTypeEnum } from '@/gen/Enums';
import { ProTable } from '@ant-design/pro-components';
import { Space } from 'antd';
import Create from './modals/Create';
import Update from './modals/Update';
export default function Index({ title = '资金方' }) {
return (
<MyPageContainer title={title}>
<ProTable
{...MyProTableProps.props}
request={async (params, sort) =>
MyProTableProps.request(params, sort, Apis.LoanCompanies.List)
}
toolBarRender={(action) => [
<Create key="Create" reload={action?.reload} title={title} />,
]}
columns={[
MyColumns.ID(),
{
title: '类型',
dataIndex: 'type',
valueEnum: LoanCompaniesTypeEnum,
},
{
title: '简称',
dataIndex: 'name',
},
{
title: '全称',
dataIndex: 'full_name',
},
{
title: '上游商户号',
dataIndex: 'up_merchant_no',
},
{
dataIndex: 'address',
title: '地址',
},
{
dataIndex: 'contact',
title: '联系人',
},
{
dataIndex: 'phone',
title: '联系电话',
},
MyColumns.CreatedAt(),
MyColumns.Option({
render: (_, item: any, index, action) => (
<Space key={index}>
<Update item={item} reload={action?.reload} title={title} />
<MyButtons.Delete
onConfirm={() =>
Apis.LoanCompanies.Delete({ id: item.id }).then(() =>
action?.reload(),
)
}
/>
</Space>
),
}),
]}
/>
</MyPageContainer>
);
}

View File

@ -0,0 +1,66 @@
import {
MyBetaModalFormProps,
MyButtons,
MyColumns,
MyModalFormProps,
rulesHelper,
} from '@/common';
import { Apis } from '@/gen/Apis';
import { LoanCompaniesTypeEnum } from '@/gen/Enums';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { message } from 'antd';
export default function Create(props: MyBetaModalFormProps) {
return (
<BetaSchemaForm<ApiTypes.LoanCompanies.Store>
{...MyModalFormProps.props}
title={`添加${props.title}`}
wrapperCol={{ span: 24 }}
width="500px"
trigger={<MyButtons.Create title={`添加${props.title}`} />}
onFinish={async (values) =>
Apis.LoanCompanies.Store(values)
.then(() => {
props.reload?.();
message.success(props.title + '成功');
return true;
})
.catch(() => false)
}
columns={[
MyColumns.EnumTag({
key: 'type',
title: '类型',
valueEnum: LoanCompaniesTypeEnum,
}),
{
key: 'name',
title: '简称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'full_name',
title: '全称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'up_merchant_no',
title: '上游商户号',
formItemProps: { ...rulesHelper.text },
},
{
key: 'address',
title: '地址',
},
{
key: 'contact',
title: '联系人',
},
{
key: 'phone',
title: '联系电话',
},
]}
/>
);
}

View File

@ -0,0 +1,67 @@
import {
MyBetaModalFormProps,
MyButtons,
MyColumns,
MyModalFormProps,
rulesHelper,
} from '@/common';
import { Apis } from '@/gen/Apis';
import { LoanCompaniesTypeEnum } from '@/gen/Enums';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { message } from 'antd';
export default function Update(props: MyBetaModalFormProps) {
return (
<BetaSchemaForm<ApiTypes.LoanCompanies.Update>
{...MyModalFormProps.props}
title={`编辑${props.title}`}
trigger={<MyButtons.Edit />}
wrapperCol={{ span: 24 }}
width="500px"
request={() => Promise.resolve(props.item)}
onFinish={async (values) =>
Apis.LoanCompanies.Update({ ...values, id: props.item?.id ?? 0 })
.then(() => {
props.reload?.();
message.success(props.title + '成功');
return true;
})
.catch(() => false)
}
columns={[
MyColumns.EnumTag({
key: 'type',
title: '类型',
valueEnum: LoanCompaniesTypeEnum,
}),
{
key: 'name',
title: '简称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'full_name',
title: '全称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'up_merchant_no',
title: '上游商户号',
formItemProps: { ...rulesHelper.text },
},
{
key: 'address',
title: '地址',
},
{
key: 'contact',
title: '联系人',
},
{
key: 'phone',
title: '联系电话',
},
]}
/>
);
}

View File

@ -0,0 +1,104 @@
import {
MyButtons,
MyColumns,
MyPageContainer,
MyProTableProps,
} from '@/common';
import { Selects } from '@/components/Selects';
import { Apis } from '@/gen/Apis';
import { LoansOrderStatusEnum } from '@/gen/Enums';
import { ProTable } from '@ant-design/pro-components';
import { useState } from 'react';
export default function Index({ title = '还款记录' }) {
const [query, setQuery] = useState();
return (
<MyPageContainer title={`${title}管理`}>
<ProTable
{...MyProTableProps.props}
request={async (params, sort) =>
MyProTableProps.request(
params,
sort,
Apis.LoanDailyDetails.List,
setQuery,
)
}
toolBarRender={() => [
<MyButtons.Export
title="还款计划导出"
key="Export"
api={Apis.LoanDailyDetails.Export}
params={query}
/>,
]}
columns={[
MyColumns.ID(),
{
title: '所属商户',
dataIndex: 'merchants_name',
search: false,
},
Selects.Merchants({
hideInTable: true,
}),
{
title: '所属资金方',
dataIndex: 'loan_companies_name',
search: false,
},
Selects.LoanCompany({
hideInTable: true,
}),
MyColumns.EnumTag({
title: '订单状态',
dataIndex: 'order_status',
valueEnum: LoansOrderStatusEnum,
}),
{
title: '日期',
dataIndex: 'date',
},
{
title: '应还-本金',
dataIndex: 'due_principal',
search: false,
valueType: 'money',
},
{
title: '应还-利息',
dataIndex: 'due_interest',
search: false,
valueType: 'money',
},
{
title: '应还-递延本金',
dataIndex: 'due_deferred_principal',
search: false,
valueType: 'money',
},
{
title: '应还-总金额',
dataIndex: 'due_total_amount',
search: false,
valueType: 'money',
},
{
title: '已还-总金额',
dataIndex: 'paid_total_amount',
search: false,
valueType: 'money',
},
{
title: '未还-总金额',
dataIndex: 'remaining_total_amount',
search: false,
valueType: 'money',
},
// MyColumns.CreatedAt(),
]}
/>
</MyPageContainer>
);
}

193
src/pages/loans/index.tsx Normal file
View File

@ -0,0 +1,193 @@
import {
MyButtons,
MyColumns,
MyPageContainer,
MyProTableProps,
} from '@/common';
import { Apis } from '@/gen/Apis';
import { LoansOrderStatusEnum, LoansTypeEnum } from '@/gen/Enums';
import { ProTable } from '@ant-design/pro-components';
import { Space } from 'antd';
import Create from './modals/Create';
export default function Index({ title = '贷款订单' }) {
return (
<MyPageContainer title={`${title}管理`}>
<ProTable
{...MyProTableProps.props}
request={async (params, sort) =>
MyProTableProps.request(params, sort, Apis.Loans.List)
}
toolBarRender={(action) => [
<Create key="Create" reload={action?.reload} title={title} />,
]}
columns={[
MyColumns.ID(),
{
title: '所属商户',
dataIndex: ['merchant', 'name'],
search: false,
},
{
title: '所属资金方',
dataIndex: ['loan_company', 'name'],
search: false,
},
{
title: '名称',
dataIndex: 'name',
},
MyColumns.EnumTag({
title: '类型',
dataIndex: 'type',
valueEnum: LoansTypeEnum,
}),
MyColumns.EnumTag({
title: '订单状态',
dataIndex: 'order_status',
valueEnum: LoansOrderStatusEnum,
}),
{
title: '贷款天数',
dataIndex: 'loan_days',
search: false,
},
{
title: '开始时间',
dataIndex: 'started_at',
search: false,
},
{
title: '结束时间',
dataIndex: 'ended_at',
search: false,
},
{
title: '总应还',
search: false,
children: [
{
title: '本金',
dataIndex: 'total_due_principal',
search: false,
valueType: 'money',
},
{
title: '利息',
dataIndex: 'total_due_interest',
search: false,
valueType: 'money',
},
{
title: '总金额',
dataIndex: 'total_due_amount',
search: false,
valueType: 'money',
},
],
},
{
title: '每日应还',
search: false,
children: [
{
title: '本金',
dataIndex: 'daily_due_principal',
search: false,
valueType: 'money',
},
{
title: '利息',
dataIndex: 'daily_due_interest',
search: false,
valueType: 'money',
},
{
title: '总金额',
dataIndex: 'daily_due_amount',
search: false,
valueType: 'money',
},
],
},
{
title: '已还',
search: false,
children: [
{
title: '天数',
dataIndex: 'repaid_days',
search: false,
},
{
title: '本金',
dataIndex: 'repaid_total_principal',
search: false,
valueType: 'money',
},
{
title: '利息',
dataIndex: 'repaid_total_interest',
search: false,
valueType: 'money',
},
{
title: '总金额',
dataIndex: 'repaid_total_amount',
search: false,
valueType: 'money',
},
],
},
{
title: '剩余应还',
search: false,
children: [
{
title: '天数',
dataIndex: 'remaining_due_days',
search: false,
},
{
title: '本金',
dataIndex: 'remaining_due_principal',
search: false,
valueType: 'money',
},
{
title: '利息',
dataIndex: 'remaining_due_interest',
search: false,
valueType: 'money',
},
{
title: '总金额',
dataIndex: 'remaining_due_amount',
search: false,
valueType: 'money',
},
],
},
MyColumns.CreatedAt(),
MyColumns.Option({
render: (_, item: any, index, action) => (
<Space key={index}>
<MyButtons.Delete
onConfirm={() =>
Apis.Loans.Delete({ id: item.id }).then(() =>
action?.reload(),
)
}
/>
</Space>
),
}),
]}
/>
</MyPageContainer>
);
}

View File

@ -0,0 +1,90 @@
import {
MyBetaModalFormProps,
MyButtons,
MyFormItems,
MyModalFormProps,
rulesHelper,
} from '@/common';
import { Selects } from '@/components/Selects';
import { Apis } from '@/gen/Apis';
import { LoansTypeEnum } from '@/gen/Enums';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { Form, message } from 'antd';
export default function Create(props: MyBetaModalFormProps) {
const [form] = Form.useForm();
return (
<BetaSchemaForm<ApiTypes.Loans.Store>
{...MyModalFormProps.props}
title={`添加${props.title}`}
width="600px"
wrapperCol={{ span: 24 }}
trigger={<MyButtons.Create title={`添加${props.title}`} />}
onFinish={async (values) =>
Apis.Loans.Store(values)
.then(() => {
props.reload?.();
message.success(props.title + '成功');
return true;
})
.catch(() => false)
}
form={form}
onOpenChange={(open: boolean) => {
if (open) {
form.setFieldsValue({ repayment_date: 21, loan_terms: 3 });
}
}}
columns={[
Selects.Merchants({
colProps: { span: 24 },
required: true,
}),
Selects.LoanCompany({
colProps: { span: 24 },
required: true,
}),
{
title: '名称',
key: 'name',
formItemProps: { ...rulesHelper.text },
},
MyFormItems.EnumRadio({
title: '算法类型',
key: 'type',
valueEnum: LoansTypeEnum,
formItemProps: { ...rulesHelper.text },
}),
{
title: '贷款天数',
key: 'loan_days',
colProps: { span: 12 },
formItemProps: { ...rulesHelper.number },
width: '100%',
},
{
title: '还款起始日',
key: 'started_at',
valueType: 'date',
colProps: { span: 12 },
formItemProps: { ...rulesHelper.number },
width: '100%',
},
MyFormItems.Money({
title: '总应还-本金',
key: 'total_due_principal',
colProps: { span: 12 },
formItemProps: { ...rulesHelper.number },
width: '100%',
}),
MyFormItems.Money({
title: '总应还-利息',
key: 'total_due_interest',
colProps: { span: 12 },
formItemProps: { ...rulesHelper.number },
width: '100%',
}),
]}
/>
);
}

5
src/pages/login.tsx Normal file
View File

@ -0,0 +1,5 @@
import { MyLoginPage1 } from '@/common';
export default function Login() {
return <MyLoginPage1 />;
}

View File

@ -0,0 +1,60 @@
import {
MyButtons,
MyColumns,
MyPageContainer,
MyProTableProps,
} from '@/common';
import { Apis } from '@/gen/Apis';
import { ProTable } from '@ant-design/pro-components';
import { Space } from 'antd';
import Create from './modals/Create';
import Update from './modals/Update';
export default function Index({ title = '上游市场' }) {
return (
<MyPageContainer title={title}>
<ProTable
{...MyProTableProps.props}
search={false}
request={async (params, sort) =>
MyProTableProps.request(params, sort, Apis.Markets.List)
}
toolBarRender={(action) => [
<Create key="Create" reload={action?.reload} title={title} />,
]}
columns={[
MyColumns.ID(),
{
title: '市场名称',
dataIndex: 'name',
},
{
title: '市场编号',
dataIndex: 'market_no',
},
{
title: '上游公钥',
dataIndex: 'public_key',
search: false,
},
MyColumns.UpdatedAt(),
MyColumns.CreatedAt(),
MyColumns.Option({
render: (_, item: any, index, action) => (
<Space key={index}>
<Update item={item} reload={action?.reload} title={title} />
<MyButtons.Delete
onConfirm={() =>
Apis.Markets.Delete({ id: item.id }).then(() =>
action?.reload(),
)
}
/>
</Space>
),
}),
]}
/>
</MyPageContainer>
);
}

View File

@ -0,0 +1,48 @@
import {
MyBetaModalFormProps,
MyButtons,
MyModalFormProps,
rulesHelper,
} from '@/common';
import { Apis } from '@/gen/Apis';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { message } from 'antd';
export default function Create(props: MyBetaModalFormProps) {
return (
<BetaSchemaForm<ApiTypes.Markets.Store>
{...MyModalFormProps.props}
title={`添加${props.title}`}
wrapperCol={{ span: 24 }}
width="500px"
trigger={<MyButtons.Create title={`添加${props.title}`} />}
onFinish={async (values) =>
Apis.Markets.Store(values)
.then(() => {
props.reload?.();
message.success(props.title + '成功');
return true;
})
.catch(() => false)
}
columns={[
{
key: 'name',
title: '市场名称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'market_no',
title: '市场编号',
formItemProps: { ...rulesHelper.text },
},
{
key: 'public_key',
title: '上游公钥',
valueType: 'textarea',
formItemProps: { ...rulesHelper.text },
},
]}
/>
);
}

View File

@ -0,0 +1,49 @@
import {
MyBetaModalFormProps,
MyButtons,
MyModalFormProps,
rulesHelper,
} from '@/common';
import { Apis } from '@/gen/Apis';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { message } from 'antd';
export default function Update(props: MyBetaModalFormProps) {
return (
<BetaSchemaForm<ApiTypes.Markets.Update>
{...MyModalFormProps.props}
title={`编辑${props.title}`}
trigger={<MyButtons.Edit />}
wrapperCol={{ span: 24 }}
width="500px"
request={() => Promise.resolve(props.item)}
onFinish={async (values) =>
Apis.Markets.Update({ ...values, id: props.item?.id ?? 0 })
.then(() => {
props.reload?.();
message.success(props.title + '成功');
return true;
})
.catch(() => false)
}
columns={[
{
key: 'name',
title: '市场名称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'market_no',
title: '市场编号',
formItemProps: { ...rulesHelper.text },
},
{
key: 'public_key',
title: '上游公钥',
valueType: 'textarea',
formItemProps: { ...rulesHelper.text },
},
]}
/>
);
}

View File

@ -0,0 +1,136 @@
import {
MyButtons,
MyColumns,
MyPageContainer,
MyProTableProps,
} from '@/common';
import { Selects } from '@/components/Selects';
import { Apis } from '@/gen/Apis';
import { ProTable } from '@ant-design/pro-components';
import { Space } from 'antd';
import Create from './modals/Create';
import CreateCounters from './modals/CreateCounters';
import Update from './modals/Update';
import UpdateCounters from './modals/UpdateCounters';
export default function Index({ title = '商户' }) {
const ExpandedRowRender = (e: any) => {
const columns = [
{ title: 'id', dataIndex: 'id' },
{ title: '上游', dataIndex: ['market', 'name'] },
{ title: '名称', dataIndex: 'name' },
{ title: '上游商家编号', dataIndex: 'up_merchant_no' },
{ title: '平台商户号', dataIndex: 'plat_merchant_no' },
{ title: '平台柜台号', dataIndex: 'plat_counter_no' },
MyColumns.Option({
render: (_, item: any, index, action) => (
<Space key={index}>
<UpdateCounters item={item} reload={action?.reload} title="柜台" />
<MyButtons.Delete
onConfirm={() =>
Apis.MerchantCounters.Delete({ id: item.id }).then(() =>
action?.reload(),
)
}
/>
</Space>
),
}),
];
return (
<ProTable
{...MyProTableProps.props}
pagination={false}
columns={columns}
request={async (params, sort) =>
MyProTableProps.request(
{ ...params, merchants_id: e?.id },
sort,
Apis.MerchantCounters.List,
)
}
toolBarRender={(action) => [
<CreateCounters
key="Create"
reload={action?.reload}
title="添加柜台"
item={{ merchants_id: e?.id }}
/>,
]}
headerTitle="柜台信息"
search={false}
/>
);
};
return (
<MyPageContainer title={title}>
<ProTable
{...MyProTableProps.props}
request={async (params, sort) =>
MyProTableProps.request(params, sort, Apis.Merchants.List)
}
toolBarRender={(action) => [
<Create key="Create" reload={action?.reload} title={title} />,
]}
expandable={{
expandedRowRender: ExpandedRowRender,
}}
columns={[
MyColumns.ID(),
Selects.Agents({ dataIndex: 'agents_name' }),
Selects.Bosses({ dataIndex: 'bosses_name' }),
Selects.Factories({ dataIndex: 'factories_name' }),
Selects.Platforms({ dataIndex: 'platforms_name' }),
// {
// title: '代理',
// dataIndex: 'agents_name',
// search: false,
// },
// {
// title: '厂家',
// dataIndex: 'factories_name',
// search: false,
// },
// {
// title: '平台',
// dataIndex: 'platforms_name',
// search: false,
// },
// {
// title: '老板',
// dataIndex: 'bosses_name',
// search: false,
// },
{
title: '简称',
dataIndex: 'name',
},
{
title: '全称',
dataIndex: 'full_name',
},
{
title: '平台商户号',
dataIndex: 'plat_merchant_no',
},
MyColumns.CreatedAt(),
MyColumns.Option({
render: (_, item: any, index, action) => (
<Space key={index}>
<Update item={item} reload={action?.reload} title={title} />
<MyButtons.Delete
onConfirm={() =>
Apis.Merchants.Delete({ id: item.id }).then(() =>
action?.reload(),
)
}
/>
</Space>
),
}),
]}
/>
</MyPageContainer>
);
}

View File

@ -0,0 +1,47 @@
import {
MyBetaModalFormProps,
MyButtons,
MyModalFormProps,
rulesHelper,
} from '@/common';
import { Selects } from '@/components/Selects';
import { Apis } from '@/gen/Apis';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { message } from 'antd';
export default function Create(props: MyBetaModalFormProps) {
return (
<BetaSchemaForm<ApiTypes.Merchants.Store>
{...MyModalFormProps.props}
title={`添加${props.title}`}
wrapperCol={{ span: 24 }}
width="500px"
trigger={<MyButtons.Create title={`添加${props.title}`} />}
onFinish={async (values) =>
Apis.Merchants.Store(values)
.then(() => {
props.reload?.();
message.success(props.title + '成功');
return true;
})
.catch(() => false)
}
columns={[
Selects.Agents({ required: true }),
Selects.Bosses({ required: true }),
Selects.Factories({ required: true }),
Selects.Platforms({ required: true }),
{
key: 'name',
title: '简称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'full_name',
title: '全称',
formItemProps: { ...rulesHelper.text },
},
]}
/>
);
}

View File

@ -0,0 +1,88 @@
import {
MyBetaModalFormProps,
MyButtons,
MyFormItems,
MyModalFormProps,
rulesHelper,
} from '@/common';
import { Selects } from '@/components/Selects';
import { Apis } from '@/gen/Apis';
import { CommissionTypeEnum } from '@/gen/Enums';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { message } from 'antd';
export default function CreateCounters(props: MyBetaModalFormProps) {
return (
<BetaSchemaForm<ApiTypes.MerchantCounters.Store>
{...MyModalFormProps.props}
title={`添加${props.title}`}
wrapperCol={{ span: 24 }}
width="800px"
trigger={<MyButtons.Create title={`添加${props.title}`} />}
onFinish={async (values) =>
Apis.MerchantCounters.Store({ ...props?.item, ...values })
.then(() => {
props.reload?.();
message.success(props.title + '成功');
return true;
})
.catch(() => false)
}
columns={[
Selects.Markets({ required: true }),
{
key: 'name',
title: '名称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'up_merchant_no',
title: '上游商家编号',
formItemProps: { ...rulesHelper.text },
},
{
valueType: 'formList',
dataIndex: 'commissions',
title: '分佣配置',
formItemProps: { ...rulesHelper.array },
fieldProps: {
copyIconProps: false,
},
columns: [
{
valueType: 'group',
colProps: { span: 24 },
columns: [
{
key: 'seq',
colProps: { span: 3 },
title: '序号',
width: '100%',
formItemProps: { ...rulesHelper.text },
},
MyFormItems.EnumSelect({
key: 'type',
title: '类型',
colProps: { span: 4 },
valueEnum: CommissionTypeEnum,
required: true,
formItemProps: { ...rulesHelper.text },
}),
{
key: 'merchant_no',
colProps: { span: 9 },
title: '商户编号',
},
{
key: 'percent',
colProps: { span: 8 },
title: '分佣比例%(0为自动计算)',
},
],
},
],
},
]}
/>
);
}

View File

@ -0,0 +1,48 @@
import {
MyBetaModalFormProps,
MyButtons,
MyModalFormProps,
rulesHelper,
} from '@/common';
import { Selects } from '@/components/Selects';
import { Apis } from '@/gen/Apis';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { message } from 'antd';
export default function Update(props: MyBetaModalFormProps) {
return (
<BetaSchemaForm<ApiTypes.Merchants.Update>
{...MyModalFormProps.props}
title={`编辑${props.title}`}
trigger={<MyButtons.Edit />}
wrapperCol={{ span: 24 }}
width="500px"
request={() => Promise.resolve(props.item)}
onFinish={async (values) =>
Apis.Merchants.Update({ ...values, id: props.item?.id ?? 0 })
.then(() => {
props.reload?.();
message.success(props.title + '成功');
return true;
})
.catch(() => false)
}
columns={[
Selects.Agents({ required: true }),
Selects.Bosses({ required: true }),
Selects.Factories({ required: true }),
Selects.Platforms({ required: true }),
{
key: 'name',
title: '简称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'full_name',
title: '全称',
formItemProps: { ...rulesHelper.text },
},
]}
/>
);
}

View File

@ -0,0 +1,89 @@
import {
MyBetaModalFormProps,
MyButtons,
MyFormItems,
MyModalFormProps,
rulesHelper,
} from '@/common';
import { Selects } from '@/components/Selects';
import { Apis } from '@/gen/Apis';
import { CommissionTypeEnum } from '@/gen/Enums';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { message } from 'antd';
export default function UpdateCounters(props: MyBetaModalFormProps) {
return (
<BetaSchemaForm<ApiTypes.MerchantCounters.Update>
{...MyModalFormProps.props}
title={`编辑${props.title}`}
trigger={<MyButtons.Edit />}
wrapperCol={{ span: 24 }}
width="800px"
request={() => Promise.resolve(props.item)}
onFinish={async (values) =>
Apis.MerchantCounters.Update({ ...values, id: props.item?.id ?? 0 })
.then(() => {
props.reload?.();
message.success(props.title + '成功');
return true;
})
.catch(() => false)
}
columns={[
Selects.Markets({ required: true }),
{
key: 'name',
title: '名称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'up_merchant_no',
title: '上游商家编号',
formItemProps: { ...rulesHelper.text },
},
{
valueType: 'formList',
dataIndex: 'commissions',
title: '分佣配置',
formItemProps: { ...rulesHelper.array },
fieldProps: {
copyIconProps: false,
},
columns: [
{
valueType: 'group',
colProps: { span: 24 },
columns: [
{
key: 'seq',
colProps: { span: 3 },
title: '序号',
width: '100%',
formItemProps: { ...rulesHelper.text },
},
MyFormItems.EnumSelect({
key: 'type',
title: '类型',
colProps: { span: 4 },
valueEnum: CommissionTypeEnum,
required: true,
formItemProps: { ...rulesHelper.text },
}),
{
key: 'merchant_no',
colProps: { span: 9 },
title: '商户编号',
},
{
key: 'percent',
colProps: { span: 8 },
title: '分佣比例%(0为自动计算)',
},
],
},
],
},
]}
/>
);
}

View File

@ -0,0 +1,71 @@
import {
MyButtons,
MyColumns,
MyPageContainer,
MyProTableProps,
} from '@/common';
import { Apis } from '@/gen/Apis';
import { ProTable } from '@ant-design/pro-components';
import { Space } from 'antd';
import Create from './modals/Create';
import Update from './modals/Update';
import { OrgUsersTypeEnum } from '@/gen/Enums';
export default function Index({ title = '账号管理' }) {
return (
<MyPageContainer title={title}>
<ProTable
{...MyProTableProps.props}
request={async (params, sort) =>
MyProTableProps.request(params, sort, Apis.OrgUsers.List)
}
toolBarRender={(action) => [
<Create key="Create" reload={action?.reload} title={title} />,
]}
columns={[
MyColumns.ID(),
MyColumns.EnumTag({
title: '类型',
dataIndex: 'type',
valueEnum: OrgUsersTypeEnum,
}),
{
title: '机构名称',
dataIndex: ['org', 'name'],
key: 'org_name'
},
{
title: '账号',
dataIndex: 'username',
},
{
title: 'last_login_ip',
dataIndex: 'last_login_ip',
search: false,
},
{
title: 'last_login_at',
dataIndex: 'last_login_at',
search: false,
},
MyColumns.UpdatedAt(),
MyColumns.CreatedAt(),
MyColumns.Option({
render: (_, item: any, index, action) => (
<Space key={index}>
<Update item={item} reload={action?.reload} title={title} />
<MyButtons.Delete
onConfirm={() =>
Apis.OrgUsers.Delete({ id: item.id }).then(() =>
action?.reload(),
)
}
/>
</Space>
),
}),
]}
/>
</MyPageContainer>
);
}

View File

@ -0,0 +1,57 @@
import {
MyBetaModalFormProps,
MyButtons,
MyColumns,
MyFormItems,
MyModalFormProps,
rulesHelper,
} from '@/common';
import { Selects } from '@/components/Selects';
import { Apis } from '@/gen/Apis';
import { OrgUsersTypeEnum } from '@/gen/Enums';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { message } from 'antd';
export default function Create(props: MyBetaModalFormProps) {
return (
<BetaSchemaForm<ApiTypes.OrgUsers.Store>
{...MyModalFormProps.props}
title={`添加${props.title}`}
wrapperCol={{ span: 24 }}
width="500px"
trigger={<MyButtons.Create title={`添加${props.title}`} />}
onFinish={async (values) =>
Apis.OrgUsers.Store(values)
.then(() => {
props.reload?.();
message.success(props.title + '成功');
return true;
})
.catch(() => false)
}
columns={[
MyFormItems.EnumRadio({
key: 'type',
title: '类型',
valueEnum: OrgUsersTypeEnum
}),
{
key: 'org_name',
title: '主体名称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'username',
title: '用户名',
formItemProps: { ...rulesHelper.text },
},
{
key: 'password',
title: '密码',
valueType: 'password',
formItemProps: { ...rulesHelper.text },
},
]}
/>
);
}

View File

@ -0,0 +1,55 @@
import {
MyBetaModalFormProps,
MyButtons,
MyModalFormProps,
rulesHelper,
} from '@/common';
import { Selects } from '@/components/Selects';
import { Apis } from '@/gen/Apis';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { message } from 'antd';
export default function Update(props: MyBetaModalFormProps) {
return (
<BetaSchemaForm<ApiTypes.OrgUsers.Update>
{...MyModalFormProps.props}
title={`编辑${props.title}`}
trigger={<MyButtons.Edit />}
wrapperCol={{ span: 24 }}
width="500px"
request={() => Promise.resolve(props.item)}
onFinish={async (values) =>
Apis.OrgUsers.Update({ ...values, id: props.item?.id ?? 0 })
.then(() => {
props.reload?.();
message.success(props.title + '成功');
return true;
})
.catch(() => false)
}
columns={[
{
key: 'type',
title: '类型',
readonly: true,
},
{
key: ['org','name'],
title: '机构名称',
readonly: true,
},
{
key: 'username',
title: '用户名',
readonly: true,
},
{
key: 'password',
title: '密码',
valueType: 'password',
formItemProps: { ...rulesHelper.text },
},
]}
/>
);
}

View File

@ -0,0 +1,75 @@
import { MyColumns, MyPageContainer, MyProTableProps } from '@/common';
import { Apis } from '@/gen/Apis';
import { PayOrderStatusEnum, PayOrdersTypeEnum } from '@/gen/Enums';
import { ProTable } from '@ant-design/pro-components';
export default function Index({ title = '支付明细' }) {
return (
<MyPageContainer title={title}>
<ProTable
{...MyProTableProps.props}
request={async (params, sort) =>
MyProTableProps.request(params, sort, Apis.PayOrders.List)
}
columns={[
{
title: '交易时间',
search: false,
dataIndex: 'up_order_at',
},
{
title: '交易时间范围',
key: 'up_order_at_range',
valueType: 'dateRange',
hideInTable: true,
},
{
title: '商户',
dataIndex: 'merchant_name',
},
MyColumns.EnumTag({
title: '交易类型',
dataIndex: 'type',
valueEnum: PayOrdersTypeEnum,
}),
{
title: '下单金额',
dataIndex: 'amount_order',
},
MyColumns.EnumTag({
title: '订单状态',
dataIndex: 'order_status',
valueEnum: PayOrderStatusEnum,
}),
{
title: '平台商户号',
dataIndex: 'plat_merchant_no',
search: false,
},
{
title: '平台柜台号',
dataIndex: 'plat_counter_no',
search: false,
},
{
title: '上游商家编号',
dataIndex: 'up_merchant_no',
search: false,
},
{
title: '下游订单号',
dataIndex: 'down_order_no',
},
{
title: '上游订单号',
dataIndex: 'up_order_no',
},
{
title: '上游流水号',
dataIndex: 'up_tx_no',
},
]}
/>
</MyPageContainer>
);
}

View File

@ -0,0 +1,61 @@
import {
MyButtons,
MyColumns,
MyPageContainer,
MyProTableProps,
} from '@/common';
import { Apis } from '@/gen/Apis';
import { ProTable } from '@ant-design/pro-components';
import { Space } from 'antd';
import Create from './modals/Create';
import Update from './modals/Update';
export default function Index({ title = '平台' }) {
return (
<MyPageContainer title={title}>
<ProTable
{...MyProTableProps.props}
search={false}
request={async (params, sort) =>
MyProTableProps.request(params, sort, Apis.Platforms.List)
}
toolBarRender={(action) => [
<Create key="Create" reload={action?.reload} title={title} />,
]}
columns={[
MyColumns.ID(),
{
title: '平台名称',
dataIndex: 'name',
},
{
title: '平台私钥',
dataIndex: 'private_key',
search: false,
},
{
title: '平台公钥',
dataIndex: 'public_key',
search: false,
},
MyColumns.UpdatedAt(),
MyColumns.CreatedAt(),
MyColumns.Option({
render: (_, item: any, index, action) => (
<Space key={index}>
<Update item={item} reload={action?.reload} title={title} />
<MyButtons.Delete
onConfirm={() =>
Apis.Platforms.Delete({ id: item.id }).then(() =>
action?.reload(),
)
}
/>
</Space>
),
}),
]}
/>
</MyPageContainer>
);
}

View File

@ -0,0 +1,49 @@
import {
MyBetaModalFormProps,
MyButtons,
MyModalFormProps,
rulesHelper,
} from '@/common';
import { Apis } from '@/gen/Apis';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { message } from 'antd';
export default function Create(props: MyBetaModalFormProps) {
return (
<BetaSchemaForm<ApiTypes.Platforms.Store>
{...MyModalFormProps.props}
title={`添加${props.title}`}
wrapperCol={{ span: 24 }}
width="500px"
trigger={<MyButtons.Create title={`添加${props.title}`} />}
onFinish={async (values) =>
Apis.Platforms.Store(values)
.then(() => {
props.reload?.();
message.success(props.title + '成功');
return true;
})
.catch(() => false)
}
columns={[
{
key: 'name',
title: '平台名称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'private_key',
title: '平台私钥',
valueType: 'textarea',
formItemProps: { ...rulesHelper.text },
},
{
key: 'public_key',
title: '平台公钥',
valueType: 'textarea',
formItemProps: { ...rulesHelper.text },
},
]}
/>
);
}

View File

@ -0,0 +1,50 @@
import {
MyBetaModalFormProps,
MyButtons,
MyModalFormProps,
rulesHelper,
} from '@/common';
import { Apis } from '@/gen/Apis';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { message } from 'antd';
export default function Update(props: MyBetaModalFormProps) {
return (
<BetaSchemaForm<ApiTypes.Platforms.Update>
{...MyModalFormProps.props}
title={`编辑${props.title}`}
trigger={<MyButtons.Edit />}
wrapperCol={{ span: 24 }}
width="500px"
request={() => Promise.resolve(props.item)}
onFinish={async (values) =>
Apis.Platforms.Update({ ...values, id: props.item?.id ?? 0 })
.then(() => {
props.reload?.();
message.success(props.title + '成功');
return true;
})
.catch(() => false)
}
columns={[
{
key: 'name',
title: '平台名称',
formItemProps: { ...rulesHelper.text },
},
{
key: 'private_key',
title: '平台私钥',
valueType: 'textarea',
formItemProps: { ...rulesHelper.text },
},
{
key: 'public_key',
title: '平台公钥',
valueType: 'textarea',
formItemProps: { ...rulesHelper.text },
},
]}
/>
);
}

View File

@ -0,0 +1,68 @@
import {
MyButtons,
MyColumns,
MyPageContainer,
MyProTableProps,
renderTextHelper,
} from '@/common';
import { Apis } from '@/gen/Apis';
import { ProTable } from '@ant-design/pro-components';
import { Space } from 'antd';
import Create from './modals/Create';
import Update from './modals/Update';
export default function Index({ title = '管理员' }) {
return (
<MyPageContainer title={title}>
<ProTable
{...MyProTableProps.props}
search={false}
request={async (params, sort) =>
MyProTableProps.request(params, sort, Apis.Admins.List)
}
toolBarRender={(action) => [
<Create key="Create" reload={action?.reload} title={title} />,
]}
columns={[
MyColumns.ID(),
{
title: '账号',
dataIndex: 'username',
},
{
title: '角色',
dataIndex: 'roles',
renderText: renderTextHelper.TagList,
hideInSearch: true,
},
{
title: 'last_login_ip',
dataIndex: 'last_login_ip',
search: false,
},
{
title: 'last_login_at',
dataIndex: 'last_login_at',
search: false,
},
MyColumns.UpdatedAt(),
MyColumns.CreatedAt(),
MyColumns.Option({
render: (_, item: any, index, action) => (
<Space key={index}>
<Update item={item} reload={action?.reload} title={title} />
<MyButtons.Delete
onConfirm={() =>
Apis.Admins.Delete({ id: item.id }).then(() =>
action?.reload(),
)
}
/>
</Space>
),
}),
]}
/>
</MyPageContainer>
);
}

Some files were not shown because too many files have changed in this diff Show More