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 './chinaRegion';
|
||||
export * from './collection';
|
||||
export * from './color';
|
||||
export * from './createdAt';
|
||||
export * from './createdBy';
|
||||
export * from './datetime';
|
||||
|
@ -711,6 +711,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",
|
||||
"Tag color field":"Tag color field",
|
||||
"Sync successfully":"Sync successfully",
|
||||
"Sync from form fields":"Sync from form fields",
|
||||
"Select all":"Select all"
|
||||
|
@ -621,6 +621,8 @@ export default {
|
||||
"Allow add new, update and delete actions":"削除変更操作の許可",
|
||||
"Date display format":"日付表示形式",
|
||||
"Assign data scope for the template":"テンプレートのデータ範囲の指定",
|
||||
"Tag":"タブ",
|
||||
"Tag color field":"ラベルの色フィールド",
|
||||
"Sync successfully":"同期成功",
|
||||
"Sync from form fields":"フォームフィールドの同期",
|
||||
"Select all":"すべて選択"
|
||||
|
@ -796,6 +796,8 @@ export default {
|
||||
"Date display format":"日期显示格式",
|
||||
"Assign data scope for the template":"为模板指定数据范围",
|
||||
"Table selected records":"表格中选中的记录",
|
||||
"Tag":"标签",
|
||||
"Tag color field":"标签颜色字段",
|
||||
"Sync successfully":"同步成功",
|
||||
"Sync from form fields":"同步表单字段",
|
||||
"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 { useField, observer } from '@formily/react';
|
||||
import { observer } from '@formily/react';
|
||||
import React from 'react';
|
||||
import { AssociationFieldProvider } from './AssociationFieldProvider';
|
||||
import { InternalNester } from './InternalNester';
|
||||
import { ReadPrettyInternalViewer } from './InternalViewer';
|
||||
import { InternalSubTable } from './InternalSubTable';
|
||||
import { FileManageReadPretty } from './FileManager';
|
||||
import { useAssociationFieldContext } from './hooks';
|
||||
import { InternalNester } from './InternalNester';
|
||||
import { InternalSubTable } from './InternalSubTable';
|
||||
import { ReadPrettyInternalTag } from './InternalTag';
|
||||
import { ReadPrettyInternalViewer } from './InternalViewer';
|
||||
|
||||
const ReadPrettyAssociationField = observer(
|
||||
(props: any) => {
|
||||
@ -14,6 +15,7 @@ const ReadPrettyAssociationField = observer(
|
||||
return (
|
||||
<>
|
||||
{['Select', 'Picker'].includes(currentMode) && <ReadPrettyInternalViewer {...props} />}
|
||||
{currentMode === 'Tag' && <ReadPrettyInternalTag {...props} />}
|
||||
{currentMode === 'Nester' && <InternalNester {...props} />}
|
||||
{currentMode === 'SubTable' && <InternalSubTable {...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) {
|
||||
const newArr = [];
|
||||
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 { HTMLEncode } from '../input/shared';
|
||||
import { FilterDynamicComponent } from '../table-v2/FilterDynamicComponent';
|
||||
import { useColorFields } from '../table-v2/Table.Column.Designer';
|
||||
import { FilterFormDesigner } from './FormItem.FilterFormDesigner';
|
||||
import { useEnsureOperatorsValid } from './SchemaSettingOptions';
|
||||
|
||||
@ -183,6 +184,7 @@ FormItem.Designer = function Designer() {
|
||||
value: field?.name,
|
||||
label: compile(field?.uiSchema?.title) || field?.name,
|
||||
}));
|
||||
const colorFieldOptions = useColorFields(collectionField?.target ?? collectionField?.targetCollection);
|
||||
|
||||
let readOnlyMode = 'editable';
|
||||
if (fieldSchema['x-disabled'] === true) {
|
||||
@ -529,10 +531,6 @@ FormItem.Designer = function Designer() {
|
||||
schema['x-component-props'] = fieldSchema['x-component-props'];
|
||||
field.componentProps = field.componentProps || {};
|
||||
field.componentProps.mode = mode;
|
||||
// if (mode === 'Nester') {
|
||||
// const initValue = ['hasMany', 'belongsToMany'].includes(collectionField?.type) ? [{}] : {};
|
||||
// field.value = field.value || initValue;
|
||||
// }
|
||||
dn.emit('patch', {
|
||||
schema,
|
||||
});
|
||||
@ -811,6 +809,29 @@ FormItem.Designer = function Designer() {
|
||||
/>
|
||||
)}
|
||||
{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 />}
|
||||
<SchemaSettings.Remove
|
||||
key="remove"
|
||||
|
@ -9,6 +9,7 @@ export * from './card-item';
|
||||
export * from './cascader';
|
||||
export * from './checkbox';
|
||||
export * from './collection-select';
|
||||
export * from './color-picker';
|
||||
export * from './color-select';
|
||||
export * from './cron';
|
||||
export * from './date-picker';
|
||||
@ -49,4 +50,5 @@ export * from './time-picker';
|
||||
export * from './tree-select';
|
||||
export * from './upload';
|
||||
export * from './variable';
|
||||
|
||||
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) => {
|
||||
const { uiSchema, fieldSchema, collectionField } = props;
|
||||
const { getInterface, getCollection } = useCollectionManager();
|
||||
@ -37,6 +53,7 @@ export const TableColumnDesigner = (props) => {
|
||||
const fieldNames =
|
||||
fieldSchema?.['x-component-props']?.['fieldNames'] || uiSchema?.['x-component-props']?.['fieldNames'];
|
||||
const options = useLabelFields(collectionField?.target ?? collectionField?.targetCollection);
|
||||
const colorFieldOptions = useColorFields(collectionField?.target ?? collectionField?.targetCollection);
|
||||
const intefaceCfg = getInterface(collectionField?.interface);
|
||||
const targetCollection = getCollection(collectionField?.target);
|
||||
const isFileField = isFileCollection(targetCollection);
|
||||
@ -45,6 +62,7 @@ export const TableColumnDesigner = (props) => {
|
||||
const defaultFilter = fieldSchema?.['x-component-props']?.service?.params?.filter || {};
|
||||
const dataSource = useCollectionFilterOptions(collectionField?.target);
|
||||
const isDateField = ['datetime', 'createdAt', 'updatedAt'].includes(collectionField?.interface);
|
||||
const fieldMode = fieldSchema?.['x-component-props']?.['mode'] || 'Select';
|
||||
let readOnlyMode = 'editable';
|
||||
if (fieldSchema['x-disabled'] === true) {
|
||||
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'] && (
|
||||
<SchemaSettings.SwitchItem
|
||||
|
@ -297,7 +297,6 @@ export const Table: any = observer(
|
||||
console.warn('move cancel');
|
||||
return;
|
||||
}
|
||||
|
||||
const fromIndex = e.active?.data.current?.sortable?.index;
|
||||
const toIndex = e.over?.data.current?.sortable?.index;
|
||||
const from = field.value[fromIndex];
|
||||
@ -324,6 +323,11 @@ export const Table: any = observer(
|
||||
.nb-read-pretty-input-number {
|
||||
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('File manager'), value: 'FileManager' },
|
||||
{ label: t('Tag'), value: 'Tag' },
|
||||
]
|
||||
: [
|
||||
{ label: t('Select'), value: 'Select' },
|
||||
@ -37,6 +38,7 @@ export const useFieldModeOptions = () => {
|
||||
return isReadPretty
|
||||
? [
|
||||
{ label: t('Title'), value: 'Select' },
|
||||
{ label: t('Tag'), value: 'Tag' },
|
||||
{ label: t('Sub-table'), value: 'SubTable' },
|
||||
{ label: t('Sub-details'), value: 'Nester' },
|
||||
]
|
||||
@ -50,6 +52,7 @@ export const useFieldModeOptions = () => {
|
||||
return isReadPretty
|
||||
? [
|
||||
{ label: t('Title'), value: 'Select' },
|
||||
{ label: t('Tag'), value: 'Tag' },
|
||||
{ label: t('Sub-details'), value: 'Nester' },
|
||||
{ label: t('Sub-table'), value: 'SubTable' },
|
||||
]
|
||||
@ -64,6 +67,7 @@ export const useFieldModeOptions = () => {
|
||||
return isReadPretty
|
||||
? [
|
||||
{ label: t('Title'), value: 'Select' },
|
||||
{ label: t('Tag'), value: 'Tag' },
|
||||
{ label: t('Sub-details'), value: 'Nester' },
|
||||
]
|
||||
: [
|
||||
@ -76,6 +80,7 @@ export const useFieldModeOptions = () => {
|
||||
return isReadPretty
|
||||
? [
|
||||
{ label: t('Title'), value: 'Select' },
|
||||
{ label: t('Tag'), value: 'Tag' },
|
||||
{ label: t('Sub-details'), value: 'Nester' },
|
||||
]
|
||||
: [
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { css } from '@emotion/css';
|
||||
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 { uid } from '@formily/shared';
|
||||
import { error } from '@nocobase/utils/client';
|
||||
@ -21,32 +21,32 @@ import {
|
||||
} from 'antd';
|
||||
import _, { cloneDeep } from 'lodash';
|
||||
import React, {
|
||||
ReactNode,
|
||||
createContext,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
// @ts-ignore
|
||||
useTransition as useReactTransition,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
APIClientProvider,
|
||||
ActionContextProvider,
|
||||
APIClientProvider,
|
||||
CollectionFieldOptions,
|
||||
CollectionManagerContext,
|
||||
CollectionProvider,
|
||||
createDesignable,
|
||||
Designable,
|
||||
findFormBlock,
|
||||
FormDialog,
|
||||
FormProvider,
|
||||
RemoteSchemaComponent,
|
||||
SchemaComponent,
|
||||
SchemaComponentContext,
|
||||
SchemaComponentOptions,
|
||||
createDesignable,
|
||||
findFormBlock,
|
||||
useAPIClient,
|
||||
useBlockRequestContext,
|
||||
useCollection,
|
||||
|
Loading…
Reference in New Issue
Block a user