refactor(app): clean up the client code

This commit is contained in:
chenos 2021-04-06 12:52:59 +08:00
parent 9495917e97
commit efb0320427
174 changed files with 1459 additions and 5051 deletions

View File

@ -1,5 +1,3 @@
import { values } from 'lodash';
import { stringify } from 'querystring';
import { request } from 'umi';
interface ActionParams {
@ -24,6 +22,7 @@ interface Resource {
[name: string]: (params?: ActionParams) => Promise<any>;
}
// TODO 待改进,提供一个封装度更完整的 SDKrequest 可以自由替换
class ApiClient {
resource(name: string): Resource {
const proxy: any = new Proxy(

View File

@ -32,6 +32,7 @@ export const request: RequestConfig = {
],
};
// TODO目前这块是写在代码里的待改进
const pathnames = ['/login', '/register', '/lostpassword', '/resetpassword'];
export async function getInitialState() {

View File

@ -1,49 +0,0 @@
import React, { useRef } from 'react';
import { Button } from 'antd';
import ViewFactory from '@/components/views';
import { PlusOutlined } from '@ant-design/icons';
export function Create(props) {
console.log(props);
const { title, viewName, collection_name } = props.schema;
const { activeTab = {}, item = {}, associatedName, associatedKey } = props;
const { associationField } = activeTab;
const params = {};
if (associationField && associationField.target) {
params['resourceName'] = associationField.name;
params['resourceTarget'] = associationField.target;
params['associatedName'] = associatedName;
params['associatedKey'] = associatedKey;
} else {
params['resourceName'] = props.resourceName;
params['resourceTarget'] = collection_name;
params['resourceKey'] = item.itemId;
}
console.log(params);
const drawerRef = useRef<any>();
return (
<>
<ViewFactory
{...props}
reference={drawerRef}
viewName={viewName}
{...params}
/>
<Button
icon={<PlusOutlined />}
type={'primary'}
onClick={() => {
drawerRef.current.setVisible(true);
}}
>
{title}
</Button>
</>
);
}
export default Create;

View File

@ -1,28 +0,0 @@
import React, { useRef } from 'react';
import { Button, Popconfirm } from 'antd';
import ViewFactory from '@/components/views';
import { DeleteOutlined } from '@ant-design/icons';
export function Destroy(props) {
console.log(props);
const { onTrigger } = props;
const { title, viewId, isBulk = true } = props.schema;
const drawerRef = useRef<any>();
return (
<>
<Popconfirm
title="确认删除吗?"
onConfirm={() => {
console.log('destroy', onTrigger);
onTrigger && onTrigger();
}}
>
<Button icon={<DeleteOutlined />} type={'ghost'} danger>
{title}
</Button>
</Popconfirm>
</>
);
}
export default Destroy;

View File

@ -1,75 +0,0 @@
import React, { useRef, useState } from 'react';
import { Button, Popover } from 'antd';
import ViewFactory from '@/components/views';
import { FilterOutlined } from '@ant-design/icons';
export function Filter(props) {
console.log(props);
const drawerRef = useRef<any>();
const [visible, setVisible] = useState(false);
const { title, viewName, collection_name } = props.schema;
const {
filterCount,
activeTab = {},
item = {},
associatedName,
associatedKey,
} = props;
const { associationField } = activeTab;
const params = {};
if (associationField && associationField.target) {
params['resourceName'] = associationField.target;
params['resourceTarget'] = associationField.target;
params['associatedName'] = associatedName;
params['associatedKey'] = associatedKey;
} else {
params['resourceName'] = collection_name;
params['resourceKey'] = item.itemId;
}
return (
<>
<Popover
// title="设置筛选"
trigger="click"
visible={visible}
placement={'bottomLeft'}
onVisibleChange={visible => {
setVisible(visible);
}}
className={'filters-popover'}
style={{}}
overlayStyle={{
minWidth: 500,
}}
content={
<>
<div
className={'popover-button-mask'}
onClick={() => setVisible(false)}
></div>
<ViewFactory
{...props}
setVisible={setVisible}
viewName={'filter'}
{...params}
/>
</>
}
>
<Button
icon={<FilterOutlined />}
onClick={() => {
setVisible(true);
}}
>
{filterCount ? `${filterCount}${title}` : title}
</Button>
</Popover>
</>
);
}
export default Filter;

View File

@ -1,46 +0,0 @@
import React, { useRef } from 'react';
import { Button } from 'antd';
import ViewFactory from '@/components/views';
import { EditOutlined } from '@ant-design/icons';
export function Update(props) {
const { title, viewName, resourceName, collection_name } = props.schema;
const { resourceKey, activeTab = {}, item = {} } = props;
const { associationField } = activeTab;
const params = {};
if (associationField && associationField.target) {
params['resourceName'] = associationField.target;
params['resourceTarget'] = associationField.target;
params['associatedName'] = resourceName;
params['associatedKey'] = item.itemId;
} else {
params['resourceName'] = collection_name;
params['resourceKey'] = item.itemId || resourceKey;
}
const drawerRef = useRef<any>();
return (
<>
<ViewFactory
{...props}
reference={drawerRef}
viewName={viewName}
mode={'update'}
{...params}
/>
<Button
icon={<EditOutlined />}
type={'primary'}
onClick={() => {
drawerRef.current.setVisible(true);
drawerRef.current.getData(item.itemId || resourceKey);
}}
>
{title}
</Button>
</>
);
}
export default Update;

View File

@ -1,22 +1,363 @@
import React from 'react';
import Create from './Create';
import Update from './Update';
import Destroy from './Destroy';
import Filter from './Filter';
// import { Space } from 'antd';
import React, { useState } from 'react';
import { Space, Button, Popconfirm, Popover } from 'antd';
import {
FilterOutlined,
PlusOutlined,
SelectOutlined,
DeleteOutlined,
PrinterOutlined,
ExportOutlined,
} from '@ant-design/icons';
import Drawer from '@/components/drawer';
import View from '@/components/views';
import get from 'lodash/get';
import set from 'lodash/set';
import ReactToPrint from 'react-to-print';
import api from '@/api-client';
import './style.less';
const ACTIONS = new Map<string, any>();
export function Create(props) {
const { size, onFinish, schema = {}, associatedKey, ...restProps } = props;
const { title, pageTitle, viewName, transform, componentProps = {} } = schema;
return (
<>
<Button
size={size}
onClick={() => {
Drawer.open({
title: pageTitle || title,
content: ({ resolve, closeWithConfirm }) => (
<div>
<View
{...restProps}
onValueChange={() => {
closeWithConfirm && closeWithConfirm(true);
}}
associatedKey={associatedKey}
viewName={viewName}
onReset={resolve}
onDraft={async item => {
const values = transform ? {} : item;
for (const [sourceKey, targetKey] of Object.entries<string>(
transform || {},
)) {
const value = get({ data: item }, sourceKey);
set(values, targetKey, value);
}
await resolve();
console.log('onFinish', values);
onFinish && (await onFinish(values));
}}
onFinish={async item => {
const values = transform ? {} : item;
for (const [sourceKey, targetKey] of Object.entries<string>(
transform || {},
)) {
const value = get({ data: item }, sourceKey);
set(values, targetKey, value);
}
await resolve();
console.log('onFinish', values);
onFinish && (await onFinish(values));
}}
/>
</div>
),
});
}}
icon={<PlusOutlined />}
type={'primary'}
{...componentProps}
>
{title}
</Button>
</>
);
}
export function Update(props) {
const { onFinish, data, schema = {}, associatedKey, ...restProps } = props;
const { title, viewName } = schema;
return (
<>
<Button
onClick={() => {
Drawer.open({
title: title,
content: ({ resolve, closeWithConfirm }) => (
<div>
<View
{...restProps}
associatedKey={associatedKey}
data={data}
viewName={viewName}
onReset={resolve}
onDraft={async values => {
await resolve();
onFinish && (await onFinish(values));
}}
onValueChange={() => {
closeWithConfirm && closeWithConfirm(true);
}}
onFinish={async values => {
await resolve();
onFinish && (await onFinish(values));
}}
/>
</div>
),
});
}}
icon={<PlusOutlined />}
type={'primary'}
>
{title}
</Button>
</>
);
}
export function Add(props) {
const { size, onFinish, schema = {}, associatedKey, ...restProps } = props;
console.log({ associatedKey }, 'add');
const { filter, title, viewName, transform, componentProps = {} } = schema;
return (
<>
<Button
size={size}
onClick={() => {
Drawer.open({
title: title,
content: ({ resolve }) => {
const [selectedRows, setSelectedRows] = useState([]);
return (
<div>
<View
{...restProps}
defaultFilter={filter}
viewName={viewName}
associatedKey={associatedKey}
onSelected={values => {
console.log(values);
setSelectedRows(
values.map(item => {
if (!transform) {
return;
}
const data = {};
for (const [sourceKey, targetKey] of Object.entries<
string
>(transform)) {
const value = get({ data: item }, sourceKey);
set(data, targetKey, value);
}
return data;
}),
);
}}
/>
<Drawer.Footer>
<Space>
<Button onClick={resolve}></Button>
<Button
type={'primary'}
onClick={async () => {
console.log({ schema, onFinish });
onFinish && (await onFinish(selectedRows));
resolve();
}}
>
</Button>
</Space>
</Drawer.Footer>
</div>
);
},
});
}}
icon={<SelectOutlined />}
{...componentProps}
>
{title}
</Button>
</>
);
}
export function Destroy(props) {
const { size, schema = {}, onFinish } = props;
const { title, componentProps = {} } = schema;
return (
<Popconfirm
title="确认删除吗?"
onConfirm={async e => {
onFinish && (await onFinish());
}}
>
<Button
size={size}
danger
type={'ghost'}
icon={<DeleteOutlined />}
{...componentProps}
>
{title}
</Button>
</Popconfirm>
);
}
export function Filter(props) {
const { schema = {}, onFinish } = props;
const { title, fields = [] } = schema;
const [visible, setVisible] = useState(false);
const [data, setData] = useState({});
const [filterCount, setFilterCount] = useState(0);
console.log('Filter', { visible, data });
return (
<>
{visible && (
<div
style={{
height: '100vh',
width: '100vw',
zIndex: 1000,
position: 'fixed',
background: 'rgba(0, 0, 0, 0)',
top: 0,
left: 0,
}}
onClick={() => setVisible(false)}
></div>
)}
<Popover
// title="设置筛选"
trigger="click"
visible={visible}
defaultVisible={visible}
placement={'bottomLeft'}
destroyTooltipOnHide
onVisibleChange={visible => {
setVisible(visible);
}}
className={'filters-popover'}
style={{}}
overlayStyle={{
minWidth: 500,
}}
content={
<>
<View
data={data}
onFinish={async values => {
if (values) {
const items = values.filter.and || values.filter.or;
setFilterCount(Array.isArray(items) ? items.length : 0);
setData(values);
onFinish && (await onFinish(values));
}
setVisible(false);
}}
schema={{
type: 'filterForm',
fields: [
{
dataIndex: ['filter'],
name: 'filter',
interface: 'json',
type: 'json',
component: {
type: 'filter',
'x-component-props': {
fields,
},
},
},
],
}}
/>
</>
}
>
<Button icon={<FilterOutlined />}>
{filterCount ? `${filterCount}${title}` : title}
</Button>
</Popover>
</>
);
}
export function Print(props) {
const { schema = {}, contentRef } = props;
const { title, buttonProps = {} } = schema;
return (
<ReactToPrint
trigger={() => (
<Button {...buttonProps} icon={<PrinterOutlined />}>
{title}
</Button>
)}
content={() => contentRef.current}
pageStyle={`
@page {
margin: 1cm;
}
table { page-break-inside:auto }
tr { page-break-inside:avoid; page-break-after:auto }
`}
documentTitle={' '}
/>
);
}
export function Export(props) {
const { schema = {}, onFinish } = props;
const { title, buttonProps = {} } = schema;
return (
<Button
{...buttonProps}
icon={<ExportOutlined />}
onClick={async () => {
onFinish && (await onFinish({ fields: [] }));
}}
>
{title}
</Button>
);
}
export function Actions(props) {
const { onTrigger = {}, actions = [], style, ...restProps } = props;
return (
actions.length > 0 && (
<div className={'action-buttons'} style={style}>
{actions.map(
action =>
ACTIONS.has(action.type) && (
<div key={action.__index} className={`${action.type}-action-button action-button`}>
<Action
{...restProps}
onFinish={onTrigger[action.type]}
schema={action}
/>
</div>
),
)}
</div>
)
);
}
export default Actions;
export function registerAction(type: string, Action: any) {
ACTIONS.set(type, Action);
}
registerAction('update', Update);
registerAction('create', Create);
registerAction('destroy', Destroy);
registerAction('filter', Filter);
export function getAction(type: string) {
return ACTIONS.get(type);
}
@ -29,25 +370,10 @@ export function Action(props) {
return Component && <Component {...props} />;
}
export function Actions(props) {
const { onTrigger = {}, style, schema, actions = [], ...restProps } = props;
console.log(onTrigger);
return (
actions.length > 0 && (
<div className={'action-buttons'} style={style}>
{actions.map(action => (
<div className={`${action.name}-action-button action-button`}>
<Action
{...restProps}
view={schema}
schema={action}
onTrigger={onTrigger[action.name]}
/>
</div>
))}
</div>
)
);
}
export default Actions;
registerAction('add', Add);
registerAction('update', Update);
registerAction('create', Create);
registerAction('destroy', Destroy);
registerAction('filter', Filter);
registerAction('print', Print);
registerAction('export', Export);

View File

@ -12,4 +12,4 @@
left: 24px;
margin-left: 0;
}
}
}

View File

@ -1,7 +1,5 @@
import React, { Fragment, useLayoutEffect, useRef, useState } from 'react';
import ReactDOM, { createPortal } from 'react-dom';
import { createForm } from '@formily/core';
// import { FormProvider } from '@formily/react'
import { isNum, isStr, isBool, isFn } from '@formily/shared';
import { Modal, Drawer } from 'antd';
import { DrawerProps } from 'antd/lib/drawer';

View File

@ -8,9 +8,8 @@ import {
isStr,
isArr,
} from '../shared';
import ViewFactory from '@/components/views';
import Drawer from '@/components/pages/AdminLoader/Drawer';
import View from '@/components/pages/AdminLoader/View';
import Drawer from '@/components/drawer';
import View from '@/components/views';
function transform({ value, multiple, labelField, valueField = 'id' }) {
let selectedKeys = [];

View File

@ -21,7 +21,6 @@ import { Cascader } from './cascader';
import { Icon } from './icons';
import { ColorSelect } from './color-select';
import { Permissions } from './permissions';
import { DraggableTable } from './draggable-table';
import { Values } from './values';
import { Automations } from './automations';
import { Wysiwyg } from './wysiwyg';
@ -61,7 +60,6 @@ export const setup = () => {
drawerSelect: DrawerSelect,
colorSelect: ColorSelect,
subTable: SubTable,
draggableTable: DraggableTable,
values: Values,
wysiwyg: Wysiwyg,
markdown: Markdown,

View File

@ -11,7 +11,7 @@ import {
} from '../shared';
import { useRequest } from 'umi';
import api from '@/api-client';
import { Spin } from '@nocobase/client';
import { Spin } from '@/components/spin';
import get from 'lodash/get';
function RemoteSelectComponent(props) {

View File

@ -1,15 +1,5 @@
import React, { useEffect, useRef, useState } from 'react';
import ReactDOM from 'react-dom';
import { Table as AntdTable, Button, Space, Popconfirm } from 'antd';
import { Actions } from '@/components/actions';
import ViewFactory from '@/components/views';
import { useRequest } from 'umi';
import api from '@/api-client';
import { components, fields2columns } from '@/components/views/SortableTable';
import Form from './Form';
import { Spin } from '@nocobase/client';
import maxBy from 'lodash/maxBy';
import View from '@/components/pages/AdminLoader/View';
import View from '@/components/views';
export interface SimpleTableProps {
schema?: any;

View File

@ -9,7 +9,6 @@ import {
isStr,
isArr,
} from '../shared';
import ViewFactory from '@/components/views';
import Table from './Table';
export const SubTable = connect({

View File

@ -1,109 +0,0 @@
import React, { useState } from 'react';
import { connect } from '@formily/react-schema-renderer';
import moment from 'moment';
import { Select, Table } from 'antd';
import get from 'lodash/get';
import {
mapStyledProps,
mapTextComponent,
compose,
isStr,
isArr,
} from '../shared';
import { useRequest } from 'umi';
import api from '@/api-client';
import { Spin } from '@nocobase/client';
import { fields2columns, components } from '@/components/views/SortableTable';
function DraggableTableComponent(props) {
const {
mode = 'showInDetail',
value,
onChange,
disabled,
rowKey = 'id',
fields = [],
resourceName,
associatedKey,
filter,
labelField,
valueField = 'id',
objectValue,
placeholder,
} = props;
const { data = [], loading = true, mutate } = useRequest(
() => {
return api.resource(resourceName).list({
associatedKey,
filter,
perPage: -1,
});
},
{
refreshDeps: [resourceName, associatedKey],
},
);
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const onTableChange = (selectedRowKeys: React.ReactText[]) => {
onChange(selectedRowKeys);
};
const tableProps: any = {};
tableProps.rowSelection = {
selectedRowKeys: Array.isArray(value)
? value
: data.map(item => item[rowKey]),
onChange: onTableChange,
};
const dataSource = data.filter(item => {
return get(item, ['component', mode]);
});
// const sortIds = get(value, 'sort')||[];
// let values = [];
// sortIds.forEach(id => {
// const item = dataSource.find(item => item[rowKey] === id);
// if (item) {
// values.push(item);
// }
// });
// if (values.length === 0) {
// values = dataSource;
// }
return (
<>
<Table
scroll={{ y: 300 }}
size={'small'}
dataSource={dataSource}
rowKey={rowKey}
loading={loading}
columns={fields2columns(fields || [])}
pagination={false}
{...tableProps}
// components={components({
// data: {
// list: data,
// },
// mutate: (values) => {
// onChange({
// sort: values.list.map(item => item[rowKey]),
// selectedRowKeys: get(value, 'selectedRowKeys')||data.map(item => item[rowKey]),
// });
// mutate(values.list);
// // setDataSource(values.list);
// console.log('mutate', values);
// },
// rowKey,
// onMoved: async ({resourceKey, target}) => {
// }
// })}
/>
</>
);
}
export const DraggableTable = connect({
getProps: mapStyledProps,
getComponent: mapTextComponent,
})(DraggableTableComponent);
export default DraggableTable;

View File

@ -1,163 +0,0 @@
import React, {
useState,
useEffect,
useImperativeHandle,
forwardRef,
useRef,
} from 'react';
import { Button, Drawer } from 'antd';
import { Tooltip, Input, Space, Modal } from 'antd';
import isEqual from 'lodash/isEqual';
import {
SchemaForm,
SchemaMarkupField as Field,
createFormActions,
FormSpy,
LifeCycleTypes,
createAsyncFormActions,
Submit,
Reset,
FormButtonGroup,
registerFormFields,
FormValidator,
setValidationLanguage,
} from '@formily/antd';
import { QuestionCircleOutlined } from '@ant-design/icons';
import { useRequest } from 'umi';
import api from '@/api-client';
import { Spin } from '@nocobase/client';
import cleanDeep from 'clean-deep';
import scopes from '@/components/views/Form/scopes';
const actions = createFormActions();
export default forwardRef((props: any, ref) => {
console.log(props);
const { target, onFinish } = props;
const { data: schema = {}, loading } = useRequest(() =>
api.resource(target).getView({
resourceKey: 'form',
}),
);
const [state, setState] = useState<any>({});
const [form, setForm] = useState<any>({});
const [changed, setChanged] = useState(false);
const [visible, setVisible] = useState(false);
const [data, setData] = useState({});
const [title, setTitle] = useState('创建子字段');
const [index, setIndex] = useState();
useImperativeHandle(ref, () => ({
setVisible,
setData,
setTitle,
setIndex,
}));
console.log({ onFinish });
const { fields = {} } = schema;
if (loading) {
return <Spin />;
}
return (
<Drawer
{...props}
destroyOnClose
visible={visible}
className={'noco-drawer'}
width={'40%'}
onClose={() => {
if (changed) {
Modal.confirm({
title: '表单内容发生变化,确定不保存吗?',
onOk() {
setChanged(false);
setVisible(false);
},
});
} else {
setChanged(false);
setVisible(false);
}
}}
title={title}
footer={
<div
style={{
textAlign: 'right',
}}
>
<Space>
<Button
onClick={() => {
setVisible(false);
setChanged(false);
}}
>
</Button>
<Button
type={'primary'}
onClick={async () => {
await form.submit();
// const { values = {} } = await actions.submit();
// setVisible(false);
// onFinish && onFinish(values, index);
}}
>
</Button>
</Space>
</div>
}
>
<SchemaForm
colon={true}
layout={'vertical'}
initialValues={data}
onChange={values => {
setChanged(true);
}}
onSubmit={async values => {
setVisible(false);
setChanged(false);
onFinish && onFinish(values, index);
}}
// actions={actions}
schema={{
type: 'object',
properties: fields,
}}
expressionScope={scopes}
>
<FormSpy
selector={[
LifeCycleTypes.ON_FORM_MOUNT,
LifeCycleTypes.ON_FORM_SUBMIT_START,
LifeCycleTypes.ON_FORM_SUBMIT_END,
]}
reducer={(state, action) => {
switch (action.type) {
case LifeCycleTypes.ON_FORM_SUBMIT_START:
return {
...state,
submitting: true,
};
case LifeCycleTypes.ON_FORM_SUBMIT_END:
return {
...state,
submitting: false,
};
default:
return state;
}
}}
>
{({ state, form }) => {
setState(state);
setForm(form);
return <div />;
}}
</FormSpy>
</SchemaForm>
</Drawer>
);
});

View File

@ -1,60 +1,108 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import { Layout, Menu, Breadcrumb } from 'antd';
import { Link, useLocation } from 'umi';
import { Link as UmiLink, useLocation } from 'umi';
import Icon from '@/components/icons';
import './style.less';
function pathcamp(path1: string, path2: string) {
return true;
if (path1 === path2) {
return true;
}
return path1.indexOf(`${path2}/`) === 0;
}
function Link(props: any) {
const { to, children } = props;
if (/^http/.test(to)) {
return (
<a target={'_blank'} href={to}>
{children}
</a>
);
}
return <UmiLink {...props} to={`/admin/${to}`} />;
}
export default (props: any) => {
const { items = [], hideChildren, ...restProps } = props;
const location = useLocation();
let paths = items.map(item => item.path);
const {
menuId,
currentPageName,
items = [],
hideChildren,
...restProps
} = props;
if (items.length === 0) {
return null;
}
const toPaths = data => {
const paths = [];
data.forEach(item => {
if (item.path && item.path === currentPageName) {
paths.push(`${item.name}`);
}
if (item.paths && item.paths.includes(currentPageName)) {
paths.push(`${item.name}`);
}
paths.push(...toPaths(item.children || []));
});
return paths;
};
const keys = toPaths(items);
console.log({ menuId, currentPageName, items, keys });
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
setTimeout(() => {
setLoading(false);
});
}, [menuId]);
if (loading) {
return null;
}
const renderChildren = items => {
return items.map(item => {
const { children = [] } = item;
// const subItems = children.filter(child => child.showInMenu);
if (!hideChildren && children.length) {
return (
<Menu.SubMenu
key={`${item.name}`}
icon={item.icon && <Icon type={item.icon} />}
title={<>{item.title}</>}
>
{renderChildren(children)}
</Menu.SubMenu>
);
}
return (
<Menu.Item
icon={item.icon && <Icon type={item.icon} />}
key={`${item.name}`}
>
<Link to={item.path}>{item.title}</Link>
</Menu.Item>
);
});
};
return (
<Menu
defaultSelectedKeys={paths
.filter(path => pathcamp(location.pathname, path))
.concat(location.pathname)}
defaultOpenKeys={paths
.filter(path => pathcamp(location.pathname, path))
.concat(location.pathname)}
defaultSelectedKeys={keys}
defaultOpenKeys={keys}
// selectedKeys={keys}
// openKeys={keys}
onOpenChange={openKeys => {
console.log({ openKeys });
}}
onSelect={info => {
console.log({ info });
}}
onDeselect={info => {
console.log({ info });
}}
{...restProps}
>
{items.map(item => {
if (!item.showInMenu) {
return null;
}
const { children = [] } = item;
const subItems = children.filter(child => child.showInMenu);
if (!hideChildren && subItems.length > 1) {
return (
<Menu.SubMenu
key={`${item.path}`}
icon={<Icon type={item.icon} />}
title={<>{item.title}</>}
>
{subItems.map((child: any) => (
<Menu.Item key={child.path}>
<Link to={child.path}>{child.title}</Link>
</Menu.Item>
))}
</Menu.SubMenu>
);
}
return (
<Menu.Item icon={<Icon type={item.icon} />} key={item.path}>
<Link to={item.path}>{item.title}</Link>
</Menu.Item>
);
})}
{renderChildren(items)}
</Menu>
);
};

View File

@ -1,378 +0,0 @@
import React, { useState } from 'react';
import { Space, Button, Popconfirm, Popover } from 'antd';
import {
FilterOutlined,
PlusOutlined,
SelectOutlined,
DeleteOutlined,
PrinterOutlined,
ExportOutlined,
} from '@ant-design/icons';
import Drawer from '@/components/pages/AdminLoader/Drawer';
import View from '@/components/pages/AdminLoader/View';
import get from 'lodash/get';
import set from 'lodash/set';
import ReactToPrint from 'react-to-print';
import api from '@/api-client';
const ACTIONS = new Map<string, any>();
export function Create(props) {
const { size, onFinish, schema = {}, associatedKey, ...restProps } = props;
const { title, pageTitle, viewName, transform, componentProps = {} } = schema;
return (
<>
<Button
size={size}
onClick={() => {
Drawer.open({
title: pageTitle || title,
content: ({ resolve, closeWithConfirm }) => (
<div>
<View
{...restProps}
onValueChange={() => {
closeWithConfirm && closeWithConfirm(true);
}}
associatedKey={associatedKey}
viewName={viewName}
onReset={resolve}
onDraft={async item => {
const values = transform ? {} : item;
for (const [sourceKey, targetKey] of Object.entries<string>(
transform || {},
)) {
const value = get({ data: item }, sourceKey);
set(values, targetKey, value);
}
await resolve();
console.log('onFinish', values);
onFinish && (await onFinish(values));
}}
onFinish={async item => {
const values = transform ? {} : item;
for (const [sourceKey, targetKey] of Object.entries<string>(
transform || {},
)) {
const value = get({ data: item }, sourceKey);
set(values, targetKey, value);
}
await resolve();
console.log('onFinish', values);
onFinish && (await onFinish(values));
}}
/>
</div>
),
});
}}
icon={<PlusOutlined />}
type={'primary'}
{...componentProps}
>
{title}
</Button>
</>
);
}
export function Update(props) {
const { onFinish, data, schema = {}, associatedKey, ...restProps } = props;
const { title, viewName } = schema;
return (
<>
<Button
onClick={() => {
Drawer.open({
title: title,
content: ({ resolve, closeWithConfirm }) => (
<div>
<View
{...restProps}
associatedKey={associatedKey}
data={data}
viewName={viewName}
onReset={resolve}
onDraft={async values => {
await resolve();
onFinish && (await onFinish(values));
}}
onValueChange={() => {
closeWithConfirm && closeWithConfirm(true);
}}
onFinish={async values => {
await resolve();
onFinish && (await onFinish(values));
}}
/>
</div>
),
});
}}
icon={<PlusOutlined />}
type={'primary'}
>
{title}
</Button>
</>
);
}
export function Add(props) {
const { size, onFinish, schema = {}, associatedKey, ...restProps } = props;
console.log({ associatedKey }, 'add');
const { filter, title, viewName, transform, componentProps = {} } = schema;
return (
<>
<Button
size={size}
onClick={() => {
Drawer.open({
title: title,
content: ({ resolve }) => {
const [selectedRows, setSelectedRows] = useState([]);
return (
<div>
<View
{...restProps}
defaultFilter={filter}
viewName={viewName}
associatedKey={associatedKey}
onSelected={values => {
console.log(values);
setSelectedRows(
values.map(item => {
if (!transform) {
return;
}
const data = {};
for (const [sourceKey, targetKey] of Object.entries<
string
>(transform)) {
const value = get({ data: item }, sourceKey);
set(data, targetKey, value);
}
return data;
}),
);
}}
/>
<Drawer.Footer>
<Space>
<Button onClick={resolve}></Button>
<Button
type={'primary'}
onClick={async () => {
console.log({ schema, onFinish });
onFinish && (await onFinish(selectedRows));
resolve();
}}
>
</Button>
</Space>
</Drawer.Footer>
</div>
);
},
});
}}
icon={<SelectOutlined />}
{...componentProps}
>
{title}
</Button>
</>
);
}
export function Destroy(props) {
const { size, schema = {}, onFinish } = props;
const { title, componentProps = {} } = schema;
return (
<Popconfirm
title="确认删除吗?"
onConfirm={async e => {
onFinish && (await onFinish());
}}
>
<Button
size={size}
danger
type={'ghost'}
icon={<DeleteOutlined />}
{...componentProps}
>
{title}
</Button>
</Popconfirm>
);
}
export function Filter(props) {
const { schema = {}, onFinish } = props;
const { title, fields = [] } = schema;
const [visible, setVisible] = useState(false);
const [data, setData] = useState({});
const [filterCount, setFilterCount] = useState(0);
console.log('Filter', { visible, data });
return (
<>
{visible && (
<div
style={{
height: '100vh',
width: '100vw',
zIndex: 1000,
position: 'fixed',
background: 'rgba(0, 0, 0, 0)',
top: 0,
left: 0,
}}
onClick={() => setVisible(false)}
></div>
)}
<Popover
// title="设置筛选"
trigger="click"
visible={visible}
defaultVisible={visible}
placement={'bottomLeft'}
destroyTooltipOnHide
onVisibleChange={visible => {
setVisible(visible);
}}
className={'filters-popover'}
style={{}}
overlayStyle={{
minWidth: 500,
}}
content={
<>
<View
data={data}
onFinish={async values => {
if (values) {
const items = values.filter.and || values.filter.or;
setFilterCount(Array.isArray(items) ? items.length : 0);
setData(values);
onFinish && (await onFinish(values));
}
setVisible(false);
}}
schema={{
type: 'filterForm',
fields: [
{
dataIndex: ['filter'],
name: 'filter',
interface: 'json',
type: 'json',
component: {
type: 'filter',
'x-component-props': {
fields,
},
},
},
],
}}
/>
</>
}
>
<Button icon={<FilterOutlined />}>
{filterCount ? `${filterCount}${title}` : title}
</Button>
</Popover>
</>
);
}
export function Print(props) {
const { schema = {}, contentRef } = props;
const { title, buttonProps = {} } = schema;
return (
<ReactToPrint
trigger={() => (
<Button {...buttonProps} icon={<PrinterOutlined />}>
{title}
</Button>
)}
content={() => contentRef.current}
pageStyle={`
@page {
margin: 1cm;
}
table { page-break-inside:auto }
tr { page-break-inside:avoid; page-break-after:auto }
`}
documentTitle={' '}
/>
);
}
export function Export(props) {
const { schema = {}, onFinish } = props;
const { title, buttonProps = {} } = schema;
return (
<Button
{...buttonProps}
icon={<ExportOutlined />}
onClick={async () => {
onFinish && (await onFinish({ fields: [] }));
}}
>
{title}
</Button>
);
}
export function Actions(props) {
const { onTrigger = {}, actions = [], style, ...restProps } = props;
return (
actions.length > 0 && (
<div className={'action-buttons'} style={style}>
{actions.map(
action =>
ACTIONS.has(action.type) && (
<div key={action.__index} className={`${action.type}-action-button action-button`}>
<Action
{...restProps}
onFinish={onTrigger[action.type]}
schema={action}
/>
</div>
),
)}
</div>
)
);
}
export default Actions;
export function registerAction(type: string, Action: any) {
ACTIONS.set(type, Action);
}
export function getAction(type: string) {
return ACTIONS.get(type);
}
export function Action(props) {
const { schema = {} } = props;
// cnsole.log(schema);
const { type } = schema;
const Component = getAction(type);
return Component && <Component {...props} />;
}
registerAction('add', Add);
registerAction('update', Update);
registerAction('create', Create);
registerAction('destroy', Destroy);
registerAction('filter', Filter);
registerAction('print', Print);
registerAction('export', Export);

View File

@ -1,100 +0,0 @@
import React, { useState } from 'react';
import { Layout, Breadcrumb, Drawer } from 'antd';
import { Link } from 'umi';
import './style.less';
import Menu from '../menu';
import { MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons';
import { useLocalStorageState } from 'ahooks';
import { useResponsive } from 'ahooks';
export function SideMenuLayout(props: any) {
const { currentPageName, menu = [], menuId } = props;
const [visible, setVisible] = useState(false);
// console.log(menu);
const [collapsed, setCollapsed] = useLocalStorageState(
`nocobase-menu-collapsed-${menuId}`,
false,
);
const responsive = useResponsive();
const isMobile = responsive.small && !responsive.middle && !responsive.large;
document.body.className = collapsed ? 'collapsed' : '';
return (
<Layout style={{ height: 'calc(100vh - 48px)' }}>
{!isMobile && (
<Layout.Sider
className={`nb-sider${collapsed ? ' collapsed' : ''}`}
theme={'light'}
>
<Menu
menuId={menuId}
currentPageName={currentPageName}
items={menu}
mode={'inline'}
/>
<div
onClick={() => {
setCollapsed(!collapsed);
setVisible(true);
document.body.className = collapsed ? 'collapsed' : '';
}}
className={'menu-toggle'}
>
{React.createElement(
collapsed ? MenuUnfoldOutlined : MenuFoldOutlined,
{
style: { fontSize: 16 },
},
)}
</div>
</Layout.Sider>
)}
<Layout.Content id={'content'}>
{props.children}
{isMobile && (
<Drawer
visible={visible}
onClose={() => {
setCollapsed(!collapsed);
setVisible(false);
document.body.className = collapsed ? 'collapsed' : '';
}}
placement={'left'}
closable={false}
bodyStyle={{ padding: 0 }}
>
<Menu
onSelect={() => {
setCollapsed(false);
setVisible(false);
document.body.className = collapsed ? 'collapsed' : '';
}}
currentPageName={currentPageName}
menuId={menuId}
items={menu}
mode={'inline'}
/>
</Drawer>
)}
{isMobile && (
<div
onClick={() => {
setCollapsed(!collapsed);
setVisible(true);
document.body.className = collapsed ? 'collapsed' : '';
}}
className={'menu-toggle'}
>
{React.createElement(
collapsed ? MenuUnfoldOutlined : MenuFoldOutlined,
{
style: { fontSize: 16 },
},
)}
</div>
)}
</Layout.Content>
</Layout>
);
}
export default SideMenuLayout;

View File

@ -1,21 +0,0 @@
.nb-sider {
position: relative;
left: 0;
z-index: 100;
box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
&.collapsed {
margin-left: -200px;
}
.ant-menu-light {
border-right-color: transparent !important;
}
}
.menu-toggle {
position: fixed;
bottom: 12px;
left: 12px;
line-height: 1;
cursor: pointer;
z-index: 1000;
}

View File

@ -1,97 +0,0 @@
import React, { useState } from 'react';
import { Layout, Dropdown, Avatar, Drawer } from 'antd';
import './style.less';
import { history, Link, request, useModel } from 'umi';
import { UserOutlined, CodeOutlined, MenuOutlined } from '@ant-design/icons';
import AvatarDropdown from '@/components/pages/AvatarDropdown';
import Menu from '../menu';
import { ReactComponent as Logo } from './logo-white.svg';
import { useResponsive, useLocalStorageState } from 'ahooks';
import get from 'lodash/get';
export function TopMenuLayout(props: any) {
const { currentPageName, menu = [] } = props;
console.log({ menu });
// const [visible, setVisible] = useState(false);
const [visible, setVisible] = useLocalStorageState(
`nocobase-nav-visible`,
false,
);
const responsive = useResponsive();
const isMobile = responsive.small && !responsive.middle && !responsive.large;
const {
initialState = {},
loading,
error,
refresh,
setInitialState,
} = useModel('@@initialState');
const logoUrl = get(initialState, 'systemSettings.logo.url');
console.log({ logoUrl });
return (
<Layout style={{ height: '100vh' }}>
<Layout.Header
style={{ height: 48, lineHeight: '48px', padding: 0 }}
className="nb-header"
>
<div className="logo" style={{ width: 200, height: 48, float: 'left' }}>
{!logoUrl ? <Logo /> : <img src={logoUrl} />}
</div>
{!isMobile && (
<Menu
currentPageName={currentPageName}
hideChildren={true}
items={menu}
className={'noco-top-menu'}
style={{ float: 'left' }}
theme="dark"
mode="horizontal"
/>
)}
{!isMobile && <AvatarDropdown />}
{isMobile && (
<MenuOutlined
onClick={() => {
setVisible(true);
}}
style={{
fontSize: 16,
color: '#fff',
position: 'absolute',
right: 16,
top: 16,
}}
/>
)}
{isMobile && (
<Drawer
visible={visible}
onClose={() => {
setVisible(false);
}}
placement={'right'}
closable={false}
bodyStyle={{ background: '#001529', padding: 0 }}
>
<Menu
currentPageName={currentPageName}
onSelect={() => {
setVisible(false);
}}
mode={'inline'}
hideChildren={true}
items={menu}
className={'noco-top-menu'}
style={{ float: 'left' }}
theme="dark"
/>
<AvatarDropdown />
</Drawer>
)}
</Layout.Header>
<Layout.Content>{props.children}</Layout.Content>
</Layout>
);
}
export default TopMenuLayout;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,37 +0,0 @@
.logo {
// font-family: 'Michroma', sans-serif;
color: rgba(255, 255, 255, 0.8);
font-size: 18px;
letter-spacing: 2px;
font-weight: 300;
padding: 0 15px;
img {
height: 100%;
}
svg {
margin-top: 12px;
}
}
body {
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.noco-top-menu.ant-menu-dark.ant-menu-horizontal > .ant-menu-item:hover,
.noco-top-menu.ant-menu.ant-menu-dark .ant-menu-item-selected,
.noco-top-menu.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected {
background-color: rgba(255, 255, 255, 0.1);
}
.ant-layout-content {
overflow: auto;
}
.ant-layout-header {
display: flex;
}
.ant-menu-dark.ant-menu-horizontal {
margin-right: 100px;
}

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