style: code formatting

This commit is contained in:
chenos 2021-03-28 13:34:51 +08:00
parent 5e9959b987
commit ce4a22fbb9
275 changed files with 6911 additions and 5067 deletions

View File

@ -187,10 +187,12 @@ describe('list', () => {
const User = db.getModel('users'); const User = db.getModel('users');
const expected = await User.findOne({ const expected = await User.findOne({
where: { where: {
nicknames: { [Op.or]: [ nicknames: {
[Op.or]: [
{ [Op.contains]: 'aaa' }, { [Op.contains]: 'aaa' },
{ [Op.contains]: 'aa' } { [Op.contains]: 'aa' }
] } ]
}
} }
}); });
const response = await agent.get('/users?filter[nicknames.$anyOf]=aaa,aa'); const response = await agent.get('/users?filter[nicknames.$anyOf]=aaa,aa');
@ -459,7 +461,8 @@ describe('list', () => {
const response = await agent.get('/posts?fields[only]=title&fields[appends]=user.name&filter[title]=title0'); const response = await agent.get('/posts?fields[only]=title&fields[appends]=user.name&filter[title]=title0');
expect(response.body.rows[0].user.name).toEqual('a'); expect(response.body.rows[0].user.name).toEqual('a');
expect(response.body.rows).toEqual([{ expect(response.body.rows).toEqual([{
title: 'title0', user: { id: 1, nicknames: ['aa', 'aaa'], name: 'a', ...timestampsStrings } }]); title: 'title0', user: { id: 1, nicknames: ['aa', 'aaa'], name: 'a', ...timestampsStrings }
}]);
}); });
}); });
}); });

View File

@ -26,10 +26,20 @@ interface Resource {
class ApiClient { class ApiClient {
resource(name: string): Resource { resource(name: string): Resource {
const proxy: any = new Proxy({}, { const proxy: any = new Proxy(
{},
{
get(target, method, receiver) { get(target, method, receiver) {
return (params: ActionParams = {}) => { return (params: ActionParams = {}) => {
let { associatedKey, resourceKey, filter, sorter, sort = [], values, ...restParams } = params; let {
associatedKey,
resourceKey,
filter,
sorter,
sort = [],
values,
...restParams
} = params;
let url = `/${name}`; let url = `/${name}`;
sort = sort || []; sort = sort || [];
let options: any = { let options: any = {
@ -72,8 +82,9 @@ class ApiClient {
console.log({ url, params }); console.log({ url, params });
return request(url, options); return request(url, options);
}; };
} },
}); },
);
return proxy; return proxy;
} }
} }

View File

@ -12,7 +12,7 @@ configResponsive({
export const request: RequestConfig = { export const request: RequestConfig = {
prefix: process.env.API, prefix: process.env.API,
errorConfig: { errorConfig: {
adaptor: (resData) => { adaptor: resData => {
return { return {
...resData, ...resData,
success: true, success: true,
@ -28,23 +28,21 @@ export const request: RequestConfig = {
headers['Authorization'] = `Bearer ${token}`; headers['Authorization'] = `Bearer ${token}`;
} }
await next(); await next();
} },
], ],
}; };
const pathnames = [ const pathnames = ['/login', '/register', '/lostpassword', '/resetpassword'];
'/login',
'/register',
'/lostpassword',
'/resetpassword',
];
export async function getInitialState() { export async function getInitialState() {
const { pathname, search } = location; const { pathname, search } = location;
console.log(location); console.log(location);
const { data: systemSettings = {} } = await umiRequest('/system_settings:get?fields[appends]=logo,logo.storage', { const { data: systemSettings = {} } = await umiRequest(
'/system_settings:get?fields[appends]=logo,logo.storage',
{
method: 'get', method: 'get',
}); },
);
let redirect = `?redirect=${pathname}${search}`; let redirect = `?redirect=${pathname}${search}`;
if (!pathnames.includes(pathname)) { if (!pathnames.includes(pathname)) {
@ -66,7 +64,7 @@ export async function getInitialState() {
currentUser: data, currentUser: data,
}; };
} catch (error) { } catch (error) {
console.log(error) console.log(error);
history.push('/login' + redirect); history.push('/login' + redirect);
} }
} }

View File

@ -33,11 +33,17 @@ export function Create(props) {
viewName={viewName} viewName={viewName}
{...params} {...params}
/> />
<Button icon={<PlusOutlined />} type={'primary'} onClick={() => { <Button
icon={<PlusOutlined />}
type={'primary'}
onClick={() => {
drawerRef.current.setVisible(true); drawerRef.current.setVisible(true);
}}>{title}</Button> }}
>
{title}
</Button>
</> </>
) );
} }
export default Create; export default Create;

View File

@ -10,14 +10,19 @@ export function Destroy(props) {
const drawerRef = useRef<any>(); const drawerRef = useRef<any>();
return ( return (
<> <>
<Popconfirm title="确认删除吗?" onConfirm={() => { <Popconfirm
title="确认删除吗?"
onConfirm={() => {
console.log('destroy', onTrigger); console.log('destroy', onTrigger);
onTrigger && onTrigger(); onTrigger && onTrigger();
}}> }}
<Button icon={<DeleteOutlined />} type={'ghost'} danger>{title}</Button> >
<Button icon={<DeleteOutlined />} type={'ghost'} danger>
{title}
</Button>
</Popconfirm> </Popconfirm>
</> </>
) );
} }
export default Destroy; export default Destroy;

View File

@ -8,7 +8,13 @@ export function Filter(props) {
const drawerRef = useRef<any>(); const drawerRef = useRef<any>();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const { title, viewName, collection_name } = props.schema; const { title, viewName, collection_name } = props.schema;
const { filterCount, activeTab = {}, item = {}, associatedName, associatedKey } = props; const {
filterCount,
activeTab = {},
item = {},
associatedName,
associatedKey,
} = props;
const { associationField } = activeTab; const { associationField } = activeTab;
const params = {}; const params = {};
@ -30,18 +36,20 @@ export function Filter(props) {
trigger="click" trigger="click"
visible={visible} visible={visible}
placement={'bottomLeft'} placement={'bottomLeft'}
onVisibleChange={(visible) => { onVisibleChange={visible => {
setVisible(visible); setVisible(visible);
}} }}
className={'filters-popover'} className={'filters-popover'}
style={{ style={{}}
}}
overlayStyle={{ overlayStyle={{
minWidth: 500 minWidth: 500,
}} }}
content={( content={
<> <>
<div className={'popover-button-mask'} onClick={() => setVisible(false)}></div> <div
className={'popover-button-mask'}
onClick={() => setVisible(false)}
></div>
<ViewFactory <ViewFactory
{...props} {...props}
setVisible={setVisible} setVisible={setVisible}
@ -49,14 +57,19 @@ export function Filter(props) {
{...params} {...params}
/> />
</> </>
)} }
> >
<Button icon={<FilterOutlined />} onClick={() => { <Button
icon={<FilterOutlined />}
onClick={() => {
setVisible(true); setVisible(true);
}}>{filterCount ? `${filterCount}${title}` : title}</Button> }}
>
{filterCount ? `${filterCount}${title}` : title}
</Button>
</Popover> </Popover>
</> </>
) );
} }
export default Filter; export default Filter;

View File

@ -29,12 +29,18 @@ export function Update(props) {
mode={'update'} mode={'update'}
{...params} {...params}
/> />
<Button icon={<EditOutlined />} type={'primary'} onClick={() => { <Button
icon={<EditOutlined />}
type={'primary'}
onClick={() => {
drawerRef.current.setVisible(true); drawerRef.current.setVisible(true);
drawerRef.current.getData(item.itemId || resourceKey); drawerRef.current.getData(item.itemId || resourceKey);
}}>{title}</Button> }}
>
{title}
</Button>
</> </>
) );
} }
export default Update; export default Update;

View File

@ -1,4 +1,3 @@
import React from 'react'; import React from 'react';
import Create from './Create'; import Create from './Create';
import Update from './Update'; import Update from './Update';
@ -33,7 +32,8 @@ export function Action(props) {
export function Actions(props) { export function Actions(props) {
const { onTrigger = {}, style, schema, actions = [], ...restProps } = props; const { onTrigger = {}, style, schema, actions = [], ...restProps } = props;
console.log(onTrigger); console.log(onTrigger);
return actions.length > 0 && ( return (
actions.length > 0 && (
<div className={'action-buttons'} style={style}> <div className={'action-buttons'} style={style}>
{actions.map(action => ( {actions.map(action => (
<div className={`${action.name}-action-button action-button`}> <div className={`${action.name}-action-button action-button`}>
@ -46,6 +46,7 @@ export function Actions(props) {
</div> </div>
))} ))}
</div> </div>
)
); );
} }

View File

@ -1,20 +1,20 @@
import React, { Fragment } from 'react' import React, { Fragment } from 'react';
import { import {
ISchemaFieldComponentProps, ISchemaFieldComponentProps,
SchemaField SchemaField,
} from '@formily/react-schema-renderer' } from '@formily/react-schema-renderer';
import { toArr, isFn, FormPath } from '@formily/shared' import { toArr, isFn, FormPath } from '@formily/shared';
import { ArrayList } from '@formily/react-shared-components' import { ArrayList } from '@formily/react-shared-components';
import { CircleButton } from '../circle-button' import { CircleButton } from '../circle-button';
import { TextButton } from '../text-button' import { TextButton } from '../text-button';
import { Card } from 'antd' import { Card } from 'antd';
import { import {
PlusOutlined, PlusOutlined,
DeleteOutlined, DeleteOutlined,
DownOutlined, DownOutlined,
UpOutlined UpOutlined,
} from '@ant-design/icons' } from '@ant-design/icons';
import styled from 'styled-components' import styled from 'styled-components';
const ArrayComponents = { const ArrayComponents = {
CircleButton, CircleButton,
@ -22,12 +22,12 @@ const ArrayComponents = {
AdditionIcon: () => <PlusOutlined />, AdditionIcon: () => <PlusOutlined />,
RemoveIcon: () => <DeleteOutlined />, RemoveIcon: () => <DeleteOutlined />,
MoveDownIcon: () => <DownOutlined />, MoveDownIcon: () => <DownOutlined />,
MoveUpIcon: () => <UpOutlined /> MoveUpIcon: () => <UpOutlined />,
} };
export const ArrayCards: any = styled( export const ArrayCards: any = styled(
(props: ISchemaFieldComponentProps & { className: string }) => { (props: ISchemaFieldComponentProps & { className: string }) => {
const { value, schema, className, editable, path, mutators } = props const { value, schema, className, editable, path, mutators } = props;
const { const {
renderAddition, renderAddition,
renderRemove, renderRemove,
@ -36,17 +36,17 @@ export const ArrayCards: any = styled(
renderEmpty, renderEmpty,
renderExtraOperations, renderExtraOperations,
...componentProps ...componentProps
} = schema.getExtendsComponentProps() || {} } = schema.getExtendsComponentProps() || {};
const schemaItems = Array.isArray(schema.items) const schemaItems = Array.isArray(schema.items)
? schema.items[schema.items.length - 1] ? schema.items[schema.items.length - 1]
: schema.items : schema.items;
const onAdd = () => { const onAdd = () => {
if (schemaItems) { if (schemaItems) {
mutators.push(schemaItems.getEmptyValue()) mutators.push(schemaItems.getEmptyValue());
}
} }
};
return ( return (
<div className={className}> <div className={className}>
<ArrayList <ArrayList
@ -60,7 +60,7 @@ export const ArrayCards: any = styled(
renderRemove, renderRemove,
renderMoveDown, renderMoveDown,
renderMoveUp, renderMoveUp,
renderEmpty renderEmpty,
}} }}
> >
{toArr(value).map((item, index) => { {toArr(value).map((item, index) => {
@ -72,7 +72,8 @@ export const ArrayCards: any = styled(
key={index} key={index}
title={ title={
<span> <span>
{index + 1}<span>.</span> {componentProps.title || schema.title} {index + 1}
<span>.</span> {componentProps.title || schema.title}
</span> </span>
} }
extra={ extra={
@ -102,7 +103,7 @@ export const ArrayCards: any = styled(
/> />
)} )}
</Card> </Card>
) );
})} })}
<ArrayList.Empty> <ArrayList.Empty>
{({ children, allowAddition }) => { {({ children, allowAddition }) => {
@ -110,12 +111,14 @@ export const ArrayCards: any = styled(
<Card <Card
{...componentProps} {...componentProps}
size="small" size="small"
className={`card-list-item card-list-empty ${allowAddition ? 'add-pointer' : ''}`} className={`card-list-item card-list-empty ${
allowAddition ? 'add-pointer' : ''
}`}
onClick={allowAddition ? onAdd : undefined} onClick={allowAddition ? onAdd : undefined}
> >
<div className="empty-wrapper">{children}</div> <div className="empty-wrapper">{children}</div>
</Card> </Card>
) );
}} }}
</ArrayList.Empty> </ArrayList.Empty>
<ArrayList.Addition> <ArrayList.Addition>
@ -125,14 +128,14 @@ export const ArrayCards: any = styled(
<div className="array-cards-addition" onClick={onAdd}> <div className="array-cards-addition" onClick={onAdd}>
{children} {children}
</div> </div>
) );
} }
}} }}
</ArrayList.Addition> </ArrayList.Addition>
</ArrayList> </ArrayList>
</div> </div>
) );
} },
)<ISchemaFieldComponentProps>` )<ISchemaFieldComponentProps>`
width: 100%; width: 100%;
.ant-card { .ant-card {
@ -190,8 +193,8 @@ export const ArrayCards: any = styled(
margin-right: 8px; margin-right: 8px;
} }
} }
` `;
ArrayCards.isFieldComponent = true ArrayCards.isFieldComponent = true;
export default ArrayCards export default ArrayCards;

View File

@ -1 +1 @@
import 'antd/lib/card/style/index' import 'antd/lib/card/style/index';

View File

