mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 08:55:33 +00:00
refactor(association field): association field support tag field mode (#2251)
* refactor: association field support tag field mode * refactor: association field support tag field mode * refactor: locale improve * refactor: color field * refactor: locale improve * refactor: code improve * refactor: code improve * refactor: code improve * refactor: code improve * refactor: colorPicker refactor * refactor: colorPicker refactor * fix: color field failed to config defaultValue * style: style improve * refactor: code improve * fix: merge bug * refactor: color field defaultValue --------- Co-authored-by: chenos <chenlinxh@gmail.com>
This commit is contained in:
parent
8f1d0d80af
commit
18bf6e507e
@ -0,0 +1,26 @@
|
|||||||
|
import { defaultProps, operators } from './properties';
|
||||||
|
import { IField } from './types';
|
||||||
|
|
||||||
|
export const color: IField = {
|
||||||
|
name: 'color',
|
||||||
|
type: 'object',
|
||||||
|
group: 'basic',
|
||||||
|
order: 10,
|
||||||
|
title: '{{t("Color")}}',
|
||||||
|
default: {
|
||||||
|
type: 'string',
|
||||||
|
uiSchema: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'ColorPicker',
|
||||||
|
default: '#1677FF',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
availableTypes: ['string'],
|
||||||
|
hasDefaultValue: true,
|
||||||
|
properties: {
|
||||||
|
...defaultProps,
|
||||||
|
},
|
||||||
|
filterable: {
|
||||||
|
operators: operators.string,
|
||||||
|
},
|
||||||
|
};
|
@ -2,6 +2,7 @@ export * from './checkbox';
|
|||||||
export * from './checkboxGroup';
|
export * from './checkboxGroup';
|
||||||
export * from './chinaRegion';
|
export * from './chinaRegion';
|
||||||
export * from './collection';
|
export * from './collection';
|
||||||
|
export * from './color';
|
||||||
export * from './createdAt';
|
export * from './createdAt';
|
||||||
export * from './createdBy';
|
export * from './createdBy';
|
||||||
export * from './datetime';
|
export * from './datetime';
|
||||||
|
@ -711,6 +711,8 @@ export default {
|
|||||||
"Date display format":"Date display format",
|
"Date display format":"Date display format",
|
||||||
"Assign data scope for the template":"Assign data scope for the template",
|
"Assign data scope for the template":"Assign data scope for the template",
|
||||||
"Table selected records":"Table selected records",
|
"Table selected records":"Table selected records",
|
||||||
|
"Tag":"Tag",
|
||||||
|
"Tag color field":"Tag color field",
|
||||||
"Sync successfully":"Sync successfully",
|
"Sync successfully":"Sync successfully",
|
||||||
"Sync from form fields":"Sync from form fields",
|
"Sync from form fields":"Sync from form fields",
|
||||||
"Select all":"Select all"
|
"Select all":"Select all"
|
||||||
|
@ -621,6 +621,8 @@ export default {
|
|||||||
"Allow add new, update and delete actions":"削除変更操作の許可",
|
"Allow add new, update and delete actions":"削除変更操作の許可",
|
||||||
"Date display format":"日付表示形式",
|
"Date display format":"日付表示形式",
|
||||||
"Assign data scope for the template":"テンプレートのデータ範囲の指定",
|
"Assign data scope for the template":"テンプレートのデータ範囲の指定",
|
||||||
|
"Tag":"タブ",
|
||||||
|
"Tag color field":"ラベルの色フィールド",
|
||||||
"Sync successfully":"同期成功",
|
"Sync successfully":"同期成功",
|
||||||
"Sync from form fields":"フォームフィールドの同期",
|
"Sync from form fields":"フォームフィールドの同期",
|
||||||
"Select all":"すべて選択"
|
"Select all":"すべて選択"
|
||||||
|
@ -796,6 +796,8 @@ export default {
|
|||||||
"Date display format":"日期显示格式",
|
"Date display format":"日期显示格式",
|
||||||
"Assign data scope for the template":"为模板指定数据范围",
|
"Assign data scope for the template":"为模板指定数据范围",
|
||||||
"Table selected records":"表格中选中的记录",
|
"Table selected records":"表格中选中的记录",
|
||||||
|
"Tag":"标签",
|
||||||
|
"Tag color field":"标签颜色字段",
|
||||||
"Sync successfully":"同步成功",
|
"Sync successfully":"同步成功",
|
||||||
"Sync from form fields":"同步表单字段",
|
"Sync from form fields":"同步表单字段",
|
||||||
"Select all":"全选"
|
"Select all":"全选"
|
||||||
|
@ -0,0 +1,123 @@
|
|||||||
|
import { observer, RecursionField, useField, useFieldSchema } from '@formily/react';
|
||||||
|
import { toArr } from '@formily/shared';
|
||||||
|
import flat from 'flat';
|
||||||
|
import React, { Fragment, useRef, useState } from 'react';
|
||||||
|
import { useDesignable } from '../../';
|
||||||
|
import { BlockAssociationContext, WithoutTableFieldResource } from '../../../block-provider';
|
||||||
|
import { CollectionProvider } from '../../../collection-manager';
|
||||||
|
import { RecordProvider, useRecord } from '../../../record-provider';
|
||||||
|
import { FormProvider } from '../../core';
|
||||||
|
import { useCompile } from '../../hooks';
|
||||||
|
import { ActionContextProvider, useActionContext } from '../action';
|
||||||
|
import { EllipsisWithTooltip } from '../input/EllipsisWithTooltip';
|
||||||
|
import { useAssociationFieldContext, useFieldNames, useInsertSchema } from './hooks';
|
||||||
|
import schema from './schema';
|
||||||
|
import { getTabFormatValue, useLabelUiSchema } from './util';
|
||||||
|
|
||||||
|
interface IEllipsisWithTooltipRef {
|
||||||
|
setPopoverVisible: (boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toValue = (value, placeholder) => {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return placeholder;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
export const ReadPrettyInternalTag: React.FC = observer(
|
||||||
|
(props: any) => {
|
||||||
|
const fieldSchema = useFieldSchema();
|
||||||
|
const recordCtx = useRecord();
|
||||||
|
const { enableLink, tagColorField } = fieldSchema['x-component-props'];
|
||||||
|
// value 做了转换,但 props.value 和原来 useField().value 的值不一致
|
||||||
|
const field = useField();
|
||||||
|
const fieldNames = useFieldNames(props);
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const insertViewer = useInsertSchema('Viewer');
|
||||||
|
const { options: collectionField } = useAssociationFieldContext();
|
||||||
|
const [record, setRecord] = useState({});
|
||||||
|
const compile = useCompile();
|
||||||
|
const { designable } = useDesignable();
|
||||||
|
const labelUiSchema = useLabelUiSchema(collectionField, fieldNames?.label || 'label');
|
||||||
|
const { snapshot } = useActionContext();
|
||||||
|
const ellipsisWithTooltipRef = useRef<IEllipsisWithTooltipRef>();
|
||||||
|
const tagColor = flat(recordCtx)[`${fieldSchema.name}.${tagColorField}`];
|
||||||
|
const renderRecords = () =>
|
||||||
|
toArr(props.value).map((record, index, arr) => {
|
||||||
|
const val = toValue(compile(record?.[fieldNames?.label || 'label']), 'N/A');
|
||||||
|
const text = getTabFormatValue(compile(labelUiSchema), val, tagColor);
|
||||||
|
return (
|
||||||
|
<Fragment key={`${record.id}_${index}`}>
|
||||||
|
<span>
|
||||||
|
{snapshot ? (
|
||||||
|
text
|
||||||
|
) : enableLink !== false ? (
|
||||||
|
<a
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
if (designable) {
|
||||||
|
insertViewer(schema.Viewer);
|
||||||
|
}
|
||||||
|
setVisible(true);
|
||||||
|
setRecord(record);
|
||||||
|
ellipsisWithTooltipRef?.current?.setPopoverVisible(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
text
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
{index < arr.length - 1 ? <span style={{ marginRight: 4, color: '#aaa' }}>,</span> : null}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderWithoutTableFieldResourceProvider = () => (
|
||||||
|
<WithoutTableFieldResource.Provider value={true}>
|
||||||
|
<FormProvider>
|
||||||
|
<RecursionField
|
||||||
|
schema={fieldSchema}
|
||||||
|
onlyRenderProperties
|
||||||
|
basePath={field.address}
|
||||||
|
filterProperties={(s) => {
|
||||||
|
return s['x-component'] === 'AssociationField.Viewer';
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormProvider>
|
||||||
|
</WithoutTableFieldResource.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderRecordProvider = () => {
|
||||||
|
const collectionFieldNames = fieldSchema?.['x-collection-field']?.split('.');
|
||||||
|
|
||||||
|
return collectionFieldNames && collectionFieldNames.length > 2 ? (
|
||||||
|
<RecordProvider record={recordCtx[collectionFieldNames[1]]}>
|
||||||
|
<RecordProvider record={record}>{renderWithoutTableFieldResourceProvider()}</RecordProvider>
|
||||||
|
</RecordProvider>
|
||||||
|
) : (
|
||||||
|
<RecordProvider record={record}>{renderWithoutTableFieldResourceProvider()}</RecordProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<BlockAssociationContext.Provider value={`${collectionField?.collectionName}.${collectionField?.name}`}>
|
||||||
|
<CollectionProvider name={collectionField?.target ?? collectionField?.targetCollection}>
|
||||||
|
<EllipsisWithTooltip ellipsis={true} ref={ellipsisWithTooltipRef}>
|
||||||
|
{renderRecords()}
|
||||||
|
</EllipsisWithTooltip>
|
||||||
|
<ActionContextProvider
|
||||||
|
value={{ visible, setVisible, openMode: 'drawer', snapshot: collectionField?.interface === 'snapshot' }}
|
||||||
|
>
|
||||||
|
{renderRecordProvider()}
|
||||||
|
</ActionContextProvider>
|
||||||
|
</CollectionProvider>
|
||||||
|
</BlockAssociationContext.Provider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{ displayName: 'ReadPrettyInternalTag' },
|
||||||
|
);
|
@ -1,11 +1,12 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import { observer } from '@formily/react';
|
||||||
import { useField, observer } from '@formily/react';
|
import React from 'react';
|
||||||
import { AssociationFieldProvider } from './AssociationFieldProvider';
|
import { AssociationFieldProvider } from './AssociationFieldProvider';
|
||||||
import { InternalNester } from './InternalNester';
|
|
||||||
import { ReadPrettyInternalViewer } from './InternalViewer';
|
|
||||||
import { InternalSubTable } from './InternalSubTable';
|
|
||||||
import { FileManageReadPretty } from './FileManager';
|
import { FileManageReadPretty } from './FileManager';
|
||||||
import { useAssociationFieldContext } from './hooks';
|
import { useAssociationFieldContext } from './hooks';
|
||||||
|
import { InternalNester } from './InternalNester';
|
||||||
|
import { InternalSubTable } from './InternalSubTable';
|
||||||
|
import { ReadPrettyInternalTag } from './InternalTag';
|
||||||
|
import { ReadPrettyInternalViewer } from './InternalViewer';
|
||||||
|
|
||||||
const ReadPrettyAssociationField = observer(
|
const ReadPrettyAssociationField = observer(
|
||||||
(props: any) => {
|
(props: any) => {
|
||||||
@ -14,6 +15,7 @@ const ReadPrettyAssociationField = observer(
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{['Select', 'Picker'].includes(currentMode) && <ReadPrettyInternalViewer {...props} />}
|
{['Select', 'Picker'].includes(currentMode) && <ReadPrettyInternalViewer {...props} />}
|
||||||
|
{currentMode === 'Tag' && <ReadPrettyInternalTag {...props} />}
|
||||||
{currentMode === 'Nester' && <InternalNester {...props} />}
|
{currentMode === 'Nester' && <InternalNester {...props} />}
|
||||||
{currentMode === 'SubTable' && <InternalSubTable {...props} />}
|
{currentMode === 'SubTable' && <InternalSubTable {...props} />}
|
||||||
{currentMode === 'FileManager' && <FileManageReadPretty {...props} />}
|
{currentMode === 'FileManager' && <FileManageReadPretty {...props} />}
|
||||||
|
@ -48,6 +48,28 @@ export const getLabelFormatValue = (labelUiSchema: ISchema, value: any, isTag =
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getTabFormatValue = (labelUiSchema: ISchema, value: any, tagColor): any => {
|
||||||
|
const options = labelUiSchema?.enum;
|
||||||
|
if (Array.isArray(options) && value) {
|
||||||
|
const values = toArr(value).map((val) => {
|
||||||
|
const opt: any = options.find((option: any) => option.value === val);
|
||||||
|
return React.createElement(Tag, { color: tagColor||opt?.color }, opt?.label);
|
||||||
|
});
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
switch (labelUiSchema?.['x-component']) {
|
||||||
|
case 'DatePicker':
|
||||||
|
return React.createElement(
|
||||||
|
Tag,
|
||||||
|
{ color: tagColor },
|
||||||
|
getDatePickerLabels({ ...labelUiSchema?.['x-component-props'], value }),
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return React.createElement(Tag, { color: tagColor }, value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export function flatData(data) {
|
export function flatData(data) {
|
||||||
const newArr = [];
|
const newArr = [];
|
||||||
for (let i = 0; i < data.length; i++) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
|
import { usePrefixCls } from '@formily/antd-v5/esm/__builtins__';
|
||||||
|
import { connect, mapProps, mapReadPretty } from '@formily/react';
|
||||||
|
import { ColorPicker as AntdColorPicker } from 'antd';
|
||||||
|
import cls from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const ColorPicker = connect(
|
||||||
|
(props) => {
|
||||||
|
const { value, onChange, ...others } = props;
|
||||||
|
return (
|
||||||
|
<AntdColorPicker
|
||||||
|
value={value}
|
||||||
|
trigger="hover"
|
||||||
|
{...others}
|
||||||
|
destroyTooltipOnHide
|
||||||
|
getPopupContainer={(current) => current}
|
||||||
|
presets={[
|
||||||
|
{
|
||||||
|
label: 'Recommended',
|
||||||
|
colors: [
|
||||||
|
'#8BBB11',
|
||||||
|
'#52C41A',
|
||||||
|
'#13A8A8',
|
||||||
|
'#1677FF',
|
||||||
|
'#F5222D',
|
||||||
|
'#FADB14',
|
||||||
|
'#FA8C164D',
|
||||||
|
'#FADB144D',
|
||||||
|
'#52C41A4D',
|
||||||
|
'#1677FF4D',
|
||||||
|
'#2F54EB4D',
|
||||||
|
'#722ED14D',
|
||||||
|
'#EB2F964D',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
onChange={(color) => onChange(color.toHexString())}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
mapProps((props, field) => {
|
||||||
|
return {
|
||||||
|
...props,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
mapReadPretty((props) => {
|
||||||
|
const prefixCls = usePrefixCls('description-color-picker', props);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cls(
|
||||||
|
prefixCls,
|
||||||
|
css`
|
||||||
|
display: inline-flex;
|
||||||
|
.ant-color-picker-trigger-disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<AntdColorPicker disabled value={props.value} size="small" {...props} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ColorPicker;
|
@ -0,0 +1,24 @@
|
|||||||
|
import { usePrefixCls } from '@formily/antd-v5/esm/__builtins__';
|
||||||
|
import type { ColorPickerProps as AntdColorPickerProps } from 'antd/es/color-picker';
|
||||||
|
import cls from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
type Composed = {
|
||||||
|
ColorPicker: React.FC<AntdColorPickerProps>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ReadPretty: Composed = () => null;
|
||||||
|
|
||||||
|
ReadPretty.ColorPicker = function ColorPicker(props: any) {
|
||||||
|
const prefixCls = usePrefixCls('description-color-picker', props);
|
||||||
|
|
||||||
|
if (!props.value) {
|
||||||
|
return <div></div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cls(prefixCls, props.className)}>
|
||||||
|
<ColorPicker showText disabled value={props.value} size='small'/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* title: ColorPicker
|
||||||
|
*/
|
||||||
|
import { FormItem } from '@formily/antd-v5';
|
||||||
|
import { ColorPicker, Input, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const schema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
input: {
|
||||||
|
type: 'boolean',
|
||||||
|
title: `Editable`,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ColorPicker',
|
||||||
|
'x-reactions': {
|
||||||
|
target: '*(read1,read2)',
|
||||||
|
fulfill: {
|
||||||
|
state: {
|
||||||
|
value: '{{$self.value}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
read1: {
|
||||||
|
type: 'boolean',
|
||||||
|
title: `Read pretty`,
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'ColorPicker',
|
||||||
|
},
|
||||||
|
read2: {
|
||||||
|
type: 'string',
|
||||||
|
title: `Value`,
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-component-props': {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
return (
|
||||||
|
<SchemaComponentProvider components={{ Input, ColorPicker, FormItem }}>
|
||||||
|
<SchemaComponent schema={schema} />
|
||||||
|
</SchemaComponentProvider>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
group:
|
||||||
|
title: Schema Components
|
||||||
|
order: 3
|
||||||
|
---
|
||||||
|
|
||||||
|
# ColorPicker
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Basic
|
||||||
|
|
||||||
|
<code src="./demos/demo1.tsx"></code>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1 @@
|
|||||||
|
export * from './ColorPicker';
|
@ -0,0 +1,158 @@
|
|||||||
|
import { dayjs, getDefaultFormat, str2moment, toGmt, toLocal } from '@nocobase/utils/client';
|
||||||
|
import type { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
|
const toStringByPicker = (value, picker, timezone: 'gmt' | 'local') => {
|
||||||
|
if (!dayjs.isDayjs(value)) return value;
|
||||||
|
if (timezone === 'local') {
|
||||||
|
const offset = new Date().getTimezoneOffset();
|
||||||
|
return dayjs(toStringByPicker(value, picker, 'gmt')).add(offset, 'minutes').toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (picker === 'year') {
|
||||||
|
return value.format('YYYY') + '-01-01T00:00:00.000Z';
|
||||||
|
}
|
||||||
|
if (picker === 'month') {
|
||||||
|
return value.format('YYYY-MM') + '-01T00:00:00.000Z';
|
||||||
|
}
|
||||||
|
if (picker === 'quarter') {
|
||||||
|
return value.startOf('quarter').format('YYYY-MM') + '-01T00:00:00.000Z';
|
||||||
|
}
|
||||||
|
if (picker === 'week') {
|
||||||
|
return value.startOf('week').add(1, 'day').format('YYYY-MM-DD') + 'T00:00:00.000Z';
|
||||||
|
}
|
||||||
|
return value.format('YYYY-MM-DDTHH:mm:ss.SSS') + 'Z';
|
||||||
|
};
|
||||||
|
|
||||||
|
const toGmtByPicker = (value: Dayjs, picker?: any) => {
|
||||||
|
if (!value || !dayjs.isDayjs(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return toStringByPicker(value, picker, 'gmt');
|
||||||
|
};
|
||||||
|
|
||||||
|
const toLocalByPicker = (value: Dayjs, picker?: any) => {
|
||||||
|
if (!value || !dayjs.isDayjs(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return toStringByPicker(value, picker, 'local');
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface Moment2strOptions {
|
||||||
|
showTime?: boolean;
|
||||||
|
gmt?: boolean;
|
||||||
|
utc?: boolean;
|
||||||
|
picker?: 'year' | 'month' | 'week' | 'quarter';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const moment2str = (value?: Dayjs | null, options: Moment2strOptions = {}) => {
|
||||||
|
const { showTime, gmt, picker, utc = true } = options;
|
||||||
|
if (!value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (!utc) {
|
||||||
|
const format = showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD';
|
||||||
|
return value.format(format);
|
||||||
|
}
|
||||||
|
if (showTime) {
|
||||||
|
return gmt ? toGmt(value) : toLocal(value);
|
||||||
|
}
|
||||||
|
if (typeof gmt === 'boolean') {
|
||||||
|
return gmt ? toGmtByPicker(value, picker) : toLocalByPicker(value, picker);
|
||||||
|
}
|
||||||
|
return toGmtByPicker(value, picker);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mapDatePicker = function () {
|
||||||
|
return (props: any) => {
|
||||||
|
const format = getDefaultFormat(props) as any;
|
||||||
|
const onChange = props.onChange;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...props,
|
||||||
|
format: format,
|
||||||
|
value: str2moment(props.value, props),
|
||||||
|
onChange: (value: Dayjs | null) => {
|
||||||
|
if (onChange) {
|
||||||
|
if (!props.showTime && value) {
|
||||||
|
value = value.startOf('day');
|
||||||
|
}
|
||||||
|
onChange(moment2str(value, props));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mapRangePicker = function () {
|
||||||
|
return (props: any) => {
|
||||||
|
const format = getDefaultFormat(props) as any;
|
||||||
|
const onChange = props.onChange;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...props,
|
||||||
|
format: format,
|
||||||
|
value: str2moment(props.value, props),
|
||||||
|
onChange: (value: Dayjs[]) => {
|
||||||
|
if (onChange) {
|
||||||
|
onChange(
|
||||||
|
value
|
||||||
|
? [moment2str(getRangeStart(value[0], props), props), moment2str(getRangeEnd(value[1], props), props)]
|
||||||
|
: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function getRangeStart(value: Dayjs, options: Moment2strOptions) {
|
||||||
|
const { showTime } = options;
|
||||||
|
if (showTime) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return value.startOf('day');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRangeEnd(value: Dayjs, options: Moment2strOptions) {
|
||||||
|
const { showTime } = options;
|
||||||
|
if (showTime) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return value.endOf('day');
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStart = (offset: any, unit: any) => {
|
||||||
|
return dayjs()
|
||||||
|
.add(offset, unit === 'isoWeek' ? 'week' : unit)
|
||||||
|
.startOf(unit);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getEnd = (offset: any, unit: any) => {
|
||||||
|
return dayjs()
|
||||||
|
.add(offset, unit === 'isoWeek' ? 'week' : unit)
|
||||||
|
.endOf(unit);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDateRanges = () => {
|
||||||
|
return {
|
||||||
|
today: () => [getStart(0, 'day'), getEnd(0, 'day')],
|
||||||
|
lastWeek: () => [getStart(-1, 'isoWeek'), getEnd(-1, 'isoWeek')],
|
||||||
|
thisWeek: () => [getStart(0, 'isoWeek'), getEnd(0, 'isoWeek')],
|
||||||
|
nextWeek: () => [getStart(1, 'isoWeek'), getEnd(1, 'isoWeek')],
|
||||||
|
lastMonth: () => [getStart(-1, 'month'), getEnd(-1, 'month')],
|
||||||
|
thisMonth: () => [getStart(0, 'month'), getEnd(0, 'month')],
|
||||||
|
nextMonth: () => [getStart(1, 'month'), getEnd(1, 'month')],
|
||||||
|
lastQuarter: () => [getStart(-1, 'quarter'), getEnd(-1, 'quarter')],
|
||||||
|
thisQuarter: () => [getStart(0, 'quarter'), getEnd(0, 'quarter')],
|
||||||
|
nextQuarter: () => [getStart(1, 'quarter'), getEnd(1, 'quarter')],
|
||||||
|
lastYear: () => [getStart(-1, 'year'), getEnd(-1, 'year')],
|
||||||
|
thisYear: () => [getStart(0, 'year'), getEnd(0, 'year')],
|
||||||
|
nextYear: () => [getStart(1, 'year'), getEnd(1, 'year')],
|
||||||
|
last7Days: () => [getStart(-6, 'days'), getEnd(0, 'days')],
|
||||||
|
next7Days: () => [getStart(1, 'day'), getEnd(7, 'days')],
|
||||||
|
last30Days: () => [getStart(-29, 'days'), getEnd(0, 'days')],
|
||||||
|
next30Days: () => [getStart(1, 'day'), getEnd(30, 'days')],
|
||||||
|
last90Days: () => [getStart(-89, 'days'), getEnd(0, 'days')],
|
||||||
|
next90Days: () => [getStart(1, 'day'), getEnd(90, 'days')],
|
||||||
|
};
|
||||||
|
};
|
@ -28,6 +28,7 @@ import { BlockItem } from '../block-item';
|
|||||||
import { removeNullCondition } from '../filter';
|
import { removeNullCondition } from '../filter';
|
||||||
import { HTMLEncode } from '../input/shared';
|
import { HTMLEncode } from '../input/shared';
|
||||||
import { FilterDynamicComponent } from '../table-v2/FilterDynamicComponent';
|
import { FilterDynamicComponent } from '../table-v2/FilterDynamicComponent';
|
||||||
|
import { useColorFields } from '../table-v2/Table.Column.Designer';
|
||||||
import { FilterFormDesigner } from './FormItem.FilterFormDesigner';
|
import { FilterFormDesigner } from './FormItem.FilterFormDesigner';
|
||||||
import { useEnsureOperatorsValid } from './SchemaSettingOptions';
|
import { useEnsureOperatorsValid } from './SchemaSettingOptions';
|
||||||
|
|
||||||
@ -183,6 +184,7 @@ FormItem.Designer = function Designer() {
|
|||||||
value: field?.name,
|
value: field?.name,
|
||||||
label: compile(field?.uiSchema?.title) || field?.name,
|
label: compile(field?.uiSchema?.title) || field?.name,
|
||||||
}));
|
}));
|
||||||
|
const colorFieldOptions = useColorFields(collectionField?.target ?? collectionField?.targetCollection);
|
||||||
|
|
||||||
let readOnlyMode = 'editable';
|
let readOnlyMode = 'editable';
|
||||||
if (fieldSchema['x-disabled'] === true) {
|
if (fieldSchema['x-disabled'] === true) {
|
||||||
@ -529,10 +531,6 @@ FormItem.Designer = function Designer() {
|
|||||||
schema['x-component-props'] = fieldSchema['x-component-props'];
|
schema['x-component-props'] = fieldSchema['x-component-props'];
|
||||||
field.componentProps = field.componentProps || {};
|
field.componentProps = field.componentProps || {};
|
||||||
field.componentProps.mode = mode;
|
field.componentProps.mode = mode;
|
||||||
// if (mode === 'Nester') {
|
|
||||||
// const initValue = ['hasMany', 'belongsToMany'].includes(collectionField?.type) ? [{}] : {};
|
|
||||||
// field.value = field.value || initValue;
|
|
||||||
// }
|
|
||||||
dn.emit('patch', {
|
dn.emit('patch', {
|
||||||
schema,
|
schema,
|
||||||
});
|
});
|
||||||
@ -811,6 +809,29 @@ FormItem.Designer = function Designer() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isDateField && <SchemaSettings.DataFormat fieldSchema={fieldSchema} />}
|
{isDateField && <SchemaSettings.DataFormat fieldSchema={fieldSchema} />}
|
||||||
|
|
||||||
|
{isAssociationField && ['Tag'].includes(fieldMode) && (
|
||||||
|
<SchemaSettings.SelectItem
|
||||||
|
key="title-field"
|
||||||
|
title={t('Tag color field')}
|
||||||
|
options={colorFieldOptions}
|
||||||
|
value={field?.componentProps?.tagColorField}
|
||||||
|
onChange={(tagColorField) => {
|
||||||
|
const schema = {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
};
|
||||||
|
|
||||||
|
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
||||||
|
fieldSchema['x-component-props']['tagColorField'] = tagColorField;
|
||||||
|
schema['x-component-props'] = fieldSchema['x-component-props'];
|
||||||
|
field.componentProps.tagColorField = tagColorField;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
dn.refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{collectionField && <SchemaSettings.Divider />}
|
{collectionField && <SchemaSettings.Divider />}
|
||||||
<SchemaSettings.Remove
|
<SchemaSettings.Remove
|
||||||
key="remove"
|
key="remove"
|
||||||
|
@ -9,6 +9,7 @@ export * from './card-item';
|
|||||||
export * from './cascader';
|
export * from './cascader';
|
||||||
export * from './checkbox';
|
export * from './checkbox';
|
||||||
export * from './collection-select';
|
export * from './collection-select';
|
||||||
|
export * from './color-picker';
|
||||||
export * from './color-select';
|
export * from './color-select';
|
||||||
export * from './cron';
|
export * from './cron';
|
||||||
export * from './date-picker';
|
export * from './date-picker';
|
||||||
@ -49,4 +50,5 @@ export * from './time-picker';
|
|||||||
export * from './tree-select';
|
export * from './tree-select';
|
||||||
export * from './upload';
|
export * from './upload';
|
||||||
export * from './variable';
|
export * from './variable';
|
||||||
|
|
||||||
import './index.less';
|
import './index.less';
|
||||||
|
@ -27,6 +27,22 @@ const useLabelFields = (collectionName?: any) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useColorFields = (collectionName?: any) => {
|
||||||
|
const compile = useCompile();
|
||||||
|
const { getCollectionFields } = useCollectionManager();
|
||||||
|
if (!collectionName) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const targetFields = getCollectionFields(collectionName);
|
||||||
|
return targetFields
|
||||||
|
?.filter?.((field) => field?.interface === 'color')
|
||||||
|
?.map?.((field) => {
|
||||||
|
return {
|
||||||
|
value: field.name,
|
||||||
|
label: compile(field?.uiSchema?.title || field.name),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
export const TableColumnDesigner = (props) => {
|
export const TableColumnDesigner = (props) => {
|
||||||
const { uiSchema, fieldSchema, collectionField } = props;
|
const { uiSchema, fieldSchema, collectionField } = props;
|
||||||
const { getInterface, getCollection } = useCollectionManager();
|
const { getInterface, getCollection } = useCollectionManager();
|
||||||
@ -37,6 +53,7 @@ export const TableColumnDesigner = (props) => {
|
|||||||
const fieldNames =
|
const fieldNames =
|
||||||
fieldSchema?.['x-component-props']?.['fieldNames'] || uiSchema?.['x-component-props']?.['fieldNames'];
|
fieldSchema?.['x-component-props']?.['fieldNames'] || uiSchema?.['x-component-props']?.['fieldNames'];
|
||||||
const options = useLabelFields(collectionField?.target ?? collectionField?.targetCollection);
|
const options = useLabelFields(collectionField?.target ?? collectionField?.targetCollection);
|
||||||
|
const colorFieldOptions = useColorFields(collectionField?.target ?? collectionField?.targetCollection);
|
||||||
const intefaceCfg = getInterface(collectionField?.interface);
|
const intefaceCfg = getInterface(collectionField?.interface);
|
||||||
const targetCollection = getCollection(collectionField?.target);
|
const targetCollection = getCollection(collectionField?.target);
|
||||||
const isFileField = isFileCollection(targetCollection);
|
const isFileField = isFileCollection(targetCollection);
|
||||||
@ -45,6 +62,7 @@ export const TableColumnDesigner = (props) => {
|
|||||||
const defaultFilter = fieldSchema?.['x-component-props']?.service?.params?.filter || {};
|
const defaultFilter = fieldSchema?.['x-component-props']?.service?.params?.filter || {};
|
||||||
const dataSource = useCollectionFilterOptions(collectionField?.target);
|
const dataSource = useCollectionFilterOptions(collectionField?.target);
|
||||||
const isDateField = ['datetime', 'createdAt', 'updatedAt'].includes(collectionField?.interface);
|
const isDateField = ['datetime', 'createdAt', 'updatedAt'].includes(collectionField?.interface);
|
||||||
|
const fieldMode = fieldSchema?.['x-component-props']?.['mode'] || 'Select';
|
||||||
let readOnlyMode = 'editable';
|
let readOnlyMode = 'editable';
|
||||||
if (fieldSchema['x-disabled'] === true) {
|
if (fieldSchema['x-disabled'] === true) {
|
||||||
readOnlyMode = 'readonly';
|
readOnlyMode = 'readonly';
|
||||||
@ -232,6 +250,55 @@ export const TableColumnDesigner = (props) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{readOnlyMode === 'read-pretty' &&
|
||||||
|
['linkTo', 'm2m', 'm2o', 'o2m', 'obo', 'oho', 'snapshot'].includes(collectionField?.interface) && (
|
||||||
|
<SchemaSettings.SelectItem
|
||||||
|
key="field-mode"
|
||||||
|
title={t('Field component')}
|
||||||
|
options={[
|
||||||
|
{ label: t('Title'), value: 'Select' },
|
||||||
|
{ label: t('Tag'), value: 'Tag' },
|
||||||
|
]}
|
||||||
|
value={fieldMode}
|
||||||
|
onChange={(mode) => {
|
||||||
|
const schema = {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
};
|
||||||
|
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
||||||
|
fieldSchema['x-component-props']['mode'] = mode;
|
||||||
|
schema['x-component-props'] = fieldSchema['x-component-props'];
|
||||||
|
field.componentProps = field.componentProps || {};
|
||||||
|
field.componentProps.mode = mode;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
dn.refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{['Tag'].includes(fieldMode) && (
|
||||||
|
<SchemaSettings.SelectItem
|
||||||
|
key="title-field"
|
||||||
|
title={t('Tag color field')}
|
||||||
|
options={colorFieldOptions}
|
||||||
|
value={fieldSchema?.['x-component-props']?.tagColorField}
|
||||||
|
onChange={(tagColorField) => {
|
||||||
|
const schema = {
|
||||||
|
['x-uid']: fieldSchema['x-uid'],
|
||||||
|
};
|
||||||
|
|
||||||
|
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
||||||
|
fieldSchema['x-component-props']['tagColorField'] = tagColorField;
|
||||||
|
schema['x-component-props'] = fieldSchema['x-component-props'];
|
||||||
|
field.componentProps.tagColorField = tagColorField;
|
||||||
|
dn.emit('patch', {
|
||||||
|
schema,
|
||||||
|
});
|
||||||
|
dn.refresh();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{isSubTableColumn && !field.readPretty && !uiSchema?.['x-read-pretty'] && (
|
{isSubTableColumn && !field.readPretty && !uiSchema?.['x-read-pretty'] && (
|
||||||
<SchemaSettings.SwitchItem
|
<SchemaSettings.SwitchItem
|
||||||
|
@ -297,7 +297,6 @@ export const Table: any = observer(
|
|||||||
console.warn('move cancel');
|
console.warn('move cancel');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fromIndex = e.active?.data.current?.sortable?.index;
|
const fromIndex = e.active?.data.current?.sortable?.index;
|
||||||
const toIndex = e.over?.data.current?.sortable?.index;
|
const toIndex = e.over?.data.current?.sortable?.index;
|
||||||
const from = field.value[fromIndex];
|
const from = field.value[fromIndex];
|
||||||
@ -324,6 +323,11 @@ export const Table: any = observer(
|
|||||||
.nb-read-pretty-input-number {
|
.nb-read-pretty-input-number {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
.ant-color-picker-trigger{
|
||||||
|
position:absolute;
|
||||||
|
top:50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -24,6 +24,7 @@ export const useFieldModeOptions = () => {
|
|||||||
? [
|
? [
|
||||||
{ label: t('Title'), value: 'Select' },
|
{ label: t('Title'), value: 'Select' },
|
||||||
{ label: t('File manager'), value: 'FileManager' },
|
{ label: t('File manager'), value: 'FileManager' },
|
||||||
|
{ label: t('Tag'), value: 'Tag' },
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
{ label: t('Select'), value: 'Select' },
|
{ label: t('Select'), value: 'Select' },
|
||||||
@ -37,6 +38,7 @@ export const useFieldModeOptions = () => {
|
|||||||
return isReadPretty
|
return isReadPretty
|
||||||
? [
|
? [
|
||||||
{ label: t('Title'), value: 'Select' },
|
{ label: t('Title'), value: 'Select' },
|
||||||
|
{ label: t('Tag'), value: 'Tag' },
|
||||||
{ label: t('Sub-table'), value: 'SubTable' },
|
{ label: t('Sub-table'), value: 'SubTable' },
|
||||||
{ label: t('Sub-details'), value: 'Nester' },
|
{ label: t('Sub-details'), value: 'Nester' },
|
||||||
]
|
]
|
||||||
@ -50,6 +52,7 @@ export const useFieldModeOptions = () => {
|
|||||||
return isReadPretty
|
return isReadPretty
|
||||||
? [
|
? [
|
||||||
{ label: t('Title'), value: 'Select' },
|
{ label: t('Title'), value: 'Select' },
|
||||||
|
{ label: t('Tag'), value: 'Tag' },
|
||||||
{ label: t('Sub-details'), value: 'Nester' },
|
{ label: t('Sub-details'), value: 'Nester' },
|
||||||
{ label: t('Sub-table'), value: 'SubTable' },
|
{ label: t('Sub-table'), value: 'SubTable' },
|
||||||
]
|
]
|
||||||
@ -64,6 +67,7 @@ export const useFieldModeOptions = () => {
|
|||||||
return isReadPretty
|
return isReadPretty
|
||||||
? [
|
? [
|
||||||
{ label: t('Title'), value: 'Select' },
|
{ label: t('Title'), value: 'Select' },
|
||||||
|
{ label: t('Tag'), value: 'Tag' },
|
||||||
{ label: t('Sub-details'), value: 'Nester' },
|
{ label: t('Sub-details'), value: 'Nester' },
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
@ -76,6 +80,7 @@ export const useFieldModeOptions = () => {
|
|||||||
return isReadPretty
|
return isReadPretty
|
||||||
? [
|
? [
|
||||||
{ label: t('Title'), value: 'Select' },
|
{ label: t('Title'), value: 'Select' },
|
||||||
|
{ label: t('Tag'), value: 'Tag' },
|
||||||
{ label: t('Sub-details'), value: 'Nester' },
|
{ label: t('Sub-details'), value: 'Nester' },
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { ArrayCollapse, ArrayItems, FormItem, FormLayout, Input } from '@formily/antd-v5';
|
import { ArrayCollapse, ArrayItems, FormItem, FormLayout, Input } from '@formily/antd-v5';
|
||||||
import { Field, GeneralField, createForm } from '@formily/core';
|
import { createForm, Field, GeneralField } from '@formily/core';
|
||||||
import { ISchema, Schema, SchemaOptionsContext, useField, useFieldSchema, useForm } from '@formily/react';
|
import { ISchema, Schema, SchemaOptionsContext, useField, useFieldSchema, useForm } from '@formily/react';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import { error } from '@nocobase/utils/client';
|
import { error } from '@nocobase/utils/client';
|
||||||
@ -21,32 +21,32 @@ import {
|
|||||||
} from 'antd';
|
} from 'antd';
|
||||||
import _, { cloneDeep } from 'lodash';
|
import _, { cloneDeep } from 'lodash';
|
||||||
import React, {
|
import React, {
|
||||||
ReactNode,
|
|
||||||
createContext,
|
createContext,
|
||||||
|
ReactNode,
|
||||||
useCallback,
|
useCallback,
|
||||||
useContext,
|
useContext,
|
||||||
useMemo,
|
useMemo,
|
||||||
|
useState,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
useTransition as useReactTransition,
|
useTransition as useReactTransition,
|
||||||
useState,
|
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
APIClientProvider,
|
|
||||||
ActionContextProvider,
|
ActionContextProvider,
|
||||||
|
APIClientProvider,
|
||||||
CollectionFieldOptions,
|
CollectionFieldOptions,
|
||||||
CollectionManagerContext,
|
CollectionManagerContext,
|
||||||
CollectionProvider,
|
CollectionProvider,
|
||||||
|
createDesignable,
|
||||||
Designable,
|
Designable,
|
||||||
|
findFormBlock,
|
||||||
FormDialog,
|
FormDialog,
|
||||||
FormProvider,
|
FormProvider,
|
||||||
RemoteSchemaComponent,
|
RemoteSchemaComponent,
|
||||||
SchemaComponent,
|
SchemaComponent,
|
||||||
SchemaComponentContext,
|
SchemaComponentContext,
|
||||||
SchemaComponentOptions,
|
SchemaComponentOptions,
|
||||||
createDesignable,
|
|
||||||
findFormBlock,
|
|
||||||
useAPIClient,
|
useAPIClient,
|
||||||
useBlockRequestContext,
|
useBlockRequestContext,
|
||||||
useCollection,
|
useCollection,
|
||||||
|
Loading…
Reference in New Issue
Block a user