@ -1,33 +1,37 @@
import React, { useContext } from 'react' import React, { useContext } from 'react';
import { import {
ISchemaFieldComponentProps, ISchemaFieldComponentProps,
SchemaField, SchemaField,
Schema, Schema,
complieExpression, complieExpression,
FormExpressionScopeContext FormExpressionScopeContext,
} from '@formily/react-schema-renderer' } from '@formily/react-schema-renderer';
import { toArr, isFn, isArr, FormPath } from '@formily/shared' import { toArr, isFn, isArr, FormPath } from '@formily/shared';
import { ArrayList, DragListView } from '@formily/react-shared-components' import { ArrayList, DragListView } from '@formily/react-shared-components';
import { CircleButton } from '../circle-button' import { CircleButton } from '../circle-button';
import { TextButton } from '../text-button' import { TextButton } from '../text-button';
import { Table, Form, Button } from 'antd' import { Table, Form, Button } from 'antd';
import { FormItemShallowProvider } from '@formily/antd' import { FormItemShallowProvider } from '@formily/antd';
import { import {
PlusOutlined, PlusOutlined,
DeleteOutlined, DeleteOutlined,
DownOutlined, DownOutlined,
UpOutlined UpOutlined,
} from '@ant-design/icons' } from '@ant-design/icons';
import styled from 'styled-components' import styled from 'styled-components';
const ArrayComponents = { const ArrayComponents = {
CircleButton, CircleButton,
TextButton, TextButton,
AdditionIcon: () => <><PlusOutlined/> </>, AdditionIcon: () => (
<>
<PlusOutlined />
</>
),
RemoveIcon: () => <DeleteOutlined />, RemoveIcon: () => <DeleteOutlined />,
MoveDownIcon: () => <DownOutlined />, MoveDownIcon: () => <DownOutlined />,
MoveUpIcon: () => <UpOutlined /> MoveUpIcon: () => <UpOutlined />,
} };
const DragHandler = styled.span` const DragHandler = styled.span`
width: 7px; width: 7px;
@ -38,12 +42,12 @@ const DragHandler = styled.span`
border-bottom: 0; border-bottom: 0;
cursor: move; cursor: move;
margin-bottom: 24px; margin-bottom: 24px;
` `;
export const ArrayTable: any = styled( export const ArrayTable: any = styled(
(props: ISchemaFieldComponentProps & { className: string }) => { (props: ISchemaFieldComponentProps & { className: string }) => {
const expressionScope = useContext(FormExpressionScopeContext) const expressionScope = useContext(FormExpressionScopeContext);
const { value, schema, className, editable, path, mutators } = props const { value, schema, className, editable, path, mutators } = props;
const { const {
renderAddition, renderAddition,
renderRemove, renderRemove,
@ -55,31 +59,31 @@ export const ArrayTable: any = styled(
operations, operations,
draggable, draggable,
...componentProps ...componentProps
} = schema.getExtendsComponentProps() || {} } = schema.getExtendsComponentProps() || {};
const schemaItems = Array.isArray(schema.items) const schemaItems = Array.isArray(schema.items)
? schema.items[schema.items.length - 1] ? schema.items[schema.items.length - 1]
: schema.items : schema.items;
const onAdd = () => { const onAdd = () => {
if (schemaItems) { if (schemaItems) {
mutators.push(schemaItems.getEmptyValue()) mutators.push(schemaItems.getEmptyValue());
}
} }
};
const onMove = (dragIndex, dropIndex) => { const onMove = (dragIndex, dropIndex) => {
mutators.move(dragIndex, dropIndex) mutators.move(dragIndex, dropIndex);
} };
const renderColumns = (items: Schema) => { const renderColumns = (items: Schema) => {
return items.mapProperties((props, key) => { return items.mapProperties((props, key) => {
const itemProps = { const itemProps = {
...props.getExtendsItemProps(), ...props.getExtendsItemProps(),
...props.getExtendsProps() ...props.getExtendsProps(),
} };
return { return {
title: complieExpression(props.title, expressionScope), title: complieExpression(props.title, expressionScope),
...itemProps, ...itemProps,
key, key,
dataIndex: key, dataIndex: key,
render: (value: any, record: any, index: number) => { render: (value: any, record: any, index: number) => {
const newPath = FormPath.parse(path).concat(index, key) const newPath = FormPath.parse(path).concat(index, key);
return ( return (
<FormItemShallowProvider <FormItemShallowProvider
key={newPath.toString()} key={newPath.toString()}
@ -89,19 +93,19 @@ export const ArrayTable: any = styled(
> >
<SchemaField path={newPath} schema={props} /> <SchemaField path={newPath} schema={props} />
</FormItemShallowProvider> </FormItemShallowProvider>
) );
} },
} };
}) });
} };
// 兼容异步items schema传入 // 兼容异步items schema传入
let columns = [] let columns = [];
if (schema.items) { if (schema.items) {
columns = isArr(schema.items) columns = isArr(schema.items)
? schema.items.reduce((buf, items) => { ? schema.items.reduce((buf, items) => {
return buf.concat(renderColumns(items)) return buf.concat(renderColumns(items));
}, []) }, [])
: renderColumns(schema.items) : renderColumns(schema.items);
} }
if (editable && operations !== false) { if (editable && operations !== false) {
columns.push({ columns.push({
@ -130,32 +134,32 @@ export const ArrayTable: any = styled(
: renderExtraOperations} : renderExtraOperations}
</div> </div>
</Form.Item> </Form.Item>
) );
} },
}) });
} }
if (draggable) { if (draggable) {
columns.unshift({ columns.unshift({
width: 20, width: 20,
key: 'dragHandler', key: 'dragHandler',
render: () => { render: () => {
return <DragHandler className="drag-handler" /> return <DragHandler className="drag-handler" />;
} },
}) });
} }
const renderTable = () => { const renderTable = () => {
return ( return (
<Table <Table
{...componentProps} {...componentProps}
rowKey={record => { rowKey={record => {
return toArr(value).indexOf(record) return toArr(value).indexOf(record);
}} }}
pagination={false} pagination={false}
columns={columns} columns={columns}
dataSource={toArr(value)} dataSource={toArr(value)}
></Table> ></Table>
) );
} };
return ( return (
<div className={className}> <div className={className}>
<ArrayList <ArrayList
@ -169,7 +173,7 @@ export const ArrayTable: any = styled(
renderRemove, renderRemove,
renderMoveDown, renderMoveDown,
renderMoveUp, renderMoveUp,
renderEmpty renderEmpty,
}} }}
> >
{draggable ? ( {draggable ? (
@ -191,13 +195,13 @@ export const ArrayTable: any = styled(
{children} {children}
</div> </div>
) )
) );
}} }}
</ArrayList.Addition> </ArrayList.Addition>
</ArrayList> </ArrayList>
</div> </div>
) );
} },
)` )`
width: 100%; width: 100%;
margin-bottom: 10px; margin-bottom: 10px;
@ -227,8 +231,8 @@ export const ArrayTable: any = styled(
margin-right: 8px; margin-right: 8px;
} }
} }
` `;
ArrayTable.isFieldComponent = true ArrayTable.isFieldComponent = true;
export default ArrayTable export default ArrayTable;

View File

@ -1 +1 @@
import 'antd/lib/table/style/index' import 'antd/lib/table/style/index';

View File

@ -1,11 +1,19 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import { Button, Select, DatePicker, Tag, InputNumber, TimePicker, Input } from 'antd'; import {
Button,
Select,
DatePicker,
Tag,
InputNumber,
TimePicker,
Input,
} from 'antd';
import { import {
Select as AntdSelect, Select as AntdSelect,
mapStyledProps, mapStyledProps,
mapTextComponent mapTextComponent,
} from '../shared' } from '../shared';
import moment from 'moment'; import moment from 'moment';
import './style.less'; import './style.less';
import api from '@/api-client'; import api from '@/api-client';
@ -14,11 +22,11 @@ import { useRequest } from 'umi';
export const DateTime = connect({ export const DateTime = connect({
getProps: mapStyledProps, getProps: mapStyledProps,
getComponent: mapTextComponent, getComponent: mapTextComponent,
})((props) => { })(props => {
const { associatedKey, automationType, filter, onChange } = props; const { associatedKey, automationType, filter, onChange } = props;
const [aKey, setaKey] = useState(associatedKey); const [aKey, setaKey] = useState(associatedKey);
const [aType, setaType] = useState(automationType); const [aType, setaType] = useState(automationType);
console.log('Automations.DateTime', aKey, associatedKey) console.log('Automations.DateTime', aKey, associatedKey);
const [value, setValue] = useState(props.value || {}); const [value, setValue] = useState(props.value || {});
const [offsetType, setOffsetType] = useState(() => { const [offsetType, setOffsetType] = useState(() => {
if (!value.offset) { if (!value.offset) {
@ -31,7 +39,7 @@ export const DateTime = connect({
return 'before'; return 'before';
} }
return 'current'; return 'current';
}) });
useEffect(() => { useEffect(() => {
if (associatedKey !== aKey || automationType !== aType) { if (associatedKey !== aKey || automationType !== aType) {
@ -48,45 +56,62 @@ export const DateTime = connect({
offset: 0, offset: 0,
unit: undefined, unit: undefined,
}); });
setaKey(associatedKey) setaKey(associatedKey);
} }
}, [associatedKey, automationType, aKey, aType]); }, [associatedKey, automationType, aKey, aType]);
const { data = [], loading = true } = useRequest(() => { const { data = [], loading = true } = useRequest(
return associatedKey && automationType !== 'schedule' ? api.resource('collections.fields').list({ () => {
return associatedKey && automationType !== 'schedule'
? api.resource('collections.fields').list({
associatedKey, associatedKey,
filter: filter || { filter: filter || {
type: 'date', type: 'date',
}, },
}) : Promise.resolve({data: []}); })
}, { : Promise.resolve({ data: [] });
refreshDeps: [associatedKey, automationType, filter] },
}); {
refreshDeps: [associatedKey, automationType, filter],
},
);
console.log({ data }); console.log({ data });
return ( return (
<div> <div>
<div> <div>
{automationType === 'schedule' ? ( {automationType === 'schedule' ? (
<DatePicker showTime onChange={(m, dateString) => { <DatePicker
showTime
onChange={(m, dateString) => {
onChange({ value: m.toISOString() }); onChange({ value: m.toISOString() });
setValue({ value: m.toISOString() }); setValue({ value: m.toISOString() });
// console.log('Automations.DateTime', m.toISOString(), {m, dateString}) // console.log('Automations.DateTime', m.toISOString(), {m, dateString})
}} defaultValue={(() => { }}
defaultValue={(() => {
if (!value.value) { if (!value.value) {
return undefined; return undefined;
} }
const m = moment(value.value); const m = moment(value.value);
return m.isValid() ? m : undefined; return m.isValid() ? m : undefined;
})()}/> })()}
/>
) : ( ) : (
<Input.Group compact> <Input.Group compact>
<Select style={{width: 120}} value={value.byField} onChange={(v) => { <Select
style={{ width: 120 }}
value={value.byField}
onChange={v => {
setValue({ ...value, byField: v }); setValue({ ...value, byField: v });
onChange({ ...value, byField: v }); onChange({ ...value, byField: v });
}} loading={loading} options={data.map(item => ({ }}
loading={loading}
options={data.map(item => ({
value: item.name, value: item.name,
label: item.title || item.name, label: item.title || item.name,
}))} placeholder={'选择日期字段'}></Select> }))}
<Select onChange={(offsetType) => { placeholder={'选择日期字段'}
></Select>
<Select
onChange={offsetType => {
let values = { ...value }; let values = { ...value };
switch (offsetType) { switch (offsetType) {
case 'current': case 'current':
@ -106,16 +131,24 @@ export const DateTime = connect({
setOffsetType(offsetType); setOffsetType(offsetType);
setValue(values); setValue(values);
onChange(values); onChange(values);
}} value={offsetType} placeholder={'选择日期字段'}> }}
value={offsetType}
placeholder={'选择日期字段'}
>
<Select.Option value={'current'}></Select.Option> <Select.Option value={'current'}></Select.Option>
<Select.Option value={'before'}></Select.Option> <Select.Option value={'before'}></Select.Option>
<Select.Option value={'after'}></Select.Option> <Select.Option value={'after'}></Select.Option>
</Select> </Select>
{offsetType !== 'current' && ( {offsetType !== 'current' && (
<InputNumber step={1} min={1} value={Math.abs(value.offset)||undefined} onChange={(offset: number) => { <InputNumber
step={1}
min={1}
value={Math.abs(value.offset) || undefined}
onChange={(offset: number) => {
const values = { const values = {
unit: 'day',...value, unit: 'day',
} ...value,
};
if (offsetType === 'before') { if (offsetType === 'before') {
values.offset = -1 * Math.abs(offset); values.offset = -1 * Math.abs(offset);
} else if (offsetType === 'after') { } else if (offsetType === 'after') {
@ -123,15 +156,21 @@ export const DateTime = connect({
} }
setValue(values); setValue(values);
onChange(values); onChange(values);
console.log('Automations.DateTime', values) console.log('Automations.DateTime', values);
// console.log(offsetType); // console.log(offsetType);
}} placeholder={'数字'}/> }}
placeholder={'数字'}
/>
)} )}
{offsetType !== 'current' && ( {offsetType !== 'current' && (
<Select onChange={(v) => { <Select
onChange={v => {
setValue({ ...value, unit: v }); setValue({ ...value, unit: v });
onChange({ ...value, unit: v }); onChange({ ...value, unit: v });
}} value={value.unit} placeholder={'选择单位'}> }}
value={value.unit}
placeholder={'选择单位'}
>
<Select.Option value={'second'}></Select.Option> <Select.Option value={'second'}></Select.Option>
<Select.Option value={'minute'}></Select.Option> <Select.Option value={'minute'}></Select.Option>
<Select.Option value={'hour'}></Select.Option> <Select.Option value={'hour'}></Select.Option>
@ -140,22 +179,27 @@ export const DateTime = connect({
<Select.Option value={'month'}></Select.Option> <Select.Option value={'month'}></Select.Option>
</Select> </Select>
)} )}
{offsetType !== 'current' && value.unit && ['day', 'week', 'month'].indexOf(value.unit) !== -1 && ( {offsetType !== 'current' &&
<TimePicker value={(() => { value.unit &&
['day', 'week', 'month'].indexOf(value.unit) !== -1 && (
<TimePicker
value={(() => {
const m = moment(value.time, 'HH:mm:ss'); const m = moment(value.time, 'HH:mm:ss');
return m.isValid() ? m : undefined; return m.isValid() ? m : undefined;
})()} onChange={(m, dateString) => { })()}
console.log('Automations.DateTime', m, dateString) onChange={(m, dateString) => {
console.log('Automations.DateTime', m, dateString);
setValue({ ...value, time: dateString }); setValue({ ...value, time: dateString });
onChange({ ...value, time: dateString }); onChange({ ...value, time: dateString });
}}/> }}
/>
)} )}
</Input.Group> </Input.Group>
)} )}
</div> </div>
</div> </div>
) );
}) });
const cronmap = { const cronmap = {
none: '不重复', none: '不重复',
@ -171,10 +215,10 @@ const cronmap = {
export const Cron = connect({ export const Cron = connect({
getProps: mapStyledProps, getProps: mapStyledProps,
getComponent: mapTextComponent, getComponent: mapTextComponent,
})((props) => { })(props => {
const { value, onChange } = props; const { value, onChange } = props;
console.log('Automations.DateTime', {value}) console.log('Automations.DateTime', { value });
const re = /every_(\d+)_(.+)/i; const re = /every_(\d+)_(.+)/i;
@ -187,33 +231,42 @@ export const Cron = connect({
return 'none'; return 'none';
} }
return match ? 'custom' : cronmap[value]; return match ? 'custom' : cronmap[value];
}) });
return ( return (
<div> <div>
<Input.Group compact> <Input.Group compact>
<Select value={cron} onChange={(v) => { <Select
value={cron}
onChange={v => {
setCron(v); setCron(v);
onChange(v); onChange(v);
}}> }}
>
{Object.keys(cronmap).map(key => { {Object.keys(cronmap).map(key => {
return ( return <Select.Option value={key}>{cronmap[key]}</Select.Option>;
<Select.Option value={key}>{cronmap[key]}</Select.Option>
);
})} })}
</Select> </Select>
{cron === 'custom' && ( {cron === 'custom' && (
<Input type={'number'} onChange={(e) => { <Input
type={'number'}
onChange={e => {
const v = parseInt(e.target.value); const v = parseInt(e.target.value);
setNum(v); setNum(v);
onChange(`every_${v}_${unit}`); onChange(`every_${v}_${unit}`);
}} defaultValue={num} addonBefore={'每'}/> }}
defaultValue={num}
addonBefore={'每'}
/>
)} )}
{cron === 'custom' && ( {cron === 'custom' && (
<Select onChange={(v) => { <Select
onChange={v => {
setUnit(v); setUnit(v);
onChange(`every_${num}_${v}`); onChange(`every_${num}_${v}`);
}} defaultValue={unit}> }}
defaultValue={unit}
>
<Select.Option value={'seconds'}></Select.Option> <Select.Option value={'seconds'}></Select.Option>
<Select.Option value={'minutes'}></Select.Option> <Select.Option value={'minutes'}></Select.Option>
<Select.Option value={'hours'}></Select.Option> <Select.Option value={'hours'}></Select.Option>
@ -224,13 +277,13 @@ export const Cron = connect({
)} )}
</Input.Group> </Input.Group>
</div> </div>
) );
}) });
export const EndMode = connect({ export const EndMode = connect({
getProps: mapStyledProps, getProps: mapStyledProps,
getComponent: mapTextComponent, getComponent: mapTextComponent,
})((props) => { })(props => {
const { value = 'none', onChange, automationType } = props; const { value = 'none', onChange, automationType } = props;
const re = /after_(\d+)_times/i; const re = /after_(\d+)_times/i;
const match = re.exec(value); const match = re.exec(value);
@ -238,7 +291,10 @@ export const EndMode = connect({
const [mode, setMode] = useState(() => { const [mode, setMode] = useState(() => {
if (automationType === 'schedule' && value === 'byField') { if (automationType === 'schedule' && value === 'byField') {
return 'none'; return 'none';
} else if (automationType === 'collections:schedule' && value === 'customTime') { } else if (
automationType === 'collections:schedule' &&
value === 'customTime'
) {
return 'none'; return 'none';
} }
@ -249,38 +305,57 @@ export const EndMode = connect({
useEffect(() => { useEffect(() => {
if (automationType === 'schedule' && value === 'byField') { if (automationType === 'schedule' && value === 'byField') {
setMode('none') setMode('none');
onChange('none') onChange('none');
} else if (automationType === 'collections:schedule' && value === 'customTime') { } else if (
setMode('none') automationType === 'collections:schedule' &&
onChange('none') value === 'customTime'
) {
setMode('none');
onChange('none');
} }
}, [automationType]); }, [automationType]);
console.log('Automations.DateTime', {value, automationType, mode}) console.log('Automations.DateTime', { value, automationType, mode });
return ( return (
<div> <div>
<Input.Group compact> <Input.Group compact>
<Select style={{width: 150}} value={mode} onChange={(v) => { <Select
style={{ width: 150 }}
value={mode}
onChange={v => {
setMode(v); setMode(v);
onChange(v); onChange(v);
}}> }}
>
<Select.Option value={'none'}></Select.Option> <Select.Option value={'none'}></Select.Option>
<Select.Option value={'times'}></Select.Option> <Select.Option value={'times'}></Select.Option>
{automationType === 'schedule' && <Select.Option value={'customTime'}></Select.Option>} {automationType === 'schedule' && (
{automationType === 'collections:schedule' && <Select.Option value={'byField'}></Select.Option>} <Select.Option value={'customTime'}></Select.Option>
)}
{automationType === 'collections:schedule' && (
<Select.Option value={'byField'}></Select.Option>
)}
</Select> </Select>
{mode === 'times' && <Input type={'number'} onChange={(e) => { {mode === 'times' && (
<Input
type={'number'}
onChange={e => {
const v = parseInt(e.target.value); const v = parseInt(e.target.value);
setNum(v); setNum(v);
onChange(`after_${v}_times`); onChange(`after_${v}_times`);
}} defaultValue={num} addonAfter={'次'}/>} }}
defaultValue={num}
addonAfter={'次'}
/>
)}
</Input.Group> </Input.Group>
</div> </div>
) );
}) });
export const Automations = { export const Automations = {
DateTime, Cron, EndMode DateTime,
Cron,
EndMode,
}; };

View File

@ -1,18 +1,24 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import { Cascader as AntdCascader } from 'antd' import { Cascader as AntdCascader } from 'antd';
import { useRequest } from 'umi'; import { useRequest } from 'umi';
import api from '@/api-client'; import api from '@/api-client';
import { import {
transformDataSourceKey, transformDataSourceKey,
mapStyledProps, mapStyledProps,
mapTextComponent mapTextComponent,
} from '../shared' } from '../shared';
function findTreeNode(tree, values, { value = 'value', children = 'children' }) { function findTreeNode(
tree,
values,
{ value = 'value', children = 'children' },
) {
let node, i; let node, i;
for (node = tree, i = 0; node && values[i]; i++) { for (node = tree, i = 0; node && values[i]; i++) {
node = (node[children] || []).find(item => item[value] === values[i][value]); node = (node[children] || []).find(
item => item[value] === values[i][value],
);
} }
return node; return node;
@ -55,17 +61,17 @@ export const Cascader = connect({
// 是否可以不选择到最深一级 // 是否可以不选择到最深一级
// 'x-component-props': { changeOnSelect: true } // 'x-component-props': { changeOnSelect: true }
incompletely: changeOnSelect, incompletely: changeOnSelect,
} = schema; } = schema;
const fieldNames = { const fieldNames = {
label: labelField, label: labelField,
value: valueField, value: valueField,
children: 'children' children: 'children',
}; };
const [options, setOptions] = useState([]); const [options, setOptions] = useState([]);
const { loading, run } = useRequest(async (selectedOptions = []) => { const { loading, run } = useRequest(
async (selectedOptions = []) => {
if (maxLevel != null && selectedOptions.length >= maxLevel) { if (maxLevel != null && selectedOptions.length >= maxLevel) {
return; return;
} }
@ -80,17 +86,18 @@ export const Cascader = connect({
return api.resource(target).list({ return api.resource(target).list({
filter: { filter: {
[parentField]: last && last[valueField] [parentField]: last && last[valueField],
}, },
perPage: -1, perPage: -1,
sort: [valueField] sort: [valueField],
}); });
// TODO(bug): 关联资源加载问题较多,暂时先用 filter 解决 // TODO(bug): 关联资源加载问题较多,暂时先用 filter 解决
// return api.resource(`${target}.${target}`).list({ // return api.resource(`${target}.${target}`).list({
// associatedKey: last, // associatedKey: last,
// perPage: -1 // perPage: -1
// }); // });
}, { },
{
manual: true, manual: true,
onSuccess(result, [selectedOptions = []]) { onSuccess(result, [selectedOptions = []]) {
if (!result) { if (!result) {
@ -99,7 +106,7 @@ export const Cascader = connect({
const data = result.map(item => ({ const data = result.map(item => ({
...item, ...item,
isLeaf: maxLevel != null && item.level >= maxLevel isLeaf: maxLevel != null && item.level >= maxLevel,
})); }));
// 找到已有值指向的 options 节点 // 找到已有值指向的 options 节点
const root = { [fieldNames.children]: options }; const root = { [fieldNames.children]: options };
@ -113,13 +120,17 @@ export const Cascader = connect({
} else { } else {
setOptions(data); setOptions(data);
} }
} },
}); },
);
// 根据 value 的值,按需预加载相应的数据 // 根据 value 的值,按需预加载相应的数据
useEffect(() => { useEffect(() => {
if (value.length) { if (value.length) {
value.reduce((promise, option, i) => promise.then(() => run(value.slice(0, i))), Promise.resolve()); value.reduce(
(promise, option, i) => promise.then(() => run(value.slice(0, i))),
Promise.resolve(),
);
} else { } else {
run([]); run([]);
} }
@ -143,6 +154,6 @@ export const Cascader = connect({
fieldNames={fieldNames} fieldNames={fieldNames}
/> />
); );
}) });
export default Cascader export default Cascader;

View File

@ -1 +1 @@
import 'antd/lib/cascader/style/index' import 'antd/lib/cascader/style/index';

View File

@ -1,21 +1,19 @@
import { import { connect } from '@formily/react-schema-renderer';
connect import { Checkbox as AntdCheckbox } from 'antd';
} from '@formily/react-schema-renderer'
import { Checkbox as AntdCheckbox } from 'antd'
import { import {
transformDataSourceKey, transformDataSourceKey,
mapStyledProps, mapStyledProps,
mapTextComponent mapTextComponent,
} from '../shared' } from '../shared';
export const Checkbox = connect<'Group'>({ export const Checkbox = connect<'Group'>({
valueName: 'checked', valueName: 'checked',
getProps: mapStyledProps getProps: mapStyledProps,
})(AntdCheckbox) })(AntdCheckbox);
Checkbox.Group = connect({ Checkbox.Group = connect({
getProps: mapStyledProps, getProps: mapStyledProps,
getComponent: mapTextComponent getComponent: mapTextComponent,
})(transformDataSourceKey(AntdCheckbox.Group, 'options')) })(transformDataSourceKey(AntdCheckbox.Group, 'options'));
export default Checkbox export default Checkbox;

View File

@ -1 +1 @@
import 'antd/lib/checkbox/style/index' import 'antd/lib/checkbox/style/index';

View File

@ -1,16 +1,16 @@
import React from 'react' import React from 'react';
import { Button } from 'antd' import { Button } from 'antd';
import { ButtonProps } from 'antd/lib/button' import { ButtonProps } from 'antd/lib/button';
export const CircleButton: React.FC<ButtonProps> = props => { export const CircleButton: React.FC<ButtonProps> = props => {
const hasText = String(props.className || '').indexOf('has-text') > -1 const hasText = String(props.className || '').indexOf('has-text') > -1;
return ( return (
<Button <Button
type={hasText ? 'link' : undefined} type={hasText ? 'link' : undefined}
shape={hasText ? undefined : 'circle'} shape={hasText ? undefined : 'circle'}
{...props} {...props}
/> />
) );
} };
export default CircleButton export default CircleButton;

View File

@ -1 +1 @@
import 'antd/lib/button/style/index' import 'antd/lib/button/style/index';

View File

@ -1,30 +1,29 @@
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import React from 'react'; import React from 'react';
import { Select, Tag } from 'antd'; import { Select, Tag } from 'antd';
import { import {
Select as AntdSelect, Select as AntdSelect,
mapStyledProps, mapStyledProps,
mapTextComponent mapTextComponent,
} from '../shared' } from '../shared';
export const ColorSelect = connect({ export const ColorSelect = connect({
getProps: mapStyledProps, getProps: mapStyledProps,
getComponent: mapTextComponent, getComponent: mapTextComponent,
})((props) => { })(props => {
const colors = { const colors = {
'red': '薄暮', red: '薄暮',
'magenta': '法式洋红', magenta: '法式洋红',
'volcano': '火山', volcano: '火山',
'orange': '日暮', orange: '日暮',
'gold': '金盏花', gold: '金盏花',
'lime': '青柠', lime: '青柠',
'green': '极光绿', green: '极光绿',
'cyan': '明青', cyan: '明青',
'blue': '拂晓蓝', blue: '拂晓蓝',
'geekblue': '极客蓝', geekblue: '极客蓝',
'purple': '酱紫', purple: '酱紫',
'default': '默认' default: '默认',
}; };
return ( return (
@ -35,7 +34,7 @@ export const ColorSelect = connect({
</Select.Option> </Select.Option>
))} ))}
</Select> </Select>
) );
}) });
export default ColorSelect export default ColorSelect;

View File

@ -1,105 +1,105 @@
import React from 'react' import React from 'react';
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import moment from 'moment' import moment from 'moment';
import { DatePicker as AntdDatePicker } from 'antd' import { DatePicker as AntdDatePicker } from 'antd';
import { import {
mapStyledProps, mapStyledProps,
mapTextComponent, mapTextComponent,
compose, compose,
isStr, isStr,
isArr isArr,
} from '../shared' } from '../shared';
class YearPicker extends React.Component { class YearPicker extends React.Component {
public render() { public render() {
return <AntdDatePicker {...this.props} picker={'year'} /> return <AntdDatePicker {...this.props} picker={'year'} />;
} }
} }
const transformMoment = (value, format = 'YYYY-MM-DD HH:mm:ss') => { const transformMoment = (value, format = 'YYYY-MM-DD HH:mm:ss') => {
if (value === '') return undefined if (value === '') return undefined;
return value && value.format ? value.format(format) : value return value && value.format ? value.format(format) : value;
} };
const mapMomentValue = (props: any, fieldProps: any) => { const mapMomentValue = (props: any, fieldProps: any) => {
const { value, showTime = false } = props const { value, showTime = false } = props;
if (!fieldProps.editable) return props if (!fieldProps.editable) return props;
try { try {
if (isStr(value) && value) { if (isStr(value) && value) {
props.value = moment( props.value = moment(
value, value,
showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD' showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD',
) );
} else if (isArr(value) && value.length) { } else if (isArr(value) && value.length) {
props.value = value.map( props.value = value.map(
item => item =>
(item && (item &&
moment(item, showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD')) || moment(item, showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD')) ||
'' '',
) );
} }
} catch (e) { } catch (e) {
throw new Error(e) throw new Error(e);
}
return props
} }
return props;
};
export const DatePicker = connect< export const DatePicker = connect<
'RangePicker' | 'MonthPicker' | 'YearPicker' | 'WeekPicker' 'RangePicker' | 'MonthPicker' | 'YearPicker' | 'WeekPicker'
>({ >({
getValueFromEvent(_, value) { getValueFromEvent(_, value) {
const props = this.props || {} const props = this.props || {};
return transformMoment( return transformMoment(
value, value,
props.format || (props.showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD') props.format || (props.showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD'),
) );
}, },
getProps: compose(mapStyledProps, mapMomentValue), getProps: compose(mapStyledProps, mapMomentValue),
getComponent: mapTextComponent getComponent: mapTextComponent,
})(AntdDatePicker) })(AntdDatePicker);
DatePicker.RangePicker = connect({ DatePicker.RangePicker = connect({
getValueFromEvent(_, [startDate, endDate]) { getValueFromEvent(_, [startDate, endDate]) {
const props = this.props || {} const props = this.props || {};
const format = const format =
props.format || (props.showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD') props.format || (props.showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD');
return [ return [
transformMoment(startDate, format), transformMoment(startDate, format),
transformMoment(endDate, format) transformMoment(endDate, format),
] ];
}, },
getProps: compose(mapStyledProps, mapMomentValue), getProps: compose(mapStyledProps, mapMomentValue),
getComponent: mapTextComponent getComponent: mapTextComponent,
})(AntdDatePicker.RangePicker) })(AntdDatePicker.RangePicker);
DatePicker.MonthPicker = connect({ DatePicker.MonthPicker = connect({
getValueFromEvent(_, value) { getValueFromEvent(_, value) {
return transformMoment(value) return transformMoment(value);
}, },
getProps: compose(mapStyledProps, mapMomentValue), getProps: compose(mapStyledProps, mapMomentValue),
getComponent: mapTextComponent getComponent: mapTextComponent,
})(AntdDatePicker.MonthPicker) })(AntdDatePicker.MonthPicker);
DatePicker.WeekPicker = connect({ DatePicker.WeekPicker = connect({
getValueFromEvent(_, value) { getValueFromEvent(_, value) {
return transformMoment(value, 'gggg-wo') return transformMoment(value, 'gggg-wo');
}, },
getProps: compose(mapStyledProps, props => { getProps: compose(mapStyledProps, props => {
if (isStr(props.value) && props.value) { if (isStr(props.value) && props.value) {
const parsed = props.value.match(/\D*(\d+)\D*(\d+)\D*/) || ['', '', ''] const parsed = props.value.match(/\D*(\d+)\D*(\d+)\D*/) || ['', '', ''];
props.value = moment(parsed[1], 'YYYY').add(parsed[2] - 1, 'weeks') props.value = moment(parsed[1], 'YYYY').add(parsed[2] - 1, 'weeks');
} }
return props return props;
}), }),
getComponent: mapTextComponent getComponent: mapTextComponent,
})(AntdDatePicker.WeekPicker) })(AntdDatePicker.WeekPicker);
DatePicker.YearPicker = connect({ DatePicker.YearPicker = connect({
getValueFromEvent(_, value) { getValueFromEvent(_, value) {
return transformMoment(value, 'YYYY') return transformMoment(value, 'YYYY');
}, },
getProps: compose(mapStyledProps, mapMomentValue), getProps: compose(mapStyledProps, mapMomentValue),
getComponent: mapTextComponent getComponent: mapTextComponent,
})(YearPicker) })(YearPicker);
export default DatePicker export default DatePicker;

View File

@ -1 +1 @@
import 'antd/lib/date-picker/style/index' import 'antd/lib/date-picker/style/index';

View File

@ -1,42 +1,61 @@
import React, { useState } from 'react' import React, { useState } from 'react';
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import moment from 'moment' import moment from 'moment';
import { Select, Table } from 'antd' import { Select, Table } from 'antd';
import get from 'lodash/get'; import get from 'lodash/get';
import { import {
mapStyledProps, mapStyledProps,
mapTextComponent, mapTextComponent,
compose, compose,
isStr, isStr,
isArr isArr,
} from '../shared' } from '../shared';
import { useRequest } from 'umi'; import { useRequest } from 'umi';
import api from '@/api-client'; import api from '@/api-client';
import { Spin } from '@nocobase/client' import { Spin } from '@nocobase/client';
import { fields2columns, components } from '@/components/views/SortableTable' import { fields2columns, components } from '@/components/views/SortableTable';
function DraggableTableComponent(props) { function DraggableTableComponent(props) {
const { mode = 'showInDetail', value, onChange, disabled, rowKey = 'id', fields = [], resourceName, associatedKey, filter, labelField, valueField = 'id', objectValue, placeholder } = props; const {
const { data = [], loading = true, mutate } = useRequest(() => { 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({ return api.resource(resourceName).list({
associatedKey, associatedKey,
filter, filter,
perPage: -1, perPage: -1,
}); });
}, { },
refreshDeps: [resourceName, associatedKey] {
}); refreshDeps: [resourceName, associatedKey],
},
);
const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const onTableChange = (selectedRowKeys: React.ReactText[]) => { const onTableChange = (selectedRowKeys: React.ReactText[]) => {
onChange(selectedRowKeys); onChange(selectedRowKeys);
} };
const tableProps: any = {}; const tableProps: any = {};
tableProps.rowSelection = { tableProps.rowSelection = {
selectedRowKeys: Array.isArray(value) ? value : data.map(item => item[rowKey]), selectedRowKeys: Array.isArray(value)
? value
: data.map(item => item[rowKey]),
onChange: onTableChange, onChange: onTableChange,
} };
const dataSource = data.filter(item => { const dataSource = data.filter(item => {
return get(item, ['component', mode]) return get(item, ['component', mode]);
}); });
// const sortIds = get(value, 'sort')||[]; // const sortIds = get(value, 'sort')||[];
// let values = []; // let values = [];
@ -85,6 +104,6 @@ function DraggableTableComponent(props) {
export const DraggableTable = connect({ export const DraggableTable = connect({
getProps: mapStyledProps, getProps: mapStyledProps,
getComponent: mapTextComponent, getComponent: mapTextComponent,
})(DraggableTableComponent) })(DraggableTableComponent);
export default DraggableTable export default DraggableTable;

View File

@ -1,14 +1,14 @@
import React, { useState } from 'react' import React, { useState } from 'react';
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import { Select, Button, Space } from 'antd' import { Select, Button, Space } from 'antd';
import { import {
mapStyledProps, mapStyledProps,
mapTextComponent, mapTextComponent,
compose, compose,
isStr, isStr,
isArr isArr,
} from '../shared' } from '../shared';
import ViewFactory from '@/components/views' import ViewFactory from '@/components/views';
import Drawer from '@/components/pages/AdminLoader/Drawer'; import Drawer from '@/components/pages/AdminLoader/Drawer';
import View from '@/components/pages/AdminLoader/View'; import View from '@/components/pages/AdminLoader/View';
@ -17,11 +17,13 @@ function transform({value, multiple, labelField, valueField = 'id'}) {
let selectedValue = []; let selectedValue = [];
const values = Array.isArray(value) ? value : [value]; const values = Array.isArray(value) ? value : [value];
selectedKeys = values.filter(item => item).map(item => item[valueField]); selectedKeys = values.filter(item => item).map(item => item[valueField]);
selectedValue = values.filter(item => item).map(item => { selectedValue = values
.filter(item => item)
.map(item => {
return { return {
value: item[valueField], value: item[valueField],
label: item[labelField], label: item[labelField],
} };
}); });
if (!multiple) { if (!multiple) {
return [selectedKeys.shift(), selectedValue.shift()]; return [selectedKeys.shift(), selectedValue.shift()];
@ -30,14 +32,35 @@ function transform({value, multiple, labelField, valueField = 'id'}) {
} }
export function DrawerSelectComponent(props) { export function DrawerSelectComponent(props) {
const { __parent, size, schema = {}, disabled, viewName, target, multiple, filter, resourceName, associatedKey, valueField = 'id', value, onChange } = props; const {
__parent,
size,
schema = {},
disabled,
viewName,
target,
multiple,
filter,
resourceName,
associatedKey,
valueField = 'id',
value,
onChange,
} = props;
const labelField = props.labelField || schema.labelField; const labelField = props.labelField || schema.labelField;
const [selectedKeys, selectedValue] = transform({value, multiple, labelField, valueField }); const [selectedKeys, selectedValue] = transform({
const [selectedRowKeys, setSelectedRowKeys] = useState(multiple ? selectedKeys : [selectedKeys]); value,
multiple,
labelField,
valueField,
});
const [selectedRowKeys, setSelectedRowKeys] = useState(
multiple ? selectedKeys : [selectedKeys],
);
const [selectedRows, setSelectedRows] = useState(selectedValue); const [selectedRows, setSelectedRows] = useState(selectedValue);
const [options, setOptions] = useState(selectedValue); const [options, setOptions] = useState(selectedValue);
const { title = '' } = schema; const { title = '' } = schema;
console.log({schema}) console.log({ schema });
return ( return (
<> <>
<Select <Select
@ -49,7 +72,7 @@ export function DrawerSelectComponent(props) {
allowClear={true} allowClear={true}
value={options} value={options}
notFoundContent={''} notFoundContent={''}
onChange={(data) => { onChange={data => {
setOptions(data); setOptions(data);
if (Array.isArray(data)) { if (Array.isArray(data)) {
const srks = data.map(item => item.value); const srks = data.map(item => item.value);
@ -70,10 +93,17 @@ export function DrawerSelectComponent(props) {
Drawer.open({ Drawer.open({
title: `选择要关联的${title}数据`, title: `选择要关联的${title}数据`,
content: ({ resolve }) => { content: ({ resolve }) => {
console.log('valuevaluevaluevaluevaluevalue', selectedRowKeys, selectedRows, options); console.log(
'valuevaluevaluevaluevaluevalue',
selectedRowKeys,
selectedRows,
options,
);
const [rows, setRows] = useState(selectedRows); const [rows, setRows] = useState(selectedRows);
const [rowKeys, setRowKeys] = useState(selectedRowKeys) const [rowKeys, setRowKeys] = useState(selectedRowKeys);
const [selected, setSelected] = useState(Array.isArray(value) ? value : [value]); const [selected, setSelected] = useState(
Array.isArray(value) ? value : [value],
);
console.log({ selectedRowKeys }); console.log({ selectedRowKeys });
return ( return (
<> <>
@ -83,9 +113,14 @@ export function DrawerSelectComponent(props) {
multiple={multiple} multiple={multiple}
defaultFilter={filter} defaultFilter={filter}
defaultSelectedRowKeys={selectedRowKeys} defaultSelectedRowKeys={selectedRowKeys}
onSelected={(values) => { onSelected={values => {
setSelected(values); setSelected(values);
const [selectedKeys, selectedValue] = transform({value: values, multiple: true, labelField, valueField }); const [selectedKeys, selectedValue] = transform({
value: values,
multiple: true,
labelField,
valueField,
});
setSelectedRows(selectedValue); setSelectedRows(selectedValue);
setRows(selectedValue); setRows(selectedValue);
setSelectedRowKeys(selectedKeys); setSelectedRowKeys(selectedKeys);
@ -98,19 +133,24 @@ export function DrawerSelectComponent(props) {
<Drawer.Footer> <Drawer.Footer>
<Space> <Space>
<Button onClick={resolve}></Button> <Button onClick={resolve}></Button>
<Button onClick={() => { <Button
onClick={() => {
setOptions(rows); setOptions(rows);
// console.log('valuevaluevaluevaluevaluevalue', {selectedRowKeys}); // console.log('valuevaluevaluevaluevaluevalue', {selectedRowKeys});
onChange(multiple ? selected : selected.shift()); onChange(multiple ? selected : selected.shift());
// console.log({rows, rowKeys}); // console.log({rows, rowKeys});
resolve(); resolve();
}} type={'primary'}></Button> }}
type={'primary'}
>
</Button>
</Space> </Space>
</Drawer.Footer> </Drawer.Footer>
</> </>
) );
}, },
}) });
// setVisible(true); // setVisible(true);
} }
}} }}
@ -122,6 +162,6 @@ export function DrawerSelectComponent(props) {
export const DrawerSelect = connect({ export const DrawerSelect = connect({
getProps: mapStyledProps, getProps: mapStyledProps,
getComponent: mapTextComponent, getComponent: mapTextComponent,
})(DrawerSelectComponent) })(DrawerSelectComponent);
export default DrawerSelect export default DrawerSelect;

View File

@ -1,9 +1,19 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { Button, Select, Input, Space, Form, InputNumber, DatePicker, TimePicker, Radio } from 'antd'; import {
Button,
Select,
Input,
Space,
Form,
InputNumber,
DatePicker,
TimePicker,
Radio,
} from 'antd';
import { PlusCircleOutlined, CloseCircleOutlined } from '@ant-design/icons'; import { PlusCircleOutlined, CloseCircleOutlined } from '@ant-design/icons';
import useDynamicList from './useDynamicList'; import useDynamicList from './useDynamicList';
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import { mapStyledProps } from '../shared' import { mapStyledProps } from '../shared';
import get from 'lodash/get'; import get from 'lodash/get';
import moment from 'moment'; import moment from 'moment';
import './style.less'; import './style.less';
@ -11,12 +21,22 @@ import api from '@/api-client';
import { useRequest } from 'umi'; import { useRequest } from 'umi';
export function FilterGroup(props: any) { export function FilterGroup(props: any) {
const { showDeleteButton = true, fields = [], sourceFields = [], onDelete, onChange, onAdd, dataSource = {} } = props; const {
const { list, getKey, push, remove, replace } = useDynamicList<any>(dataSource.list || [ showDeleteButton = true,
fields = [],
sourceFields = [],
onDelete,
onChange,
onAdd,
dataSource = {},
} = props;
const { list, getKey, push, remove, replace } = useDynamicList<any>(
dataSource.list || [
{ {
type: 'item', type: 'item',
}, },
]); ],
);
let style: any = { let style: any = {
position: 'relative', position: 'relative',
}; };
@ -26,20 +46,22 @@ export function FilterGroup(props: any) {
marginBottom: 14, marginBottom: 14,
padding: 14, padding: 14,
border: '1px dashed #dedede', border: '1px dashed #dedede',
} };
} }
return ( return (
<div style={style}> <div style={style}>
<div style={{ marginBottom: 14 }}> <div style={{ marginBottom: 14 }}>
{' '}
{' '} <Select
<Select style={{width: 80}} onChange={(value) => { style={{ width: 80 }}
onChange={value => {
onChange({ type: 'group', list, andor: value }); onChange({ type: 'group', list, andor: value });
}} defaultValue={dataSource.andor||'and'}> }}
defaultValue={dataSource.andor || 'and'}
>
<Select.Option value={'and'}></Select.Option> <Select.Option value={'and'}></Select.Option>
<Select.Option value={'or'}></Select.Option> <Select.Option value={'or'}></Select.Option>
</Select> </Select>{' '}
{' '}
</div> </div>
<div> <div>
@ -48,12 +70,13 @@ export function FilterGroup(props: any) {
const Component = item.type === 'group' ? FilterGroup : FilterItem; const Component = item.type === 'group' ? FilterGroup : FilterItem;
return ( return (
<div style={{ marginBottom: 8 }}> <div style={{ marginBottom: 8 }}>
{<Component {
<Component
fields={fields} fields={fields}
sourceFields={sourceFields} sourceFields={sourceFields}
dataSource={item} dataSource={item}
// showDeleteButton={list.length > 1} // showDeleteButton={list.length > 1}
onChange={(value) => { onChange={value => {
replace(index, value); replace(index, value);
const newList = [...list]; const newList = [...list];
newList[index] = value; newList[index] = value;
@ -67,25 +90,29 @@ export function FilterGroup(props: any) {
onChange({ ...dataSource, list: newList }); onChange({ ...dataSource, list: newList });
// console.log(list, index); // console.log(list, index);
}} }}
/>} />
}
</div> </div>
); );
})} })}
</div> </div>
<div> <div>
<Space> <Space>
<Button style={{padding: 0}} type={'link'} onClick={() => { <Button
style={{ padding: 0 }}
type={'link'}
onClick={() => {
const data = { const data = {
type: 'item' type: 'item',
}; };
push(data); push(data);
const newList = [...list]; const newList = [...list];
newList.push(data); newList.push(data);
onChange({ ...dataSource, list: newList }); onChange({ ...dataSource, list: newList });
}}> }}
>
<PlusCircleOutlined /> <PlusCircleOutlined />
</Button> </Button>{' '}
{' '}
<Button <Button
style={{ padding: 0 }} style={{ padding: 0 }}
type={'link'} type={'link'}
@ -106,11 +133,24 @@ export function FilterGroup(props: any) {
> >
<PlusCircleOutlined /> <PlusCircleOutlined />
</Button> </Button>
{showDeleteButton && <Button className={'filter-remove-link filter-group'} style={{padding: 0, position: 'absolute', top: 0, right: 0, width: 32}} type={'link'} onClick={(e) => { {showDeleteButton && (
<Button
className={'filter-remove-link filter-group'}
style={{
padding: 0,
position: 'absolute',
top: 0,
right: 0,
width: 32,
}}
type={'link'}
onClick={e => {
onDelete && onDelete(e); onDelete && onDelete(e);
}}> }}
>
<CloseCircleOutlined /> <CloseCircleOutlined />
</Button>} </Button>
)}
</Space> </Space>
</div> </div>
</div> </div>
@ -227,22 +267,26 @@ const op = {
attachment: OP_MAP.file, attachment: OP_MAP.file,
}; };
const StringInput = (props) => { const StringInput = props => {
const { value, onChange, ...restProps } = props; const { value, onChange, ...restProps } = props;
return ( return (
<Input {...restProps} defaultValue={value} onChange={(e) => { <Input
{...restProps}
defaultValue={value}
onChange={e => {
onChange(e.target.value); onChange(e.target.value);
}}/> }}
/>
); );
} };
const controls = { const controls = {
string: StringInput, string: StringInput,
textarea: StringInput, textarea: StringInput,
number: InputNumber, number: InputNumber,
percent: (props) => ( percent: props => (
<InputNumber <InputNumber
formatter={value => value ? `${value}%` : ''} formatter={value => (value ? `${value}%` : '')}
parser={value => value.replace('%', '')} parser={value => value.replace('%', '')}
{...props} {...props}
/> />
@ -265,9 +309,13 @@ function DateControl(props: any) {
// } // }
const m = moment(value, format); const m = moment(value, format);
return ( return (
<DatePicker format={format} value={m.isValid() ? m : null} onChange={(value) => { <DatePicker
onChange(value ? value.format('YYYY-MM-DD') : null) format={format}
}}/> value={m.isValid() ? m : null}
onChange={value => {
onChange(value ? value.format('YYYY-MM-DD') : null);
}}
/>
); );
// return ( // return (
// <DatePicker format={format} showTime={field.showTime} value={m.isValid() ? m : null} onChange={(value) => { // <DatePicker format={format} showTime={field.showTime} value={m.isValid() ? m : null} onChange={(value) => {
@ -280,13 +328,15 @@ function TimeControl(props: any) {
const { field, value, onChange, ...restProps } = props; const { field, value, onChange, ...restProps } = props;
let format = field.timeFormat; let format = field.timeFormat;
const m = moment(value, format); const m = moment(value, format);
return <TimePicker return (
<TimePicker
value={m.isValid() ? m : null} value={m.isValid() ? m : null}
format={field.timeFormat} format={field.timeFormat}
onChange={(value) => { onChange={value => {
onChange(value ? value.format('HH:mm:ss') : null) onChange(value ? value.format('HH:mm:ss') : null);
}} }}
/> />
);
} }
function OptionControl(props) { function OptionControl(props) {
@ -296,19 +346,27 @@ function OptionControl(props) {
mode = undefined; mode = undefined;
} }
return ( return (
<Select style={{ minWidth: 120 }} mode={mode} value={value} onChange={(value) => { <Select
style={{ minWidth: 120 }}
mode={mode}
value={value}
onChange={value => {
onChange(value); onChange(value);
}} options={options}> }}
</Select> options={options}
></Select>
); );
} }
function BooleanControl(props) { function BooleanControl(props) {
const { value, onChange, ...restProps } = props; const { value, onChange, ...restProps } = props;
return ( return (
<Radio.Group value={value} onChange={(e) => { <Radio.Group
value={value}
onChange={e => {
onChange(e.target.value); onChange(e.target.value);
}}> }}
>
<Radio value={true}></Radio> <Radio value={true}></Radio>
<Radio value={false}></Radio> <Radio value={false}></Radio>
</Radio.Group> </Radio.Group>
@ -331,8 +389,16 @@ function getComponentTypeByField(field) {
} }
export function FilterItem(props: FilterItemProps) { export function FilterItem(props: FilterItemProps) {
const { index, fields = [], sourceFields = [], showDeleteButton = true, onDelete, onChange } = props; const {
const defaultField: any = fields.find(field => field.name === props.dataSource.column) || {}; index,
fields = [],
sourceFields = [],
showDeleteButton = true,
onDelete,
onChange,
} = props;
const defaultField: any =
fields.find(field => field.name === props.dataSource.column) || {};
const componentType = getComponentTypeByField(defaultField); const componentType = getComponentTypeByField(defaultField);
const [type, setType] = useState(defaultField.interface || 'string'); const [type, setType] = useState(defaultField.interface || 'string');
const [field, setField] = useState<any>({}); const [field, setField] = useState<any>({});
@ -352,11 +418,11 @@ export function FilterItem(props: FilterItemProps) {
if (/^{{.+}}$/.test(props.dataSource.value)) { if (/^{{.+}}$/.test(props.dataSource.value)) {
setValueType('ref'); setValueType('ref');
} }
}, [ }, [props.dataSource, type]);
props.dataSource, type,
]);
let ValueControl = controls[componentType] || controls.string; let ValueControl = controls[componentType] || controls.string;
if (['$null', '$notNull', '$isTruly', '$isFalsy'].indexOf(dataSource.op) !== -1) { if (
['$null', '$notNull', '$isTruly', '$isFalsy'].indexOf(dataSource.op) !== -1
) {
ValueControl = NullControl; ValueControl = NullControl;
} }
if (['boolean', 'checkbox'].indexOf(componentType) !== -1) { if (['boolean', 'checkbox'].indexOf(componentType) !== -1) {
@ -368,8 +434,9 @@ export function FilterItem(props: FilterItemProps) {
console.log({ componentType, defaultField, field, valueType, opOptions }); console.log({ componentType, defaultField, field, valueType, opOptions });
return ( return (
<Space> <Space>
<Select value={dataSource.column} <Select
onChange={(value) => { value={dataSource.column}
onChange={value => {
const field = fields.find(field => field.name === value); const field = fields.find(field => field.name === value);
let componentType = field.component.type; let componentType = field.component.type;
if (field.component.type === 'select' && field.multiple) { if (field.component.type === 'select' && field.multiple) {
@ -377,16 +444,24 @@ export function FilterItem(props: FilterItemProps) {
} }
setType(componentType); setType(componentType);
setValueType('custom'); setValueType('custom');
onChange({...dataSource, column: value, op: get(op, [componentType, 0, 'value']), value: undefined}); onChange({
...dataSource,
column: value,
op: get(op, [componentType, 0, 'value']),
value: undefined,
});
}} }}
style={{ width: 120 }} style={{ width: 120 }}
placeholder={'选择字段'}> placeholder={'选择字段'}
>
{fields.map(field => ( {fields.map(field => (
<Select.Option value={field.name}>{field.title}</Select.Option> <Select.Option value={field.name}>{field.title}</Select.Option>
))} ))}
</Select> </Select>
<Select value={dataSource.column ? dataSource.op : null} style={{ minWidth: 100 }} <Select
onChange={(value) => { value={dataSource.column ? dataSource.op : null}
style={{ minWidth: 100 }}
onChange={value => {
onChange({ ...dataSource, op: value }); onChange({ ...dataSource, op: value });
}} }}
// value={get(opOptions, [0, 'value'])} // value={get(opOptions, [0, 'value'])}
@ -399,12 +474,13 @@ export function FilterItem(props: FilterItemProps) {
{sourceFields.length > 0 && ( {sourceFields.length > 0 && (
<Select <Select
style={{ minWidth: 100 }} style={{ minWidth: 100 }}
onChange={(value) => { onChange={value => {
setDataSource({...dataSource, value: undefined}) setDataSource({ ...dataSource, value: undefined });
onChange({ ...dataSource, value: undefined }); onChange({ ...dataSource, value: undefined });
setValueType(value); setValueType(value);
}} }}
defaultValue={valueType}> defaultValue={valueType}
>
<Select.Option value={'custom'}></Select.Option> <Select.Option value={'custom'}></Select.Option>
<Select.Option value={'ref'}></Select.Option> <Select.Option value={'ref'}></Select.Option>
</Select> </Select>
@ -416,27 +492,38 @@ export function FilterItem(props: FilterItemProps) {
op={dataSource.op} op={dataSource.op}
options={defaultField.dataSource} options={defaultField.dataSource}
value={dataSource.value} value={dataSource.value}
onChange={(value) => { onChange={value => {
onChange({ ...dataSource, value: value }); onChange({ ...dataSource, value: value });
}} }}
style={{ width: 180 }} style={{ width: 180 }}
/> />
) : (sourceFields.length > 0 ? ( ) : sourceFields.length > 0 ? (
<Select value={dataSource.value} <Select
onChange={(value) => { value={dataSource.value}
onChange={value => {
onChange({ ...dataSource, value: value }); onChange({ ...dataSource, value: value });
}} }}
style={{ width: 120 }} style={{ width: 120 }}
placeholder={'选择字段'}> placeholder={'选择字段'}
>
{sourceFields.map(field => ( {sourceFields.map(field => (
<Select.Option value={`{{ ${field.name} }}`}>{field.title}</Select.Option> <Select.Option value={`{{ ${field.name} }}`}>
{field.title}
</Select.Option>
))} ))}
</Select> </Select>
) : null)} ) : null}
{showDeleteButton && ( {showDeleteButton && (
<Button className={'filter-remove-link filter-item'} type={'link'} style={{padding: 0}} onClick={(e) => { <Button
className={'filter-remove-link filter-item'}
type={'link'}
style={{ padding: 0 }}
onClick={e => {
onDelete && onDelete(e); onDelete && onDelete(e);
}}><CloseCircleOutlined /></Button> }}
>
<CloseCircleOutlined />
</Button>
)} )}
</Space> </Space>
); );
@ -447,10 +534,19 @@ function toFilter(values: any) {
let { type, andor = 'and', list = [], column, op, value } = values; let { type, andor = 'and', list = [], column, op, value } = values;
if (type === 'group') { if (type === 'group') {
filter = { filter = {
[andor]: list.map(value => toFilter(value)).filter(Boolean) [andor]: list.map(value => toFilter(value)).filter(Boolean),
} };
} else if (type === 'item' && column && op) { } else if (type === 'item' && column && op) {
if (['id.$null', 'id.$notNull', '$null', '$notNull', '$isTruly', '$isFalsy'].indexOf(op) !== -1) { if (
[
'id.$null',
'id.$notNull',
'$null',
'$notNull',
'$isTruly',
'$isFalsy',
].indexOf(op) !== -1
) {
value = true; value = true;
} }
// if (op === 'id.gt') { // if (op === 'id.gt') {
@ -458,7 +554,7 @@ function toFilter(values: any) {
// } // }
filter = { filter = {
[`${column}`]: { [op]: value }, [`${column}`]: { [op]: value },
} };
} }
return filter; return filter;
} }
@ -483,45 +579,72 @@ function toValues(filter: any = {}) {
export const Filter = connect({ export const Filter = connect({
getProps: mapStyledProps, getProps: mapStyledProps,
})((props) => { })(props => {
const dataSource = { const dataSource = {
type: 'group', type: 'group',
list: [ list: [
{ {
type: 'item', type: 'item',
} },
], ],
}; };
const { value, onChange, associatedKey, filter = {}, sourceName, sourceFilter = {}, fields = [], ...restProps } = props; const {
console.log('filter', {associatedKey}) value,
const { data = [], loading = true } = useRequest(() => { onChange,
return associatedKey ? api.resource(`collections.fields`).list({ associatedKey,
filter = {},
sourceName,
sourceFilter = {},
fields = [],
...restProps
} = props;
console.log('filter', { associatedKey });
const { data = [], loading = true } = useRequest(
() => {
return associatedKey
? api.resource(`collections.fields`).list({
associatedKey, associatedKey,
filter, filter,
}) : Promise.resolve({ })
: Promise.resolve({
data: fields, data: fields,
}); });
}, { },
refreshDeps: [associatedKey] {
}); refreshDeps: [associatedKey],
},
);
const { data: sourceFields = [] } = useRequest(
const { data: sourceFields = [] } = useRequest(() => { () => {
return sourceName ? api.resource(`collections.fields`).list({ return sourceName
? api.resource(`collections.fields`).list({
associatedKey: sourceName, associatedKey: sourceName,
filter: sourceFilter, filter: sourceFilter,
}) : Promise.resolve({ })
: Promise.resolve({
data: [], data: [],
}); });
}, { },
refreshDeps: [sourceName] {
}); refreshDeps: [sourceName],
},
);
console.log({ sourceName, sourceFields }); console.log({ sourceName, sourceFields });
return <FilterGroup showDeleteButton={false} dataSource={value ? toValues(value) : dataSource} onChange={(values) => { return (
<FilterGroup
showDeleteButton={false}
dataSource={value ? toValues(value) : dataSource}
onChange={values => {
console.log(values); console.log(values);
onChange(toFilter(values)); onChange(toFilter(values));
}} {...restProps} sourceFields={sourceFields} fields={data.filter(item => item.filterable)}/> }}
{...restProps}
sourceFields={sourceFields}
fields={data.filter(item => item.filterable)}
/>
);
}); });
export default Filter; export default Filter;

View File

@ -30,7 +30,7 @@ export default <T>(initialValue: T[]) => {
}; };
const insert = (index: number, obj: T) => { const insert = (index: number, obj: T) => {
setList((l) => { setList(l => {
const temp = [...l]; const temp = [...l];
temp.splice(index, 0, obj); temp.splice(index, 0, obj);
setKey(index); setKey(index);
@ -40,10 +40,11 @@ export default <T>(initialValue: T[]) => {
const getAll = () => list; const getAll = () => list;
const getKey = (index: number) => keyList.current[index]; const getKey = (index: number) => keyList.current[index];
const getIndex = (index: number) => keyList.current.findIndex((ele) => ele === index); const getIndex = (index: number) =>
keyList.current.findIndex(ele => ele === index);
const merge = (index: number, obj: T[]) => { const merge = (index: number, obj: T[]) => {
setList((l) => { setList(l => {
const temp = [...l]; const temp = [...l];
obj.forEach((_, i) => { obj.forEach((_, i) => {
setKey(index + i); setKey(index + i);
@ -54,7 +55,7 @@ export default <T>(initialValue: T[]) => {
}; };
const replace = (index: number, obj: T) => { const replace = (index: number, obj: T) => {
setList((l) => { setList(l => {
const temp = [...l]; const temp = [...l];
temp[index] = obj; temp[index] = obj;
return temp; return temp;
@ -62,7 +63,7 @@ export default <T>(initialValue: T[]) => {
}; };
const remove = (index: number) => { const remove = (index: number) => {
setList((l) => { setList(l => {
const temp = [...l]; const temp = [...l];
temp.splice(index, 1); temp.splice(index, 1);
@ -80,14 +81,16 @@ export default <T>(initialValue: T[]) => {
if (oldIndex === newIndex) { if (oldIndex === newIndex) {
return; return;
} }
setList((l) => { setList(l => {
const newList = [...l]; const newList = [...l];
const temp = newList.filter((_: {}, index: number) => index !== oldIndex); const temp = newList.filter((_: {}, index: number) => index !== oldIndex);
temp.splice(newIndex, 0, newList[oldIndex]); temp.splice(newIndex, 0, newList[oldIndex]);
// move keys if necessary // move keys if necessary
try { try {
const keyTemp = keyList.current.filter((_: {}, index: number) => index !== oldIndex); const keyTemp = keyList.current.filter(
(_: {}, index: number) => index !== oldIndex,
);
keyTemp.splice(newIndex, 0, keyList.current[oldIndex]); keyTemp.splice(newIndex, 0, keyList.current[oldIndex]);
keyList.current = keyTemp; keyList.current = keyTemp;
} catch (e) { } catch (e) {
@ -99,7 +102,7 @@ export default <T>(initialValue: T[]) => {
}; };
const push = (obj: T) => { const push = (obj: T) => {
setList((l) => { setList(l => {
setKey(l.length); setKey(l.length);
return l.concat([obj]); return l.concat([obj]);
}); });
@ -113,11 +116,11 @@ export default <T>(initialValue: T[]) => {
console.error(e); console.error(e);
} }
setList((l) => l.slice(0, l.length - 1)); setList(l => l.slice(0, l.length - 1));
}; };
const unshift = (obj: T) => { const unshift = (obj: T) => {
setList((l) => { setList(l => {
setKey(0); setKey(0);
return [obj].concat(l); return [obj].concat(l);
}); });
@ -127,8 +130,8 @@ export default <T>(initialValue: T[]) => {
result result
.map((item, index) => ({ key: index, item })) // add index into obj .map((item, index) => ({ key: index, item })) // add index into obj
.sort((a, b) => getIndex(a.key) - getIndex(b.key)) // sort based on the index of table .sort((a, b) => getIndex(a.key) - getIndex(b.key)) // sort based on the index of table
.filter((item) => !!item.item) // remove undefined(s) .filter(item => !!item.item) // remove undefined(s)
.map((item) => item.item); // retrive the data .map(item => item.item); // retrive the data
const shift = () => { const shift = () => {
// remove keys if necessary // remove keys if necessary
@ -137,7 +140,7 @@ export default <T>(initialValue: T[]) => {
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
setList((l) => l.slice(1, l.length)); setList(l => l.slice(1, l.length));
}; };
return { return {

View File

@ -1,8 +1,8 @@
import React from 'react' import React from 'react';
import { createVirtualBox } from '@formily/react-schema-renderer' import { createVirtualBox } from '@formily/react-schema-renderer';
import { Card } from 'antd' import { Card } from 'antd';
import { CardProps } from 'antd/lib/card' import { CardProps } from 'antd/lib/card';
import styled from 'styled-components' import styled from 'styled-components';
export const FormBlock = createVirtualBox<CardProps>( export const FormBlock = createVirtualBox<CardProps>(
'block', 'block',
@ -11,14 +11,14 @@ export const FormBlock = createVirtualBox<CardProps>(
<Card className={className} size="small" {...props}> <Card className={className} size="small" {...props}>
{children} {children}
</Card> </Card>
) );
})` })`
margin-bottom: 10px !important; margin-bottom: 10px !important;
&.ant-card { &.ant-card {
border: none; border: none;
box-shadow: none; box-shadow: none;
} }
` `,
) );
export default FormBlock export default FormBlock;

View File

@ -1 +1 @@
import 'antd/lib/card/style/index' import 'antd/lib/card/style/index';

View File

@ -1,8 +1,8 @@
import React from 'react' import React from 'react';
import { createVirtualBox } from '@formily/react-schema-renderer' import { createVirtualBox } from '@formily/react-schema-renderer';
import { Card } from 'antd' import { Card } from 'antd';
import { CardProps } from 'antd/lib/card' import { CardProps } from 'antd/lib/card';
import styled from 'styled-components' import styled from 'styled-components';
export const FormCard = createVirtualBox<CardProps>( export const FormCard = createVirtualBox<CardProps>(
'card', 'card',
@ -11,10 +11,10 @@ export const FormCard = createVirtualBox<CardProps>(
<Card className={className} size="small" {...props}> <Card className={className} size="small" {...props}>
{children} {children}
</Card> </Card>
) );
})` })`
margin-bottom: 10px !important; margin-bottom: 10px !important;
` `,
) );
export default FormCard export default FormCard;

View File

@ -1 +1 @@
import 'antd/lib/card/style/index' import 'antd/lib/card/style/index';

View File

@ -1,21 +1,30 @@
import React from 'react' import React from 'react';
import { createVirtualBox } from '@formily/react-schema-renderer' import { createVirtualBox } from '@formily/react-schema-renderer';
import { Card } from 'antd' import { Card } from 'antd';
import styled from 'styled-components' import styled from 'styled-components';
import { markdown } from '@/components/views/Field' import { markdown } from '@/components/views/Field';
export const FormDescription = createVirtualBox( export const FormDescription = createVirtualBox(
'description', 'description',
styled(({ schema = {}, children, className, ...props }) => { styled(({ schema = {}, children, className, ...props }) => {
const { title, tooltip } = schema as any; const { title, tooltip } = schema as any;
console.log({schema}) console.log({ schema });
return ( return (
<Card title={title} size={'small'} headStyle={{padding: 0}} bodyStyle={{ <Card
title={title}
size={'small'}
headStyle={{ padding: 0 }}
bodyStyle={{
padding: 0, padding: 0,
}} className={className} {...props}> }}
{typeof tooltip === 'string' && tooltip && <div dangerouslySetInnerHTML={{__html: markdown(tooltip)}}></div>} className={className}
{...props}
>
{typeof tooltip === 'string' && tooltip && (
<div dangerouslySetInnerHTML={{ __html: markdown(tooltip) }}></div>
)}
</Card> </Card>
) );
})` })`
margin-bottom: 24px !important; margin-bottom: 24px !important;
&.ant-card { &.ant-card {
@ -35,7 +44,7 @@ export const FormDescription = createVirtualBox(
margin-bottom: 0; margin-bottom: 0;
} }
} }
` `,
) );
export default FormDescription export default FormDescription;

View File

@ -1,10 +1,10 @@
import React from 'react' import React from 'react';
import { createVirtualBox } from '@formily/react-schema-renderer' import { createVirtualBox } from '@formily/react-schema-renderer';
import { Col } from 'antd' import { Col } from 'antd';
import { ColProps } from 'antd/lib/grid' import { ColProps } from 'antd/lib/grid';
export const FormGridCol = createVirtualBox<ColProps>('grid-col', props => { export const FormGridCol = createVirtualBox<ColProps>('grid-col', props => {
return <Col {...props}>{props.children}</Col> return <Col {...props}>{props.children}</Col>;
}) });
export default FormGridCol export default FormGridCol;

View File

@ -1 +1 @@
import 'antd/lib/col/style/index' import 'antd/lib/col/style/index';

View File

@ -1,25 +1,25 @@
import React from 'react' import React from 'react';
import { AntdSchemaFieldAdaptor, pickFormItemProps } from '@formily/antd' import { AntdSchemaFieldAdaptor, pickFormItemProps } from '@formily/antd';
import { createVirtualBox } from '@formily/react-schema-renderer' import { createVirtualBox } from '@formily/react-schema-renderer';
import { Row } from 'antd' import { Row } from 'antd';
import { RowProps } from 'antd/lib/grid' import { RowProps } from 'antd/lib/grid';
import { FormItemProps as ItemProps } from 'antd/lib/form' import { FormItemProps as ItemProps } from 'antd/lib/form';
import { IItemProps } from '../types' import { IItemProps } from '../types';
export const FormGridRow = createVirtualBox<RowProps & ItemProps & IItemProps>( export const FormGridRow = createVirtualBox<RowProps & ItemProps & IItemProps>(
'grid-row', 'grid-row',
props => { props => {
const { title, label } = props const { title, label } = props;
const grids = <Row {...props}>{props.children}</Row> const grids = <Row {...props}>{props.children}</Row>;
if (title || label) { if (title || label) {
return ( return (
<AntdSchemaFieldAdaptor {...pickFormItemProps(props)}> <AntdSchemaFieldAdaptor {...pickFormItemProps(props)}>
{grids} {grids}
</AntdSchemaFieldAdaptor> </AntdSchemaFieldAdaptor>
) );
} }
return grids return grids;
} },
) );
export default FormGridRow export default FormGridRow;

View File

@ -1 +1 @@
import 'antd/lib/row/style/index' import 'antd/lib/row/style/index';

View File

@ -1,15 +1,15 @@
import React, { Fragment } from 'react' import React, { Fragment } from 'react';
import { import {
AntdSchemaFieldAdaptor, AntdSchemaFieldAdaptor,
pickFormItemProps, pickFormItemProps,
pickNotFormItemProps pickNotFormItemProps,
} from '@formily/antd' } from '@formily/antd';
import { createVirtualBox } from '@formily/react-schema-renderer' import { createVirtualBox } from '@formily/react-schema-renderer';
import { toArr } from '@formily/shared' import { toArr } from '@formily/shared';
import { Row, Col } from 'antd' import { Row, Col } from 'antd';
import { FormItemProps as ItemProps } from 'antd/lib/form' import { FormItemProps as ItemProps } from 'antd/lib/form';
import { IFormItemGridProps, IItemProps } from '../types' import { IFormItemGridProps, IItemProps } from '../types';
import { normalizeCol } from '../shared' import { normalizeCol } from '../shared';
export const FormItemGrid = createVirtualBox< export const FormItemGrid = createVirtualBox<
React.PropsWithChildren<IFormItemGridProps & ItemProps & IItemProps> React.PropsWithChildren<IFormItemGridProps & ItemProps & IItemProps>
@ -18,16 +18,16 @@ export const FormItemGrid = createVirtualBox<
cols: rawCols, cols: rawCols,
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
title, title,
label label,
} = props } = props;
const formItemProps = pickFormItemProps(props) const formItemProps = pickFormItemProps(props);
const gridProps = pickNotFormItemProps(props) const gridProps = pickNotFormItemProps(props);
const children = toArr(props.children) const children = toArr(props.children);
const cols = toArr(rawCols).map(col => normalizeCol(col)) const cols = toArr(rawCols).map(col => normalizeCol(col));
const childNum = children.length const childNum = children.length;
if (cols.length < childNum) { if (cols.length < childNum) {
let offset: number = childNum - cols.length let offset: number = childNum - cols.length;
let lastSpan: number = let lastSpan: number =
24 - 24 -
cols.reduce((buf, col) => { cols.reduce((buf, col) => {
@ -35,10 +35,10 @@ export const FormItemGrid = createVirtualBox<
buf + buf +
Number(col.span ? col.span : 0) + Number(col.span ? col.span : 0) +
Number(col.offset ? col.offset : 0) Number(col.offset ? col.offset : 0)
) );
}, 0) }, 0);
for (let i = 0; i < offset; i++) { for (let i = 0; i < offset; i++) {
cols.push({ span: Math.floor(lastSpan / offset) }) cols.push({ span: Math.floor(lastSpan / offset) });
} }
} }
const grids = ( const grids = (
@ -48,21 +48,21 @@ export const FormItemGrid = createVirtualBox<
? buf.concat( ? buf.concat(
<Col key={key} {...cols[key]}> <Col key={key} {...cols[key]}>
{child} {child}
</Col> </Col>,
) )
: buf : buf;
}, [])} }, [])}
</Row> </Row>
) );
if (title || label) { if (title || label) {
return ( return (
<AntdSchemaFieldAdaptor {...formItemProps}> <AntdSchemaFieldAdaptor {...formItemProps}>
{grids} {grids}
</AntdSchemaFieldAdaptor> </AntdSchemaFieldAdaptor>
) );
} }
return <Fragment>{grids}</Fragment> return <Fragment>{grids}</Fragment>;
}) });
export default FormItemGrid export default FormItemGrid;

View File

@ -1,2 +1,2 @@
import 'antd/lib/row/style/index' import 'antd/lib/row/style/index';
import 'antd/lib/col/style/index' import 'antd/lib/col/style/index';

View File

@ -1,19 +1,19 @@
import React from 'react' import React from 'react';
import { FormItemDeepProvider, useDeepFormItem } from '@formily/antd' import { FormItemDeepProvider, useDeepFormItem } from '@formily/antd';
import { createVirtualBox } from '@formily/react-schema-renderer' import { createVirtualBox } from '@formily/react-schema-renderer';
import cls from 'classnames' import cls from 'classnames';
import { IFormItemTopProps } from '../types' import { IFormItemTopProps } from '../types';
export const FormLayout = createVirtualBox<IFormItemTopProps>( export const FormLayout = createVirtualBox<IFormItemTopProps>(
'layout', 'layout',
props => { props => {
const { inline } = useDeepFormItem() const { inline } = useDeepFormItem();
const isInline = props.inline || inline const isInline = props.inline || inline;
const children = const children =
isInline || props.className || props.style ? ( isInline || props.className || props.style ? (
<div <div
className={cls(props.className, { className={cls(props.className, {
'ant-form ant-form-inline': isInline 'ant-form ant-form-inline': isInline,
})} })}
style={props.style} style={props.style}
> >
@ -21,9 +21,9 @@ export const FormLayout = createVirtualBox<IFormItemTopProps>(
</div> </div>
) : ( ) : (
props.children props.children
) );
return <FormItemDeepProvider {...props}>{children}</FormItemDeepProvider> return <FormItemDeepProvider {...props}>{children}</FormItemDeepProvider>;
} },
) );
export default FormLayout export default FormLayout;

View File

@ -1,6 +1,3 @@
import { MegaLayout, FormMegaLayout } from '@formily/antd' import { MegaLayout, FormMegaLayout } from '@formily/antd';
export { export { MegaLayout, FormMegaLayout };
MegaLayout,
FormMegaLayout,
}

View File

@ -1,5 +1,5 @@
import { FormSlot } from '@formily/react-schema-renderer' import { FormSlot } from '@formily/react-schema-renderer';
export { FormSlot } export { FormSlot };
export default FormSlot export default FormSlot;

View File

@ -1,35 +1,35 @@
import React, { useRef, Fragment, useEffect } from 'react' import React, { useRef, Fragment, useEffect } from 'react';
import { import {
createControllerBox, createControllerBox,
ISchemaVirtualFieldComponentProps, ISchemaVirtualFieldComponentProps,
createEffectHook, createEffectHook,
useFormEffects, useFormEffects,
useFieldState, useFieldState,
IVirtualBoxProps IVirtualBoxProps,
} from '@formily/react-schema-renderer' } from '@formily/react-schema-renderer';
import { toArr } from '@formily/shared' import { toArr } from '@formily/shared';
import { Steps } from 'antd' import { Steps } from 'antd';
import { createMatchUpdate } from '../shared' import { createMatchUpdate } from '../shared';
import { IFormStep } from '../types' import { IFormStep } from '../types';
enum StateMap { enum StateMap {
ON_FORM_STEP_NEXT = 'onFormStepNext', ON_FORM_STEP_NEXT = 'onFormStepNext',
ON_FORM_STEP_PREVIOUS = 'onFormStepPrevious', ON_FORM_STEP_PREVIOUS = 'onFormStepPrevious',
ON_FORM_STEP_GO_TO = 'onFormStepGoto', ON_FORM_STEP_GO_TO = 'onFormStepGoto',
ON_FORM_STEP_CURRENT_CHANGE = 'onFormStepCurrentChange', ON_FORM_STEP_CURRENT_CHANGE = 'onFormStepCurrentChange',
ON_FORM_STEP_DATA_SOURCE_CHANGED = 'onFormStepDataSourceChanged' ON_FORM_STEP_DATA_SOURCE_CHANGED = 'onFormStepDataSourceChanged',
} }
const EffectHooks = { const EffectHooks = {
onStepNext$: createEffectHook<void>(StateMap.ON_FORM_STEP_NEXT), onStepNext$: createEffectHook<void>(StateMap.ON_FORM_STEP_NEXT),
onStepPrevious$: createEffectHook<void>(StateMap.ON_FORM_STEP_PREVIOUS), onStepPrevious$: createEffectHook<void>(StateMap.ON_FORM_STEP_PREVIOUS),
onStepGoto$: createEffectHook<void>(StateMap.ON_FORM_STEP_GO_TO), onStepGoto$: createEffectHook<void>(StateMap.ON_FORM_STEP_GO_TO),
onStepCurrentChange$: createEffectHook<{ onStepCurrentChange$: createEffectHook<{
value: number value: number;
preValue: number preValue: number;
}>(StateMap.ON_FORM_STEP_CURRENT_CHANGE) }>(StateMap.ON_FORM_STEP_CURRENT_CHANGE),
} };
type ExtendsProps = StateMap & typeof EffectHooks type ExtendsProps = StateMap & typeof EffectHooks;
export const FormStep: React.FC<IVirtualBoxProps<IFormStep>> & export const FormStep: React.FC<IVirtualBoxProps<IFormStep>> &
ExtendsProps = createControllerBox<IFormStep>( ExtendsProps = createControllerBox<IFormStep>(
@ -39,54 +39,54 @@ export const FormStep: React.FC<IVirtualBoxProps<IFormStep>> &
schema, schema,
path, path,
name, name,
children children,
}: ISchemaVirtualFieldComponentProps) => { }: ISchemaVirtualFieldComponentProps) => {
const { dataSource, ...stepProps } = schema.getExtendsComponentProps() const { dataSource, ...stepProps } = schema.getExtendsComponentProps();
const [{ current }, setFieldState] = useFieldState({ const [{ current }, setFieldState] = useFieldState({
current: stepProps.current || 0 current: stepProps.current || 0,
}) });
const ref = useRef(current) const ref = useRef(current);
const itemsRef = useRef([]) const itemsRef = useRef([]);
itemsRef.current = toArr(dataSource) itemsRef.current = toArr(dataSource);
const matchUpdate = createMatchUpdate(name, path) const matchUpdate = createMatchUpdate(name, path);
const update = (cur: number) => { const update = (cur: number) => {
form.notify(StateMap.ON_FORM_STEP_CURRENT_CHANGE, { form.notify(StateMap.ON_FORM_STEP_CURRENT_CHANGE, {
path, path,
name, name,
value: cur, value: cur,
preValue: current preValue: current,
}) });
setFieldState({ setFieldState({
current: cur current: cur,
}) });
} };
useEffect(() => { useEffect(() => {
form.notify(StateMap.ON_FORM_STEP_DATA_SOURCE_CHANGED, { form.notify(StateMap.ON_FORM_STEP_DATA_SOURCE_CHANGED, {
path, path,
name, name,
value: itemsRef.current value: itemsRef.current,
}) });
}, [itemsRef.current.length]) }, [itemsRef.current.length]);
useFormEffects(($, { setFieldState }) => { useFormEffects(($, { setFieldState }) => {
const updateFields = () => { const updateFields = () => {
itemsRef.current.forEach(({ name }, index) => { itemsRef.current.forEach(({ name }, index) => {
setFieldState(name, (state: any) => { setFieldState(name, (state: any) => {
state.display = index === current state.display = index === current;
}) });
}) });
} };
updateFields() updateFields();
$(StateMap.ON_FORM_STEP_DATA_SOURCE_CHANGED).subscribe( $(StateMap.ON_FORM_STEP_DATA_SOURCE_CHANGED).subscribe(
({ name, path }) => { ({ name, path }) => {
matchUpdate(name, path, () => { matchUpdate(name, path, () => {
updateFields() updateFields();
}) });
} },
) );
$(StateMap.ON_FORM_STEP_CURRENT_CHANGE).subscribe( $(StateMap.ON_FORM_STEP_CURRENT_CHANGE).subscribe(
({ value, name, path }: any = {}) => { ({ value, name, path }: any = {}) => {
@ -95,16 +95,16 @@ export const FormStep: React.FC<IVirtualBoxProps<IFormStep>> &
itemsRef.current.forEach(({ name }, index) => { itemsRef.current.forEach(({ name }, index) => {
if (!name) if (!name)
throw new Error( throw new Error(
'FormStep dataSource must include `name` property' 'FormStep dataSource must include `name` property',
) );
setFieldState(name, (state: any) => { setFieldState(name, (state: any) => {
state.display = index === value state.display = index === value;
}) });
}) });
}) });
}) });
} },
) );
$(StateMap.ON_FORM_STEP_NEXT).subscribe(({ name, path }: any = {}) => { $(StateMap.ON_FORM_STEP_NEXT).subscribe(({ name, path }: any = {}) => {
matchUpdate(name, path, () => { matchUpdate(name, path, () => {
@ -113,45 +113,45 @@ export const FormStep: React.FC<IVirtualBoxProps<IFormStep>> &
update( update(
ref.current + 1 > itemsRef.current.length - 1 ref.current + 1 > itemsRef.current.length - 1
? ref.current ? ref.current
: ref.current + 1 : ref.current + 1,
) );
} }
}) });
}) });
}) });
$(StateMap.ON_FORM_STEP_PREVIOUS).subscribe( $(StateMap.ON_FORM_STEP_PREVIOUS).subscribe(
({ name, path }: any = {}) => { ({ name, path }: any = {}) => {
matchUpdate(name, path, () => { matchUpdate(name, path, () => {
update(ref.current - 1 < 0 ? ref.current : ref.current - 1) update(ref.current - 1 < 0 ? ref.current : ref.current - 1);
}) });
} },
) );
$(StateMap.ON_FORM_STEP_GO_TO).subscribe( $(StateMap.ON_FORM_STEP_GO_TO).subscribe(
({ name, path, value }: any = {}) => { ({ name, path, value }: any = {}) => {
matchUpdate(name, path, () => { matchUpdate(name, path, () => {
if (!(value < 0 || value > itemsRef.current.length)) { if (!(value < 0 || value > itemsRef.current.length)) {
update(value) update(value);
} }
}) });
} },
) );
}) });
ref.current = current ref.current = current;
return ( return (
<Fragment> <Fragment>
<Steps {...stepProps} current={current}> <Steps {...stepProps} current={current}>
{itemsRef.current.map((props, key) => { {itemsRef.current.map((props, key) => {
return <Steps.Step {...props} key={key} /> return <Steps.Step {...props} key={key} />;
})} })}
</Steps>{' '} </Steps>{' '}
{children} {children}
</Fragment> </Fragment>
) );
} },
) as any ) as any;
Object.assign(FormStep, StateMap, EffectHooks) Object.assign(FormStep, StateMap, EffectHooks);
export default FormStep export default FormStep;

View File

@ -1 +1 @@
import 'antd/lib/steps/style/index' import 'antd/lib/steps/style/index';

View File

@ -1,4 +1,4 @@
import React, { Fragment, useEffect, useRef } from 'react' import React, { Fragment, useEffect, useRef } from 'react';
import { import {
createControllerBox, createControllerBox,
ISchemaVirtualFieldComponentProps, ISchemaVirtualFieldComponentProps,
@ -8,140 +8,156 @@ import {
FormEffectHooks, FormEffectHooks,
SchemaField, SchemaField,
FormPath, FormPath,
IVirtualBoxProps IVirtualBoxProps,
} from '@formily/react-schema-renderer' } from '@formily/react-schema-renderer';
import { Tabs, Badge } from 'antd' import { Tabs, Badge } from 'antd';
import { TabPaneProps } from 'antd/lib/tabs' import { TabPaneProps } from 'antd/lib/tabs';
import { IFormTab } from '../types' import { IFormTab } from '../types';
import { createMatchUpdate } from '../shared' import { createMatchUpdate } from '../shared';
enum StateMap { enum StateMap {
ON_FORM_TAB_ACTIVE_KEY_CHANGE = 'onFormTabActiveKeyChange' ON_FORM_TAB_ACTIVE_KEY_CHANGE = 'onFormTabActiveKeyChange',
} }
const { onFormChange$ } = FormEffectHooks const { onFormChange$ } = FormEffectHooks;
const EffectHooks = { const EffectHooks = {
onTabActiveKeyChange$: createEffectHook<{ onTabActiveKeyChange$: createEffectHook<{
name?: string name?: string;
path?: string path?: string;
value?: any value?: any;
}>(StateMap.ON_FORM_TAB_ACTIVE_KEY_CHANGE) }>(StateMap.ON_FORM_TAB_ACTIVE_KEY_CHANGE),
} };
const parseTabItems = (items: any, hiddenKeys?: string[]) => { const parseTabItems = (items: any, hiddenKeys?: string[]) => {
return items.reduce((buf: any, { schema, key }) => { return items.reduce((buf: any, { schema, key }) => {
if (Array.isArray(hiddenKeys)) { if (Array.isArray(hiddenKeys)) {
if (hiddenKeys.includes(key)) { if (hiddenKeys.includes(key)) {
return buf return buf;
} }
} }
if (schema.getExtendsComponent() === 'tabpane') { if (schema.getExtendsComponent() === 'tabpane') {
return buf.concat({ return buf.concat({
props: schema.getExtendsComponentProps(), props: schema.getExtendsComponentProps(),
schema, schema,
key key,
}) });
}
return buf
}, [])
} }
return buf;
}, []);
};
const parseDefaultActiveKey = (hiddenKeys: Array<string> = [], items: any, defaultActiveKey) => { const parseDefaultActiveKey = (
if(!hiddenKeys.includes(defaultActiveKey))return defaultActiveKey hiddenKeys: Array<string> = [],
items: any,
defaultActiveKey,
) => {
if (!hiddenKeys.includes(defaultActiveKey)) return defaultActiveKey;
const index = items.findIndex(item => !hiddenKeys.includes(item.key)) const index = items.findIndex(item => !hiddenKeys.includes(item.key));
return index >= 0 ? items[index].key : '' return index >= 0 ? items[index].key : '';
} };
const parseChildrenErrors = (errors: any, target: string) => { const parseChildrenErrors = (errors: any, target: string) => {
return errors.filter(({ path }) => { return errors.filter(({ path }) => {
return FormPath.parse(path).includes(target) return FormPath.parse(path).includes(target);
}) });
} };
const addErrorBadge = ( const addErrorBadge = (
tab: React.ReactNode, tab: React.ReactNode,
currentPath: FormPath, currentPath: FormPath,
childrenErrors: any[] childrenErrors: any[],
) => { ) => {
const currentErrors = childrenErrors.filter(({ path }) => { const currentErrors = childrenErrors.filter(({ path }) => {
return FormPath.parse(path).includes(currentPath) return FormPath.parse(path).includes(currentPath);
}) });
if (currentErrors.length > 0) { if (currentErrors.length > 0) {
return ( return (
<Badge offset={[12, 0]} count={currentErrors.length}> <Badge offset={[12, 0]} count={currentErrors.length}>
{tab} {tab}
</Badge> </Badge>
) );
}
return tab
} }
return tab;
};
type ExtendsProps = StateMap & type ExtendsProps = StateMap &
typeof EffectHooks & { typeof EffectHooks & {
TabPane: React.FC<IVirtualBoxProps<TabPaneProps>> TabPane: React.FC<IVirtualBoxProps<TabPaneProps>>;
} };
type ExtendsState = { type ExtendsState = {
activeKey?: string activeKey?: string;
childrenErrors?: any childrenErrors?: any;
} };
export const FormTab: React.FC<IVirtualBoxProps<IFormTab>> & export const FormTab: React.FC<IVirtualBoxProps<IFormTab>> &
ExtendsProps = createControllerBox<IFormTab>( ExtendsProps = createControllerBox<IFormTab>(
'tab', 'tab',
({ form, schema, name, path }: ISchemaVirtualFieldComponentProps) => { ({ form, schema, name, path }: ISchemaVirtualFieldComponentProps) => {
const orderProperties = schema.getOrderProperties() const orderProperties = schema.getOrderProperties();
let { hiddenKeys, defaultActiveKey, ...componentProps } = schema.getExtendsComponentProps() let {
hiddenKeys = hiddenKeys || [] hiddenKeys,
defaultActiveKey,
...componentProps
} = schema.getExtendsComponentProps();
hiddenKeys = hiddenKeys || [];
const [{ activeKey, childrenErrors }, setFieldState] = useFieldState< const [{ activeKey, childrenErrors }, setFieldState] = useFieldState<
ExtendsState ExtendsState
>({ >({
activeKey: parseDefaultActiveKey(hiddenKeys, orderProperties, defaultActiveKey), activeKey: parseDefaultActiveKey(
childrenErrors: [] hiddenKeys,
}) orderProperties,
const itemsRef = useRef([]) defaultActiveKey,
itemsRef.current = parseTabItems(orderProperties, hiddenKeys) ),
childrenErrors: [],
});
const itemsRef = useRef([]);
itemsRef.current = parseTabItems(orderProperties, hiddenKeys);
const update = (cur: string) => { const update = (cur: string) => {
form.notify(StateMap.ON_FORM_TAB_ACTIVE_KEY_CHANGE, { form.notify(StateMap.ON_FORM_TAB_ACTIVE_KEY_CHANGE, {
name, name,
path, path,
value: cur value: cur,
}) });
} };
const matchUpdate = createMatchUpdate(name, path) const matchUpdate = createMatchUpdate(name, path);
useEffect(() => { useEffect(() => {
if (Array.isArray(hiddenKeys)) { if (Array.isArray(hiddenKeys)) {
setFieldState({ setFieldState({
activeKey: parseDefaultActiveKey(hiddenKeys, orderProperties, defaultActiveKey) activeKey: parseDefaultActiveKey(
}) hiddenKeys,
orderProperties,
defaultActiveKey,
),
});
} }
}, [hiddenKeys.length]) }, [hiddenKeys.length]);
useFormEffects(({ hasChanged }) => { useFormEffects(({ hasChanged }) => {
onFormChange$().subscribe(formState => { onFormChange$().subscribe(formState => {
const errorsChanged = hasChanged(formState, 'errors') const errorsChanged = hasChanged(formState, 'errors');
if (errorsChanged) { if (errorsChanged) {
setFieldState({ setFieldState({
childrenErrors: parseChildrenErrors(formState.errors, path) childrenErrors: parseChildrenErrors(formState.errors, path),
}) });
} }
}) });
EffectHooks.onTabActiveKeyChange$().subscribe( EffectHooks.onTabActiveKeyChange$().subscribe(
({ value, name, path }: any = {}) => { ({ value, name, path }: any = {}) => {
if(!itemsRef.current.map(item => item.key).includes(value))return if (!itemsRef.current.map(item => item.key).includes(value)) return;
matchUpdate(name, path, () => { matchUpdate(name, path, () => {
setFieldState({ setFieldState({
activeKey: value activeKey: value,
}) });
}) });
} },
) );
}) });
return ( return (
<Tabs {...componentProps} activeKey={activeKey} onChange={update}> <Tabs {...componentProps} activeKey={activeKey} onChange={update}>
{itemsRef.current.map(({ props, schema, key }) => { {itemsRef.current.map(({ props, schema, key }) => {
const currentPath = FormPath.parse(path).concat(key) const currentPath = FormPath.parse(path).concat(key);
return ( return (
<Tabs.TabPane <Tabs.TabPane
{...props} {...props}
@ -159,20 +175,20 @@ export const FormTab: React.FC<IVirtualBoxProps<IFormTab>> &
onlyRenderProperties onlyRenderProperties
/> />
</Tabs.TabPane> </Tabs.TabPane>
) );
})} })}
</Tabs> </Tabs>
) );
} },
) as any ) as any;
FormTab.TabPane = createControllerBox<TabPaneProps>( FormTab.TabPane = createControllerBox<TabPaneProps>(
'tabpane', 'tabpane',
({ children }) => { ({ children }) => {
return <Fragment>{children}</Fragment> return <Fragment>{children}</Fragment>;
} },
) );
Object.assign(FormTab, StateMap, EffectHooks) Object.assign(FormTab, StateMap, EffectHooks);
export default FormTab export default FormTab;

View File

@ -1,2 +1,2 @@
import 'antd/lib/tabs/style/index' import 'antd/lib/tabs/style/index';
import 'antd/lib/badge/style/index' import 'antd/lib/badge/style/index';

View File

@ -1,64 +1,64 @@
import React, { useRef, useLayoutEffect } from 'react' import React, { useRef, useLayoutEffect } from 'react';
import { createControllerBox, Schema } from '@formily/react-schema-renderer' import { createControllerBox, Schema } from '@formily/react-schema-renderer';
import { IFormTextBox } from '../types' import { IFormTextBox } from '../types';
import { toArr } from '@formily/shared' import { toArr } from '@formily/shared';
import { FormItemProps as ItemProps } from 'antd/lib/form' import { FormItemProps as ItemProps } from 'antd/lib/form';
import { version } from 'antd' import { version } from 'antd';
import { AntdSchemaFieldAdaptor, pickFormItemProps } from '@formily/antd' import { AntdSchemaFieldAdaptor, pickFormItemProps } from '@formily/antd';
import styled from 'styled-components' import styled from 'styled-components';
const isV4 = /^4\./.test(version) const isV4 = /^4\./.test(version);
export const FormTextBox = createControllerBox<IFormTextBox & ItemProps>( export const FormTextBox = createControllerBox<IFormTextBox & ItemProps>(
'text-box', 'text-box',
styled(({ props, form, className, children }) => { styled(({ props, form, className, children }) => {
const schema = new Schema(props) const schema = new Schema(props);
const mergeProps = schema.getExtendsComponentProps() const mergeProps = schema.getExtendsComponentProps();
const { title, label, text, gutter, style } = Object.assign( const { title, label, text, gutter, style } = Object.assign(
{ {
gutter: 5 gutter: 5,
}, },
mergeProps mergeProps,
) );
const formItemProps = pickFormItemProps(mergeProps) const formItemProps = pickFormItemProps(mergeProps);
const ref: React.RefObject<HTMLDivElement> = useRef() const ref: React.RefObject<HTMLDivElement> = useRef();
const arrChildren = toArr(children) const arrChildren = toArr(children);
const split = text.split('%s') const split = text.split('%s');
let index = 0 let index = 0;
useLayoutEffect(() => { useLayoutEffect(() => {
if (ref.current) { if (ref.current) {
const elements = ref.current.querySelectorAll('.text-box-field') const elements = ref.current.querySelectorAll('.text-box-field');
const syncLayouts = Array.prototype.map.call( const syncLayouts = Array.prototype.map.call(
elements, elements,
(el: HTMLElement) => { (el: HTMLElement) => {
return [ return [
el, el,
() => { () => {
const ctrl = el.querySelector('.ant-form-item-children') const ctrl = el.querySelector('.ant-form-item-children');
setTimeout(() => { setTimeout(() => {
if (ctrl) { if (ctrl) {
const editable = form.getFormState(state => state.editable) const editable = form.getFormState(state => state.editable);
el.style.width = editable el.style.width = editable
? ctrl.getBoundingClientRect().width + 'px' ? ctrl.getBoundingClientRect().width + 'px'
: 'auto' : 'auto';
} }
}) });
} },
] ];
} },
) );
syncLayouts.forEach(([el, handler]) => { syncLayouts.forEach(([el, handler]) => {
handler() handler();
el.addEventListener('DOMSubtreeModified', handler) el.addEventListener('DOMSubtreeModified', handler);
}) });
return () => { return () => {
syncLayouts.forEach(([el, handler]) => { syncLayouts.forEach(([el, handler]) => {
el.removeEventListener('DOMSubtreeModified', handler) el.removeEventListener('DOMSubtreeModified', handler);
}) });
};
} }
} }, []);
}, [])
const newChildren = split.reduce((buf, item, key) => { const newChildren = split.reduce((buf, item, key) => {
return buf.concat( return buf.concat(
item ? ( item ? (
@ -68,7 +68,7 @@ export const FormTextBox = createControllerBox<IFormTextBox & ItemProps>(
style={{ style={{
marginRight: gutter / 2, marginRight: gutter / 2,
marginLeft: gutter / 2, marginLeft: gutter / 2,
...style ...style,
}} }}
> >
{item} {item}
@ -78,29 +78,29 @@ export const FormTextBox = createControllerBox<IFormTextBox & ItemProps>(
<div key={index++} className="text-box-field"> <div key={index++} className="text-box-field">
{arrChildren[key]} {arrChildren[key]}
</div> </div>
) : null ) : null,
) );
}, []) }, []);
const textChildren = ( const textChildren = (
<div <div
className={`${className} ${mergeProps.className}`} className={`${className} ${mergeProps.className}`}
style={{ style={{
marginRight: -gutter / 2, marginRight: -gutter / 2,
marginLeft: -gutter / 2 marginLeft: -gutter / 2,
}} }}
ref={ref} ref={ref}
> >
{newChildren} {newChildren}
</div> </div>
) );
if (!title && !label) return textChildren if (!title && !label) return textChildren;
return ( return (
<AntdSchemaFieldAdaptor {...formItemProps}> <AntdSchemaFieldAdaptor {...formItemProps}>
{textChildren} {textChildren}
</AntdSchemaFieldAdaptor> </AntdSchemaFieldAdaptor>
) );
})` })`
display: flex; display: flex;
.text-box-words:nth-child(1) { .text-box-words:nth-child(1) {
@ -122,7 +122,7 @@ export const FormTextBox = createControllerBox<IFormTextBox & ItemProps>(
.preview-text { .preview-text {
text-align: center !important; text-align: center !important;
} }
` `,
) );
export default FormTextBox export default FormTextBox;

View File

@ -1,7 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import { Popover, Button } from 'antd' import { Popover, Button } from 'antd';
import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared' import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared';
import { icons, hasIcon, Icon as IconComponent } from '@/components/icons'; import { icons, hasIcon, Icon as IconComponent } from '@/components/icons';
function IconField(props: any) { function IconField(props: any) {
@ -9,19 +9,33 @@ function IconField(props: any) {
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
return ( return (
<div> <div>
<Popover placement={'bottom'} visible={visible} onVisibleChange={(val) => { <Popover
setVisible(val) placement={'bottom'}
}} content={( visible={visible}
onVisibleChange={val => {
setVisible(val);
}}
content={
<div> <div>
{[...icons.keys()].map(key => ( {[...icons.keys()].map(key => (
<span style={{fontSize: 18, marginRight: 10, cursor: 'pointer'}} onClick={() => { <span
style={{ fontSize: 18, marginRight: 10, cursor: 'pointer' }}
onClick={() => {
onChange(key); onChange(key);
setVisible(false) setVisible(false);
}}><IconComponent type={key}/></span> }}
>
<IconComponent type={key} />
</span>
))} ))}
</div> </div>
)} title="图标" trigger="click"> }
<Button>{ hasIcon(value) ? <IconComponent type={value}/> : '选择图标'}</Button> title="图标"
trigger="click"
>
<Button>
{hasIcon(value) ? <IconComponent type={value} /> : '选择图标'}
</Button>
</Popover> </Popover>
</div> </div>
); );
@ -29,7 +43,7 @@ function IconField(props: any) {
export const Icon = connect({ export const Icon = connect({
getProps: mapStyledProps, getProps: mapStyledProps,
getComponent: mapTextComponent getComponent: mapTextComponent,
})(IconField) })(IconField);
export default Icon export default Icon;

View File

@ -1,30 +1,30 @@
export * from './text-button' export * from './text-button';
export * from './time-picker' export * from './time-picker';
export * from './transfer' export * from './transfer';
export * from './switch' export * from './switch';
export * from './array-cards' export * from './array-cards';
export * from './array-table' export * from './array-table';
export * from './checkbox' export * from './checkbox';
export * from './circle-button' export * from './circle-button';
export * from './date-picker' export * from './date-picker';
export * from './form-block' export * from './form-block';
export * from './form-card' export * from './form-card';
export * from './form-tab' export * from './form-tab';
export * from './form-grid-col' export * from './form-grid-col';
export * from './form-grid-row' export * from './form-grid-row';
export * from './form-item-grid' export * from './form-item-grid';
export * from './form-layout' export * from './form-layout';
export * from './form-mega-layout' export * from './form-mega-layout';
export * from './form-description' export * from './form-description';
export * from './form-step' export * from './form-step';
export * from './form-text-box' export * from './form-text-box';
export * from './form-slot' export * from './form-slot';
export * from './input' export * from './input';
export * from './select' export * from './select';
export * from './number-picker' export * from './number-picker';
export * from './password' export * from './password';
export * from './radio' export * from './radio';
export * from './range' export * from './range';
export * from './rating' export * from './rating';
export * from './upload' export * from './upload';
export * from './registry' export * from './registry';

View File

@ -1,25 +1,31 @@
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import React from 'react'; import React from 'react';
import { Input as AntdInput } from 'antd' import { Input as AntdInput } from 'antd';
import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared' import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared';
export const Input = connect<'TextArea'>({ export const Input = connect<'TextArea'>({
getProps: mapStyledProps, getProps: mapStyledProps,
getComponent: mapTextComponent getComponent: mapTextComponent,
})(acceptEnum(({onChange, ...restProps}) => ( })(
acceptEnum(({ onChange, ...restProps }) => (
<AntdInput <AntdInput
autoComplete={'off'} autoComplete={'off'}
{...restProps} {...restProps}
onChange={(e) => { onChange={e => {
// 文本字段,如果空要 null 处理 // 文本字段,如果空要 null 处理
onChange(e.target.value ? e : null); onChange(e.target.value ? e : null);
}} }}
/> />
))) )),
);
Input.TextArea = connect({ Input.TextArea = connect({
getProps: mapStyledProps, getProps: mapStyledProps,
getComponent: mapTextComponent getComponent: mapTextComponent,
})(acceptEnum((props) => <AntdInput.TextArea autoSize={{minRows: 2, maxRows: 12}} {...props}/>)) })(
acceptEnum(props => (
<AntdInput.TextArea autoSize={{ minRows: 2, maxRows: 12 }} {...props} />
)),
);
export default Input export default Input;

View File

@ -1 +1 @@
import 'antd/lib/input/style/index' import 'antd/lib/input/style/index';

View File

@ -1,11 +1,15 @@
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import React from 'react'; import React from 'react';
import { Input as AntdInput } from 'antd' import { Input as AntdInput } from 'antd';
import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared' import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared';
export const Markdown = connect({ export const Markdown = connect({
getProps: mapStyledProps, getProps: mapStyledProps,
getComponent: mapTextComponent getComponent: mapTextComponent,
})(acceptEnum((props) => <AntdInput.TextArea autoSize={{minRows: 2, maxRows: 12}} {...props}/>)) })(
acceptEnum(props => (
<AntdInput.TextArea autoSize={{ minRows: 2, maxRows: 12 }} {...props} />
)),
);
export default Markdown export default Markdown;

View File

@ -1,22 +1,24 @@
import React from 'react'; import React from 'react';
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import { InputNumber } from 'antd' import { InputNumber } from 'antd';
import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared' import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared';
export const NumberPicker = connect({ export const NumberPicker = connect({
getProps: mapStyledProps, getProps: mapStyledProps,
getComponent: mapTextComponent getComponent: mapTextComponent,
})(acceptEnum(InputNumber)) })(acceptEnum(InputNumber));
export const Percent = connect({ export const Percent = connect({
getProps: mapStyledProps, getProps: mapStyledProps,
getComponent: mapTextComponent getComponent: mapTextComponent,
})(acceptEnum((props) => ( })(
acceptEnum(props => (
<InputNumber <InputNumber
formatter={value => value ? `${value}%` : ''} formatter={value => (value ? `${value}%` : '')}
parser={value => value.replace('%', '')} parser={value => value.replace('%', '')}
{...props} {...props}
/> />
))) )),
);
export default NumberPicker export default NumberPicker;

View File

@ -1 +1 @@
import 'antd/lib/input-number/style/index' import 'antd/lib/input-number/style/index';

View File

@ -1,19 +1,19 @@
import React, { useState } from 'react' import React, { useState } from 'react';
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import { Input } from 'antd' import { Input } from 'antd';
import { PasswordProps } from 'antd/lib/input' import { PasswordProps } from 'antd/lib/input';
import { PasswordStrength } from '@formily/react-shared-components' import { PasswordStrength } from '@formily/react-shared-components';
import styled from 'styled-components' import styled from 'styled-components';
import { mapStyledProps } from '../shared' import { mapStyledProps } from '../shared';
export interface IPasswordProps extends PasswordProps { export interface IPasswordProps extends PasswordProps {
checkStrength: boolean checkStrength: boolean;
} }
export const Password = connect({ export const Password = connect({
getProps: mapStyledProps getProps: mapStyledProps,
})(styled((props: IPasswordProps) => { })(styled((props: IPasswordProps) => {
const { value, className, checkStrength, onChange, ...others } = props const { value, className, checkStrength, onChange, ...others } = props;
return ( return (
<span className={className}> <span className={className}>
@ -21,7 +21,7 @@ export const Password = connect({
autoComplete={'new-password'} autoComplete={'new-password'}
{...others} {...others}
value={value} value={value}
onChange={(e) => { onChange={e => {
// 密码字段,如果没有设置不处理 // 密码字段,如果没有设置不处理
onChange(e.target.value ? e : undefined); onChange(e.target.value ? e : undefined);
}} }}
@ -38,16 +38,16 @@ export const Password = connect({
<div <div
className="password-strength-bar" className="password-strength-bar"
style={{ style={{
clipPath: `polygon(0 0,${score}% 0,${score}% 100%,0 100%)` clipPath: `polygon(0 0,${score}% 0,${score}% 100%,0 100%)`,
}} }}
/> />
</div> </div>
) );
}} }}
</PasswordStrength> </PasswordStrength>
)} )}
</span> </span>
) );
})` })`
.password-strength-wrapper { .password-strength-wrapper {
background: #e0e0e0; background: #e0e0e0;
@ -83,6 +83,6 @@ export const Password = connect({
margin-top: 5px; margin-top: 5px;
} }
} }
`) `);
export default Password export default Password;

View File

@ -1 +1 @@
import 'antd/lib/input/style/index' import 'antd/lib/input/style/index';

View File

@ -1,7 +1,7 @@
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Input as AntdInput, Table, Checkbox, Select, Tag } from 'antd' import { Input as AntdInput, Table, Checkbox, Select, Tag } from 'antd';
import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared' import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared';
import api from '@/api-client'; import api from '@/api-client';
import { useRequest } from 'umi'; import { useRequest } from 'umi';
import { useDynamicList } from 'ahooks'; import { useDynamicList } from 'ahooks';
@ -10,22 +10,30 @@ import get from 'lodash/get';
import set from 'lodash/set'; import set from 'lodash/set';
import { DrawerSelectComponent } from '../drawer-select'; import { DrawerSelectComponent } from '../drawer-select';
export const Permissions = {} as {Actions: any, Fields: any, Tabs: any}; export const Permissions = {} as { Actions: any; Fields: any; Tabs: any };
Permissions.Actions = connect({ Permissions.Actions = connect({
getProps: mapStyledProps, getProps: mapStyledProps,
getComponent: mapTextComponent getComponent: mapTextComponent,
})(({ onChange, value = [], resourceKey, ...restProps }) => { })(({ onChange, value = [], resourceKey, ...restProps }) => {
const { data = [], loading = true } = useRequest(() => { const { data = [], loading = true } = useRequest(
() => {
return api.resource('collections.actions').list({ return api.resource('collections.actions').list({
associatedKey: resourceKey, associatedKey: resourceKey,
perPage: -1, perPage: -1,
}); });
}, { },
refreshDeps: [resourceKey] {
}); refreshDeps: [resourceKey],
},
);
return <Table size={'small'} pagination={false} dataSource={data} columns={[ return (
<Table
size={'small'}
pagination={false}
dataSource={data}
columns={[
{ {
title: '操作', title: '操作',
dataIndex: ['title'], dataIndex: ['title'],
@ -33,19 +41,29 @@ Permissions.Actions = connect({
{ {
title: '类型', title: '类型',
dataIndex: ['type'], dataIndex: ['type'],
render: (type) => { render: type => {
return type === 'create' ? <Tag color={'green'}></Tag> : <Tag color={'blue'}></Tag>; return type === 'create' ? (
} <Tag color={'green'}></Tag>
) : (
<Tag color={'blue'}></Tag>
);
},
}, },
{ {
title: '允许操作', title: '允许操作',
dataIndex: ['name'], dataIndex: ['name'],
render: (val, record) => { render: (val, record) => {
const values = [...value||[]]; const values = [...(value || [])];
const index = findIndex(values, (item: any) => item && item.name === `${resourceKey}:${record.name}`); const index = findIndex(
values,
(item: any) =>
item && item.name === `${resourceKey}:${record.name}`,
);
console.log(values); console.log(values);
return ( return (
<Checkbox defaultChecked={index >= 0} onChange={(e) => { <Checkbox
defaultChecked={index >= 0}
onChange={e => {
// const index = findIndex(values, (item: any) => item && item.name === `${resourceKey}:${record.name}`); // const index = findIndex(values, (item: any) => item && item.name === `${resourceKey}:${record.name}`);
if (index >= 0) { if (index >= 0) {
if (!e.target.checked) { if (!e.target.checked) {
@ -57,9 +75,10 @@ Permissions.Actions = connect({
}); });
} }
onChange(values); onChange(values);
}}/> }}
) />
} );
},
}, },
{ {
title: '可操作的数据范围', title: '可操作的数据范围',
@ -68,9 +87,18 @@ Permissions.Actions = connect({
if (['filter', 'create'].indexOf(record.type) !== -1) { if (['filter', 'create'].indexOf(record.type) !== -1) {
return null; return null;
} }
const values = [...value||[]]; const values = [...(value || [])];
const index = findIndex(values, (item: any) => item && item.name === `${resourceKey}:${record.name}`); const index = findIndex(
console.log(values, index, `${resourceKey}:${record.name}`, get(values, [index, 'scope'])); values,
(item: any) =>
item && item.name === `${resourceKey}:${record.name}`,
);
console.log(
values,
index,
`${resourceKey}:${record.name}`,
get(values, [index, 'scope']),
);
return ( return (
<DrawerSelectComponent <DrawerSelectComponent
schema={{ schema={{
@ -84,9 +112,13 @@ Permissions.Actions = connect({
labelField={'title'} labelField={'title'}
valueField={'id'} valueField={'id'}
value={get(values, [index, 'scope'])} value={get(values, [index, 'scope'])}
onChange={(data) => { onChange={data => {
const values = [...value||[]]; const values = [...(value || [])];
const index = findIndex(values, (item: any) => item && item.name === `${resourceKey}:${record.name}`); const index = findIndex(
values,
(item: any) =>
item && item.name === `${resourceKey}:${record.name}`,
);
if (index === -1) { if (index === -1) {
values.push({ values.push({
name: `${resourceKey}:${record.name}`, name: `${resourceKey}:${record.name}`,
@ -95,7 +127,7 @@ Permissions.Actions = connect({
} else { } else {
set(values, [index, 'scope_id'], data.id); set(values, [index, 'scope_id'], data.id);
} }
console.log('valvalvalvalval', {values}) console.log('valvalvalvalval', { values });
onChange(values); onChange(values);
console.log('valvalvalvalval', data); console.log('valvalvalvalval', data);
}} }}
@ -125,17 +157,19 @@ Permissions.Actions = connect({
// console.log('valvalvalvalval', data); // console.log('valvalvalvalval', data);
// }} // }}
// /> // />
) );
}
}, },
]} loading={loading}/> },
}) ]}
loading={loading}
/>
);
});
Permissions.Fields = connect<'TextArea'>({ Permissions.Fields = connect<'TextArea'>({
getProps: mapStyledProps, getProps: mapStyledProps,
getComponent: mapTextComponent getComponent: mapTextComponent,
})(({ onChange, value = [], resourceKey, ...restProps }) => { })(({ onChange, value = [], resourceKey, ...restProps }) => {
const actions = {}; const actions = {};
value.forEach(item => { value.forEach(item => {
actions[item.field_id] = item.actions; actions[item.field_id] = item.actions;
@ -145,22 +179,26 @@ Permissions.Fields = connect<'TextArea'>({
const [fields, setFields] = useState(value || []); const [fields, setFields] = useState(value || []);
const { data = [], loading = true } = useRequest(() => { const { data = [], loading = true } = useRequest(
() => {
return api.resource('collections.fields').list({ return api.resource('collections.fields').list({
associatedKey: resourceKey, associatedKey: resourceKey,
perPage: -1, perPage: -1,
}); });
}, { },
refreshDeps: [resourceKey] {
}); refreshDeps: [resourceKey],
},
);
console.log({ resourceKey, data }); console.log({ resourceKey, data });
const columns = [ const columns = [
{ {
title: '字段名称', title: '字段名称',
dataIndex: ['title'], dataIndex: ['title'],
} },
].concat([ ].concat(
[
{ {
title: '查看', title: '查看',
action: `${resourceKey}:list`, action: `${resourceKey}:list`,
@ -174,9 +212,15 @@ Permissions.Fields = connect<'TextArea'>({
action: `${resourceKey}:create`, action: `${resourceKey}:create`,
}, },
].map(({ title, action }) => { ].map(({ title, action }) => {
let checked = value.filter(({ actions = [] }) => actions.indexOf(action) !== -1).length === data.length; let checked =
value.filter(({ actions = [] }) => actions.indexOf(action) !== -1)
.length === data.length;
return { return {
title: <><Checkbox checked={checked} onChange={(e) => { title: (
<>
<Checkbox
checked={checked}
onChange={e => {
const values = data.map(field => { const values = data.map(field => {
const items = actions[field.id] || []; const items = actions[field.id] || [];
const index = items.indexOf(action); const index = items.indexOf(action);
@ -192,22 +236,31 @@ Permissions.Fields = connect<'TextArea'>({
return { return {
field_id: field.id, field_id: field.id,
actions: items, actions: items,
} };
}); });
// console.log(values); // console.log(values);
setFields([...values]); setFields([...values]);
onChange([...values]); onChange([...values]);
}}/> {title}</>, }}
/>{' '}
{title}
</>
),
dataIndex: ['id'], dataIndex: ['id'],
render: (val, record) => { render: (val, record) => {
const items = actions[record.id]||[] const items = actions[record.id] || [];
// console.log({items}, items.indexOf(action)); // console.log({items}, items.indexOf(action));
return ( return (
<Checkbox checked={items.indexOf(action) !== -1} onChange={e => { <Checkbox
checked={items.indexOf(action) !== -1}
onChange={e => {
const values = [...value]; const values = [...value];
const index = findIndex(values, ({field_id, actions = []}) => { const index = findIndex(
values,
({ field_id, actions = [] }) => {
return field_id === record.id; return field_id === record.id;
}); },
);
if (e.target.checked && index === -1) { if (e.target.checked && index === -1) {
values.push({ values.push({
field_id: record.id, field_id: record.id,
@ -225,28 +278,40 @@ Permissions.Fields = connect<'TextArea'>({
} }
onChange(values); onChange(values);
setFields(values); setFields(values);
}}/> }}
) />
} );
} },
}) as any) };
}) as any,
);
return <Table size={'small'} loading={loading} pagination={false} dataSource={data} columns={columns}/> return (
}) <Table
size={'small'}
loading={loading}
pagination={false}
dataSource={data}
columns={columns}
/>
);
});
Permissions.Tabs = connect<'TextArea'>({ Permissions.Tabs = connect<'TextArea'>({
getProps: mapStyledProps, getProps: mapStyledProps,
getComponent: mapTextComponent getComponent: mapTextComponent,
})(({ onChange, value = [], resourceKey, ...restProps }) => { })(({ onChange, value = [], resourceKey, ...restProps }) => {
const { data = [], loading = true, mutate } = useRequest(
const { data = [], loading = true, mutate } = useRequest(() => { () => {
return api.resource('collections.tabs').list({ return api.resource('collections.tabs').list({
associatedKey: resourceKey, associatedKey: resourceKey,
perPage: -1, perPage: -1,
}); });
}, { },
refreshDeps: [resourceKey] {
}); refreshDeps: [resourceKey],
},
);
// const [checked, setChecked] = useState(false); // const [checked, setChecked] = useState(false);
@ -259,7 +324,12 @@ Permissions.Tabs = connect<'TextArea'>({
// data, // data,
// ]); // ]);
return <Table size={'small'} pagination={false} dataSource={data} columns={[ return (
<Table
size={'small'}
pagination={false}
dataSource={data}
columns={[
{ {
title: '标签页', title: '标签页',
dataIndex: ['title'], dataIndex: ['title'],
@ -267,16 +337,24 @@ Permissions.Tabs = connect<'TextArea'>({
{ {
title: ( title: (
<> <>
<Checkbox checked={data.length === value.length} onChange={(e) => { <Checkbox
onChange(e.target.checked ? data.map(record => record.id) : []); checked={data.length === value.length}
}}/> onChange={e => {
onChange(
e.target.checked ? data.map(record => record.id) : [],
);
}}
/>{' '}
</> </>
), ),
dataIndex: ['id'], dataIndex: ['id'],
render: (val, record) => { render: (val, record) => {
const values = [...value]; const values = [...value];
return ( return (
<Checkbox checked={values.indexOf(record.id) !== -1} onChange={(e) => { <Checkbox
checked={values.indexOf(record.id) !== -1}
onChange={e => {
const index = values.indexOf(record.id); const index = values.indexOf(record.id);
if (index !== -1) { if (index !== -1) {
if (!e.target.checked) { if (!e.target.checked) {
@ -286,9 +364,13 @@ Permissions.Tabs = connect<'TextArea'>({
values.push(record.id); values.push(record.id);
} }
onChange(values); onChange(values);
}}/> }}
) />
} );
}, },
]} loading={loading}/> },
]}
loading={loading}
/>
);
}); });

View File

@ -1,19 +1,19 @@
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import { Radio as AntdRadio } from 'antd' import { Radio as AntdRadio } from 'antd';
import { import {
transformDataSourceKey, transformDataSourceKey,
mapStyledProps, mapStyledProps,
mapTextComponent mapTextComponent,
} from '../shared' } from '../shared';
export const Radio = connect<'Group'>({ export const Radio = connect<'Group'>({
valueName: 'checked', valueName: 'checked',
getProps: mapStyledProps getProps: mapStyledProps,
})(AntdRadio) })(AntdRadio);
Radio.Group = connect({ Radio.Group = connect({
getProps: mapStyledProps, getProps: mapStyledProps,
getComponent: mapTextComponent getComponent: mapTextComponent,
})(transformDataSourceKey(AntdRadio.Group, 'options')) })(transformDataSourceKey(AntdRadio.Group, 'options'));
export default Radio export default Radio;

View File

@ -1 +1 @@
import 'antd/lib/radio/style/index' import 'antd/lib/radio/style/index';

View File

@ -1,47 +1,47 @@
import React from 'react' import React from 'react';
import { Slider } from 'antd' import { Slider } from 'antd';
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import { mapStyledProps } from '../shared' import { mapStyledProps } from '../shared';
export interface ISliderMarks { export interface ISliderMarks {
[key: number]: [key: number]:
| React.ReactNode | React.ReactNode
| { | {
style: React.CSSProperties style: React.CSSProperties;
label: React.ReactNode label: React.ReactNode;
} };
} }
export declare type SliderValue = number | [number, number] export declare type SliderValue = number | [number, number];
// TODO 并不是方法,最好能引用组件的 typescript 接口定义 // TODO 并不是方法,最好能引用组件的 typescript 接口定义
export interface ISliderProps { export interface ISliderProps {
min?: number min?: number;
max?: number max?: number;
marks?: ISliderMarks marks?: ISliderMarks;
value?: SliderValue value?: SliderValue;
defaultValue?: SliderValue defaultValue?: SliderValue;
onChange?: (value: SliderValue) => void onChange?: (value: SliderValue) => void;
} }
export const Range = connect({ export const Range = connect({
defaultProps: { defaultProps: {
style: { style: {
width: 320 width: 320,
}
}, },
getProps: mapStyledProps },
getProps: mapStyledProps,
})( })(
class Component extends React.Component<ISliderProps> { class Component extends React.Component<ISliderProps> {
public render() { public render() {
const { onChange, value, min, max, marks, ...rest } = this.props const { onChange, value, min, max, marks, ...rest } = this.props;
let newMarks = {} let newMarks = {};
if (Array.isArray(marks)) { if (Array.isArray(marks)) {
marks.forEach(mark => { marks.forEach(mark => {
newMarks[mark] = mark newMarks[mark] = mark;
}) });
} else { } else {
newMarks = marks newMarks = marks;
} }
return ( return (
<Slider <Slider
@ -52,9 +52,9 @@ export const Range = connect({
max={max} max={max}
marks={newMarks} marks={newMarks}
/> />
) );
} }
} },
) );
export default Range export default Range;

View File

@ -1 +1 @@
import 'antd/lib/slider/style/index' import 'antd/lib/slider/style/index';

View File

@ -1,9 +1,9 @@
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import { Rate } from 'antd' import { Rate } from 'antd';
import { mapStyledProps } from '../shared' import { mapStyledProps } from '../shared';
export const Rating = connect({ export const Rating = connect({
getProps: mapStyledProps getProps: mapStyledProps,
})(Rate) })(Rate);
export default Rating export default Rating;

View File

@ -1 +1 @@
import 'antd/lib/rate/style/index' import 'antd/lib/rate/style/index';

View File

@ -1,31 +1,31 @@
import { registerFormFields } from '@formily/antd' import { registerFormFields } from '@formily/antd';
import { TimePicker } from './time-picker' import { TimePicker } from './time-picker';
import { Transfer } from './transfer' import { Transfer } from './transfer';
import { Switch } from './switch' import { Switch } from './switch';
import { ArrayCards } from './array-cards' import { ArrayCards } from './array-cards';
import { ArrayTable } from './array-table' import { ArrayTable } from './array-table';
import { Checkbox } from './checkbox' import { Checkbox } from './checkbox';
import { DatePicker } from './date-picker' import { DatePicker } from './date-picker';
import { Input } from './input' import { Input } from './input';
import { NumberPicker, Percent } from './number-picker' import { NumberPicker, Percent } from './number-picker';
import { Password } from './password' import { Password } from './password';
import { Radio } from './radio' import { Radio } from './radio';
import { Range } from './range' import { Range } from './range';
import { Rating } from './rating' import { Rating } from './rating';
import { Upload } from './upload' import { Upload } from './upload';
import { Filter } from './filter' import { Filter } from './filter';
import { RemoteSelect } from './remote-select' import { RemoteSelect } from './remote-select';
import { DrawerSelect } from './drawer-select' import { DrawerSelect } from './drawer-select';
import { SubTable } from './sub-table' import { SubTable } from './sub-table';
import { Cascader } from './cascader' import { Cascader } from './cascader';
import { Icon } from './icons' import { Icon } from './icons';
import { ColorSelect } from './color-select' import { ColorSelect } from './color-select';
import { Permissions } from './permissions' import { Permissions } from './permissions';
import { DraggableTable } from './draggable-table' import { DraggableTable } from './draggable-table';
import { Values } from './values' import { Values } from './values';
import { Automations } from './automations' import { Automations } from './automations';
import { Wysiwyg } from './wysiwyg' import { Wysiwyg } from './wysiwyg';
import { Markdown } from './markdown' import { Markdown } from './markdown';
export const setup = () => { export const setup = () => {
registerFormFields({ registerFormFields({
@ -72,4 +72,4 @@ export const setup = () => {
'automations.endmode': Automations.EndMode, 'automations.endmode': Automations.EndMode,
'automations.cron': Automations.Cron, 'automations.cron': Automations.Cron,
}); });
} };

View File

@ -1,21 +1,34 @@
import React, { useEffect } from 'react' import React, { useEffect } from 'react';
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import moment from 'moment' import moment from 'moment';
import { Select } from 'antd' import { Select } from 'antd';
import { import {
mapStyledProps, mapStyledProps,
mapTextComponent, mapTextComponent,
compose, compose,
isStr, isStr,
isArr isArr,
} from '../shared' } from '../shared';
import { useRequest } from 'umi'; import { useRequest } from 'umi';
import api from '@/api-client'; import api from '@/api-client';
import { Spin } from '@nocobase/client' import { Spin } from '@nocobase/client';
import get from 'lodash/get'; import get from 'lodash/get';
function RemoteSelectComponent(props) { function RemoteSelectComponent(props) {
let { schema = {}, value, onChange, disabled, resourceName, associatedKey, filter, labelField, valueField, objectValue, placeholder, multiple } = props; let {
schema = {},
value,
onChange,
disabled,
resourceName,
associatedKey,
filter,
labelField,
valueField,
objectValue,
placeholder,
multiple,
} = props;
console.log({ schema }); console.log({ schema });
if (!resourceName) { if (!resourceName) {
resourceName = get(schema, 'component.resourceName'); resourceName = get(schema, 'component.resourceName');
@ -32,19 +45,22 @@ function RemoteSelectComponent(props) {
if (!valueField) { if (!valueField) {
valueField = 'id'; valueField = 'id';
} }
const { data = [], loading = true } = useRequest(() => { const { data = [], loading = true } = useRequest(
() => {
return api.resource(resourceName).list({ return api.resource(resourceName).list({
associatedKey, associatedKey,
filter, filter,
}); });
}, { },
refreshDeps: [resourceName, associatedKey] {
}); refreshDeps: [resourceName, associatedKey],
},
);
const selectProps: any = {}; const selectProps: any = {};
if (multiple) { if (multiple) {
selectProps.mode = 'multiple' selectProps.mode = 'multiple';
} }
console.log({ data, props, associatedKey }) console.log({ data, props, associatedKey });
return ( return (
<> <>
<Select <Select
@ -65,7 +81,12 @@ function RemoteSelectComponent(props) {
onChange(objectValue ? item : value); onChange(objectValue ? item : value);
}} }}
> >
{!loading && data.map(item => (<Select.Option item={item} value={item[valueField]}>{item[labelField]}</Select.Option>))} {!loading &&
data.map(item => (
<Select.Option item={item} value={item[valueField]}>
{item[labelField]}
</Select.Option>
))}
</Select> </Select>
</> </>
); );
@ -74,6 +95,6 @@ function RemoteSelectComponent(props) {
export const RemoteSelect = connect({ export const RemoteSelect = connect({
getProps: mapStyledProps, getProps: mapStyledProps,
getComponent: mapTextComponent, getComponent: mapTextComponent,
})(RemoteSelectComponent) })(RemoteSelectComponent);
export default RemoteSelect export default RemoteSelect;

View File

@ -1,13 +1,13 @@
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import { import {
Select as AntdSelect, Select as AntdSelect,
mapStyledProps, mapStyledProps,
mapTextComponent mapTextComponent,
} from '../shared' } from '../shared';
export const Select = connect({ export const Select = connect({
getProps: mapStyledProps, getProps: mapStyledProps,
getComponent: mapTextComponent, getComponent: mapTextComponent,
})(AntdSelect) })(AntdSelect);
export default Select export default Select;

View File

@ -1 +1 @@
import 'antd/lib/select/style/index' import 'antd/lib/select/style/index';

View File

@ -1,47 +1,47 @@
import React from 'react' import React from 'react';
import { mapTextComponent, mapStyledProps, normalizeCol } from '@formily/antd' import { mapTextComponent, mapStyledProps, normalizeCol } from '@formily/antd';
import { Select as AntSelect } from 'antd' import { Select as AntSelect } from 'antd';
import { SelectProps as AntSelectProps } from 'antd/lib/select' import { SelectProps as AntSelectProps } from 'antd/lib/select';
import styled from 'styled-components' import styled from 'styled-components';
import { isArr, FormPath } from '@formily/shared' import { isArr, FormPath } from '@formily/shared';
export * from '@formily/shared' export * from '@formily/shared';
export const compose = (...args: any[]) => { export const compose = (...args: any[]) => {
return (payload: any, ...extra: any[]) => { return (payload: any, ...extra: any[]) => {
return args.reduce((buf, fn) => { return args.reduce((buf, fn) => {
return buf !== undefined ? fn(buf, ...extra) : fn(payload, ...extra) return buf !== undefined ? fn(buf, ...extra) : fn(payload, ...extra);
}, payload) }, payload);
} };
} };
interface SelectOption { interface SelectOption {
label: React.ReactText label: React.ReactText;
value: any value: any;
[key: string]: any [key: string]: any;
} }
type SelectProps = AntSelectProps & { type SelectProps = AntSelectProps & {
dataSource?: SelectOption[] dataSource?: SelectOption[];
} };
const createEnum = (enums: any) => { const createEnum = (enums: any) => {
if (isArr(enums)) { if (isArr(enums)) {
return enums.map(item => { return enums.map(item => {
if (typeof item === 'object') { if (typeof item === 'object') {
return { return {
...item ...item,
} };
} else { } else {
return { return {
label: item, label: item,
value: item value: item,
};
} }
} });
})
} }
return [] return [];
} };
export const Select: React.FC<SelectProps> = styled((props: SelectProps) => { export const Select: React.FC<SelectProps> = styled((props: SelectProps) => {
const { dataSource = [], onChange, value, ...others } = props; const { dataSource = [], onChange, value, ...others } = props;
@ -72,8 +72,8 @@ export const Select: React.FC<SelectProps> = styled((props: SelectProps) => {
> >
{label} {label}
</AntSelect.Option> </AntSelect.Option>
) );
}) });
return ( return (
<AntSelect <AntSelect
className={props.className} className={props.className}
@ -85,59 +85,59 @@ export const Select: React.FC<SelectProps> = styled((props: SelectProps) => {
isArr(options) isArr(options)
? options.map(item => ({ ? options.map(item => ({
...item, ...item,
props: undefined props: undefined,
})) }))
: { : {
...options, ...options,
props: undefined //干掉循环引用 props: undefined, //干掉循环引用
} },
) );
}} }}
> >
{children} {children}
</AntSelect> </AntSelect>
) );
})` })`
min-width: 100px; min-width: 100px;
width: 100%; width: 100%;
` `;
export const acceptEnum = (component: React.JSXElementConstructor<any>) => { export const acceptEnum = (component: React.JSXElementConstructor<any>) => {
return ({ dataSource, ...others }) => { return ({ dataSource, ...others }) => {
if (dataSource) { if (dataSource) {
return React.createElement(Select, { dataSource, ...others }) return React.createElement(Select, { dataSource, ...others });
} else { } else {
return React.createElement(component, others) return React.createElement(component, others);
}
}
} }
};
};
export const transformDataSourceKey = (component, dataSourceKey) => { export const transformDataSourceKey = (component, dataSourceKey) => {
return ({ dataSource, ...others }) => { return ({ dataSource, ...others }) => {
return React.createElement(component, { return React.createElement(component, {
[dataSourceKey]: dataSource, [dataSourceKey]: dataSource,
...others ...others,
}) });
} };
} };
export const createMatchUpdate = (name: string, path: string) => ( export const createMatchUpdate = (name: string, path: string) => (
targetName: string, targetName: string,
targetPath: string, targetPath: string,
callback: () => void callback: () => void,
) => { ) => {
if (targetName || targetPath) { if (targetName || targetPath) {
if (targetName) { if (targetName) {
if (FormPath.parse(targetName).matchAliasGroup(name, path)) { if (FormPath.parse(targetName).matchAliasGroup(name, path)) {
callback() callback();
} }
} else if (targetPath) { } else if (targetPath) {
if (FormPath.parse(targetPath).matchAliasGroup(name, path)) { if (FormPath.parse(targetPath).matchAliasGroup(name, path)) {
callback() callback();
} }
} }
} else { } else {
callback() callback();
}
} }
};
export { mapTextComponent, mapStyledProps, normalizeCol } export { mapTextComponent, mapStyledProps, normalizeCol };

View File

@ -1,4 +1,10 @@
import React, { useState, useEffect, useImperativeHandle, forwardRef, useRef } from 'react'; import React, {
useState,
useEffect,
useImperativeHandle,
forwardRef,
useRef,
} from 'react';
import { Button, Drawer } from 'antd'; import { Button, Drawer } from 'antd';
import { Tooltip, Input, Space, Modal } from 'antd'; import { Tooltip, Input, Space, Modal } from 'antd';
import isEqual from 'lodash/isEqual'; import isEqual from 'lodash/isEqual';
@ -27,13 +33,12 @@ const actions = createFormActions();
export default forwardRef((props: any, ref) => { export default forwardRef((props: any, ref) => {
console.log(props); console.log(props);
const { const { target, onFinish } = props;
target, const { data: schema = {}, loading } = useRequest(() =>
onFinish, api.resource(target).getView({
} = props; resourceKey: 'form',
const { data: schema = {}, loading } = useRequest(() => api.resource(target).getView({ }),
resourceKey: 'form' );
}));
const [state, setState] = useState<any>({}); const [state, setState] = useState<any>({});
const [form, setForm] = useState<any>({}); const [form, setForm] = useState<any>({});
const [changed, setChanged] = useState(false); const [changed, setChanged] = useState(false);
@ -66,7 +71,7 @@ export default forwardRef((props: any, ref) => {
onOk() { onOk() {
setChanged(false); setChanged(false);
setVisible(false); setVisible(false);
} },
}); });
} else { } else {
setChanged(false); setChanged(false);
@ -74,35 +79,44 @@ export default forwardRef((props: any, ref) => {
} }
}} }}
title={title} title={title}
footer={( footer={
<div <div
style={{ style={{
textAlign: 'right', textAlign: 'right',
}} }}
> >
<Space> <Space>
<Button onClick={() => { <Button
onClick={() => {
setVisible(false); setVisible(false);
setChanged(false); setChanged(false);
}}></Button> }}
<Button type={'primary'} onClick={async () => { >
</Button>
<Button
type={'primary'}
onClick={async () => {
await form.submit(); await form.submit();
// const { values = {} } = await actions.submit(); // const { values = {} } = await actions.submit();
// setVisible(false); // setVisible(false);
// onFinish && onFinish(values, index); // onFinish && onFinish(values, index);
}}></Button> }}
>
</Button>
</Space> </Space>
</div> </div>
)} }
> >
<SchemaForm <SchemaForm
colon={true} colon={true}
layout={'vertical'} layout={'vertical'}
initialValues={data} initialValues={data}
onChange={(values) => { onChange={values => {
setChanged(true); setChanged(true);
}} }}
onSubmit={async (values) => { onSubmit={async values => {
setVisible(false); setVisible(false);
setChanged(false); setChanged(false);
onFinish && onFinish(values, index); onFinish && onFinish(values, index);
@ -118,29 +132,29 @@ export default forwardRef((props: any, ref) => {
selector={[ selector={[
LifeCycleTypes.ON_FORM_MOUNT, LifeCycleTypes.ON_FORM_MOUNT,
LifeCycleTypes.ON_FORM_SUBMIT_START, LifeCycleTypes.ON_FORM_SUBMIT_START,
LifeCycleTypes.ON_FORM_SUBMIT_END LifeCycleTypes.ON_FORM_SUBMIT_END,
]} ]}
reducer={(state, action) => { reducer={(state, action) => {
switch (action.type) { switch (action.type) {
case LifeCycleTypes.ON_FORM_SUBMIT_START: case LifeCycleTypes.ON_FORM_SUBMIT_START:
return { return {
...state, ...state,
submitting: true submitting: true,
} };
case LifeCycleTypes.ON_FORM_SUBMIT_END: case LifeCycleTypes.ON_FORM_SUBMIT_END:
return { return {
...state, ...state,
submitting: false submitting: false,
} };
default: default:
return state return state;
} }
}} }}
> >
{({ state, form }) => { {({ state, form }) => {
setState(state) setState(state);
setForm(form); setForm(form);
return <div/> return <div />;
}} }}
</FormSpy> </FormSpy>
</SchemaForm> </SchemaForm>

View File

@ -21,14 +21,18 @@ export interface SimpleTableProps {
} }
export function generateIndex(): string { export function generateIndex(): string {
return `${Math.random().toString(36).replace('0.', '').slice(-4).padStart(4, '0')}`; return `${Math.random()
.toString(36)
.replace('0.', '')
.slice(-4)
.padStart(4, '0')}`;
} }
export default function Table(props: SimpleTableProps) { export default function Table(props: SimpleTableProps) {
const { schema = {}, associatedKey, value, onChange, __index } = props; const { schema = {}, associatedKey, value, onChange, __index } = props;
const { collection_name, name } = schema; const { collection_name, name } = schema;
const viewName = `${collection_name}.${name}.${schema.viewName || 'table'}`; const viewName = `${collection_name}.${name}.${schema.viewName || 'table'}`;
console.log({props, associatedKey, schema, __index, viewName, schema}) console.log({ props, associatedKey, schema, __index, viewName, schema });
return ( return (
<> <>
<View <View
@ -40,5 +44,5 @@ export default function Table(props: SimpleTableProps) {
type={'subTable'} type={'subTable'}
/> />
</> </>
) );
} }

View File

@ -1,20 +1,20 @@
import React, { useRef } from 'react' import React, { useRef } from 'react';
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import moment from 'moment' import moment from 'moment';
import { Select, Button, Table as AntdTable } from 'antd' import { Select, Button, Table as AntdTable } from 'antd';
import { import {
mapStyledProps, mapStyledProps,
mapTextComponent, mapTextComponent,
compose, compose,
isStr, isStr,
isArr isArr,
} from '../shared' } from '../shared';
import ViewFactory from '@/components/views'; import ViewFactory from '@/components/views';
import Table from './Table'; import Table from './Table';
export const SubTable = connect({ export const SubTable = connect({
getProps: mapStyledProps, getProps: mapStyledProps,
getComponent: mapTextComponent, getComponent: mapTextComponent,
})(Table) })(Table);
export default SubTable export default SubTable;

View File

@ -1,10 +1,10 @@
import { Switch as AntdSwitch } from 'antd' import { Switch as AntdSwitch } from 'antd';
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import { acceptEnum, mapStyledProps } from '../shared' import { acceptEnum, mapStyledProps } from '../shared';
export const Switch = connect({ export const Switch = connect({
valueName: 'checked', valueName: 'checked',
getProps: mapStyledProps getProps: mapStyledProps,
})(acceptEnum(AntdSwitch)) })(acceptEnum(AntdSwitch));
export default Switch; export default Switch;

View File

@ -1 +1 @@
import 'antd/lib/switch/style/index' import 'antd/lib/switch/style/index';

View File

@ -1,9 +1,9 @@
import React from 'react' import React from 'react';
import { Button } from 'antd' import { Button } from 'antd';
import { ButtonProps } from 'antd/lib/button' import { ButtonProps } from 'antd/lib/button';
export const TextButton: React.FC<ButtonProps> = props => ( export const TextButton: React.FC<ButtonProps> = props => (
<Button type="link" {...props} /> <Button type="link" {...props} />
) );
export default TextButton export default TextButton;

View File

@ -1 +1 @@
import 'antd/lib/button/style/index' import 'antd/lib/button/style/index';

View File

@ -1,50 +1,48 @@
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import moment from 'moment' import moment from 'moment';
import { TimePicker as AntdTimePicker } from 'antd' import { TimePicker as AntdTimePicker } from 'antd';
import { import {
mapStyledProps, mapStyledProps,
mapTextComponent, mapTextComponent,
compose, compose,
isStr, isStr,
isArr, isArr,
} from '../shared' } from '../shared';
const transformMoment = (value) => { const transformMoment = value => {
if (value === '') return undefined if (value === '') return undefined;
return value return value;
} };
const mapMomentValue = (props: any, fieldProps: any) => { const mapMomentValue = (props: any, fieldProps: any) => {
const { value } = props const { value } = props;
if (!fieldProps.editable) return props if (!fieldProps.editable) return props;
try { try {
if (isStr(value) && value) { if (isStr(value) && value) {
props.value = moment(value, 'HH:mm:ss') props.value = moment(value, 'HH:mm:ss');
} else if (isArr(value) && value.length) { } else if (isArr(value) && value.length) {
props.value = value.map( props.value = value.map(item => (item && moment(item, 'HH:mm:ss')) || '');
(item) => (item && moment(item, 'HH:mm:ss')) || ''
)
} }
} catch (e) { } catch (e) {
throw new Error(e) throw new Error(e);
}
return props
} }
return props;
};
export const TimePicker = connect<'RangePicker'>({ export const TimePicker = connect<'RangePicker'>({
getValueFromEvent(_, value) { getValueFromEvent(_, value) {
return transformMoment(value) return transformMoment(value);
}, },
getProps: compose(mapStyledProps, mapMomentValue), getProps: compose(mapStyledProps, mapMomentValue),
getComponent: mapTextComponent, getComponent: mapTextComponent,
})(AntdTimePicker) })(AntdTimePicker);
TimePicker.RangePicker = connect({ TimePicker.RangePicker = connect({
getValueFromEvent(_, [startDate, endDate]) { getValueFromEvent(_, [startDate, endDate]) {
return [transformMoment(startDate), transformMoment(endDate)] return [transformMoment(startDate), transformMoment(endDate)];
}, },
getProps: compose(mapStyledProps, mapMomentValue), getProps: compose(mapStyledProps, mapMomentValue),
getComponent: mapTextComponent, getComponent: mapTextComponent,
})(AntdTimePicker.RangePicker) })(AntdTimePicker.RangePicker);
export default TimePicker export default TimePicker;

View File

@ -1 +1 @@
import 'antd/lib/time-picker/style/index' import 'antd/lib/time-picker/style/index';

View File

@ -1,10 +1,10 @@
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import { Transfer as AntdTransfer } from 'antd' import { Transfer as AntdTransfer } from 'antd';
import { mapStyledProps } from '../shared' import { mapStyledProps } from '../shared';
export const Transfer = connect({ export const Transfer = connect({
getProps: mapStyledProps, getProps: mapStyledProps,
valueName: 'targetKeys' valueName: 'targetKeys',
})(AntdTransfer) })(AntdTransfer);
export default Transfer export default Transfer;

View File

@ -1 +1 @@
import 'antd/lib/transfer/style/index' import 'antd/lib/transfer/style/index';

View File

@ -1,20 +1,20 @@
import { ButtonProps } from 'antd/lib/button' import { ButtonProps } from 'antd/lib/button';
import { FormProps, FormItemProps as ItemProps } from 'antd/lib/form' import { FormProps, FormItemProps as ItemProps } from 'antd/lib/form';
import { import {
StepsProps as StepProps, StepsProps as StepProps,
StepProps as StepItemProps StepProps as StepItemProps,
} from 'antd/lib/steps' } from 'antd/lib/steps';
import { TabsProps } from 'antd/lib/tabs' import { TabsProps } from 'antd/lib/tabs';
import { import {
ISchemaFormProps, ISchemaFormProps,
IMarkupSchemaFieldProps, IMarkupSchemaFieldProps,
ISchemaFieldComponentProps, ISchemaFieldComponentProps,
FormPathPattern FormPathPattern,
} from '@formily/react-schema-renderer' } from '@formily/react-schema-renderer';
import { PreviewTextConfigProps } from '@formily/react-shared-components' import { PreviewTextConfigProps } from '@formily/react-shared-components';
import { StyledComponent } from 'styled-components' import { StyledComponent } from 'styled-components';
type ColSpanType = number | string type ColSpanType = number | string;
export type IAntdSchemaFormProps = Omit< export type IAntdSchemaFormProps = Omit<
FormProps, FormProps,
@ -22,18 +22,18 @@ export type IAntdSchemaFormProps = Omit<
> & > &
IFormItemTopProps & IFormItemTopProps &
PreviewTextConfigProps & PreviewTextConfigProps &
ISchemaFormProps ISchemaFormProps;
export type IAntdSchemaFieldProps = IMarkupSchemaFieldProps export type IAntdSchemaFieldProps = IMarkupSchemaFieldProps;
export interface ISubmitProps extends ButtonProps { export interface ISubmitProps extends ButtonProps {
onSubmit?: ISchemaFormProps['onSubmit'] onSubmit?: ISchemaFormProps['onSubmit'];
showLoading?: boolean showLoading?: boolean;
} }
export interface IResetProps extends ButtonProps { export interface IResetProps extends ButtonProps {
forceClear?: boolean forceClear?: boolean;
validate?: boolean validate?: boolean;
} }
export type IFormItemTopProps = React.PropsWithChildren< export type IFormItemTopProps = React.PropsWithChildren<
@ -41,63 +41,63 @@ export type IFormItemTopProps = React.PropsWithChildren<
Pick<ItemProps, 'prefixCls' | 'labelCol' | 'wrapperCol' | 'labelAlign'>, Pick<ItemProps, 'prefixCls' | 'labelCol' | 'wrapperCol' | 'labelAlign'>,
'labelCol' | 'wrapperCol' 'labelCol' | 'wrapperCol'
> & { > & {
inline?: boolean inline?: boolean;
className?: string className?: string;
style?: React.CSSProperties style?: React.CSSProperties;
labelCol?: number | { span: number; offset?: number } labelCol?: number | { span: number; offset?: number };
wrapperCol?: number | { span: number; offset?: number } wrapperCol?: number | { span: number; offset?: number };
} }
> >;
export type ISchemaFieldAdaptorProps = Omit< export type ISchemaFieldAdaptorProps = Omit<
ItemProps, ItemProps,
'labelCol' | 'wrapperCol' 'labelCol' | 'wrapperCol'
> & > &
Partial<ISchemaFieldComponentProps> & { Partial<ISchemaFieldComponentProps> & {
labelCol?: number | { span: number; offset?: number } labelCol?: number | { span: number; offset?: number };
wrapperCol?: number | { span: number; offset?: number } wrapperCol?: number | { span: number; offset?: number };
} };
export type StyledCP<P extends {}> = StyledComponent< export type StyledCP<P extends {}> = StyledComponent<
(props: React.PropsWithChildren<P>) => React.ReactElement, (props: React.PropsWithChildren<P>) => React.ReactElement,
any, any,
{}, {},
never never
> >;
export type StyledCC<Props, Statics = {}> = StyledCP<Props> & Statics export type StyledCC<Props, Statics = {}> = StyledCP<Props> & Statics;
export interface IFormButtonGroupProps { export interface IFormButtonGroupProps {
sticky?: boolean sticky?: boolean;
style?: React.CSSProperties style?: React.CSSProperties;
itemStyle?: React.CSSProperties itemStyle?: React.CSSProperties;
className?: string className?: string;
align?: 'left' | 'right' | 'start' | 'end' | 'top' | 'bottom' | 'center' align?: 'left' | 'right' | 'start' | 'end' | 'top' | 'bottom' | 'center';
triggerDistance?: number triggerDistance?: number;
zIndex?: number zIndex?: number;
span?: ColSpanType span?: ColSpanType;
offset?: ColSpanType offset?: ColSpanType;
} }
export interface IItemProps { export interface IItemProps {
title?: React.ReactText title?: React.ReactText;
description?: React.ReactText description?: React.ReactText;
} }
export interface IFormItemGridProps extends IItemProps { export interface IFormItemGridProps extends IItemProps {
cols?: Array<number | { span: number; offset: number }> cols?: Array<number | { span: number; offset: number }>;
gutter?: number gutter?: number;
} }
export interface IFormTextBox extends IItemProps { export interface IFormTextBox extends IItemProps {
text?: string text?: string;
gutter?: number gutter?: number;
} }
export interface IFormStep extends StepProps { export interface IFormStep extends StepProps {
dataSource: Array<StepItemProps & { name: FormPathPattern }> dataSource: Array<StepItemProps & { name: FormPathPattern }>;
} }
export interface IFormTab extends TabsProps { export interface IFormTab extends TabsProps {
hiddenKeys?: string[] hiddenKeys?: string[];
} }

View File

@ -1,14 +1,14 @@
import React, { useState } from 'react' import React, { useState } from 'react';
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import { Button, Upload as AntdUpload, Popconfirm } from 'antd' import { Button, Upload as AntdUpload, Popconfirm } from 'antd';
import { toArr, isArr, isEqual, mapStyledProps } from '../shared' import { toArr, isArr, isEqual, mapStyledProps } from '../shared';
import { import {
LoadingOutlined, LoadingOutlined,
PlusOutlined, PlusOutlined,
UploadOutlined, UploadOutlined,
InboxOutlined InboxOutlined,
} from '@ant-design/icons' } from '@ant-design/icons';
const { Dragger: UploadDragger } = AntdUpload const { Dragger: UploadDragger } = AntdUpload;
import get from 'lodash/get'; import get from 'lodash/get';
import findIndex from 'lodash/findIndex'; import findIndex from 'lodash/findIndex';
import Lightbox from 'react-image-lightbox'; import Lightbox from 'react-image-lightbox';
@ -16,77 +16,77 @@ import Lightbox from 'react-image-lightbox';
const exts = [ const exts = [
{ {
ext: /\.docx?$/i, ext: /\.docx?$/i,
icon: '//img.alicdn.com/tfs/TB1n8jfr1uSBuNjy1XcXXcYjFXa-200-200.png' icon: '//img.alicdn.com/tfs/TB1n8jfr1uSBuNjy1XcXXcYjFXa-200-200.png',
}, },
{ {
ext: /\.pptx?$/i, ext: /\.pptx?$/i,
icon: '//img.alicdn.com/tfs/TB1ItgWr_tYBeNjy1XdXXXXyVXa-200-200.png' icon: '//img.alicdn.com/tfs/TB1ItgWr_tYBeNjy1XdXXXXyVXa-200-200.png',
}, },
{ {
ext: /\.jpe?g$/i, ext: /\.jpe?g$/i,
icon: '//img.alicdn.com/tfs/TB1wrT5r9BYBeNjy0FeXXbnmFXa-200-200.png' icon: '//img.alicdn.com/tfs/TB1wrT5r9BYBeNjy0FeXXbnmFXa-200-200.png',
}, },
{ {
ext: /\.pdf$/i, ext: /\.pdf$/i,
icon: '//img.alicdn.com/tfs/TB1GwD8r9BYBeNjy0FeXXbnmFXa-200-200.png' icon: '//img.alicdn.com/tfs/TB1GwD8r9BYBeNjy0FeXXbnmFXa-200-200.png',
}, },
{ {
ext: /\.png$/i, ext: /\.png$/i,
icon: '//img.alicdn.com/tfs/TB1BHT5r9BYBeNjy0FeXXbnmFXa-200-200.png' icon: '//img.alicdn.com/tfs/TB1BHT5r9BYBeNjy0FeXXbnmFXa-200-200.png',
}, },
{ {
ext: /\.eps$/i, ext: /\.eps$/i,
icon: '//img.alicdn.com/tfs/TB1G_iGrVOWBuNjy0FiXXXFxVXa-200-200.png' icon: '//img.alicdn.com/tfs/TB1G_iGrVOWBuNjy0FiXXXFxVXa-200-200.png',
}, },
{ {
ext: /\.ai$/i, ext: /\.ai$/i,
icon: '//img.alicdn.com/tfs/TB1B2cVr_tYBeNjy1XdXXXXyVXa-200-200.png' icon: '//img.alicdn.com/tfs/TB1B2cVr_tYBeNjy1XdXXXXyVXa-200-200.png',
}, },
{ {
ext: /\.gif$/i, ext: /\.gif$/i,
icon: '//img.alicdn.com/tfs/TB1DTiGrVOWBuNjy0FiXXXFxVXa-200-200.png' icon: '//img.alicdn.com/tfs/TB1DTiGrVOWBuNjy0FiXXXFxVXa-200-200.png',
}, },
{ {
ext: /\.svg$/i, ext: /\.svg$/i,
icon: '//img.alicdn.com/tfs/TB1uUm9rY9YBuNjy0FgXXcxcXXa-200-200.png' icon: '//img.alicdn.com/tfs/TB1uUm9rY9YBuNjy0FgXXcxcXXa-200-200.png',
}, },
{ {
ext: /\.xlsx?$/i, ext: /\.xlsx?$/i,
icon: '//img.alicdn.com/tfs/TB1any1r1OSBuNjy0FdXXbDnVXa-200-200.png' icon: '//img.alicdn.com/tfs/TB1any1r1OSBuNjy0FdXXbDnVXa-200-200.png',
}, },
{ {
ext: /\.psd?$/i, ext: /\.psd?$/i,
icon: '//img.alicdn.com/tfs/TB1_nu1r1OSBuNjy0FdXXbDnVXa-200-200.png' icon: '//img.alicdn.com/tfs/TB1_nu1r1OSBuNjy0FdXXbDnVXa-200-200.png',
}, },
{ {
ext: /\.(wav|aif|aiff|au|mp1|mp2|mp3|ra|rm|ram|mid|rmi)$/i, ext: /\.(wav|aif|aiff|au|mp1|mp2|mp3|ra|rm|ram|mid|rmi)$/i,
icon: '//img.alicdn.com/tfs/TB1jPvwr49YBuNjy0FfXXXIsVXa-200-200.png' icon: '//img.alicdn.com/tfs/TB1jPvwr49YBuNjy0FfXXXIsVXa-200-200.png',
}, },
{ {
ext: /\.(avi|wmv|mpg|mpeg|vob|dat|3gp|mp4|mkv|rm|rmvb|mov|flv)$/i, ext: /\.(avi|wmv|mpg|mpeg|vob|dat|3gp|mp4|mkv|rm|rmvb|mov|flv)$/i,
icon: '//img.alicdn.com/tfs/TB1FrT5r9BYBeNjy0FeXXbnmFXa-200-200.png' icon: '//img.alicdn.com/tfs/TB1FrT5r9BYBeNjy0FeXXbnmFXa-200-200.png',
}, },
{ {
ext: /\.(zip|rar|arj|z|gz|iso|jar|ace|tar|uue|dmg|pkg|lzh|cab)$/i, ext: /\.(zip|rar|arj|z|gz|iso|jar|ace|tar|uue|dmg|pkg|lzh|cab)$/i,
icon: '//img.alicdn.com/tfs/TB10jmfr29TBuNjy0FcXXbeiFXa-200-200.png' icon: '//img.alicdn.com/tfs/TB10jmfr29TBuNjy0FcXXbeiFXa-200-200.png',
}, },
{ {
ext: /\.[^.]+$/i, ext: /\.[^.]+$/i,
icon: '//img.alicdn.com/tfs/TB10.R4r3mTBuNjy1XbXXaMrVXa-200-200.png' icon: '//img.alicdn.com/tfs/TB10.R4r3mTBuNjy1XbXXaMrVXa-200-200.png',
} },
] ];
const testOpts = (ext, options) => { const testOpts = (ext, options) => {
if (options && isArr(options.include)) { if (options && isArr(options.include)) {
return options.include.some(url => ext.test(url)) return options.include.some(url => ext.test(url));
} }
if (options && isArr(options.exclude)) { if (options && isArr(options.exclude)) {
return !options.exclude.some(url => ext.test(url)) return !options.exclude.some(url => ext.test(url));
} }
return true return true;
} };
export const testUrl = (url, options) => { export const testUrl = (url, options) => {
for (let i = 0; i < exts.length; i++) { for (let i = 0; i < exts.length; i++) {
@ -96,24 +96,24 @@ export const testUrl = (url, options) => {
} }
return true; return true;
} };
export const getImageByUrl = (url, options) => { export const getImageByUrl = (url, options) => {
for (let i = 0; i < exts.length; i++) { for (let i = 0; i < exts.length; i++) {
if (exts[i].ext.test(url) && testOpts(exts[i].ext, options)) { if (exts[i].ext.test(url) && testOpts(exts[i].ext, options)) {
return exts[i].icon || url return exts[i].icon || url;
} }
} }
return url return url;
} };
function toFileObject(item) { function toFileObject(item) {
console.log(item); console.log(item);
if (typeof item === 'number') { if (typeof item === 'number') {
return { return {
id: item, id: item,
} };
} }
if (item.id && item.uid && item.url) { if (item.id && item.uid && item.url) {
return item; return item;
@ -145,7 +145,7 @@ function toValue(item) {
if (typeof item === 'number') { if (typeof item === 'number') {
return { return {
id: item, id: item,
} };
} }
if (item.id && item.uid && item.url) { if (item.id && item.uid && item.url) {
return item; return item;
@ -176,14 +176,18 @@ function toValues(fileList) {
export function getImgUrls(value) { export function getImgUrls(value) {
const values = Array.isArray(value) ? value : [value]; const values = Array.isArray(value) ? value : [value];
return values.filter(item => testUrl(item.url, { return values
exclude: ['.png', '.jpg', '.jpeg', '.gif'] .filter(item =>
})).map(item => toValue(item)); testUrl(item.url, {
exclude: ['.png', '.jpg', '.jpeg', '.gif'],
}),
)
.map(item => toValue(item));
} }
export const Upload = connect({ export const Upload = connect({
getProps: mapStyledProps getProps: mapStyledProps,
})((props) => { })(props => {
const { multiple = true, value, onChange } = props; const { multiple = true, value, onChange } = props;
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [imgIndex, setImgIndex] = useState(0); const [imgIndex, setImgIndex] = useState(0);
@ -193,9 +197,9 @@ export const Upload = connect({
action: `${process.env.API}/attachments:upload`, action: `${process.env.API}/attachments:upload`,
onChange({ fileList }) { onChange({ fileList }) {
console.log(fileList); console.log(fileList);
setFileList((fileList)); setFileList(fileList);
const list = toValues(fileList); const list = toValues(fileList);
onChange(multiple ? list : (list.shift()||null)); onChange(multiple ? list : list.shift() || null);
}, },
}; };
const images = getImgUrls(fileList); const images = getImgUrls(fileList);
@ -207,14 +211,14 @@ export const Upload = connect({
{...uploadProps} {...uploadProps}
fileList={fileList} fileList={fileList}
multiple={true} multiple={true}
onPreview={(file) => { onPreview={file => {
const value = toValue(file) || {}; const value = toValue(file) || {};
const index = findIndex(images, image => image.id === value.id); const index = findIndex(images, image => image.id === value.id);
if (index >= 0) { if (index >= 0) {
setImgIndex(index); setImgIndex(index);
setVisible(true); setVisible(true);
} else { } else {
window.open(value.url) window.open(value.url);
// window.location.href = value.url; // window.location.href = value.url;
} }
}} }}
@ -237,7 +241,8 @@ export const Upload = connect({
</> </>
)} )}
</AntdUpload> </AntdUpload>
{visible && <Lightbox {visible && (
<Lightbox
mainSrc={get(images, [imgIndex, 'url'])} mainSrc={get(images, [imgIndex, 'url'])}
nextSrc={get(images, [imgIndex + 1, 'url'])} nextSrc={get(images, [imgIndex + 1, 'url'])}
prevSrc={get(images, [imgIndex - 1, 'url'])} prevSrc={get(images, [imgIndex - 1, 'url'])}
@ -248,9 +253,10 @@ export const Upload = connect({
onMoveNextRequest={() => { onMoveNextRequest={() => {
setImgIndex((imgIndex + 1) % images.length); setImgIndex((imgIndex + 1) % images.length);
}} }}
/>} />
)}
</div> </div>
) );
}); });
export default Upload; export default Upload;

View File

@ -1 +1 @@
import 'antd/lib/upload/style/index' import 'antd/lib/upload/style/index';

View File

@ -1,9 +1,19 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { Button, Select, Input, Space, Form, InputNumber, DatePicker, TimePicker, Radio } from 'antd'; import {
Button,
Select,
Input,
Space,
Form,
InputNumber,
DatePicker,
TimePicker,
Radio,
} from 'antd';
import { PlusCircleOutlined, CloseCircleOutlined } from '@ant-design/icons'; import { PlusCircleOutlined, CloseCircleOutlined } from '@ant-design/icons';
import useDynamicList from './useDynamicList'; import useDynamicList from './useDynamicList';
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import { mapStyledProps } from '../shared' import { mapStyledProps } from '../shared';
import get from 'lodash/get'; import get from 'lodash/get';
import moment from 'moment'; import moment from 'moment';
import './style.less'; import './style.less';
@ -11,8 +21,17 @@ import api from '@/api-client';
import { useRequest } from 'umi'; import { useRequest } from 'umi';
export function FilterGroup(props: any) { export function FilterGroup(props: any) {
const { fields = [], sourceFields = [], onDelete, onChange, onAdd, dataSource = [] } = props; const {
const { list, getKey, push, remove, replace } = useDynamicList<any>(dataSource); fields = [],
sourceFields = [],
onDelete,
onChange,
onAdd,
dataSource = [],
} = props;
const { list, getKey, push, remove, replace } = useDynamicList<any>(
dataSource,
);
let style: any = { let style: any = {
position: 'relative', position: 'relative',
}; };
@ -24,12 +43,13 @@ export function FilterGroup(props: any) {
// const Component = item.type === 'group' ? FilterGroup : FilterItem; // const Component = item.type === 'group' ? FilterGroup : FilterItem;
return ( return (
<div style={{ marginBottom: 8 }}> <div style={{ marginBottom: 8 }}>
{<FilterItem {
<FilterItem
fields={fields} fields={fields}
sourceFields={sourceFields} sourceFields={sourceFields}
dataSource={item} dataSource={item}
// showDeleteButton={list.length > 1} // showDeleteButton={list.length > 1}
onChange={(value) => { onChange={value => {
replace(index, value); replace(index, value);
const newList = [...list]; const newList = [...list];
newList[index] = value; newList[index] = value;
@ -43,20 +63,25 @@ export function FilterGroup(props: any) {
onChange(newList); onChange(newList);
// console.log(list, index); // console.log(list, index);
}} }}
/>} />
}
</div> </div>
); );
})} })}
</div> </div>
<div> <div>
<Space> <Space>
<Button style={{padding: 0}} type={'link'} onClick={() => { <Button
style={{ padding: 0 }}
type={'link'}
onClick={() => {
const data = {}; const data = {};
push(data); push(data);
const newList = [...list]; const newList = [...list];
newList.push(data); newList.push(data);
onChange(newList); onChange(newList);
}}> }}
>
<PlusCircleOutlined /> <PlusCircleOutlined />
</Button> </Button>
</Space> </Space>
@ -175,22 +200,26 @@ const op = {
attachment: OP_MAP.file, attachment: OP_MAP.file,
}; };
const StringInput = (props) => { const StringInput = props => {
const { value, onChange, ...restProps } = props; const { value, onChange, ...restProps } = props;
return ( return (
<Input {...restProps} defaultValue={value} onChange={(e) => { <Input
{...restProps}
defaultValue={value}
onChange={e => {
onChange(e.target.value); onChange(e.target.value);
}}/> }}
/>
); );
} };
const controls = { const controls = {
string: StringInput, string: StringInput,
textarea: StringInput, textarea: StringInput,
number: InputNumber, number: InputNumber,
percent: (props) => ( percent: props => (
<InputNumber <InputNumber
formatter={value => value ? `${value}%` : ''} formatter={value => (value ? `${value}%` : '')}
parser={value => value.replace('%', '')} parser={value => value.replace('%', '')}
{...props} {...props}
/> />
@ -213,9 +242,13 @@ function DateControl(props: any) {
// } // }
const m = moment(value, format); const m = moment(value, format);
return ( return (
<DatePicker format={format} value={m.isValid() ? m : null} onChange={(value) => { <DatePicker
onChange(value ? value.format('YYYY-MM-DD') : null) format={format}
}}/> value={m.isValid() ? m : null}
onChange={value => {
onChange(value ? value.format('YYYY-MM-DD') : null);
}}
/>
); );
// return ( // return (
// <DatePicker format={format} showTime={field.showTime} value={m.isValid() ? m : null} onChange={(value) => { // <DatePicker format={format} showTime={field.showTime} value={m.isValid() ? m : null} onChange={(value) => {
@ -228,13 +261,15 @@ function TimeControl(props: any) {
const { field, value, onChange, ...restProps } = props; const { field, value, onChange, ...restProps } = props;
let format = field.timeFormat; let format = field.timeFormat;
const m = moment(value, format); const m = moment(value, format);
return <TimePicker return (
<TimePicker
value={m.isValid() ? m : null} value={m.isValid() ? m : null}
format={field.timeFormat} format={field.timeFormat}
onChange={(value) => { onChange={value => {
onChange(value ? value.format('HH:mm:ss') : null) onChange(value ? value.format('HH:mm:ss') : null);
}} }}
/> />
);
} }
function OptionControl(props) { function OptionControl(props) {
@ -244,19 +279,27 @@ function OptionControl(props) {
mode = undefined; mode = undefined;
} }
return ( return (
<Select style={{ minWidth: 120 }} mode={mode} value={value} onChange={(value) => { <Select
style={{ minWidth: 120 }}
mode={mode}
value={value}
onChange={value => {
onChange(value); onChange(value);
}} options={options}> }}
</Select> options={options}
></Select>
); );
} }
function BooleanControl(props) { function BooleanControl(props) {
const { value, onChange, ...restProps } = props; const { value, onChange, ...restProps } = props;
return ( return (
<Radio.Group value={value} onChange={(e) => { <Radio.Group
value={value}
onChange={e => {
onChange(e.target.value); onChange(e.target.value);
}}> }}
>
<Radio value={true}></Radio> <Radio value={true}></Radio>
<Radio value={false}></Radio> <Radio value={false}></Radio>
</Radio.Group> </Radio.Group>
@ -268,7 +311,14 @@ function NullControl(props) {
} }
export function FilterItem(props: FilterItemProps) { export function FilterItem(props: FilterItemProps) {
const { index, fields = [], sourceFields = [], showDeleteButton = true, onDelete, onChange } = props; const {
index,
fields = [],
sourceFields = [],
showDeleteButton = true,
onDelete,
onChange,
} = props;
const [type, setType] = useState('string'); const [type, setType] = useState('string');
const [field, setField] = useState<any>({}); const [field, setField] = useState<any>({});
const [dataSource, setDataSource] = useState(props.dataSource || {}); const [dataSource, setDataSource] = useState(props.dataSource || {});
@ -283,27 +333,27 @@ export function FilterItem(props: FilterItemProps) {
setType(componentType); setType(componentType);
} }
setDataSource({ ...props.dataSource }); setDataSource({ ...props.dataSource });
}, [ }, [props.dataSource, type]);
props.dataSource, type,
]);
let ValueControl = controls[type] || controls.string; let ValueControl = controls[type] || controls.string;
if (['truncate'].indexOf(dataSource.op) !== -1) { if (['truncate'].indexOf(dataSource.op) !== -1) {
ValueControl = NullControl; ValueControl = NullControl;
} else if (dataSource.op === 'ref') { } else if (dataSource.op === 'ref') {
ValueControl = () => { ValueControl = () => {
return ( return (
<Select value={dataSource.value} <Select
onChange={(value) => { value={dataSource.value}
onChange={value => {
onChange({ ...dataSource, value: value }); onChange({ ...dataSource, value: value });
}} }}
style={{ width: 120 }} style={{ width: 120 }}
placeholder={'选择字段'}> placeholder={'选择字段'}
>
{sourceFields.map(field => ( {sourceFields.map(field => (
<Select.Option value={field.name}>{field.title}</Select.Option> <Select.Option value={field.name}>{field.title}</Select.Option>
))} ))}
</Select> </Select>
) );
} };
} }
// let multiple = true; // let multiple = true;
// if () // if ()
@ -316,24 +366,33 @@ export function FilterItem(props: FilterItemProps) {
console.log({ field, dataSource, type, ValueControl }); console.log({ field, dataSource, type, ValueControl });
return ( return (
<Space> <Space>
<Select value={dataSource.column} <Select
onChange={(value) => { value={dataSource.column}
onChange={value => {
const field = fields.find(field => field.name === value); const field = fields.find(field => field.name === value);
let componentType = field.component.type; let componentType = field.component.type;
if (field.component.type === 'select' && field.multiple) { if (field.component.type === 'select' && field.multiple) {
componentType = 'multipleSelect'; componentType = 'multipleSelect';
} }
setType(componentType); setType(componentType);
onChange({...dataSource, column: value, op: get(opOptions, [0, 'value']), value: undefined}); onChange({
...dataSource,
column: value,
op: get(opOptions, [0, 'value']),
value: undefined,
});
}} }}
style={{ width: 120 }} style={{ width: 120 }}
placeholder={'选择字段'}> placeholder={'选择字段'}
>
{fields.map(field => ( {fields.map(field => (
<Select.Option value={field.name}>{field.title}</Select.Option> <Select.Option value={field.name}>{field.title}</Select.Option>
))} ))}
</Select> </Select>
<Select value={dataSource.column ? dataSource.op : null} style={{ minWidth: 130 }} <Select
onChange={(value) => { value={dataSource.column ? dataSource.op : null}
style={{ minWidth: 130 }}
onChange={value => {
onChange({ ...dataSource, op: value, value: undefined }); onChange({ ...dataSource, op: value, value: undefined });
}} }}
defaultValue={get(opOptions, [0, 'value'])} defaultValue={get(opOptions, [0, 'value'])}
@ -349,15 +408,22 @@ export function FilterItem(props: FilterItemProps) {
op={dataSource.op} op={dataSource.op}
options={field.dataSource} options={field.dataSource}
value={dataSource.value} value={dataSource.value}
onChange={(value) => { onChange={value => {
onChange({ ...dataSource, value: value }); onChange({ ...dataSource, value: value });
}} }}
style={{ width: 180 }} style={{ width: 180 }}
/> />
{showDeleteButton && ( {showDeleteButton && (
<Button className={'filter-remove-link filter-item'} type={'link'} style={{padding: 0}} onClick={(e) => { <Button
className={'filter-remove-link filter-item'}
type={'link'}
style={{ padding: 0 }}
onClick={e => {
onDelete && onDelete(e); onDelete && onDelete(e);
}}><CloseCircleOutlined /></Button> }}
>
<CloseCircleOutlined />
</Button>
)} )}
</Space> </Space>
); );
@ -365,35 +431,65 @@ export function FilterItem(props: FilterItemProps) {
export const Values = connect({ export const Values = connect({
getProps: mapStyledProps, getProps: mapStyledProps,
})((props) => { })(props => {
const {
value = [],
onChange,
associatedKey,
sourceName,
sourceFilter = {},
filter = {},
fields = [],
...restProps
} = props;
const { value = [], onChange, associatedKey, sourceName, sourceFilter = {}, filter = {}, fields = [], ...restProps } = props; const { data = [], loading = true } = useRequest(
() => {
const { data = [], loading = true } = useRequest(() => { return associatedKey
return associatedKey ? api.resource(`collections.fields`).list({ ? api.resource(`collections.fields`).list({
associatedKey, associatedKey,
filter, filter,
}) : Promise.resolve({ })
: Promise.resolve({
data: fields, data: fields,
}); });
}, { },
refreshDeps: [associatedKey] {
}); refreshDeps: [associatedKey],
},
);
const { data: sourceFields = [] } = useRequest(() => { const { data: sourceFields = [] } = useRequest(
return sourceName ? api.resource(`collections.fields`).list({ () => {
return sourceName
? api.resource(`collections.fields`).list({
associatedKey: sourceName, associatedKey: sourceName,
filter: sourceFilter, filter: sourceFilter,
}) : Promise.resolve({ })
: Promise.resolve({
data: [], data: [],
}); });
}, { },
refreshDeps: [sourceName] {
}); refreshDeps: [sourceName],
},
);
return <FilterGroup dataSource={Array.isArray(value) ? value.filter(item => Object.keys(item).length) : []} onChange={(values) => { return (
<FilterGroup
dataSource={
Array.isArray(value)
? value.filter(item => Object.keys(item).length)
: []
}
onChange={values => {
onChange(values.filter(item => Object.keys(item).length)); onChange(values.filter(item => Object.keys(item).length));
}} {...restProps} fields={data} sourceFields={sourceFields}/> }}
{...restProps}
fields={data}
sourceFields={sourceFields}
/>
);
}); });
export default Values; export default Values;

View File

@ -30,7 +30,7 @@ export default <T>(initialValue: T[]) => {
}; };
const insert = (index: number, obj: T) => { const insert = (index: number, obj: T) => {
setList((l) => { setList(l => {
const temp = [...l]; const temp = [...l];
temp.splice(index, 0, obj); temp.splice(index, 0, obj);
setKey(index); setKey(index);
@ -40,10 +40,11 @@ export default <T>(initialValue: T[]) => {
const getAll = () => list; const getAll = () => list;
const getKey = (index: number) => keyList.current[index]; const getKey = (index: number) => keyList.current[index];
const getIndex = (index: number) => keyList.current.findIndex((ele) => ele === index); const getIndex = (index: number) =>
keyList.current.findIndex(ele => ele === index);
const merge = (index: number, obj: T[]) => { const merge = (index: number, obj: T[]) => {
setList((l) => { setList(l => {
const temp = [...l]; const temp = [...l];
obj.forEach((_, i) => { obj.forEach((_, i) => {
setKey(index + i); setKey(index + i);
@ -54,7 +55,7 @@ export default <T>(initialValue: T[]) => {
}; };
const replace = (index: number, obj: T) => { const replace = (index: number, obj: T) => {
setList((l) => { setList(l => {
const temp = [...l]; const temp = [...l];
temp[index] = obj; temp[index] = obj;
return temp; return temp;
@ -62,7 +63,7 @@ export default <T>(initialValue: T[]) => {
}; };
const remove = (index: number) => { const remove = (index: number) => {
setList((l) => { setList(l => {
const temp = [...l]; const temp = [...l];
temp.splice(index, 1); temp.splice(index, 1);
@ -80,14 +81,16 @@ export default <T>(initialValue: T[]) => {
if (oldIndex === newIndex) { if (oldIndex === newIndex) {
return; return;
} }
setList((l) => { setList(l => {
const newList = [...l]; const newList = [...l];
const temp = newList.filter((_: {}, index: number) => index !== oldIndex); const temp = newList.filter((_: {}, index: number) => index !== oldIndex);
temp.splice(newIndex, 0, newList[oldIndex]); temp.splice(newIndex, 0, newList[oldIndex]);
// move keys if necessary // move keys if necessary
try { try {
const keyTemp = keyList.current.filter((_: {}, index: number) => index !== oldIndex); const keyTemp = keyList.current.filter(
(_: {}, index: number) => index !== oldIndex,
);
keyTemp.splice(newIndex, 0, keyList.current[oldIndex]); keyTemp.splice(newIndex, 0, keyList.current[oldIndex]);
keyList.current = keyTemp; keyList.current = keyTemp;
} catch (e) { } catch (e) {
@ -99,7 +102,7 @@ export default <T>(initialValue: T[]) => {
}; };
const push = (obj: T) => { const push = (obj: T) => {
setList((l) => { setList(l => {
setKey(l.length); setKey(l.length);
return l.concat([obj]); return l.concat([obj]);
}); });
@ -113,11 +116,11 @@ export default <T>(initialValue: T[]) => {
console.error(e); console.error(e);
} }
setList((l) => l.slice(0, l.length - 1)); setList(l => l.slice(0, l.length - 1));
}; };
const unshift = (obj: T) => { const unshift = (obj: T) => {
setList((l) => { setList(l => {
setKey(0); setKey(0);
return [obj].concat(l); return [obj].concat(l);
}); });
@ -127,8 +130,8 @@ export default <T>(initialValue: T[]) => {
result result
.map((item, index) => ({ key: index, item })) // add index into obj .map((item, index) => ({ key: index, item })) // add index into obj
.sort((a, b) => getIndex(a.key) - getIndex(b.key)) // sort based on the index of table .sort((a, b) => getIndex(a.key) - getIndex(b.key)) // sort based on the index of table
.filter((item) => !!item.item) // remove undefined(s) .filter(item => !!item.item) // remove undefined(s)
.map((item) => item.item); // retrive the data .map(item => item.item); // retrive the data
const shift = () => { const shift = () => {
// remove keys if necessary // remove keys if necessary
@ -137,7 +140,7 @@ export default <T>(initialValue: T[]) => {
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
setList((l) => l.slice(1, l.length)); setList(l => l.slice(1, l.length));
}; };
return { return {

View File

@ -1,11 +1,15 @@
import { connect } from '@formily/react-schema-renderer' import { connect } from '@formily/react-schema-renderer';
import React from 'react'; import React from 'react';
import { Input as AntdInput } from 'antd' import { Input as AntdInput } from 'antd';
import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared' import { acceptEnum, mapStyledProps, mapTextComponent } from '../shared';
export const Wysiwyg = connect({ export const Wysiwyg = connect({
getProps: mapStyledProps, getProps: mapStyledProps,
getComponent: mapTextComponent getComponent: mapTextComponent,
})(acceptEnum((props) => <AntdInput.TextArea autoSize={{minRows: 2, maxRows: 12}} {...props}/>)) })(
acceptEnum(props => (
<AntdInput.TextArea autoSize={{ minRows: 2, maxRows: 12 }} {...props} />
)),
);
export default Wysiwyg export default Wysiwyg;

View File

@ -13,9 +13,7 @@ import {
} from '@ant-design/icons'; } from '@ant-design/icons';
export const IconFont = createFromIconfontCN({ export const IconFont = createFromIconfontCN({
scriptUrl: [ scriptUrl: ['//at.alicdn.com/t/font_2261954_u9jzwc44ug.js'],
'//at.alicdn.com/t/font_2261954_u9jzwc44ug.js',
],
}); });
export const icons = new Map<string, any>(); export const icons = new Map<string, any>();

View File

@ -20,8 +20,12 @@ export default (props: any) => {
} }
return ( return (
<Menu <Menu
defaultSelectedKeys={paths.filter(path => pathcamp(location.pathname, path)).concat(location.pathname)} defaultSelectedKeys={paths
defaultOpenKeys={paths.filter(path => pathcamp(location.pathname, path)).concat(location.pathname)} .filter(path => pathcamp(location.pathname, path))
.concat(location.pathname)}
defaultOpenKeys={paths
.filter(path => pathcamp(location.pathname, path))
.concat(location.pathname)}
{...restProps} {...restProps}
> >
{items.map(item => { {items.map(item => {
@ -32,20 +36,24 @@ export default (props: any) => {
const subItems = children.filter(child => child.showInMenu); const subItems = children.filter(child => child.showInMenu);
if (!hideChildren && subItems.length > 1) { if (!hideChildren && subItems.length > 1) {
return ( return (
<Menu.SubMenu key={`${item.path}`} icon={<Icon type={item.icon}/>} title={<>{item.title}</>}> <Menu.SubMenu
key={`${item.path}`}
icon={<Icon type={item.icon} />}
title={<>{item.title}</>}
>
{subItems.map((child: any) => ( {subItems.map((child: any) => (
<Menu.Item key={child.path}> <Menu.Item key={child.path}>
<Link to={child.path}>{child.title}</Link> <Link to={child.path}>{child.title}</Link>
</Menu.Item> </Menu.Item>
))} ))}
</Menu.SubMenu> </Menu.SubMenu>
) );
} }
return ( return (
<Menu.Item icon={<Icon type={item.icon} />} key={item.path}> <Menu.Item icon={<Icon type={item.icon} />} key={item.path}>
<Link to={item.path}>{item.title}</Link> <Link to={item.path}>{item.title}</Link>
</Menu.Item> </Menu.Item>
) );
})} })}
</Menu> </Menu>
); );

View File

@ -1,6 +1,11 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Space, Button, Popconfirm, Popover } from 'antd'; import { Space, Button, Popconfirm, Popover } from 'antd';
import { FilterOutlined, PlusOutlined, SelectOutlined, DeleteOutlined } from '@ant-design/icons'; import {
FilterOutlined,
PlusOutlined,
SelectOutlined,
DeleteOutlined,
} from '@ant-design/icons';
import Drawer from '@/components/pages/AdminLoader/Drawer'; import Drawer from '@/components/pages/AdminLoader/Drawer';
import View from '@/components/pages/AdminLoader/View'; import View from '@/components/pages/AdminLoader/View';
import get from 'lodash/get'; import get from 'lodash/get';
@ -26,25 +31,29 @@ export function Create(props) {
associatedKey={associatedKey} associatedKey={associatedKey}
viewName={viewName} viewName={viewName}
onReset={resolve} onReset={resolve}
onDraft={async (item) => { onDraft={async item => {
const values = transform ? {} : item; const values = transform ? {} : item;
for (const [sourceKey, targetKey] of Object.entries<string>(transform || {})) { for (const [sourceKey, targetKey] of Object.entries<string>(
transform || {},
)) {
const value = get({ data: item }, sourceKey); const value = get({ data: item }, sourceKey);
set(values, targetKey, value); set(values, targetKey, value);
} }
await resolve(); await resolve();
console.log('onFinish', values); console.log('onFinish', values);
onFinish && await onFinish(values); onFinish && (await onFinish(values));
}} }}
onFinish={async (item) => { onFinish={async item => {
const values = transform ? {} : item; const values = transform ? {} : item;
for (const [sourceKey, targetKey] of Object.entries<string>(transform || {})) { for (const [sourceKey, targetKey] of Object.entries<string>(
transform || {},
)) {
const value = get({ data: item }, sourceKey); const value = get({ data: item }, sourceKey);
set(values, targetKey, value); set(values, targetKey, value);
} }
await resolve(); await resolve();
console.log('onFinish', values); console.log('onFinish', values);
onFinish && await onFinish(values); onFinish && (await onFinish(values));
}} }}
/> />
</div> </div>
@ -54,9 +63,11 @@ export function Create(props) {
icon={<PlusOutlined />} icon={<PlusOutlined />}
type={'primary'} type={'primary'}
{...componentProps} {...componentProps}
>{ title }</Button> >
{title}
</Button>
</> </>
) );
} }
export function Update(props) { export function Update(props) {
@ -76,16 +87,16 @@ export function Update(props) {
data={data} data={data}
viewName={viewName} viewName={viewName}
onReset={resolve} onReset={resolve}
onDraft={async (values) => { onDraft={async values => {
await resolve(); await resolve();
onFinish && await onFinish(values); onFinish && (await onFinish(values));
}} }}
onValueChange={() => { onValueChange={() => {
closeWithConfirm && closeWithConfirm(true); closeWithConfirm && closeWithConfirm(true);
}} }}
onFinish={async (values) => { onFinish={async values => {
await resolve(); await resolve();
onFinish && await onFinish(values); onFinish && (await onFinish(values));
}} }}
/> />
</div> </div>
@ -94,9 +105,11 @@ export function Update(props) {
}} }}
icon={<PlusOutlined />} icon={<PlusOutlined />}
type={'primary'} type={'primary'}
>{ title }</Button> >
{title}
</Button>
</> </>
) );
} }
export function Add(props) { export function Add(props) {
@ -119,29 +132,38 @@ export function Add(props) {
defaultFilter={filter} defaultFilter={filter}
viewName={viewName} viewName={viewName}
associatedKey={associatedKey} associatedKey={associatedKey}
onSelected={(values) => { onSelected={values => {
console.log(values); console.log(values);
setSelectedRows(values.map( item => { setSelectedRows(
values.map(item => {
if (!transform) { if (!transform) {
return; return;
} }
const data = {}; const data = {};
for (const [sourceKey, targetKey] of Object.entries<string>(transform)) { for (const [sourceKey, targetKey] of Object.entries<
string
>(transform)) {
const value = get({ data: item }, sourceKey); const value = get({ data: item }, sourceKey);
set(data, targetKey, value); set(data, targetKey, value);
} }
return data; return data;
})); }),
);
}} }}
/> />
<Drawer.Footer> <Drawer.Footer>
<Space> <Space>
<Button onClick={resolve}></Button> <Button onClick={resolve}></Button>
<Button type={'primary'} onClick={async () => { <Button
type={'primary'}
onClick={async () => {
console.log({ schema, onFinish }); console.log({ schema, onFinish });
onFinish && await onFinish(selectedRows); onFinish && (await onFinish(selectedRows));
resolve(); resolve();
}}></Button> }}
>
</Button>
</Space> </Space>
</Drawer.Footer> </Drawer.Footer>
</div> </div>
@ -151,27 +173,34 @@ export function Add(props) {
}} }}
icon={<SelectOutlined />} icon={<SelectOutlined />}
{...componentProps} {...componentProps}
>{ title }</Button> >
{title}
</Button>
</> </>
) );
} }
export function Destroy(props) { export function Destroy(props) {
const { size, schema = {}, onFinish } = props; const { size, schema = {}, onFinish } = props;
const { title, componentProps = {} } = schema; const { title, componentProps = {} } = schema;
return ( return (
<Popconfirm title="确认删除吗?" onConfirm={async (e) => { <Popconfirm
onFinish && await onFinish(); title="确认删除吗?"
}}> onConfirm={async e => {
onFinish && (await onFinish());
}}
>
<Button <Button
size={size} size={size}
danger danger
type={'ghost'} type={'ghost'}
icon={<DeleteOutlined />} icon={<DeleteOutlined />}
{...componentProps} {...componentProps}
>{ title }</Button> >
{title}
</Button>
</Popconfirm> </Popconfirm>
) );
} }
export function Filter(props) { export function Filter(props) {
@ -205,52 +234,60 @@ export function Filter(props) {
defaultVisible={visible} defaultVisible={visible}
placement={'bottomLeft'} placement={'bottomLeft'}
destroyTooltipOnHide destroyTooltipOnHide
onVisibleChange={(visible) => { onVisibleChange={visible => {
setVisible(visible); setVisible(visible);
}} }}
className={'filters-popover'} className={'filters-popover'}
style={{ style={{}}
}}
overlayStyle={{ overlayStyle={{
minWidth: 500 minWidth: 500,
}} }}
content={( content={
<> <>
<View data={data} onFinish={async (values) => { <View
data={data}
onFinish={async values => {
if (values) { if (values) {
const items = values.filter.and || values.filter.or; const items = values.filter.and || values.filter.or;
setFilterCount(Object.keys(items).length); setFilterCount(Object.keys(items).length);
setData(values); setData(values);
onFinish && await onFinish(values); onFinish && (await onFinish(values));
} }
setVisible(false); setVisible(false);
}} schema={{ }}
"type": "filterForm", schema={{
"fields": [{ type: 'filterForm',
"dataIndex": ["filter"], fields: [
"name": "filter", {
"interface": "json", dataIndex: ['filter'],
"type": "json", name: 'filter',
"component": { interface: 'json',
"type": "filter", type: 'json',
component: {
type: 'filter',
'x-component-props': { 'x-component-props': {
fields, fields,
}
}, },
}], },
}}/> },
],
}}
/>
</> </>
)} }
> >
<Button icon={<FilterOutlined />} >{filterCount ? `${filterCount}${title}` : title}</Button> <Button icon={<FilterOutlined />}>
{filterCount ? `${filterCount}${title}` : title}
</Button>
</Popover> </Popover>
</> </>
) );
} }
export function Actions(props) { export function Actions(props) {
const { onTrigger = {}, actions = [], style, ...restProps } = props; const { onTrigger = {}, actions = [], style, ...restProps } = props;
return actions.length > 0 && ( return (
actions.length > 0 && (
<div className={'action-buttons'} style={style}> <div className={'action-buttons'} style={style}>
{actions.map(action => ( {actions.map(action => (
<div className={`${action.type}-action-button action-button`}> <div className={`${action.type}-action-button action-button`}>
@ -262,6 +299,7 @@ export function Actions(props) {
</div> </div>
))} ))}
</div> </div>
)
); );
} }

View File

@ -1,41 +1,41 @@
import React, { Fragment, useLayoutEffect, useRef, useState } from 'react' import React, { Fragment, useLayoutEffect, useRef, useState } from 'react';
import ReactDOM, { createPortal } from 'react-dom' import ReactDOM, { createPortal } from 'react-dom';
import { createForm } from '@formily/core' import { createForm } from '@formily/core';
// import { FormProvider } from '@formily/react' // import { FormProvider } from '@formily/react'
import { isNum, isStr, isBool, isFn } from '@formily/shared' import { isNum, isStr, isBool, isFn } from '@formily/shared';
import { Modal, Drawer } from 'antd' import { Modal, Drawer } from 'antd';
import { DrawerProps } from 'antd/lib/drawer' import { DrawerProps } from 'antd/lib/drawer';
import { useContext } from 'react' import { useContext } from 'react';
import { ConfigProvider } from 'antd' import { ConfigProvider } from 'antd';
import zhCN from 'antd/lib/locale/zh_CN'; import zhCN from 'antd/lib/locale/zh_CN';
export const usePrefixCls = ( export const usePrefixCls = (
tag?: string, tag?: string,
props?: { props?: {
prefixCls?: string prefixCls?: string;
} },
) => { ) => {
const { getPrefixCls } = useContext(ConfigProvider.ConfigContext) const { getPrefixCls } = useContext(ConfigProvider.ConfigContext);
return getPrefixCls(tag, props?.prefixCls) return getPrefixCls(tag, props?.prefixCls);
} };
type DrawerTitle = string | number | React.ReactElement type DrawerTitle = string | number | React.ReactElement;
const isDrawerTitle = (props: any): props is DrawerTitle => { const isDrawerTitle = (props: any): props is DrawerTitle => {
return ( return (
isNum(props) || isStr(props) || isBool(props) || React.isValidElement(props) isNum(props) || isStr(props) || isBool(props) || React.isValidElement(props)
) );
} };
const getDrawerProps = (props: any): DrawerProps => { const getDrawerProps = (props: any): DrawerProps => {
if (isDrawerTitle(props)) { if (isDrawerTitle(props)) {
return { return {
title: props, title: props,
} };
} else { } else {
return props return props;
}
} }
};
const createElement = (content, props?: any) => { const createElement = (content, props?: any) => {
if (!content) { if (!content) {
@ -48,21 +48,15 @@ const createElement = (content, props?: any) => {
return content; return content;
} }
return React.createElement(content, props); return React.createElement(content, props);
} };
export interface IFormDrawer { export interface IFormDrawer {
open(props?: any): void open(props?: any): void;
close(): void close(): void;
} }
export function FormDrawer( export function FormDrawer(title: DrawerProps, content: any): IFormDrawer;
title: DrawerProps, export function FormDrawer(title: DrawerTitle, content: any): IFormDrawer;
content: any,
): IFormDrawer
export function FormDrawer(
title: DrawerTitle,
content: any,
): IFormDrawer
export function FormDrawer(title: any, content: any): IFormDrawer { export function FormDrawer(title: any, content: any): IFormDrawer {
document.querySelectorAll('.env-root').forEach(el => { document.querySelectorAll('.env-root').forEach(el => {
el.className = 'env-root env-root-push'; el.className = 'env-root env-root-push';
@ -70,24 +64,24 @@ export function FormDrawer(title: any, content: any): IFormDrawer {
const env = { const env = {
root: document.createElement('div'), root: document.createElement('div'),
promise: null, promise: null,
} };
env.root.className = 'env-root'; env.root.className = 'env-root';
const props = getDrawerProps(title) const props = getDrawerProps(title);
const drawer = { const drawer = {
width: '75%', width: '75%',
...props, ...props,
onClose: (e: any) => { onClose: (e: any) => {
props?.onClose?.(e); props?.onClose?.(e);
formDrawer.close() formDrawer.close();
}, },
afterVisibleChange: (visible: boolean) => { afterVisibleChange: (visible: boolean) => {
props?.afterVisibleChange?.(visible) props?.afterVisibleChange?.(visible);
if (visible) return if (visible) return;
ReactDOM.unmountComponentAtNode(env.root) ReactDOM.unmountComponentAtNode(env.root);
env.root?.parentNode?.removeChild(env.root) env.root?.parentNode?.removeChild(env.root);
env.root = undefined env.root = undefined;
}, },
} };
const formDrawer = { const formDrawer = {
open: (props: any) => { open: (props: any) => {
@ -95,27 +89,27 @@ export function FormDrawer(title: any, content: any): IFormDrawer {
false, false,
() => { () => {
formDrawer.closeWithConfirm = false; formDrawer.closeWithConfirm = false;
formDrawer.close() formDrawer.close();
}, },
() => { () => {
formDrawer.close() formDrawer.close();
} },
) );
setTimeout(() => { setTimeout(() => {
render( render(
true, true,
() => { () => {
formDrawer.closeWithConfirm = false; formDrawer.closeWithConfirm = false;
formDrawer.close() formDrawer.close();
}, },
() => { () => {
formDrawer.close() formDrawer.close();
} },
) );
}) });
}, },
close: () => { close: () => {
if (!env.root) return if (!env.root) return;
if (formDrawer.closeWithConfirm) { if (formDrawer.closeWithConfirm) {
Modal.confirm({ Modal.confirm({
title: '表单内容发生变化,确定不保存吗?', title: '表单内容发生变化,确定不保存吗?',
@ -128,7 +122,7 @@ export function FormDrawer(title: any, content: any): IFormDrawer {
const last = els[els.length - 1]; const last = els[els.length - 1];
last.className = 'env-root'; last.className = 'env-root';
} }
render(false) render(false);
}, },
}); });
} else { } else {
@ -137,15 +131,15 @@ export function FormDrawer(title: any, content: any): IFormDrawer {
const last = els[els.length - 1]; const last = els[els.length - 1];
last.className = 'env-root'; last.className = 'env-root';
} }
render(false) render(false);
} }
}, },
closeWithConfirm: false, closeWithConfirm: false,
} };
const closeWithConfirm = (bool) => { const closeWithConfirm = bool => {
formDrawer.closeWithConfirm = bool; formDrawer.closeWithConfirm = bool;
} };
const render = (visible = true, resolve?: () => any, reject?: () => any) => { const render = (visible = true, resolve?: () => any, reject?: () => any) => {
ReactDOM.render( ReactDOM.render(
@ -158,48 +152,48 @@ export function FormDrawer(title: any, content: any): IFormDrawer {
})} })}
</Drawer> </Drawer>
</ConfigProvider>, </ConfigProvider>,
env.root env.root,
) );
} };
document.body.appendChild(env.root) document.body.appendChild(env.root);
return formDrawer return formDrawer;
} }
const DrawerFooter: React.FC = (props) => { const DrawerFooter: React.FC = props => {
const ref = useRef<HTMLDivElement>() const ref = useRef<HTMLDivElement>();
const [footer, setFooter] = useState<HTMLDivElement>() const [footer, setFooter] = useState<HTMLDivElement>();
const footerRef = useRef<HTMLDivElement>() const footerRef = useRef<HTMLDivElement>();
const prefixCls = usePrefixCls('drawer') const prefixCls = usePrefixCls('drawer');
useLayoutEffect(() => { useLayoutEffect(() => {
const content = ref.current?.closest(`.${prefixCls}-wrapper-body`) const content = ref.current?.closest(`.${prefixCls}-wrapper-body`);
if (content) { if (content) {
if (!footerRef.current) { if (!footerRef.current) {
footerRef.current = content.querySelector(`.${prefixCls}-footer`) footerRef.current = content.querySelector(`.${prefixCls}-footer`);
if (!footerRef.current) { if (!footerRef.current) {
footerRef.current = document.createElement('div') footerRef.current = document.createElement('div');
footerRef.current.classList.add(`${prefixCls}-footer`) footerRef.current.classList.add(`${prefixCls}-footer`);
content.appendChild(footerRef.current) content.appendChild(footerRef.current);
} }
} }
setFooter(footerRef.current) setFooter(footerRef.current);
} }
}) });
footerRef.current = footer footerRef.current = footer;
return ( return (
<div ref={ref} style={{ display: 'none' }}> <div ref={ref} style={{ display: 'none' }}>
{footer && createPortal(props.children, footer)} {footer && createPortal(props.children, footer)}
</div> </div>
) );
} };
FormDrawer.open = (props) => { FormDrawer.open = props => {
const { content, visible, ...rest } = props; const { content, visible, ...rest } = props;
return FormDrawer(rest, content).open({ visible }); return FormDrawer(rest, content).open({ visible });
} };
FormDrawer.Footer = DrawerFooter FormDrawer.Footer = DrawerFooter;
export default FormDrawer export default FormDrawer;

View File

@ -11,14 +11,20 @@ import { markdown } from '@/components/views/Field';
export function Page(props: any) { export function Page(props: any) {
const { currentRowId, pageName, children, ...restProps } = props; const { currentRowId, pageName, children, ...restProps } = props;
const { initialState = {}, refresh, setInitialState } = useModel('@@initialState'); const { initialState = {}, refresh, setInitialState } = useModel(
'@@initialState',
);
const siteTitle = get(initialState, 'systemSettings.title') || 'NocoBase'; const siteTitle = get(initialState, 'systemSettings.title') || 'NocoBase';
const { data = {}, loading, error } = useRequest(() => api.resource('menus').getInfo({ const { data = {}, loading, error } = useRequest(
() =>
api.resource('menus').getInfo({
resourceKey: pageName, resourceKey: pageName,
}), { }),
{
refreshDeps: [pageName], refreshDeps: [pageName],
}); },
);
const history = useHistory(); const history = useHistory();
@ -27,7 +33,7 @@ export function Page(props: any) {
} }
if (loading) { if (loading) {
return <Spin/> return <Spin />;
} }
const views = data.views || []; const views = data.views || [];
@ -35,13 +41,11 @@ export function Page(props: any) {
return ( return (
<div> <div>
<Helmet> <Helmet>
<title>{data.title} - {siteTitle}</title> <title>
{data.title} - {siteTitle}
</title>
</Helmet> </Helmet>
<PageHeader <PageHeader title={data.title} ghost={false} {...restProps} />
title={data.title}
ghost={false}
{...restProps}
/>
<div className={'page-content'}> <div className={'page-content'}>
<Row className={'nb-row'} gutter={24}> <Row className={'nb-row'} gutter={24}>
{views.map(view => { {views.map(view => {
@ -49,7 +53,8 @@ export function Page(props: any) {
let span = 24; let span = 24;
if (typeof view === 'string') { if (typeof view === 'string') {
viewName = view; viewName = view;
} if (typeof view === 'object') { }
if (typeof view === 'object') {
viewName = `${view.name}`; viewName = `${view.name}`;
if (view.width === '50%') { if (view.width === '50%') {
span = 12; span = 12;
@ -67,10 +72,19 @@ export function Page(props: any) {
message.success('草稿保存成功'); message.success('草稿保存成功');
return; return;
} }
if (view.draft.returnType === 'message' && view.draft.message) { if (
view.draft.returnType === 'message' &&
view.draft.message
) {
Modal.success({ Modal.success({
title: '草稿保存成功', title: '草稿保存成功',
content: <div dangerouslySetInnerHTML={{__html: markdown(view.draft.message)}}/>, content: (
<div
dangerouslySetInnerHTML={{
__html: markdown(view.draft.message),
}}
/>
),
}); });
} else if (view.draft.returnType === 'redirect') { } else if (view.draft.returnType === 'redirect') {
const path = get(view, 'draft.redirect.name'); const path = get(view, 'draft.redirect.name');
@ -81,7 +95,13 @@ export function Page(props: any) {
if (view.returnType === 'message' && view.message) { if (view.returnType === 'message' && view.message) {
Modal.success({ Modal.success({
title: '提交成功', title: '提交成功',
content: <div dangerouslySetInnerHTML={{__html: markdown(view.message)}}/>, content: (
<div
dangerouslySetInnerHTML={{
__html: markdown(view.message),
}}
/>
),
}); });
} else if (view.returnType === 'redirect') { } else if (view.returnType === 'redirect') {
const path = get(view, 'redirect.name'); const path = get(view, 'redirect.name');
@ -98,6 +118,6 @@ export function Page(props: any) {
</div> </div>
</div> </div>
); );
}; }
export default Page; export default Page;

View File

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

View File

@ -2,7 +2,7 @@
position: relative; position: relative;
left: 0; left: 0;
z-index: 100; z-index: 100;
box-shadow: 2px 0 8px 0 rgba(29,35,41,.05); box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
&.collapsed { &.collapsed {
margin-left: -200px; margin-left: -200px;
} }

View File

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

View File

@ -5,7 +5,15 @@ import { Spin } from '@nocobase/client';
import { useRequest, useLocation } from 'umi'; import { useRequest, useLocation } from 'umi';
import api from '@/api-client'; import api from '@/api-client';
import { Actions } from '../Actions'; import { Actions } from '../Actions';
import { Table as AntdTable, Card, Pagination, Button, Tabs, Descriptions, Tooltip } from 'antd'; import {
Table as AntdTable,
Card,
Pagination,
Button,
Tabs,
Descriptions,
Tooltip,
} from 'antd';
import { LoadingOutlined } from '@ant-design/icons'; import { LoadingOutlined } from '@ant-design/icons';
import { components, fields2columns } from '@/components/views/SortableTable'; import { components, fields2columns } from '@/components/views/SortableTable';
import ReactDragListView from 'react-drag-listview'; import ReactDragListView from 'react-drag-listview';
@ -21,7 +29,11 @@ export function Association(props) {
const { targetViewName } = schema; const { targetViewName } = schema;
return ( return (
<div> <div>
<View {...restProps} associationSchema={schema} viewName={targetViewName}/> <View
{...restProps}
associationSchema={schema}
viewName={targetViewName}
/>
</div> </div>
); );
} }

View File

@ -2,12 +2,16 @@ import React, { useEffect, useRef, useState } from 'react';
import { Table as AntdTable, Card, Pagination } from 'antd'; import { Table as AntdTable, Card, Pagination } from 'antd';
import { useRequest, useHistory } from 'umi'; import { useRequest, useHistory } from 'umi';
import api from '@/api-client'; import api from '@/api-client';
import { LoadingOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons'; import {
LoadingOutlined,
LeftOutlined,
RightOutlined,
} from '@ant-design/icons';
import './style.less'; import './style.less';
import { Calendar as BigCalendar, momentLocalizer } from 'react-big-calendar' import { Calendar as BigCalendar, momentLocalizer } from 'react-big-calendar';
import * as dates from 'react-big-calendar/lib/utils/dates'; import * as dates from 'react-big-calendar/lib/utils/dates';
import moment from 'moment'; import moment from 'moment';
const localizer = momentLocalizer(moment) // or globalizeLocalizer const localizer = momentLocalizer(moment); // or globalizeLocalizer
import Drawer from '@/components/pages/AdminLoader/Drawer'; import Drawer from '@/components/pages/AdminLoader/Drawer';
import { Details, DetailsPage } from '../Table'; import { Details, DetailsPage } from '../Table';
import { Actions } from '../../Actions'; import { Actions } from '../../Actions';
@ -37,21 +41,22 @@ function toEvents(data, options: any = {}) {
id: item[idField], id: item[idField],
title: item[labelField], title: item[labelField],
allDay: true, allDay: true,
start: moment(item[startDateField||'createdAt'] || item.created_at).toDate(), start: moment(
end: moment(item[endDateField || startDateField || 'createdAt'] || item.created_at).toDate(), item[startDateField || 'createdAt'] || item.created_at,
).toDate(),
end: moment(
item[endDateField || startDateField || 'createdAt'] || item.created_at,
).toDate(),
})); }));
} }
export function Calendar(props: CalendarProps) { export function Calendar(props: CalendarProps) {
const { const { schema = {}, associatedKey, defaultFilter } = props;
schema = {},
associatedKey,
defaultFilter,
} = props;
const paginated = false; const paginated = false;
const { rowKey = 'id', const {
rowKey = 'id',
filter: schemaFilter, filter: schemaFilter,
sort, sort,
appends, appends,
@ -64,16 +69,19 @@ export function Calendar(props: CalendarProps) {
actions = [], actions = [],
} = schema; } = schema;
console.log({schema}) console.log({ schema });
const [calendarView, setCalendarView] = useState('month'); const [calendarView, setCalendarView] = useState('month');
const history = useHistory(); const history = useHistory();
const { data, loading, mutate, refresh, run, params } = useRequest((params = {}, ...args) => { const { data, loading, mutate, refresh, run, params } = useRequest(
(params = {}, ...args) => {
const { current, pageSize, sorter, filter, ...restParams } = params; const { current, pageSize, sorter, filter, ...restParams } = params;
console.log('paramsparamsparamsparamsparams', params, args); console.log('paramsparamsparamsparamsparams', params, args);
return api.resource(resourceName).list({ return api
.resource(resourceName)
.list({
associatedKey, associatedKey,
page: paginated ? current : 1, page: paginated ? current : 1,
perPage: paginated ? pageSize : -1, perPage: paginated ? pageSize : -1,
@ -90,8 +98,8 @@ export function Calendar(props: CalendarProps) {
// __parent ? { // __parent ? {
// collection_name: __parent, // collection_name: __parent,
// } : null, // } : null,
].filter(obj => obj && Object.keys(obj).length) ].filter(obj => obj && Object.keys(obj).length),
} },
// ...args2, // ...args2,
}) })
.then(({ data = [], meta = {} }) => { .then(({ data = [], meta = {} }) => {
@ -102,9 +110,11 @@ export function Calendar(props: CalendarProps) {
}, },
}; };
}); });
}, { },
{
paginated, paginated,
}); },
);
const events = toEvents(data?.list, { const events = toEvents(data?.list, {
idField: rowKey, idField: rowKey,
@ -115,8 +125,16 @@ export function Calendar(props: CalendarProps) {
const messages = { const messages = {
allDay: '', allDay: '',
previous: <div><LeftOutlined /></div>, previous: (
next: <div><RightOutlined/></div>, <div>
<LeftOutlined />
</div>
),
next: (
<div>
<RightOutlined />
</div>
),
today: '今天', today: '今天',
month: '月', month: '月',
week: '周', week: '周',
@ -126,14 +144,15 @@ export function Calendar(props: CalendarProps) {
time: '时间', time: '时间',
event: '事件', event: '事件',
noEventsInRange: '无', noEventsInRange: '无',
showMore: (count) => `还有 ${count}` showMore: count => `还有 ${count}`,
} };
console.log('events', data);
console.log('events', data)
return ( return (
<Card bordered={false}> <Card bordered={false}>
<Actions associatedKey={associatedKey} onTrigger={{ <Actions
associatedKey={associatedKey}
onTrigger={{
async create(values) { async create(values) {
await refresh(); await refresh();
}, },
@ -143,12 +162,15 @@ export function Calendar(props: CalendarProps) {
run({ ...params[0], filter: values.filter }); run({ ...params[0], filter: values.filter });
// refresh(); // refresh();
}, },
}} actions={actions} style={{ }}
actions={actions}
style={{
position: 'absolute', position: 'absolute',
left: '157px', left: '157px',
right: '168px', right: '168px',
marginBottom: 14 marginBottom: 14,
}}/> }}
/>
<BigCalendar <BigCalendar
popup popup
selectable selectable
@ -162,20 +184,24 @@ export function Calendar(props: CalendarProps) {
if (dates.eq(start, end, 'month')) { if (dates.eq(start, end, 'month')) {
return local.format(start, 'Y年M月', culture); return local.format(start, 'Y年M月', culture);
} }
return `${local.format(start, 'Y年M月', culture)} - ${local.format(end, 'Y年M月', culture)}`; return `${local.format(start, 'Y年M月', culture)} - ${local.format(
} end,
'Y年M月',
culture,
)}`;
},
}} }}
events={events} events={events}
onSelectSlot={(slotInfo) => { onSelectSlot={slotInfo => {
// setFormMode('create'); // setFormMode('create');
// drawerRef.current.setVisible(true); // drawerRef.current.setVisible(true);
// console.log('onSelectSlot', slotInfo) // console.log('onSelectSlot', slotInfo)
}} }}
onView={(view) => { onView={view => {
setCalendarView(view); setCalendarView(view);
console.log(view) console.log(view);
}} }}
onSelectEvent={(data) => { onSelectEvent={data => {
console.log({ data }); console.log({ data });
if (!detailsOpenMode || !details.length) { if (!detailsOpenMode || !details.length) {
return; return;
@ -185,12 +211,15 @@ export function Calendar(props: CalendarProps) {
history.push(`/admin/${paths[2]}/${data[rowKey]}/0`); history.push(`/admin/${paths[2]}/${data[rowKey]}/0`);
} else { } else {
Drawer.open({ Drawer.open({
headerStyle: details.length > 1 ? { headerStyle:
details.length > 1
? {
paddingBottom: 0, paddingBottom: 0,
borderBottom: 0, borderBottom: 0,
// paddingTop: 16, // paddingTop: 16,
// marginBottom: -4, // marginBottom: -4,
} : {}, }
: {},
// title: details.length > 1 ? undefined : data[labelField], // title: details.length > 1 ? undefined : data[labelField],
title: data.title, title: data.title,
bodyStyle: { bodyStyle: {
@ -225,8 +254,8 @@ export function Calendar(props: CalendarProps) {
}); });
} }
}} }}
onRangeChange={(range) => { onRangeChange={range => {
console.log({range}) console.log({ range });
}} }}
views={['month', 'week', 'day']} views={['month', 'week', 'day']}
// step={120} // step={120}

View File

@ -2,52 +2,66 @@
.rbc-btn { .rbc-btn {
color: inherit; color: inherit;
font: inherit; font: inherit;
margin: 0; } margin: 0;
}
button.rbc-btn { button.rbc-btn {
overflow: visible; overflow: visible;
text-transform: none; text-transform: none;
-webkit-appearance: button; -webkit-appearance: button;
cursor: pointer; } cursor: pointer;
}
button[disabled].rbc-btn { button[disabled].rbc-btn {
cursor: not-allowed; } cursor: not-allowed;
}
button.rbc-input::-moz-focus-inner { button.rbc-input::-moz-focus-inner {
border: 0; border: 0;
padding: 0; } padding: 0;
}
.rbc-calendar { .rbc-calendar {
box-sizing: border-box; box-sizing: border-box;
// height: 75vh; // height: 75vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: stretch; } align-items: stretch;
}
.rbc-calendar *, .rbc-calendar *,
.rbc-calendar *:before, .rbc-calendar *:before,
.rbc-calendar *:after { .rbc-calendar *:after {
box-sizing: inherit; } box-sizing: inherit;
}
.rbc-abs-full, .rbc-row-bg { .rbc-abs-full,
.rbc-row-bg {
overflow: hidden; overflow: hidden;
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; } bottom: 0;
}
.rbc-ellipsis, .rbc-event-label, .rbc-row-segment .rbc-event-content, .rbc-show-more { .rbc-ellipsis,
.rbc-event-label,
.rbc-row-segment .rbc-event-content,
.rbc-show-more {
display: block; display: block;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; } white-space: nowrap;
}
.rbc-rtl { .rbc-rtl {
direction: rtl; } direction: rtl;
}
.rbc-off-range { .rbc-off-range {
color: rgba(0,0,0,.25); } color: rgba(0, 0, 0, 0.25);
}
.rbc-off-range-bg { .rbc-off-range-bg {
// background: #e6e6e6; // background: #e6e6e6;
@ -64,7 +78,7 @@ button.rbc-input::-moz-focus-inner {
vertical-align: middle; vertical-align: middle;
// font-weight: 500; // font-weight: 500;
min-height: 32px; min-height: 32px;
color: rgba(0,0,0,.85); color: rgba(0, 0, 0, 0.85);
margin: 0 4px; margin: 0 4px;
border-bottom: 2px solid #f0f0f0; border-bottom: 2px solid #f0f0f0;
} }
@ -73,21 +87,27 @@ button.rbc-input::-moz-focus-inner {
} }
.rbc-rtl .rbc-header + .rbc-header { .rbc-rtl .rbc-header + .rbc-header {
border-left-width: 0; border-left-width: 0;
border-right: 1px solid #f0f0f0; } border-right: 1px solid #f0f0f0;
.rbc-header > a, .rbc-header > a:active, .rbc-header > a:visited { }
.rbc-header > a,
.rbc-header > a:active,
.rbc-header > a:visited {
color: inherit; color: inherit;
text-decoration: none; } text-decoration: none;
}
.rbc-row-content { .rbc-row-content {
position: relative; position: relative;
user-select: none; user-select: none;
-webkit-user-select: none; -webkit-user-select: none;
z-index: 4; } z-index: 4;
}
.rbc-row-content-scrollable { .rbc-row-content-scrollable {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; } height: 100%;
}
.rbc-row-content-scrollable .rbc-row-content-scroll-container { .rbc-row-content-scrollable .rbc-row-content-scroll-container {
height: 100%; height: 100%;
overflow-y: scroll; overflow-y: scroll;
@ -95,10 +115,12 @@ button.rbc-input::-moz-focus-inner {
-ms-overflow-style: none; -ms-overflow-style: none;
/* IE and Edge */ /* IE and Edge */
scrollbar-width: none; scrollbar-width: none;
/* Firefox */ } /* Firefox */
.rbc-row-content-scrollable .rbc-row-content-scroll-container::-webkit-scrollbar { }
display: none; } .rbc-row-content-scrollable
.rbc-row-content-scroll-container::-webkit-scrollbar {
display: none;
}
.rbc-toolbar { .rbc-toolbar {
display: flex; display: flex;
@ -106,11 +128,13 @@ button.rbc-input::-moz-focus-inner {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
margin-bottom: 10px; margin-bottom: 10px;
font-size: 16px; } font-size: 16px;
}
.rbc-toolbar .rbc-toolbar-label { .rbc-toolbar .rbc-toolbar-label {
flex-grow: 1; flex-grow: 1;
padding: 0 10px; padding: 0 10px;
text-align: center; } text-align: center;
}
.rbc-toolbar button { .rbc-toolbar button {
outline: none; outline: none;
font-size: 14px; font-size: 14px;
@ -129,15 +153,21 @@ button.rbc-input::-moz-focus-inner {
padding: 4px 15px; padding: 4px 15px;
border-radius: 2px; border-radius: 2px;
line-height: normal; line-height: normal;
white-space: nowrap; } white-space: nowrap;
.rbc-toolbar button:active, .rbc-toolbar button.rbc-active { }
.rbc-toolbar button:active,
.rbc-toolbar button.rbc-active {
// background-image: none; // background-image: none;
// box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); // box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
// background-color: #e6e6e6; // background-color: #e6e6e6;
z-index: 2; z-index: 2;
color: #1890ff; color: #1890ff;
border-color: #1890ff; } border-color: #1890ff;
.rbc-toolbar button:active:hover, .rbc-toolbar button:active:focus, .rbc-toolbar button.rbc-active:hover, .rbc-toolbar button.rbc-active:focus { }
.rbc-toolbar button:active:hover,
.rbc-toolbar button:active:focus,
.rbc-toolbar button.rbc-active:hover,
.rbc-toolbar button.rbc-active:focus {
// color: #373a3c; // color: #373a3c;
// background-color: #d4d4d4; // background-color: #d4d4d4;
// border-color: #8c8c8c; // border-color: #8c8c8c;
@ -159,31 +189,40 @@ button.rbc-input::-moz-focus-inner {
.rbc-btn-group { .rbc-btn-group {
display: inline-block; display: inline-block;
white-space: nowrap; } white-space: nowrap;
}
.rbc-btn-group > button:first-child:not(:last-child) { .rbc-btn-group > button:first-child:not(:last-child) {
border-top-right-radius: 0; border-top-right-radius: 0;
border-bottom-right-radius: 0; } border-bottom-right-radius: 0;
}
.rbc-btn-group > button:last-child:not(:first-child) { .rbc-btn-group > button:last-child:not(:first-child) {
border-top-left-radius: 0; border-top-left-radius: 0;
border-bottom-left-radius: 0; } border-bottom-left-radius: 0;
}
.rbc-rtl .rbc-btn-group > button:first-child:not(:last-child) { .rbc-rtl .rbc-btn-group > button:first-child:not(:last-child) {
border-radius: 4px; border-radius: 4px;
border-top-left-radius: 0; border-top-left-radius: 0;
border-bottom-left-radius: 0; } border-bottom-left-radius: 0;
}
.rbc-rtl .rbc-btn-group > button:last-child:not(:first-child) { .rbc-rtl .rbc-btn-group > button:last-child:not(:first-child) {
border-radius: 4px; border-radius: 4px;
border-top-right-radius: 0; border-top-right-radius: 0;
border-bottom-right-radius: 0; } border-bottom-right-radius: 0;
}
.rbc-btn-group > button:not(:first-child):not(:last-child) { .rbc-btn-group > button:not(:first-child):not(:last-child) {
border-radius: 0; } border-radius: 0;
}
.rbc-btn-group button + button { .rbc-btn-group button + button {
margin-left: -1px; } margin-left: -1px;
}
.rbc-rtl .rbc-btn-group button + button { .rbc-rtl .rbc-btn-group button + button {
margin-left: 0; margin-left: 0;
margin-right: -1px; } margin-right: -1px;
}
.rbc-btn-group + .rbc-btn-group, .rbc-btn-group + .rbc-btn-group,
.rbc-btn-group + button { .rbc-btn-group + button {
margin-left: 10px; } margin-left: 10px;
}
.rbc-event { .rbc-event {
border: none; border: none;
@ -205,7 +244,8 @@ button.rbc-input::-moz-focus-inner {
} }
.rbc-slot-selecting .rbc-event { .rbc-slot-selecting .rbc-event {
cursor: inherit; cursor: inherit;
pointer-events: none; } pointer-events: none;
}
.rbc-event.rbc-selected { .rbc-event.rbc-selected {
background-color: #e6f7ff; background-color: #e6f7ff;
color: #1890ff; color: #1890ff;
@ -215,36 +255,45 @@ button.rbc-input::-moz-focus-inner {
} }
.rbc-event-label { .rbc-event-label {
font-size: 80%; } font-size: 80%;
}
.rbc-event-overlaps { .rbc-event-overlaps {
box-shadow: -1px 1px 5px 0px rgba(51, 51, 51, 0.5); } box-shadow: -1px 1px 5px 0px rgba(51, 51, 51, 0.5);
}
.rbc-event-continues-prior { .rbc-event-continues-prior {
border-top-left-radius: 0; border-top-left-radius: 0;
border-bottom-left-radius: 0; } border-bottom-left-radius: 0;
}
.rbc-event-continues-after { .rbc-event-continues-after {
border-top-right-radius: 0; border-top-right-radius: 0;
border-bottom-right-radius: 0; } border-bottom-right-radius: 0;
}
.rbc-event-continues-earlier { .rbc-event-continues-earlier {
border-top-left-radius: 0; border-top-left-radius: 0;
border-top-right-radius: 0; } border-top-right-radius: 0;
}
.rbc-event-continues-later { .rbc-event-continues-later {
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
border-bottom-right-radius: 0; } border-bottom-right-radius: 0;
}
.rbc-row { .rbc-row {
display: flex; display: flex;
flex-direction: row; } flex-direction: row;
}
.rbc-row-segment { .rbc-row-segment {
padding: 0 4px 1px 4px; } padding: 0 4px 1px 4px;
}
.rbc-selected-cell { .rbc-selected-cell {
background-color: rgba(0, 0, 0, 0.1); } background-color: rgba(0, 0, 0, 0.1);
}
.rbc-show-more { .rbc-show-more {
z-index: 4; z-index: 4;
@ -265,11 +314,13 @@ button.rbc-input::-moz-focus-inner {
width: 100%; width: 100%;
user-select: none; user-select: none;
-webkit-user-select: none; -webkit-user-select: none;
height: 68vh; } height: 68vh;
}
.rbc-month-header { .rbc-month-header {
display: flex; display: flex;
flex-direction: row; } flex-direction: row;
}
.rbc-month-row { .rbc-month-row {
display: flex; display: flex;
@ -278,7 +329,8 @@ button.rbc-input::-moz-focus-inner {
flex: 1 0 0; flex: 1 0 0;
flex-basis: 0px; flex-basis: 0px;
overflow: hidden; overflow: hidden;
height: 100%; } height: 100%;
}
.rbc-month-row + .rbc-month-row { .rbc-month-row + .rbc-month-row {
// border-top: 1px solid #f0f0f0; // border-top: 1px solid #f0f0f0;
} }
@ -289,12 +341,15 @@ button.rbc-input::-moz-focus-inner {
min-width: 0; min-width: 0;
padding-right: 12px; padding-right: 12px;
padding-top: 4px; padding-top: 4px;
text-align: right; } text-align: right;
}
.rbc-date-cell.rbc-now a { .rbc-date-cell.rbc-now a {
// font-weight: bold; // font-weight: bold;
color: #1890ff; color: #1890ff;
} }
.rbc-date-cell > a, .rbc-date-cell > a:active, .rbc-date-cell > a:visited { .rbc-date-cell > a,
.rbc-date-cell > a:active,
.rbc-date-cell > a:visited {
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
} }
@ -306,9 +361,8 @@ button.rbc-input::-moz-focus-inner {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex: 1 0 0; flex: 1 0 0;
overflow: hidden; } overflow: hidden;
}
.rbc-day-bg { .rbc-day-bg {
flex: 1 0 0%; flex: 1 0 0%;
@ -349,69 +403,89 @@ button.rbc-input::-moz-focus-inner {
border-radius: 2px; border-radius: 2px;
// border: 1px solid #e5e5e5; // border: 1px solid #e5e5e5;
background-color: #fff; background-color: #fff;
box-shadow: 0 3px 6px -4px rgba(0,0,0,.12), 0 6px 16px 0 rgba(0,0,0,.08), 0 9px 28px 8px rgba(0,0,0,.05); box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12),
padding: 12px 16px; } 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
padding: 12px 16px;
}
.rbc-overlay > * + * { .rbc-overlay > * + * {
margin-top: 1px; } margin-top: 1px;
}
.rbc-overlay-header { .rbc-overlay-header {
font-weight: 500; font-weight: 500;
min-height: 32px; min-height: 32px;
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0;
margin: -12px -16px 12px -16px; margin: -12px -16px 12px -16px;
padding: 5px 16px 4px; } padding: 5px 16px 4px;
}
.rbc-agenda-view { .rbc-agenda-view {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1 0 0; flex: 1 0 0;
overflow: auto; } overflow: auto;
}
.rbc-agenda-view table.rbc-agenda-table { .rbc-agenda-view table.rbc-agenda-table {
width: 100%; width: 100%;
border: 1px solid #f0f0f0; border: 1px solid #f0f0f0;
border-spacing: 0; border-spacing: 0;
border-collapse: collapse; } border-collapse: collapse;
}
.rbc-agenda-view table.rbc-agenda-table tbody > tr > td { .rbc-agenda-view table.rbc-agenda-table tbody > tr > td {
padding: 5px 10px; padding: 5px 10px;
vertical-align: top; } vertical-align: top;
}
.rbc-agenda-view table.rbc-agenda-table .rbc-agenda-time-cell { .rbc-agenda-view table.rbc-agenda-table .rbc-agenda-time-cell {
padding-left: 15px; padding-left: 15px;
padding-right: 15px; padding-right: 15px;
text-transform: lowercase; } text-transform: lowercase;
}
.rbc-agenda-view table.rbc-agenda-table tbody > tr > td + td { .rbc-agenda-view table.rbc-agenda-table tbody > tr > td + td {
border-left: 1px solid #f0f0f0; } border-left: 1px solid #f0f0f0;
}
.rbc-rtl .rbc-agenda-view table.rbc-agenda-table tbody > tr > td + td { .rbc-rtl .rbc-agenda-view table.rbc-agenda-table tbody > tr > td + td {
border-left-width: 0; border-left-width: 0;
border-right: 1px solid #f0f0f0; } border-right: 1px solid #f0f0f0;
}
.rbc-agenda-view table.rbc-agenda-table tbody > tr + tr { .rbc-agenda-view table.rbc-agenda-table tbody > tr + tr {
border-top: 1px solid #f0f0f0; } border-top: 1px solid #f0f0f0;
}
.rbc-agenda-view table.rbc-agenda-table thead > tr > th { .rbc-agenda-view table.rbc-agenda-table thead > tr > th {
padding: 3px 5px; padding: 3px 5px;
text-align: left; text-align: left;
border-bottom: 1px solid #f0f0f0; } border-bottom: 1px solid #f0f0f0;
}
.rbc-rtl .rbc-agenda-view table.rbc-agenda-table thead > tr > th { .rbc-rtl .rbc-agenda-view table.rbc-agenda-table thead > tr > th {
text-align: right; } text-align: right;
}
.rbc-agenda-time-cell { .rbc-agenda-time-cell {
text-transform: lowercase; } text-transform: lowercase;
}
.rbc-agenda-time-cell .rbc-continues-after:after { .rbc-agenda-time-cell .rbc-continues-after:after {
content: ' »'; } content: ' »';
}
.rbc-agenda-time-cell .rbc-continues-prior:before { .rbc-agenda-time-cell .rbc-continues-prior:before {
content: '« '; } content: '« ';
}
.rbc-agenda-date-cell, .rbc-agenda-date-cell,
.rbc-agenda-time-cell { .rbc-agenda-time-cell {
white-space: nowrap; } white-space: nowrap;
}
.rbc-agenda-event-cell { .rbc-agenda-event-cell {
width: 100%; } width: 100%;
}
.rbc-time-column { .rbc-time-column {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 100%; } min-height: 100%;
}
.rbc-time-column .rbc-timeslot-group { .rbc-time-column .rbc-timeslot-group {
flex: 1; } flex: 1;
}
.rbc-timeslot-group { .rbc-timeslot-group {
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0;
@ -427,23 +501,28 @@ button.rbc-input::-moz-focus-inner {
.rbc-time-gutter, .rbc-time-gutter,
.rbc-header-gutter { .rbc-header-gutter {
flex: none; } flex: none;
}
.rbc-label { .rbc-label {
padding: 0 5px; } padding: 0 5px;
}
.rbc-day-slot { .rbc-day-slot {
position: relative; } position: relative;
}
.rbc-day-slot .rbc-events-container { .rbc-day-slot .rbc-events-container {
bottom: 0; bottom: 0;
left: 0; left: 0;
position: absolute; position: absolute;
right: 0; right: 0;
margin-right: 10px; margin-right: 10px;
top: 0; } top: 0;
}
.rbc-day-slot .rbc-events-container.rbc-rtl { .rbc-day-slot .rbc-events-container.rbc-rtl {
left: 10px; left: 10px;
right: 0; } right: 0;
}
.rbc-day-slot .rbc-event { .rbc-day-slot .rbc-event {
border: 1px solid #265985; border: 1px solid #265985;
display: flex; display: flex;
@ -452,18 +531,21 @@ button.rbc-input::-moz-focus-inner {
flex-flow: column wrap; flex-flow: column wrap;
align-items: flex-start; align-items: flex-start;
overflow: hidden; overflow: hidden;
position: absolute; } position: absolute;
}
.rbc-day-slot .rbc-event-label { .rbc-day-slot .rbc-event-label {
flex: none; flex: none;
padding-right: 5px; padding-right: 5px;
width: auto; } width: auto;
}
.rbc-day-slot .rbc-event-content { .rbc-day-slot .rbc-event-content {
width: 100%; width: 100%;
flex: 1 1 0; flex: 1 1 0;
word-wrap: break-word; word-wrap: break-word;
line-height: 1; line-height: 1;
height: 100%; height: 100%;
min-height: 1em; } min-height: 1em;
}
.rbc-day-slot .rbc-time-slot { .rbc-day-slot .rbc-time-slot {
// border-top: 1px solid #f7f7f7; // border-top: 1px solid #f7f7f7;
} }
@ -504,38 +586,48 @@ button.rbc-input::-moz-focus-inner {
background-color: white; background-color: white;
border-right: 1px solid #f0f0f0; border-right: 1px solid #f0f0f0;
z-index: 10; z-index: 10;
margin-right: -1px; } margin-right: -1px;
}
.rbc-time-view-resources .rbc-time-header { .rbc-time-view-resources .rbc-time-header {
overflow: hidden; } overflow: hidden;
}
.rbc-time-view-resources .rbc-time-header-content { .rbc-time-view-resources .rbc-time-header-content {
min-width: auto; min-width: auto;
flex: 1 0 0; flex: 1 0 0;
flex-basis: 0px; } flex-basis: 0px;
}
.rbc-time-view-resources .rbc-time-header-cell-single-day { .rbc-time-view-resources .rbc-time-header-cell-single-day {
display: none; } display: none;
}
.rbc-time-view-resources .rbc-day-slot { .rbc-time-view-resources .rbc-day-slot {
min-width: 140px; } min-width: 140px;
}
.rbc-time-view-resources .rbc-header, .rbc-time-view-resources .rbc-header,
.rbc-time-view-resources .rbc-day-bg { .rbc-time-view-resources .rbc-day-bg {
width: 140px; width: 140px;
flex: 1 1 0; flex: 1 1 0;
flex-basis: 0 px; } flex-basis: 0 px;
}
.rbc-time-header-content + .rbc-time-header-content { .rbc-time-header-content + .rbc-time-header-content {
margin-left: -1px; } margin-left: -1px;
}
.rbc-time-slot { .rbc-time-slot {
flex: 1 0 0; } flex: 1 0 0;
}
.rbc-time-slot.rbc-now { .rbc-time-slot.rbc-now {
font-weight: bold; } font-weight: bold;
}
.rbc-day-header { .rbc-day-header {
text-align: center; } text-align: center;
}
.rbc-slot-selection { .rbc-slot-selection {
z-index: 10; z-index: 10;
@ -544,10 +636,12 @@ button.rbc-input::-moz-focus-inner {
color: white; color: white;
font-size: 75%; font-size: 75%;
width: 100%; width: 100%;
padding: 3px; } padding: 3px;
}
.rbc-slot-selecting { .rbc-slot-selecting {
cursor: move; } cursor: move;
}
.rbc-time-view { .rbc-time-view {
display: flex; display: flex;
@ -555,39 +649,51 @@ button.rbc-input::-moz-focus-inner {
flex: 1; flex: 1;
width: 100%; width: 100%;
border-top: 1px solid #f0f0f0; border-top: 1px solid #f0f0f0;
min-height: 0; } min-height: 0;
}
.rbc-time-view .rbc-time-gutter { .rbc-time-view .rbc-time-gutter {
white-space: nowrap; } white-space: nowrap;
}
.rbc-time-view .rbc-allday-cell { .rbc-time-view .rbc-allday-cell {
box-sizing: content-box; box-sizing: content-box;
width: 100%; width: 100%;
height: 100%; height: 100%;
position: relative; } position: relative;
}
.rbc-time-view .rbc-allday-cell + .rbc-allday-cell { .rbc-time-view .rbc-allday-cell + .rbc-allday-cell {
border-left: 1px solid #f0f0f0; } border-left: 1px solid #f0f0f0;
}
.rbc-time-view .rbc-allday-events { .rbc-time-view .rbc-allday-events {
position: relative; position: relative;
z-index: 4; } z-index: 4;
}
.rbc-time-view .rbc-row { .rbc-time-view .rbc-row {
box-sizing: border-box; box-sizing: border-box;
min-height: 20px; } min-height: 20px;
}
.rbc-time-header { .rbc-time-header {
display: flex; display: flex;
flex: 0 0 auto; flex: 0 0 auto;
flex-direction: row; } flex-direction: row;
}
.rbc-time-header.rbc-overflowing { .rbc-time-header.rbc-overflowing {
border-right: 1px solid #f0f0f0; } border-right: 1px solid #f0f0f0;
}
.rbc-rtl .rbc-time-header.rbc-overflowing { .rbc-rtl .rbc-time-header.rbc-overflowing {
border-right-width: 0; border-right-width: 0;
border-left: 1px solid #f0f0f0; } border-left: 1px solid #f0f0f0;
}
.rbc-time-header > .rbc-row:first-child { .rbc-time-header > .rbc-row:first-child {
border-bottom: 1px solid #f0f0f0; } border-bottom: 1px solid #f0f0f0;
}
.rbc-time-header > .rbc-row.rbc-row-resource { .rbc-time-header > .rbc-row.rbc-row-resource {
border-bottom: 1px solid #f0f0f0; } border-bottom: 1px solid #f0f0f0;
}
.rbc-time-header-cell-single-day { .rbc-time-header-cell-single-day {
display: none; } display: none;
}
.rbc-time-header-content { .rbc-time-header-content {
flex: 1; flex: 1;
@ -598,10 +704,12 @@ button.rbc-input::-moz-focus-inner {
} }
.rbc-rtl .rbc-time-header-content { .rbc-rtl .rbc-time-header-content {
border-left-width: 0; border-left-width: 0;
border-right: 1px solid #f0f0f0; } border-right: 1px solid #f0f0f0;
}
.rbc-time-header-content > .rbc-row.rbc-row-resource { .rbc-time-header-content > .rbc-row.rbc-row-resource {
border-bottom: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0;
flex-shrink: 0; } flex-shrink: 0;
}
.rbc-time-content { .rbc-time-content {
display: flex; display: flex;
@ -610,19 +718,23 @@ button.rbc-input::-moz-focus-inner {
width: 100%; width: 100%;
border-top: 1px solid #f0f0f0; border-top: 1px solid #f0f0f0;
overflow-y: auto; overflow-y: auto;
position: relative; } position: relative;
}
.rbc-time-content > .rbc-time-gutter { .rbc-time-content > .rbc-time-gutter {
flex: none; } flex: none;
}
.rbc-time-content > * + * > * { .rbc-time-content > * + * > * {
// border-left: 1px solid #f0f0f0; // border-left: 1px solid #f0f0f0;
} }
.rbc-rtl .rbc-time-content > * + * > * { .rbc-rtl .rbc-time-content > * + * > * {
border-left-width: 0; border-left-width: 0;
border-right: 1px solid #f0f0f0; } border-right: 1px solid #f0f0f0;
}
.rbc-time-content > .rbc-day-slot { .rbc-time-content > .rbc-day-slot {
width: 100%; width: 100%;
user-select: none; user-select: none;
-webkit-user-select: none; } -webkit-user-select: none;
}
.rbc-current-time-indicator { .rbc-current-time-indicator {
position: absolute; position: absolute;
@ -631,4 +743,5 @@ button.rbc-input::-moz-focus-inner {
right: 0; right: 0;
height: 1px; height: 1px;
background-color: #74ad31; background-color: #74ad31;
pointer-events: none; } pointer-events: none;
}

View File

@ -5,7 +5,15 @@ import { Spin } from '@nocobase/client';
import { useRequest, useLocation } from 'umi'; import { useRequest, useLocation } from 'umi';
import api from '@/api-client'; import api from '@/api-client';
import { Actions } from '../Actions'; import { Actions } from '../Actions';
import { Table as AntdTable, Card, Pagination, Button, Tabs, Descriptions as AntdDescriptions, Tooltip } from 'antd'; import {
Table as AntdTable,
Card,
Pagination,
Button,
Tabs,
Descriptions as AntdDescriptions,
Tooltip,
} from 'antd';
import { LoadingOutlined } from '@ant-design/icons'; import { LoadingOutlined } from '@ant-design/icons';
import { components, fields2columns } from '@/components/views/SortableTable'; import { components, fields2columns } from '@/components/views/SortableTable';
import ReactDragListView from 'react-drag-listview'; import ReactDragListView from 'react-drag-listview';
@ -52,11 +60,20 @@ function toGroups(fields: any[]) {
export function Descriptions(props) { export function Descriptions(props) {
const { data: record = {}, schema = {}, onDataChange } = props; const { data: record = {}, schema = {}, onDataChange } = props;
const { rowKey = 'id', resourceName, fields = [], actions = [], appends = [], associationField = {} } = schema; const {
rowKey = 'id',
resourceName,
fields = [],
actions = [],
appends = [],
associationField = {},
} = schema;
const responsive = useResponsive(); const responsive = useResponsive();
const resourceKey = props.resourceKey || record[associationField.targetKey||rowKey]; const resourceKey =
const associatedKey = props.associatedKey || record[associationField.sourceKey||'id']; props.resourceKey || record[associationField.targetKey || rowKey];
const associatedKey =
props.associatedKey || record[associationField.sourceKey || 'id'];
// console.log({resourceKey, data: record, associatedKey, associationField}) // console.log({resourceKey, data: record, associatedKey, associationField})
@ -73,11 +90,11 @@ export function Descriptions(props) {
let descriptionsProps: any = { let descriptionsProps: any = {
size: 'middle', size: 'middle',
bordered: true, bordered: true,
} };
if (responsive.small && !responsive.middle && !responsive.large) { if (responsive.small && !responsive.middle && !responsive.large) {
descriptionsProps = { descriptionsProps = {
layout: 'vertical' layout: 'vertical',
} };
} }
const groups = toGroups(fields); const groups = toGroups(fields);
return ( return (
@ -100,17 +117,41 @@ export function Descriptions(props) {
// size={'middle'} // size={'middle'}
// bordered // bordered
{...descriptionsProps} {...descriptionsProps}
title={group.title && <span>{group.title} {group.tooltip && <Tooltip title={group.tooltip}><InfoCircleOutlined /></Tooltip>}</span>} title={
column={1}> group.title && (
<span>
{group.title}{' '}
{group.tooltip && (
<Tooltip title={group.tooltip}>
<InfoCircleOutlined />
</Tooltip>
)}
</span>
)
}
column={1}
>
{group.children.map((field: any) => { {group.children.map((field: any) => {
const label = field.tooltip ? ( const label = field.tooltip ? (
<>{field.title||field.name}&nbsp;<Tooltip title={field.tooltip}><InfoCircleOutlined /></Tooltip></> <>
) : (field.title||field.name); {field.title || field.name}&nbsp;
<Tooltip title={field.tooltip}>
<InfoCircleOutlined />
</Tooltip>
</>
) : (
field.title || field.name
);
return ( return (
<AntdDescriptions.Item label={label}> <AntdDescriptions.Item label={label}>
<Field data={data} viewType={'descriptions'} schema={field} value={get(data, field.name)}/> <Field
data={data}
viewType={'descriptions'}
schema={field}
value={get(data, field.name)}
/>
</AntdDescriptions.Item> </AntdDescriptions.Item>
) );
})} })}
</AntdDescriptions> </AntdDescriptions>
))} ))}

View File

@ -27,9 +27,9 @@ export function FilterForm(props: any) {
// actions={actions} // actions={actions}
onReset={async () => { onReset={async () => {
// setData({filter: {}}); // setData({filter: {}});
onFinish && await onFinish(null); onFinish && (await onFinish(null));
}} }}
onSubmit={async (values) => { onSubmit={async values => {
if (onFinish) { if (onFinish) {
await onFinish(values); await onFinish(values);
} }

View File

@ -19,7 +19,7 @@ import api from '@/api-client';
import { useRequest, useLocation } from 'umi'; import { useRequest, useLocation } from 'umi';
import Drawer from '@/components/pages/AdminLoader/Drawer'; import Drawer from '@/components/pages/AdminLoader/Drawer';
import set from 'lodash/set'; import set from 'lodash/set';
import cloneDeep from 'lodash/cloneDeep' import cloneDeep from 'lodash/cloneDeep';
import { Spin } from '@nocobase/client'; import { Spin } from '@nocobase/client';
import { markdown } from '@/components/views/Field'; import { markdown } from '@/components/views/Field';
import scopes from '@/components/views/Form/scopes'; import scopes from '@/components/views/Form/scopes';
@ -41,7 +41,12 @@ export function fields2properties(fields = [], options: any = {}) {
} }
if (field.showTime) { if (field.showTime) {
set(data, 'x-component-props.showTime', true); set(data, 'x-component-props.showTime', true);
field.timeFormat && set(data, 'x-component-props.format', `${field.dateFormat} ${field.timeFormat}`); field.timeFormat &&
set(
data,
'x-component-props.format',
`${field.dateFormat} ${field.timeFormat}`,
);
} }
if (field.createOnly && mode !== 'create') { if (field.createOnly && mode !== 'create') {
set(data, 'x-component-props.disabled', true); set(data, 'x-component-props.disabled', true);
@ -68,16 +73,16 @@ export function fields2properties(fields = [], options: any = {}) {
const property = {}; const property = {};
Object.assign(property, { Object.assign(property, {
label: { label: {
type: "string", type: 'string',
title: "选项", title: '选项',
required: true, required: true,
'x-component-props': { 'x-component-props': {
bordered: false, bordered: false,
}, },
}, },
color: { color: {
type: "colorSelect", type: 'colorSelect',
title: "颜色", title: '颜色',
'x-component-props': { 'x-component-props': {
bordered: false, bordered: false,
}, },
@ -92,7 +97,12 @@ export function fields2properties(fields = [], options: any = {}) {
data.default = field.defaultValue; data.default = field.defaultValue;
} }
if (field.tooltip) { if (field.tooltip) {
data.description = <div className={'markdown-content'} dangerouslySetInnerHTML={{__html: markdown(field.tooltip)}}></div>; data.description = (
<div
className={'markdown-content'}
dangerouslySetInnerHTML={{ __html: markdown(field.tooltip) }}
></div>
);
} }
}); });
console.log({ properties, options }); console.log({ properties, options });
@ -101,18 +111,40 @@ export function fields2properties(fields = [], options: any = {}) {
const actions = createFormActions(); const actions = createFormActions();
export function Form(props: any) { export function Form(props: any) {
const { initialValues = {}, onValueChange, onReset, __parent, noRequest = false, onFinish, onDraft, resolve, data: record = {}, associatedKey, schema = {} } = props; const {
initialValues = {},
onValueChange,
onReset,
__parent,
noRequest = false,
onFinish,
onDraft,
resolve,
data: record = {},
associatedKey,
schema = {},
} = props;
console.log({ noRequest, record, associatedKey, __parent }); console.log({ noRequest, record, associatedKey, __parent });
const { statusable, resourceName, rowKey = 'id', fields = [], appends = [], associationField = {} } = schema; const {
statusable,
resourceName,
rowKey = 'id',
fields = [],
appends = [],
associationField = {},
} = schema;
const resourceKey = props.resourceKey || record[associationField.targetKey||rowKey]; const resourceKey =
props.resourceKey || record[associationField.targetKey || rowKey];
const { data = {}, loading, refresh } = useRequest(() => { const { data = {}, loading, refresh } = useRequest(() => {
return (!noRequest && resourceKey) ? api.resource(resourceName).get({ return !noRequest && resourceKey
? api.resource(resourceName).get({
associatedKey, associatedKey,
resourceKey, resourceKey,
'fields[appends]': appends, 'fields[appends]': appends,
}) : Promise.resolve({data: record}); })
: Promise.resolve({ data: record });
}); });
const [status, setStatus] = useState('publish'); const [status, setStatus] = useState('publish');
@ -135,22 +167,24 @@ export function Form(props: any) {
$(LifeCycleTypes.ON_FORM_INIT).subscribe(() => { $(LifeCycleTypes.ON_FORM_INIT).subscribe(() => {
setFieldState('*', state => { setFieldState('*', state => {
set(state.props, 'x-component-props.__index', resourceKey); set(state.props, 'x-component-props.__index', resourceKey);
}) });
}) });
}} }}
// actions={actions} // actions={actions}
schema={{ schema={{
type: 'object', type: 'object',
properties: fields2properties(fields, { mode: !!Object.keys(data).length ? null : 'create' }), properties: fields2properties(fields, {
mode: !!Object.keys(data).length ? null : 'create',
}),
}} }}
onReset={async () => { onReset={async () => {
onReset && await onReset(); onReset && (await onReset());
}} }}
onChange={(values) => { onChange={values => {
console.log('onValueChange') console.log('onValueChange');
onValueChange && onValueChange(values) onValueChange && onValueChange(values);
}} }}
onSubmit={async (values) => { onSubmit={async values => {
console.log({ status }); console.log({ status });
if (!noRequest) { if (!noRequest) {
resourceKey resourceKey
@ -170,7 +204,7 @@ export function Form(props: any) {
}, },
}); });
} }
onFinish && await onFinish(values); onFinish && (await onFinish(values));
}} }}
expressionScope={scopes} expressionScope={scopes}
> >
@ -181,22 +215,22 @@ export function Form(props: any) {
selector={[ selector={[
LifeCycleTypes.ON_FORM_MOUNT, LifeCycleTypes.ON_FORM_MOUNT,
LifeCycleTypes.ON_FORM_SUBMIT_START, LifeCycleTypes.ON_FORM_SUBMIT_START,
LifeCycleTypes.ON_FORM_SUBMIT_END LifeCycleTypes.ON_FORM_SUBMIT_END,
]} ]}
reducer={(state, action) => { reducer={(state, action) => {
switch (action.type) { switch (action.type) {
case LifeCycleTypes.ON_FORM_SUBMIT_START: case LifeCycleTypes.ON_FORM_SUBMIT_START:
return { return {
...state, ...state,
submitting: true submitting: true,
} };
case LifeCycleTypes.ON_FORM_SUBMIT_END: case LifeCycleTypes.ON_FORM_SUBMIT_END:
return { return {
...state, ...state,
submitting: false submitting: false,
} };
default: default:
return state return state;
} }
}} }}
> >
@ -227,10 +261,11 @@ export function Form(props: any) {
await form.reset({ await form.reset({
validate: false, validate: false,
}); });
onDraft && await onDraft({ onDraft &&
(await onDraft({
...state.values, ...state.values,
status: 'draft', status: 'draft',
}); }));
setSubmitting(false); setSubmitting(false);
})(); })();
}); });
@ -241,7 +276,7 @@ export function Form(props: any) {
> >
{'保存草稿'} {'保存草稿'}
</Button> </Button>
) );
}} }}
</FormSpy> </FormSpy>
)} )}

View File

@ -1,9 +1,9 @@
import React, { useState } from "react"; import React, { useState } from 'react';
import { useRequest, useHistory } from 'umi'; import { useRequest, useHistory } from 'umi';
import api from '@/api-client'; import api from '@/api-client';
import { Descriptions, Popconfirm, Card, Button } from 'antd'; import { Descriptions, Popconfirm, Card, Button } from 'antd';
import Board, { moveCard } from "@lourenci/react-kanban"; import Board, { moveCard } from '@lourenci/react-kanban';
import "@lourenci/react-kanban/dist/styles.css"; import '@lourenci/react-kanban/dist/styles.css';
import { PlusOutlined, CloseOutlined, RightOutlined } from '@ant-design/icons'; import { PlusOutlined, CloseOutlined, RightOutlined } from '@ant-design/icons';
import Drawer from '@/components/pages/AdminLoader/Drawer'; import Drawer from '@/components/pages/AdminLoader/Drawer';
import { Actions, Create, Destroy } from '../../Actions'; import { Actions, Create, Destroy } from '../../Actions';
@ -35,16 +35,19 @@ export function Kanban(props: any) {
rowKey = 'id', rowKey = 'id',
} = schema; } = schema;
const { dataSource = [] } = groupField; const { dataSource = [] } = groupField;
console.log({groupField, dataSource}) console.log({ groupField, dataSource });
const paginated = false; const paginated = false;
const history = useHistory(); const history = useHistory();
const { data, loading, mutate, refresh, run, params } = useRequest((params = {}, ...args) => { const { data, loading, mutate, refresh, run, params } = useRequest(
(params = {}, ...args) => {
const { current, pageSize, sorter, filter, ...restParams } = params; const { current, pageSize, sorter, filter, ...restParams } = params;
console.log('paramsparamsparamsparamsparams', params, args); console.log('paramsparamsparamsparamsparams', params, args);
return api.resource(resourceName).list({ return api
.resource(resourceName)
.list({
associatedKey, associatedKey,
page: paginated ? current : 1, page: paginated ? current : 1,
perPage: paginated ? pageSize : -1, perPage: paginated ? pageSize : -1,
@ -61,8 +64,8 @@ export function Kanban(props: any) {
// __parent ? { // __parent ? {
// collection_name: __parent, // collection_name: __parent,
// } : null, // } : null,
].filter(obj => obj && Object.keys(obj).length) ].filter(obj => obj && Object.keys(obj).length),
} },
// ...args2, // ...args2,
}) })
.then(({ data = [], meta = {} }) => { .then(({ data = [], meta = {} }) => {
@ -73,9 +76,11 @@ export function Kanban(props: any) {
}, },
}; };
}); });
}, { },
{
paginated, paginated,
}); },
);
if (loading) { if (loading) {
return <Spin />; return <Spin />;
@ -85,7 +90,7 @@ export function Kanban(props: any) {
return { return {
id: group.value, id: group.value,
title: group.label, title: group.label,
cards: filter(data?.list, (item) => { cards: filter(data?.list, item => {
return get(item, groupField.name) === group.value; return get(item, groupField.name) === group.value;
}), }),
}; };
@ -121,7 +126,9 @@ export function Kanban(props: any) {
<Actions <Actions
associatedKey={associatedKey} associatedKey={associatedKey}
style={{ marginBottom: 14 }} style={{ marginBottom: 14 }}
actions={actions.filter(action => !['create', 'destroy'].includes(action.type))} actions={actions.filter(
action => !['create', 'destroy'].includes(action.type),
)}
onTrigger={{ onTrigger={{
async filter(values) { async filter(values) {
const items = values.filter.and || values.filter.or; const items = values.filter.and || values.filter.or;
@ -139,7 +146,7 @@ export function Kanban(props: any) {
disableCardDrag={disableCardDrag} disableCardDrag={disableCardDrag}
onLaneRemove={console.log} onLaneRemove={console.log}
renderCard={(data, { dragging, removeCard }) => { renderCard={(data, { dragging, removeCard }) => {
const openDetails = (e) => { const openDetails = e => {
if (!detailsOpenMode || !details.length) { if (!detailsOpenMode || !details.length) {
return; return;
} }
@ -148,12 +155,15 @@ export function Kanban(props: any) {
history.push(`/admin/${paths[2]}/${data[rowKey]}/0`); history.push(`/admin/${paths[2]}/${data[rowKey]}/0`);
} else { } else {
Drawer.open({ Drawer.open({
headerStyle: details.length > 1 ? { headerStyle:
details.length > 1
? {
paddingBottom: 0, paddingBottom: 0,
borderBottom: 0, borderBottom: 0,
// paddingTop: 16, // paddingTop: 16,
// marginBottom: -4, // marginBottom: -4,
} : {}, }
: {},
title: data[labelField], title: data[labelField],
content: ({ resolve, closeWithConfirm }) => ( content: ({ resolve, closeWithConfirm }) => (
<div> <div>
@ -183,18 +193,19 @@ export function Kanban(props: any) {
), ),
}); });
} }
} };
return ( return (
<Card <Card
hoverable hoverable
size={'small'} size={'small'}
style={{background: '#fff', minWidth: 250, maxWidth: 250, marginBottom: 12}} style={{
background: '#fff',
minWidth: 250,
maxWidth: 250,
marginBottom: 12,
}}
bordered={false} bordered={false}
title={( title={<div onClick={openDetails}>{data[labelField]}</div>}
<div onClick={openDetails}>
{data[labelField]}
</div>
)}
bodyStyle={bodyStyle} bodyStyle={bodyStyle}
extra={ extra={
allowRemoveCard && ( allowRemoveCard && (
@ -226,9 +237,14 @@ export function Kanban(props: any) {
{fields.map((field: any) => { {fields.map((field: any) => {
return ( return (
<Descriptions.Item label={field.title || field.name}> <Descriptions.Item label={field.title || field.name}>
<Field data={data} viewType={'descriptions'} schema={field} value={get(data, field.name)}/> <Field
data={data}
viewType={'descriptions'}
schema={field}
value={get(data, field.name)}
/>
</Descriptions.Item> </Descriptions.Item>
) );
})} })}
</Descriptions> </Descriptions>
)} )}
@ -244,9 +260,15 @@ export function Kanban(props: any) {
[groupField.name]: destination.toColumnId, [groupField.name]: destination.toColumnId,
}, },
}); });
const destColumn = columns.find( column => column.id === destination.toColumnId); const destColumn = columns.find(
const targetIndex = get(destColumn, ['cards', destination.toPosition+1, rowKey]); column => column.id === destination.toColumnId,
console.log({targetIndex, card, destination}) );
const targetIndex = get(destColumn, [
'cards',
destination.toPosition + 1,
rowKey,
]);
console.log({ targetIndex, card, destination });
await api.resource(resourceName).sort({ await api.resource(resourceName).sort({
associatedKey, associatedKey,
resourceKey: card[rowKey], resourceKey: card[rowKey],
@ -269,8 +291,11 @@ export function Kanban(props: any) {
renderColumnHeader={({ title, id }, { addCard }) => { renderColumnHeader={({ title, id }, { addCard }) => {
return ( return (
<> <>
<div className="react-kanban-column-header"><span>{ title }</span></div> <div className="react-kanban-column-header">
{allowAddCard && <Create <span>{title}</span>
</div>
{allowAddCard && (
<Create
onFinish={(values: any) => { onFinish={(values: any) => {
refresh(); refresh();
}} }}
@ -290,7 +315,9 @@ export function Kanban(props: any) {
background: '#fff', background: '#fff',
}, },
}, },
})}/>} })}
/>
)}
</> </>
); );
}} }}

View File

@ -14,7 +14,7 @@
} }
.noco-card-adder-button:hover { .noco-card-adder-button:hover {
background: rgba(255, 255, 255, .4) !important; background: rgba(255, 255, 255, 0.4) !important;
} }
.noco-kanban-view { .noco-kanban-view {

View File

@ -5,7 +5,16 @@ import { Spin } from '@nocobase/client';
import { useRequest, useLocation } from 'umi'; import { useRequest, useLocation } from 'umi';
import api from '@/api-client'; import api from '@/api-client';
import { Actions } from '../Actions'; import { Actions } from '../Actions';
import { Modal, Table as AntdTable, Card, Pagination, Button, Tabs, Descriptions, Tooltip } from 'antd'; import {
Modal,
Table as AntdTable,
Card,
Pagination,
Button,
Tabs,
Descriptions,
Tooltip,
} from 'antd';
import { LoadingOutlined } from '@ant-design/icons'; import { LoadingOutlined } from '@ant-design/icons';
import { components, fields2columns } from '@/components/views/SortableTable'; import { components, fields2columns } from '@/components/views/SortableTable';
import ReactDragListView from 'react-drag-listview'; import ReactDragListView from 'react-drag-listview';
@ -19,7 +28,19 @@ import { Form } from './Form';
import { View } from './'; import { View } from './';
export function Details(props) { export function Details(props) {
const { onValueChange, onReset, __parent, noRequest, associatedKey, resourceName, onFinish, onDataChange, data, items = [], resolve } = props; const {
onValueChange,
onReset,
__parent,
noRequest,
associatedKey,
resourceName,
onFinish,
onDataChange,
data,
items = [],
resolve,
} = props;
if (!items || items.length === 0) { if (!items || items.length === 0) {
return null; return null;
} }
@ -28,9 +49,13 @@ export function Details(props) {
<div className={'page-tabs'}> <div className={'page-tabs'}>
{items.length > 1 && ( {items.length > 1 && (
<div className={'tabs-wrap'}> <div className={'tabs-wrap'}>
<Tabs size={'small'} activeKey={`${currentTabIndex}`} onChange={(activeKey) => { <Tabs
size={'small'}
activeKey={`${currentTabIndex}`}
onChange={activeKey => {
setCurrentTabIndex(activeKey); setCurrentTabIndex(activeKey);
}}> }}
>
{items.map((page, index) => ( {items.map((page, index) => (
<Tabs.TabPane tab={page.title} key={`${index}`} /> <Tabs.TabPane tab={page.title} key={`${index}`} />
))} ))}
@ -41,7 +66,8 @@ export function Details(props) {
let viewName: string; let viewName: string;
if (typeof view === 'string') { if (typeof view === 'string') {
viewName = `${resourceName}.${view}`; viewName = `${resourceName}.${view}`;
} if (typeof view === 'object') { }
if (typeof view === 'object') {
viewName = `${resourceName}.${view.name}`; viewName = `${resourceName}.${view.name}`;
} }
return ( return (
@ -54,7 +80,8 @@ export function Details(props) {
onFinish={onFinish} onFinish={onFinish}
onDataChange={onDataChange} onDataChange={onDataChange}
data={data} data={data}
viewName={viewName}/> viewName={viewName}
/>
); );
})} })}
</div> </div>
@ -62,7 +89,11 @@ export function Details(props) {
} }
export function generateIndex(): string { export function generateIndex(): string {
return `${Math.random().toString(36).replace('0.', '').slice(-4).padStart(4, '0')}`; return `${Math.random()
.toString(36)
.replace('0.', '')
.slice(-4)
.padStart(4, '0')}`;
} }
export function SubTable(props: any) { export function SubTable(props: any) {
@ -71,7 +102,7 @@ export function SubTable(props: any) {
schema = {}, schema = {},
associatedKey, associatedKey,
onChange, onChange,
size = 'middle' size = 'middle',
} = props; } = props;
const { const {
@ -111,22 +142,20 @@ export function SubTable(props: any) {
} }
if (draggable && !sortField) { if (draggable && !sortField) {
sortField = 'sort', (sortField = 'sort'),
cloneFields.unshift({ cloneFields.unshift({
"dataIndex": [ dataIndex: ['sort'],
"sort" title: '排序',
], name: 'sort',
"title": "排序", interface: 'sort',
"name": "sort", type: 'sort',
"interface": "sort", required: true,
"type": "sort", developerMode: false,
"required": true, component: {
"developerMode": false, type: 'sort',
"component": { showInTable: true,
"type": "sort", width: 60,
"showInTable": true, className: 'drag-visible',
"width": 60,
"className": "drag-visible"
}, },
}); });
} }
@ -136,15 +165,20 @@ export function SubTable(props: any) {
} }
const { type } = associationField; const { type } = associationField;
const { data = [], loading, mutate, refresh, run, params } = useRequest((params = {}, ...args) => { const { data = [], loading, mutate, refresh, run, params } = useRequest(
return !associatedKey || type === 'virtual' || type === 'json' ? Promise.resolve({ (params = {}, ...args) => {
return !associatedKey || type === 'virtual' || type === 'json'
? Promise.resolve({
data: (props.data || []).map(item => { data: (props.data || []).map(item => {
if (!item[rowKey]) { if (!item[rowKey]) {
item[rowKey] = generateIndex(); item[rowKey] = generateIndex();
} }
return item; return item;
}),
}) })
}) : api.resource(resourceName).list({ : api
.resource(resourceName)
.list({
associatedKey, associatedKey,
perPage: -1, perPage: -1,
'fields[appends]': appends, 'fields[appends]': appends,
@ -153,7 +187,7 @@ export function SubTable(props: any) {
if (!Array.isArray(data)) { if (!Array.isArray(data)) {
return { return {
data: [], data: [],
} };
} }
return { return {
data: data.map(item => { data: data.map(item => {
@ -161,12 +195,14 @@ export function SubTable(props: any) {
item[rowKey] = generateIndex(); item[rowKey] = generateIndex();
} }
return item; return item;
}) }),
} };
}); });
}, { },
{
paginated: false, paginated: false,
}); },
);
const dataSource = data as any; const dataSource = data as any;
@ -179,11 +215,11 @@ export function SubTable(props: any) {
return { ...v, [sortField]: i }; return { ...v, [sortField]: i };
}); });
mutate(data); mutate(data);
onChange && await onChange(data); onChange && (await onChange(data));
}, },
handleSelector: ".drag-handle", handleSelector: '.drag-handle',
ignoreSelector: "tr.ant-table-expanded-row", ignoreSelector: 'tr.ant-table-expanded-row',
nodeSelector: "tr.ant-table-row" nodeSelector: 'tr.ant-table-row',
}; };
const tableProps: any = {}; const tableProps: any = {};
@ -194,15 +230,23 @@ export function SubTable(props: any) {
tableProps.rowSelection = { tableProps.rowSelection = {
type: 'checkbox', type: 'checkbox',
selectedRowKeys, selectedRowKeys,
onChange: (selectedRowKeys: React.ReactText[], selectedRows: React.ReactText[]) => { onChange: (
selectedRowKeys: React.ReactText[],
selectedRows: React.ReactText[],
) => {
setSelectedRowKeys(selectedRowKeys); setSelectedRowKeys(selectedRowKeys);
}, },
} };
} }
return ( return (
<div> <div>
<Actions size={size} __parent={__parent} associatedKey={associatedKey} noRequest={true} onTrigger={{ <Actions
size={size}
__parent={__parent}
associatedKey={associatedKey}
noRequest={true}
onTrigger={{
async create(values) { async create(values) {
values[rowKey] = generateIndex(); values[rowKey] = generateIndex();
let data = [...dataSource]; let data = [...dataSource];
@ -211,7 +255,7 @@ export function SubTable(props: any) {
return { ...v, [sortField]: i }; return { ...v, [sortField]: i };
}); });
mutate(data); mutate(data);
onChange && await onChange(data); onChange && (await onChange(data));
}, },
async add(items = []) { async add(items = []) {
let data = [...dataSource]; let data = [...dataSource];
@ -223,46 +267,60 @@ export function SubTable(props: any) {
return { ...v, [sortField]: i }; return { ...v, [sortField]: i };
}); });
mutate(data); mutate(data);
onChange && await onChange(data); onChange && (await onChange(data));
}, },
async destroy() { async destroy() {
let data = dataSource.filter(item => !selectedRowKeys.includes(item[rowKey])); let data = dataSource.filter(
item => !selectedRowKeys.includes(item[rowKey]),
);
data = data.map((v: any, i) => { data = data.map((v: any, i) => {
return { ...v, [sortField]: i }; return { ...v, [sortField]: i };
}); });
mutate(data); mutate(data);
onChange && await onChange(data); onChange && (await onChange(data));
}, },
}} actions={actions} style={{ marginBottom: 14, marginTop: -31 }}/> }}
actions={actions}
style={{ marginBottom: 14, marginTop: -31 }}
/>
<ReactDragListView {...dragProps}> <ReactDragListView {...dragProps}>
<AntdTable <AntdTable
rowKey={rowKey} rowKey={rowKey}
dataSource={dataSource} dataSource={dataSource}
size={size} size={size}
columns={fields2columns(cloneFields, { mutate, dataSource, onChange })} columns={fields2columns(cloneFields, {
mutate,
dataSource,
onChange,
})}
pagination={false} pagination={false}
onChange={(pagination, filters, sorter, extra) => { onChange={(pagination, filters, sorter, extra) => {}}
}}
components={{ components={{
body: { body: {
row: ({ className, ...others }) => { row: ({ className, ...others }) => {
if (!details.length) { if (!details.length) {
return <tr className={className} {...others}/> return <tr className={className} {...others} />;
} }
return <tr className={className ? `${className} row-clickable` : 'row-clickable'} {...others}/> return (
<tr
className={
className ? `${className} row-clickable` : 'row-clickable'
}
{...others}
/>
);
},
}, },
}
}} }}
expandable={expandable} expandable={expandable}
onRow={(data, index) => ({ onRow={(data, index) => ({
onClick: (e) => { onClick: e => {
const className = (e.target as HTMLElement).className; const className = (e.target as HTMLElement).className;
if (typeof className === 'string' && if (
(className.includes('ant-table-selection-column') typeof className === 'string' &&
|| className.includes('ant-checkbox') (className.includes('ant-table-selection-column') ||
|| className.includes('ant-radio') className.includes('ant-checkbox') ||
) className.includes('ant-radio'))
) { ) {
return; return;
} }
@ -280,7 +338,7 @@ export function SubTable(props: any) {
// __parent={__parent} // __parent={__parent}
associatedKey={associatedKey} associatedKey={associatedKey}
resourceName={resourceName} resourceName={resourceName}
onFinish={async (values) => { onFinish={async values => {
let data = [...dataSource]; let data = [...dataSource];
console.log({ values }); console.log({ values });
data[index] = values; data[index] = values;
@ -288,14 +346,12 @@ export function SubTable(props: any) {
return { ...v, [sortField]: i }; return { ...v, [sortField]: i };
}); });
mutate(data); mutate(data);
onChange && await onChange(data); onChange && (await onChange(data));
console.log({ values, data }); console.log({ values, data });
resolve(); resolve();
}} }}
onReset={resolve} onReset={resolve}
onDataChange={() => { onDataChange={() => {}}
}}
onValueChange={() => { onValueChange={() => {
closeWithConfirm && closeWithConfirm(true); closeWithConfirm && closeWithConfirm(true);
}} }}

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