parent
a5010cd74c
commit
8a2548f480
7
.changeset/tiny-starfishes-smash.md
Normal file
7
.changeset/tiny-starfishes-smash.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
"@nocobase/plugin-mobile-client": patch
|
||||
"@hera/plugin-mobile": patch
|
||||
"@nocobase/client": patch
|
||||
---
|
||||
|
||||
collapse
|
@ -1,4 +1,4 @@
|
||||
import Icon, { TableOutlined } from '@ant-design/icons';
|
||||
import { TableOutlined } from '@ant-design/icons';
|
||||
import { Divider, Empty, Input, MenuProps, Spin } from 'antd';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -13,6 +13,7 @@ import { useCompile } from '../../schema-component';
|
||||
import { useSchemaTemplateManager } from '../../schema-templates';
|
||||
import { useCollectionDataSourceItems } from '../utils';
|
||||
import { DataSource } from '../../data-source';
|
||||
import { Icon } from '../../icon';
|
||||
|
||||
const MENU_ITEM_HEIGHT = 40;
|
||||
const STEP = 15;
|
||||
|
@ -2,9 +2,16 @@
|
||||
"name": "@hera/plugin-mobile",
|
||||
"version": "1.0.4",
|
||||
"main": "dist/server/index.js",
|
||||
"dependencies": {
|
||||
"devDependencies": {
|
||||
"@ant-design/icons": "5.x",
|
||||
"@nocobase/schema": "workspace:*",
|
||||
"antd-mobile": "^5.29.1"
|
||||
"@types/lodash": "^4.17.0",
|
||||
"antd": "^5.12.8",
|
||||
"antd-mobile": "^5.33.1",
|
||||
"antd-mobile-icons": "^0.3.0",
|
||||
"classnames": "^2.3.1",
|
||||
"lodash": "4.17.21",
|
||||
"react-i18next": "^11.15.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nocobase/client": "workspace:*",
|
||||
|
@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { Icon } from '@nocobase/client';
|
||||
|
||||
import NoticeSvg from './notice.svg';
|
||||
import SwiperSvg from './swiper.svg';
|
||||
import TabSearchSvg from './tab-search.svg';
|
||||
|
||||
export const loadBase64Icon = (base64: string) => () => (
|
||||
<div dangerouslySetInnerHTML={{ __html: atob(base64.replace('data:image/svg+xml;base64,', '')) }} />
|
||||
);
|
||||
|
||||
export const NoticeIcon = (props: any) => <Icon component={loadBase64Icon(NoticeSvg)} {...props} />;
|
||||
export const SwiperIcon = (props: any) => <Icon component={loadBase64Icon(SwiperSvg)} {...props} />;
|
||||
export const TabSearchIcon = (props: any) => <Icon component={loadBase64Icon(TabSearchSvg)} {...props} />;
|
||||
|
||||
Icon.register({
|
||||
'notice-block': NoticeIcon,
|
||||
'swiper-block': SwiperIcon,
|
||||
'tab-search': TabSearchIcon,
|
||||
});
|
@ -0,0 +1,6 @@
|
||||
<svg viewBox="0 0 1024 1024" width="1em" height="1em" fill="currentColor">
|
||||
<path
|
||||
d="M338.773333 362.666667H170.666667v298.666666h105.578666l45.013334 0.021334 17.258666 0.426666L682.666667 809.258667V214.890667L338.773333 362.666667z m-17.578666-85.333334L768 85.333333v853.333334l-448-191.978667L85.333333 746.666667V277.333333h235.861334zM170.666667 661.333333v85.333334h85.333333v-85.333334H170.666667zM853.333333 298.666667h85.333334v426.666666h-85.333334z"
|
||||
fill="#000000"
|
||||
></path>
|
||||
</svg>
|
After Width: | Height: | Size: 512 B |
@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 1024 1024" width="1em" height="1em" fill="currentColor">
|
||||
<path d="M0 219.428571h73.142857v585.142858H0V219.428571z m950.857143 0h73.142857v585.142858h-73.142857V219.428571z m-146.285714-146.285714a73.142857 73.142857 0 0 1 73.142857 73.142857v731.428572a73.142857 73.142857 0 0 1-73.142857 73.142857H219.428571a73.142857 73.142857 0 0 1-73.142857-73.142857V146.285714a73.142857 73.142857 0 0 1 73.142857-73.142857h585.142858z m0 73.142857H219.428571v731.428572h585.142858V146.285714zM365.714286 694.857143a36.571429 36.571429 0 1 1 0 73.142857 36.571429 36.571429 0 0 1 0-73.142857z m146.285714 0a36.571429 36.571429 0 1 1 0 73.142857 36.571429 36.571429 0 0 1 0-73.142857z m146.285714 0a36.571429 36.571429 0 1 1 0 73.142857 36.571429 36.571429 0 0 1 0-73.142857z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 804 B |
@ -0,0 +1,5 @@
|
||||
<svg viewBox="0 0 1024 1024" width="1em" height="1em" fill="currentColor">
|
||||
<path d="M1263.243636 908.939636l-156.485818-156.392727c25.6-37.050182 39.842909-79.685818 39.842909-128.093091 0-125.114182-102.4-227.514182-227.607272-227.514182a228.258909 228.258909 0 0 0-227.514182 227.514182c0 125.207273 102.4 227.607273 227.514182 227.607273 48.407273 0 91.042909-14.242909 128-39.842909l156.392727 156.392727a41.239273 41.239273 0 0 0 59.764363 0c19.921455-17.035636 19.921455-45.428364 0-59.671273zM921.786182 766.696727a140.846545 140.846545 0 0 1-142.242909-142.242909c0-79.592727 62.557091-142.149818 142.242909-142.149818 79.592727 0 142.242909 62.557091 142.242909 142.149818 0 79.685818-62.650182 142.242909-142.242909 142.242909z"></path>
|
||||
<path d="M694.272 894.696727H111.150545v-512h583.121455a43.752727 43.752727 0 0 0 42.728727-42.635636 43.752727 43.752727 0 0 0-42.728727-42.728727H111.150545V98.304h853.364364v156.392727a43.752727 43.752727 0 0 0 42.635636 42.635637 43.752727 43.752727 0 0 0 42.728728-42.635637V69.818182a57.064727 57.064727 0 0 0-56.878546-56.878546H82.757818A57.064727 57.064727 0 0 0 25.879273 69.818182v853.364363c0 31.278545 25.6 56.785455 56.785454 56.785455h611.607273a43.752727 43.752727 0 0 0 42.728727-42.635636 43.752727 43.752727 0 0 0-42.728727-42.635637z"></path>
|
||||
<path d="M637.393455 155.182545c-22.714182 0-42.635636 21.690182-42.635637 46.545455 0 24.762182 19.921455 46.545455 42.635637 46.545455h28.485818c22.714182 0 42.635636-21.783273 42.635636-46.545455 0-24.855273-19.921455-46.545455-42.635636-46.545455h-28.485818z m-199.121455 0c-22.714182 0-42.635636 21.690182-42.635636 46.545455 0 24.762182 19.921455 46.545455 42.635636 46.545455h28.485818c22.714182 0 42.635636-21.783273 42.635637-46.545455 0-24.855273-19.921455-46.545455-42.635637-46.545455h-28.485818z m-199.121455 0c-22.714182 0-42.635636 21.690182-42.635636 46.545455 0 24.762182 19.921455 46.545455 42.635636 46.545455h28.485819c22.714182 0 42.635636-21.783273 42.635636-46.545455 0-24.855273-19.921455-46.545455-42.635636-46.545455h-28.485819z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
@ -1,46 +1,16 @@
|
||||
import { Plugin } from '@nocobase/client';
|
||||
import { SwiperBlock, SwiperBlockInitializer } from './schema-initializer/SwiperBlockInitializer';
|
||||
import { TabSearchBlock, TabSearchBlockInitializer } from './schema-initializer/TabSearchBlockInitializer';
|
||||
import { NoticeBlock, NoticeBlockInitializer } from './schema-initializer/NoticeBlockInitializer';
|
||||
|
||||
export class PluginMobileClient extends Plugin {
|
||||
async afterAdd() {}
|
||||
import PluginBlock from './schma-block';
|
||||
import PluginTabSearch from './schma-component/tab-search';
|
||||
import './assets/svg/index';
|
||||
|
||||
class PluginMobileClient extends Plugin {
|
||||
async beforeLoad() {}
|
||||
|
||||
async load() {
|
||||
this.app.addComponents({
|
||||
NoticeBlock,
|
||||
NoticeBlockInitializer,
|
||||
SwiperBlock,
|
||||
SwiperBlockInitializer,
|
||||
TabSearchBlock,
|
||||
TabSearchBlockInitializer,
|
||||
});
|
||||
this.app.schemaInitializerManager.addItem('MBlockInitializers', 'dataBlocks.swiper', {
|
||||
title: 'swiper',
|
||||
name: 'swiper',
|
||||
type: 'item',
|
||||
Component: 'SwiperBlockInitializer',
|
||||
});
|
||||
this.app.schemaInitializerManager.addItem('MBlockInitializers', 'dataBlocks.notice', {
|
||||
title: 'notice',
|
||||
name: 'notice',
|
||||
type: 'item',
|
||||
Component: 'NoticeBlockInitializer',
|
||||
});
|
||||
async load() {}
|
||||
|
||||
this.app.schemaInitializerManager.addItem('MBlockInitializers', 'filterBlocks', {
|
||||
title: '{{t("Filter blocks")}}',
|
||||
type: 'itemGroup',
|
||||
children: [
|
||||
{
|
||||
name: 'tabSearch',
|
||||
title: 'tabSearch',
|
||||
Component: 'TabSearchBlockInitializer',
|
||||
},
|
||||
],
|
||||
});
|
||||
async afterAdd() {
|
||||
this.pm.add(PluginBlock);
|
||||
this.pm.add(PluginTabSearch);
|
||||
}
|
||||
}
|
||||
|
||||
|
15
packages/plugins/@hera/plugin-mobile/src/client/locale.tsx
Normal file
15
packages/plugins/@hera/plugin-mobile/src/client/locale.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { useApp, tval as nTval, i18n } from '@nocobase/client';
|
||||
|
||||
const NAMESPACE = '@hera/plugin-mobile';
|
||||
|
||||
export const useTranslation = (): any => {
|
||||
const { i18n } = useApp();
|
||||
const t = (key: string, props = {}) => i18n.t(key, { ns: NAMESPACE, ...props });
|
||||
return { t };
|
||||
};
|
||||
|
||||
export const tval = (key: string) => nTval(key, { ns: NAMESPACE });
|
||||
|
||||
export function lang(key: string) {
|
||||
return i18n.t(key, { ns: NAMESPACE });
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
import { ISchema } from '@nocobase/schema';
|
||||
import {
|
||||
Icon,
|
||||
DataBlockInitializer,
|
||||
useCollectionManager,
|
||||
useSchemaInitializer,
|
||||
useSchemaInitializerItem,
|
||||
} from '@nocobase/client';
|
||||
import React from 'react';
|
||||
import { uid } from '@nocobase/utils/client';
|
||||
import { NoticeBar } from 'antd-mobile';
|
||||
|
||||
const NoticeSvg = () => (
|
||||
<svg viewBox="0 0 1024 1024" width="1em" height="1em" fill="currentColor">
|
||||
<path
|
||||
d="M338.773333 362.666667H170.666667v298.666666h105.578666l45.013334 0.021334 17.258666 0.426666L682.666667 809.258667V214.890667L338.773333 362.666667z m-17.578666-85.333334L768 85.333333v853.333334l-448-191.978667L85.333333 746.666667V277.333333h235.861334zM170.666667 661.333333v85.333334h85.333333v-85.333334H170.666667zM853.333333 298.666667h85.333334v426.666666h-85.333334z"
|
||||
fill="#000000"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const NoticeIcon = (props: any) => <Icon type="" component={NoticeSvg} {...props} />;
|
||||
|
||||
export const NoticeBlock = () => {
|
||||
return <NoticeBar content="月底冲量佣金上调" color="alert" />;
|
||||
};
|
||||
|
||||
export const NoticeBlockInitializer = () => {
|
||||
const { insert } = useSchemaInitializer();
|
||||
const cm = useCollectionManager();
|
||||
const itemConfig = useSchemaInitializerItem();
|
||||
return (
|
||||
<DataBlockInitializer
|
||||
{...itemConfig}
|
||||
icon={<NoticeIcon />}
|
||||
componentType={'Notice'}
|
||||
onCreateBlockSchema={async ({ item }) => {
|
||||
const collection = cm.getCollection(item.name);
|
||||
const schema = createGridCardBlockSchema({
|
||||
collection: item.name,
|
||||
dataSource: item.dataSource,
|
||||
rowKey: collection.filterTargetKey || 'id',
|
||||
settings: 'blockSettings:gridCard',
|
||||
});
|
||||
insert(schema);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const createGridCardBlockSchema = (options) => {
|
||||
const {
|
||||
formItemInitializers = 'ReadPrettyFormItemInitializers',
|
||||
actionInitializers = 'GridCardActionInitializers',
|
||||
itemActionInitializers = 'GridCardItemActionInitializers',
|
||||
collection,
|
||||
association,
|
||||
resource,
|
||||
template,
|
||||
dataSource,
|
||||
settings,
|
||||
...others
|
||||
} = options;
|
||||
const resourceName = resource || association || collection;
|
||||
const schema: ISchema = {
|
||||
type: 'void',
|
||||
'x-acl-action': `${resourceName}:view`,
|
||||
'x-decorator': 'GridCard.Decorator',
|
||||
'x-decorator-props': {
|
||||
resource: resourceName,
|
||||
collection,
|
||||
association,
|
||||
dataSource,
|
||||
readPretty: true,
|
||||
action: 'list',
|
||||
params: {
|
||||
pageSize: 12,
|
||||
},
|
||||
runWhenParamsChanged: true,
|
||||
...others,
|
||||
},
|
||||
'x-component': 'BlockItem',
|
||||
'x-component-props': {
|
||||
useProps: '{{ useGridCardBlockItemProps }}',
|
||||
},
|
||||
'x-toolbar': 'BlockSchemaToolbar',
|
||||
'x-settings': settings,
|
||||
properties: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-component': 'NoticeBlock',
|
||||
},
|
||||
},
|
||||
};
|
||||
return schema;
|
||||
};
|
@ -1,120 +0,0 @@
|
||||
import { ISchema } from '@nocobase/schema';
|
||||
import {
|
||||
DataBlockInitializer,
|
||||
css,
|
||||
Icon,
|
||||
useCollectionManager,
|
||||
useSchemaInitializer,
|
||||
useSchemaInitializerItem,
|
||||
} from '@nocobase/client';
|
||||
import React from 'react';
|
||||
import { uid } from '@nocobase/utils/client';
|
||||
import { Swiper, Toast } from 'antd-mobile';
|
||||
|
||||
const SwiperSvg = () => (
|
||||
<svg viewBox="0 0 1024 1024" width="1em" height="1em" fill="currentColor">
|
||||
<path d="M0 219.428571h73.142857v585.142858H0V219.428571z m950.857143 0h73.142857v585.142858h-73.142857V219.428571z m-146.285714-146.285714a73.142857 73.142857 0 0 1 73.142857 73.142857v731.428572a73.142857 73.142857 0 0 1-73.142857 73.142857H219.428571a73.142857 73.142857 0 0 1-73.142857-73.142857V146.285714a73.142857 73.142857 0 0 1 73.142857-73.142857h585.142858z m0 73.142857H219.428571v731.428572h585.142858V146.285714zM365.714286 694.857143a36.571429 36.571429 0 1 1 0 73.142857 36.571429 36.571429 0 0 1 0-73.142857z m146.285714 0a36.571429 36.571429 0 1 1 0 73.142857 36.571429 36.571429 0 0 1 0-73.142857z m146.285714 0a36.571429 36.571429 0 1 1 0 73.142857 36.571429 36.571429 0 0 1 0-73.142857z"></path>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const SwiperIcon = (props: any) => <Icon type="" component={SwiperSvg} {...props} />;
|
||||
|
||||
export const SwiperBlock = () => {
|
||||
const colors = ['#ace0ff', '#bcffbd', '#e4fabd', '#ffcfac'];
|
||||
const items = colors.map((color, index) => (
|
||||
<Swiper.Item key={index}>
|
||||
<div
|
||||
className={css`
|
||||
height: 120px;
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 48px;
|
||||
user-select: none;
|
||||
`}
|
||||
style={{ background: color }}
|
||||
onClick={() => {
|
||||
Toast.show(`你点击了卡片 ${index + 1}`);
|
||||
}}
|
||||
>
|
||||
{index + 1}
|
||||
</div>
|
||||
</Swiper.Item>
|
||||
));
|
||||
return (
|
||||
<Swiper loop autoplay>
|
||||
{items}
|
||||
</Swiper>
|
||||
);
|
||||
};
|
||||
|
||||
export const SwiperBlockInitializer = () => {
|
||||
const { insert } = useSchemaInitializer();
|
||||
const cm = useCollectionManager();
|
||||
const itemConfig = useSchemaInitializerItem();
|
||||
return (
|
||||
<DataBlockInitializer
|
||||
{...itemConfig}
|
||||
icon={<SwiperIcon />}
|
||||
componentType={'Swiper'}
|
||||
onCreateBlockSchema={async ({ item }) => {
|
||||
const collection = cm.getCollection(item.name);
|
||||
const schema = createGridCardBlockSchema({
|
||||
collection: item.name,
|
||||
dataSource: item.dataSource,
|
||||
rowKey: collection.filterTargetKey || 'id',
|
||||
settings: 'blockSettings:gridCard',
|
||||
});
|
||||
insert(schema);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const createGridCardBlockSchema = (options) => {
|
||||
const {
|
||||
formItemInitializers = 'ReadPrettyFormItemInitializers',
|
||||
actionInitializers = 'GridCardActionInitializers',
|
||||
itemActionInitializers = 'GridCardItemActionInitializers',
|
||||
collection,
|
||||
association,
|
||||
resource,
|
||||
template,
|
||||
dataSource,
|
||||
settings,
|
||||
...others
|
||||
} = options;
|
||||
const resourceName = resource || association || collection;
|
||||
const schema: ISchema = {
|
||||
type: 'void',
|
||||
'x-acl-action': `${resourceName}:view`,
|
||||
'x-decorator': 'GridCard.Decorator',
|
||||
'x-decorator-props': {
|
||||
resource: resourceName,
|
||||
collection,
|
||||
association,
|
||||
dataSource,
|
||||
readPretty: true,
|
||||
action: 'list',
|
||||
params: {
|
||||
pageSize: 12,
|
||||
},
|
||||
runWhenParamsChanged: true,
|
||||
...others,
|
||||
},
|
||||
'x-component': 'BlockItem',
|
||||
'x-component-props': {
|
||||
useProps: '{{ useGridCardBlockItemProps }}',
|
||||
},
|
||||
'x-toolbar': 'BlockSchemaToolbar',
|
||||
'x-settings': settings,
|
||||
properties: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-component': 'SwiperBlock',
|
||||
},
|
||||
},
|
||||
};
|
||||
return schema;
|
||||
};
|
@ -1,144 +0,0 @@
|
||||
import { ISchema } from '@nocobase/schema';
|
||||
import {
|
||||
DataBlockInitializer,
|
||||
Icon,
|
||||
css,
|
||||
useCollectionManager,
|
||||
useSchemaInitializer,
|
||||
useSchemaInitializerItem,
|
||||
} from '@nocobase/client';
|
||||
import React, { useState } from 'react';
|
||||
import { uid } from '@nocobase/utils/client';
|
||||
import { Button, SearchBar, Tabs, Picker } from 'antd-mobile';
|
||||
|
||||
const TabSearchSvg = () => (
|
||||
<svg viewBox="0 0 1024 1024" width="1em" height="1em" fill="currentColor">
|
||||
<path d="M1263.243636 908.939636l-156.485818-156.392727c25.6-37.050182 39.842909-79.685818 39.842909-128.093091 0-125.114182-102.4-227.514182-227.607272-227.514182a228.258909 228.258909 0 0 0-227.514182 227.514182c0 125.207273 102.4 227.607273 227.514182 227.607273 48.407273 0 91.042909-14.242909 128-39.842909l156.392727 156.392727a41.239273 41.239273 0 0 0 59.764363 0c19.921455-17.035636 19.921455-45.428364 0-59.671273zM921.786182 766.696727a140.846545 140.846545 0 0 1-142.242909-142.242909c0-79.592727 62.557091-142.149818 142.242909-142.149818 79.592727 0 142.242909 62.557091 142.242909 142.149818 0 79.685818-62.650182 142.242909-142.242909 142.242909z"></path>
|
||||
<path d="M694.272 894.696727H111.150545v-512h583.121455a43.752727 43.752727 0 0 0 42.728727-42.635636 43.752727 43.752727 0 0 0-42.728727-42.728727H111.150545V98.304h853.364364v156.392727a43.752727 43.752727 0 0 0 42.635636 42.635637 43.752727 43.752727 0 0 0 42.728728-42.635637V69.818182a57.064727 57.064727 0 0 0-56.878546-56.878546H82.757818A57.064727 57.064727 0 0 0 25.879273 69.818182v853.364363c0 31.278545 25.6 56.785455 56.785454 56.785455h611.607273a43.752727 43.752727 0 0 0 42.728727-42.635636 43.752727 43.752727 0 0 0-42.728727-42.635637z"></path>
|
||||
<path d="M637.393455 155.182545c-22.714182 0-42.635636 21.690182-42.635637 46.545455 0 24.762182 19.921455 46.545455 42.635637 46.545455h28.485818c22.714182 0 42.635636-21.783273 42.635636-46.545455 0-24.855273-19.921455-46.545455-42.635636-46.545455h-28.485818z m-199.121455 0c-22.714182 0-42.635636 21.690182-42.635636 46.545455 0 24.762182 19.921455 46.545455 42.635636 46.545455h28.485818c22.714182 0 42.635636-21.783273 42.635637-46.545455 0-24.855273-19.921455-46.545455-42.635637-46.545455h-28.485818z m-199.121455 0c-22.714182 0-42.635636 21.690182-42.635636 46.545455 0 24.762182 19.921455 46.545455 42.635636 46.545455h28.485819c22.714182 0 42.635636-21.783273 42.635636-46.545455 0-24.855273-19.921455-46.545455-42.635636-46.545455h-28.485819z"></path>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const TabSearchIcon = (props: any) => <Icon type="" component={TabSearchSvg} {...props} />;
|
||||
|
||||
export const TabSearchBlock = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const basicColumns = [
|
||||
[
|
||||
{ label: '福建', value: 'Mon' },
|
||||
{ label: '上海', value: 'Wed' },
|
||||
],
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={css`
|
||||
display: flex;
|
||||
margin-bottom: 8px;
|
||||
`}
|
||||
>
|
||||
<Tabs
|
||||
className={css`
|
||||
flex: 1;
|
||||
`}
|
||||
>
|
||||
<Tabs.Tab title="水果" key="fruits" />
|
||||
<Tabs.Tab title="蔬菜" key="vegetables" />
|
||||
<Tabs.Tab title="动物" key="animals" />
|
||||
</Tabs>
|
||||
<Button onClick={() => setVisible(true)}>全国</Button>
|
||||
<Picker
|
||||
columns={basicColumns}
|
||||
visible={visible}
|
||||
onClose={() => {
|
||||
setVisible(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={css`
|
||||
display: flex;
|
||||
margin-bottom: 8px;
|
||||
`}
|
||||
>
|
||||
<SearchBar
|
||||
className={css`
|
||||
flex: 1;
|
||||
`}
|
||||
placeholder="请输入内容"
|
||||
/>
|
||||
<Button>搜索</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const TabSearchBlockInitializer = () => {
|
||||
const { insert } = useSchemaInitializer();
|
||||
const cm = useCollectionManager();
|
||||
const itemConfig = useSchemaInitializerItem();
|
||||
return (
|
||||
<DataBlockInitializer
|
||||
{...itemConfig}
|
||||
icon={<TabSearchIcon />}
|
||||
componentType={'TabSearch'}
|
||||
onCreateBlockSchema={async ({ item }) => {
|
||||
const collection = cm.getCollection(item.name);
|
||||
const schema = createGridCardBlockSchema({
|
||||
collection: item.name,
|
||||
dataSource: item.dataSource,
|
||||
rowKey: collection.filterTargetKey || 'id',
|
||||
settings: 'blockSettings:gridCard',
|
||||
});
|
||||
insert(schema);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const createGridCardBlockSchema = (options) => {
|
||||
const {
|
||||
formItemInitializers = 'ReadPrettyFormItemInitializers',
|
||||
actionInitializers = 'GridCardActionInitializers',
|
||||
itemActionInitializers = 'GridCardItemActionInitializers',
|
||||
collection,
|
||||
association,
|
||||
resource,
|
||||
template,
|
||||
dataSource,
|
||||
settings,
|
||||
...others
|
||||
} = options;
|
||||
const resourceName = resource || association || collection;
|
||||
const schema: ISchema = {
|
||||
type: 'void',
|
||||
'x-acl-action': `${resourceName}:view`,
|
||||
'x-decorator': 'GridCard.Decorator',
|
||||
'x-decorator-props': {
|
||||
resource: resourceName,
|
||||
collection,
|
||||
association,
|
||||
dataSource,
|
||||
readPretty: true,
|
||||
action: 'list',
|
||||
params: {
|
||||
pageSize: 12,
|
||||
},
|
||||
runWhenParamsChanged: true,
|
||||
...others,
|
||||
},
|
||||
'x-component': 'CardItem',
|
||||
'x-component-props': {
|
||||
useProps: '{{ useGridCardBlockItemProps }}',
|
||||
},
|
||||
'x-toolbar': 'BlockSchemaToolbar',
|
||||
'x-settings': settings,
|
||||
properties: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-component': 'TabSearchBlock',
|
||||
},
|
||||
},
|
||||
};
|
||||
return schema;
|
||||
};
|
@ -0,0 +1,29 @@
|
||||
import { Plugin } from '@nocobase/client';
|
||||
import { NoticeBlock, NoticeBlockInitializer } from './schema-initializer/NoticeBlockInitializer';
|
||||
import { SwiperBlock, SwiperBlockInitializer } from './schema-initializer/SwiperBlockInitializer';
|
||||
|
||||
class PluginBlock extends Plugin {
|
||||
async load() {
|
||||
this.app.addComponents({
|
||||
NoticeBlock,
|
||||
NoticeBlockInitializer,
|
||||
SwiperBlock,
|
||||
SwiperBlockInitializer,
|
||||
});
|
||||
|
||||
this.app.schemaInitializerManager.addItem('MBlockInitializers', 'dataBlocks.swiper', {
|
||||
title: 'swiper',
|
||||
name: 'swiper',
|
||||
type: 'item',
|
||||
Component: 'SwiperBlockInitializer',
|
||||
});
|
||||
this.app.schemaInitializerManager.addItem('MBlockInitializers', 'dataBlocks.notice', {
|
||||
title: 'notice',
|
||||
name: 'notice',
|
||||
type: 'item',
|
||||
Component: 'NoticeBlockInitializer',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default PluginBlock;
|
@ -0,0 +1,49 @@
|
||||
import { ISchema } from '@nocobase/schema';
|
||||
import { uid } from '@nocobase/utils/client';
|
||||
|
||||
export const createGridCardBlockSchema = (options, componentName) => {
|
||||
const {
|
||||
formItemInitializers = 'ReadPrettyFormItemInitializers',
|
||||
actionInitializers = 'GridCardActionInitializers',
|
||||
itemActionInitializers = 'GridCardItemActionInitializers',
|
||||
collection,
|
||||
association,
|
||||
resource,
|
||||
template,
|
||||
dataSource,
|
||||
settings,
|
||||
...others
|
||||
} = options;
|
||||
const resourceName = resource || association || collection;
|
||||
const schema: ISchema = {
|
||||
type: 'void',
|
||||
'x-acl-action': `${resourceName}:view`,
|
||||
'x-decorator': 'GridCard.Decorator',
|
||||
'x-decorator-props': {
|
||||
resource: resourceName,
|
||||
collection,
|
||||
association,
|
||||
dataSource,
|
||||
readPretty: true,
|
||||
action: 'list',
|
||||
params: {
|
||||
pageSize: 12,
|
||||
},
|
||||
runWhenParamsChanged: true,
|
||||
...others,
|
||||
},
|
||||
'x-component': 'BlockItem',
|
||||
'x-component-props': {
|
||||
useProps: '{{ useGridCardBlockItemProps }}',
|
||||
},
|
||||
'x-toolbar': 'BlockSchemaToolbar',
|
||||
'x-settings': settings,
|
||||
properties: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-component': componentName,
|
||||
},
|
||||
},
|
||||
};
|
||||
return schema;
|
||||
};
|
@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
DataBlockInitializer,
|
||||
Icon,
|
||||
useCollectionManager,
|
||||
useSchemaInitializer,
|
||||
useSchemaInitializerItem,
|
||||
} from '@nocobase/client';
|
||||
import { NoticeBar } from 'antd-mobile';
|
||||
import { createGridCardBlockSchema } from '../schema-create/createGridCardBlockSchma';
|
||||
import { NoticeIcon } from '../../assets/svg';
|
||||
|
||||
export const NoticeBlock = () => {
|
||||
return <NoticeBar content="月底冲量佣金上调" color="alert" />;
|
||||
};
|
||||
|
||||
export const NoticeBlockInitializer = () => {
|
||||
const { insert } = useSchemaInitializer();
|
||||
const cm = useCollectionManager();
|
||||
const itemConfig = useSchemaInitializerItem();
|
||||
const onCreateBlockSchema = async ({ item }) => {
|
||||
const collection = cm.getCollection(item.name);
|
||||
const schema = createGridCardBlockSchema(
|
||||
{
|
||||
collection: item.name,
|
||||
dataSource: item.dataSource,
|
||||
rowKey: collection.filterTargetKey || 'id',
|
||||
settings: 'blockSettings:gridCard',
|
||||
},
|
||||
'NoticeBlock',
|
||||
);
|
||||
insert(schema);
|
||||
};
|
||||
return (
|
||||
<DataBlockInitializer
|
||||
{...itemConfig}
|
||||
icon={<Icon type="notice-block" />}
|
||||
componentType={'Notice'}
|
||||
onCreateBlockSchema={onCreateBlockSchema}
|
||||
/>
|
||||
);
|
||||
};
|
@ -0,0 +1,71 @@
|
||||
import {
|
||||
DataBlockInitializer,
|
||||
Icon,
|
||||
css,
|
||||
useCollectionManager,
|
||||
useSchemaInitializer,
|
||||
useSchemaInitializerItem,
|
||||
} from '@nocobase/client';
|
||||
import React from 'react';
|
||||
import { Swiper, Toast } from 'antd-mobile';
|
||||
import { createGridCardBlockSchema } from '../schema-create/createGridCardBlockSchma';
|
||||
import { SwiperIcon } from '../../assets/svg';
|
||||
|
||||
export const SwiperBlock = () => {
|
||||
const colors = ['#ace0ff', '#bcffbd', '#e4fabd', '#ffcfac'];
|
||||
const items = colors.map((color, index) => (
|
||||
<Swiper.Item key={index}>
|
||||
<div
|
||||
className={css`
|
||||
height: 120px;
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 48px;
|
||||
user-select: none;
|
||||
`}
|
||||
style={{ background: color }}
|
||||
onClick={() => {
|
||||
Toast.show(`你点击了卡片 ${index + 1}`);
|
||||
}}
|
||||
>
|
||||
{index + 1}
|
||||
</div>
|
||||
</Swiper.Item>
|
||||
));
|
||||
return (
|
||||
<Swiper loop autoplay>
|
||||
{items}
|
||||
</Swiper>
|
||||
);
|
||||
};
|
||||
|
||||
export const SwiperBlockInitializer = () => {
|
||||
const { insert } = useSchemaInitializer();
|
||||
const cm = useCollectionManager();
|
||||
const itemConfig = useSchemaInitializerItem();
|
||||
|
||||
const onCreateBlockSchema = async ({ item }) => {
|
||||
const collection = cm.getCollection(item.name);
|
||||
const schema = createGridCardBlockSchema(
|
||||
{
|
||||
collection: item.name,
|
||||
dataSource: item.dataSource,
|
||||
rowKey: collection.filterTargetKey || 'id',
|
||||
settings: 'blockSettings:gridCard',
|
||||
},
|
||||
'SwiperBlock',
|
||||
);
|
||||
insert(schema);
|
||||
};
|
||||
|
||||
return (
|
||||
<DataBlockInitializer
|
||||
{...itemConfig}
|
||||
icon={<Icon type="swiper-block" />}
|
||||
componentType={'Swiper'}
|
||||
onCreateBlockSchema={onCreateBlockSchema}
|
||||
/>
|
||||
);
|
||||
};
|
@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import { RecursionField } from '@nocobase/schema';
|
||||
import { useTabSearchAction } from './TabSearchAction';
|
||||
|
||||
export const TabSearch = () => {
|
||||
const { fieldSchema, Designer, render, filterProperties } = useTabSearchAction();
|
||||
return (
|
||||
<>
|
||||
<Designer />
|
||||
<RecursionField schema={fieldSchema} onlyRenderProperties filterProperties={filterProperties} />
|
||||
<React.Fragment>{render()}</React.Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
@ -0,0 +1,26 @@
|
||||
import { useDesigner, useSchemaInitializerRender } from '@nocobase/client';
|
||||
import { useFieldSchema } from '@nocobase/schema';
|
||||
import { isTabSearchCollapsibleInputItem } from '../utils';
|
||||
|
||||
export const useTabSearchAction = () => {
|
||||
const Designer = useDesigner();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { render } = useSchemaInitializerRender(fieldSchema['x-initializer'], fieldSchema['x-initializer-props']);
|
||||
|
||||
const filterProperties = (s) => {
|
||||
if (!isTabSearchCollapsibleInputItem(s['x-component'])) {
|
||||
return true;
|
||||
} else {
|
||||
// 保留第一个,如果进入这个判断一定有值,所以取0不会出错
|
||||
return (
|
||||
s === Object.values(s.parent.properties).filter((s) => isTabSearchCollapsibleInputItem(s['x-component']))[0]
|
||||
);
|
||||
}
|
||||
};
|
||||
return {
|
||||
fieldSchema,
|
||||
Designer,
|
||||
render,
|
||||
filterProperties,
|
||||
};
|
||||
};
|
@ -0,0 +1,43 @@
|
||||
import { SortableItem, withDynamicSchemaProps } from '@nocobase/client';
|
||||
import { ConfigProvider, Row } from 'antd';
|
||||
import React from 'react';
|
||||
import { IButton, IInput, ISelect } from './TabSearchCollapsibleInputItemChild';
|
||||
import { useTabSearchCollapsibleInputItemAction } from './TabSearchCollapsibleInputItemAction';
|
||||
|
||||
export const TabSearchCollapsibleInputItem = withDynamicSchemaProps(
|
||||
(props) => {
|
||||
const { collectionField, Designer, options, value, onSelectChange, onInputChange, onButtonClick, customLabelKey } =
|
||||
useTabSearchCollapsibleInputItemAction(props);
|
||||
|
||||
if (!collectionField) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SortableItem>
|
||||
<Designer />
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
token: { colorBgContainer: '#f9f9f9', colorBorder: 'none' },
|
||||
components: {
|
||||
Input: {
|
||||
colorBorder: '#f9f9f9',
|
||||
activeShadow: 'none',
|
||||
hoverBorderColor: 'none',
|
||||
activeBorderColor: 'none',
|
||||
},
|
||||
Divider: { colorSplit: '#c1c2c5' },
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Row style={{ marginTop: '2vh', backgroundColor: '#f9f9f9' }}>
|
||||
<ISelect options={options} onChange={onSelectChange} customLabelKey={customLabelKey} />
|
||||
<IInput options={options} value={value} onChange={onInputChange} />
|
||||
<IButton onClick={onButtonClick} />
|
||||
</Row>
|
||||
</ConfigProvider>
|
||||
</SortableItem>
|
||||
);
|
||||
},
|
||||
{ displayName: 'TabSearchCollapsibleInputItem' },
|
||||
);
|
@ -0,0 +1,46 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useFieldSchema } from '@nocobase/schema';
|
||||
import { useTranslation } from '../../../../locale';
|
||||
import { useCollection, useDesigner } from '@nocobase/client';
|
||||
import { isTabSearchCollapsibleInputItem } from '../../utils';
|
||||
import { useTabSearchCollapsibleInputItem } from './hooks';
|
||||
|
||||
export const useTabSearchCollapsibleInputItemAction = (props) => {
|
||||
const { onSelected } = useTabSearchCollapsibleInputItem();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const collection = useCollection();
|
||||
const collectionField = useMemo(() => collection?.getField(fieldSchema.name as any), [collection, fieldSchema.name]);
|
||||
const properties = fieldSchema.parent.properties;
|
||||
const Designer = useDesigner();
|
||||
const [value, setValue] = useState('');
|
||||
const [customLabelKey, setCustomLabelKey] = useState(fieldSchema['name'] as string);
|
||||
|
||||
const options = Object.values(properties)
|
||||
.map((option) => {
|
||||
if (isTabSearchCollapsibleInputItem(option['x-component'])) {
|
||||
return {
|
||||
label: option['title'],
|
||||
value: option['name'],
|
||||
};
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
const onSelect = (value) => {
|
||||
onSelected([value], customLabelKey);
|
||||
};
|
||||
|
||||
const onSelectChange = (label) => setCustomLabelKey(label as string);
|
||||
const onInputChange = (v) => {
|
||||
const inputValue = v.target?.value || v;
|
||||
setValue(inputValue);
|
||||
if (inputValue === '') {
|
||||
onSelect(inputValue);
|
||||
}
|
||||
};
|
||||
const onButtonClick = () => {
|
||||
onSelect(value);
|
||||
};
|
||||
|
||||
return { collectionField, Designer, options, value, onSelectChange, onInputChange, onButtonClick, customLabelKey };
|
||||
};
|
@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import { Button, Col, Divider, Input, Select } from 'antd';
|
||||
import { useTranslation } from '../../../../locale';
|
||||
|
||||
export const ISelect = (props) => {
|
||||
const { options, onChange } = props;
|
||||
return options.length > 1 ? (
|
||||
<Col flex={1} style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Select style={{ width: '100%' }} options={options} defaultValue={options[0].value} onChange={onChange} />
|
||||
<Divider type="vertical" style={{ height: '70%' }} />
|
||||
</Col>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export const IInput = (props) => {
|
||||
const { options, value, onChange } = props;
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Col flex={options.length > 1 ? 3 : 4}>
|
||||
<Input placeholder={t('Please enter search content')} value={value} onChange={onChange} />
|
||||
</Col>
|
||||
);
|
||||
};
|
||||
|
||||
export const IButton = (props) => {
|
||||
const { onClick } = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Col flex={1}>
|
||||
<Button style={{ width: '100%', color: '#2c6eff' }} type="link" onClick={onClick}>
|
||||
{t('Search')}
|
||||
</Button>
|
||||
</Col>
|
||||
);
|
||||
};
|
@ -0,0 +1,28 @@
|
||||
import { SortableItem, withDynamicSchemaProps } from '@nocobase/client';
|
||||
import { Grid } from 'antd-mobile';
|
||||
import React from 'react';
|
||||
import { useTabSearchCollapsibleInputItemAction } from './TabSearchCollapsibleInputItemAction';
|
||||
import { IButton, IInput, ISelect } from './TabSearchCollapsibleInputMItemChild';
|
||||
|
||||
export const TabSearchCollapsibleInputMItem = withDynamicSchemaProps(
|
||||
(props) => {
|
||||
const { collectionField, Designer, options, value, onSelectChange, onInputChange, onButtonClick, customLabelKey } =
|
||||
useTabSearchCollapsibleInputItemAction(props);
|
||||
|
||||
if (!collectionField) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SortableItem>
|
||||
<Designer />
|
||||
<Grid columns={4} style={{ backgroundColor: '#f9f9f9', borderRadius: '5px', margin: '5px 0 0 0' }}>
|
||||
<ISelect options={options} onChange={onSelectChange} customLabelKey={customLabelKey} />
|
||||
<IInput options={options} value={value} onChange={onInputChange} />
|
||||
<IButton onClick={onButtonClick} />
|
||||
</Grid>
|
||||
</SortableItem>
|
||||
);
|
||||
},
|
||||
{ displayName: 'TabSearchCollapsibleInputItem' },
|
||||
);
|
@ -0,0 +1,60 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from '../../../../locale';
|
||||
import { Grid, Divider, Picker, Input, Space } from 'antd-mobile';
|
||||
import { DownOutline } from 'antd-mobile-icons';
|
||||
|
||||
export const ISelect = (props) => {
|
||||
const { options, onChange, customLabelKey } = props;
|
||||
const [visible, setVisible] = useState(false);
|
||||
return options.length > 1 ? (
|
||||
<Grid.Item
|
||||
style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}
|
||||
onClick={() => {
|
||||
setVisible(true);
|
||||
}}
|
||||
>
|
||||
<span style={{ textAlign: 'center', width: '100%' }}>
|
||||
{options.find((option) => option.value === customLabelKey).label}
|
||||
</span>
|
||||
<DownOutline />
|
||||
<Picker
|
||||
columns={[options]}
|
||||
visible={visible}
|
||||
onClose={() => {
|
||||
setVisible(false);
|
||||
}}
|
||||
defaultValue={customLabelKey}
|
||||
onConfirm={(v) => {
|
||||
onChange(v[0]);
|
||||
}}
|
||||
/>
|
||||
<Divider direction="vertical" style={{ height: '70%', margin: '0 0 0 5px' }} />
|
||||
</Grid.Item>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export const IInput = (props) => {
|
||||
const { options, value, onChange } = props;
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Grid.Item span={options.length > 1 ? 2 : 3}>
|
||||
<Input
|
||||
placeholder={t('Please enter search content')}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
style={{ '--font-size': '12px', marginLeft: '5px' }}
|
||||
/>
|
||||
</Grid.Item>
|
||||
);
|
||||
};
|
||||
|
||||
export const IButton = (props) => {
|
||||
const { onClick } = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Grid.Item onClick={onClick} style={{ color: '#2c6eff', textAlign: 'center', cursor: 'pointer' }}>
|
||||
{t('Search')}
|
||||
</Grid.Item>
|
||||
);
|
||||
};
|
@ -0,0 +1,30 @@
|
||||
import { SortableItem, withDynamicSchemaProps } from '@nocobase/client';
|
||||
import { ConfigProvider, Menu } from 'antd';
|
||||
import React from 'react';
|
||||
import { useTabSearchFieldItemAction } from './TabSearchFieldItemAction';
|
||||
|
||||
export const TabSearchFieldItem = withDynamicSchemaProps(
|
||||
(props) => {
|
||||
const { collectionField, Designer, items, onSelect } = useTabSearchFieldItemAction(props);
|
||||
|
||||
if (!collectionField) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SortableItem>
|
||||
<Designer />
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
components: {
|
||||
Menu: { itemColor: '#a5a5a5', itemHoverColor: '#1d2ffff18' },
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Menu items={items} mode="horizontal" onSelect={onSelect} defaultSelectedKeys={['all']} />
|
||||
</ConfigProvider>
|
||||
</SortableItem>
|
||||
);
|
||||
},
|
||||
{ displayName: 'TabSearchFieldItem' },
|
||||
);
|
@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import { MenuProps } from 'antd';
|
||||
import { useCollection, useDesigner } from '@nocobase/client';
|
||||
import { useFieldSchema } from '@nocobase/schema';
|
||||
import { useTranslation } from '../../../../locale';
|
||||
|
||||
export const useTabSearchFieldItemAction = (props) => {
|
||||
const { list, onSelected, valueKey: _valueKey, labelKey: _labelKey, filterKey } = props;
|
||||
const { t } = useTranslation();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const c = useCollection();
|
||||
const collectionField = React.useMemo(() => c?.getField(fieldSchema.name as any), [c, fieldSchema.name]);
|
||||
const Designer = useDesigner();
|
||||
const valueKey = _valueKey || collectionField?.targetKey || 'id';
|
||||
const labelKey = _labelKey || fieldSchema['x-component-props']?.fieldNames?.label || valueKey;
|
||||
const onSelect = (itemKey) => {
|
||||
const key = itemKey.keyPath?.[0] || itemKey;
|
||||
onSelected([key], null, filterKey);
|
||||
};
|
||||
const fieldNames = {
|
||||
title: labelKey || valueKey,
|
||||
key: valueKey,
|
||||
};
|
||||
const items: MenuProps['items'] = [];
|
||||
|
||||
if (list?.length) {
|
||||
list.forEach((value) => {
|
||||
items.push({
|
||||
label: value[fieldNames.title],
|
||||
key: value[fieldNames.key],
|
||||
});
|
||||
});
|
||||
}
|
||||
items?.unshift({
|
||||
label: t('all'),
|
||||
key: 'all',
|
||||
});
|
||||
|
||||
return {
|
||||
collectionField,
|
||||
Designer,
|
||||
items,
|
||||
onSelect,
|
||||
};
|
||||
};
|
@ -0,0 +1,47 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useCollection, useCollectionManager } from '@nocobase/client';
|
||||
import { useFieldSchema } from '@nocobase/schema';
|
||||
import _ from 'lodash';
|
||||
import { useTabSearchCollapsibleInputItem } from './hooks';
|
||||
import { canBeOptionalField } from '../../utils';
|
||||
|
||||
export const useTabSearchFieldItemProps = () => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const collection = useCollection();
|
||||
const optionalFieldList = useOptionalFieldList();
|
||||
const cm = useCollectionManager();
|
||||
const collectionField = useMemo(() => collection?.getField(fieldSchema.name as any), [collection, fieldSchema.name]);
|
||||
const { onSelected } = useTabSearchCollapsibleInputItem();
|
||||
const result = { list: null, valueKey: '', labelKey: '', filterKey: '' };
|
||||
result.valueKey = collectionField?.target ? cm.getCollection(collectionField.target)?.getPrimaryKey() : 'id';
|
||||
result.labelKey = fieldSchema['x-component-props']?.fieldNames?.label || result.valueKey;
|
||||
const fieldInterface = fieldSchema['x-component-props'].interface;
|
||||
if (canBeOptionalField(fieldInterface)) {
|
||||
const field = optionalFieldList.find((field) => field.name === fieldSchema.name);
|
||||
const operatorMap = {
|
||||
select: '$in',
|
||||
multipleSelect: '$anyOf',
|
||||
checkbox: '$in',
|
||||
checkboxGroup: '$anyOf',
|
||||
};
|
||||
const _list = field?.uiSchema?.enum || [];
|
||||
result.valueKey = 'value';
|
||||
result.labelKey = 'label';
|
||||
result.list = _list;
|
||||
result.filterKey = `${field.name}.${operatorMap[field.interface]}`;
|
||||
}
|
||||
|
||||
return {
|
||||
list: result.list,
|
||||
valueKey: result.valueKey,
|
||||
labelKey: result.labelKey,
|
||||
onSelected,
|
||||
filterKey: result.filterKey,
|
||||
};
|
||||
};
|
||||
|
||||
const useOptionalFieldList = () => {
|
||||
const collection = useCollection();
|
||||
const currentFields = collection?.fields ?? [];
|
||||
return currentFields.filter((field) => canBeOptionalField(field.interface) && field.uiSchema.enum);
|
||||
};
|
@ -0,0 +1,26 @@
|
||||
import { SortableItem, withDynamicSchemaProps } from '@nocobase/client';
|
||||
import React from 'react';
|
||||
import { useTabSearchFieldItemAction } from './TabSearchFieldItemAction';
|
||||
import { Tabs } from 'antd-mobile';
|
||||
|
||||
export const TabSearchFieldMItem = withDynamicSchemaProps(
|
||||
(props) => {
|
||||
const { collectionField, Designer, items, onSelect } = useTabSearchFieldItemAction(props);
|
||||
|
||||
if (!collectionField) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SortableItem>
|
||||
<Designer />
|
||||
<Tabs style={{ '--title-font-size': '12px', color: '#a5a5a5' }} onChange={onSelect}>
|
||||
{items.map((value) => (
|
||||
<Tabs.Tab title={value.label} key={value.key} />
|
||||
))}
|
||||
</Tabs>
|
||||
</SortableItem>
|
||||
);
|
||||
},
|
||||
{ displayName: 'TabSearchFieldItem' },
|
||||
);
|
@ -0,0 +1,75 @@
|
||||
// 关系字段类型
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import {
|
||||
useCollection,
|
||||
useCollectionManager,
|
||||
useDataSourceHeaders,
|
||||
useFilterBlock,
|
||||
useRequest,
|
||||
} from '@nocobase/client';
|
||||
import { useField, useFieldSchema } from '@nocobase/schema';
|
||||
import _ from 'lodash';
|
||||
import { useTabSearchCollapsibleInputItem } from './hooks';
|
||||
import { canBeRelatedField } from '../../utils';
|
||||
|
||||
export const useTabSearchFieldItemRelatedProps = () => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const collection = useCollection();
|
||||
const field = useField();
|
||||
// TODO 这里需要替换成获取 source id 的方式,不过我们暂时没有用多数据源,就不着急
|
||||
const blockProps = { dataSource: 'main' };
|
||||
const headers = useDataSourceHeaders(blockProps?.dataSource);
|
||||
const cm = useCollectionManager();
|
||||
const collectionField = useMemo(() => collection?.getField(fieldSchema.name as any), [collection, fieldSchema.name]);
|
||||
const collectionFieldName = collectionField?.name;
|
||||
const fieldInterface = fieldSchema['x-component-props'].interface;
|
||||
const result = { list: null, valueKey: '', labelKey: '', filterKey: '' };
|
||||
const { onSelected } = useTabSearchCollapsibleInputItem();
|
||||
result.valueKey = collectionField?.target ? cm.getCollection(collectionField.target)?.getPrimaryKey() : 'id';
|
||||
result.labelKey = fieldSchema['x-component-props']?.fieldNames?.label || result.valueKey;
|
||||
// eslint-disable-next-line prefer-const
|
||||
const { data, run } = useRequest<{
|
||||
data: { [key: string]: any }[];
|
||||
}>(
|
||||
{
|
||||
headers,
|
||||
resource: collectionField?.target,
|
||||
action: 'list',
|
||||
params: {
|
||||
fields: [result.labelKey, result.valueKey],
|
||||
pageSize: 200,
|
||||
page: 1,
|
||||
...field.componentProps?.params,
|
||||
},
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
debounceWait: 300,
|
||||
},
|
||||
);
|
||||
useEffect(() => {
|
||||
if (canBeRelatedField(fieldInterface) && collectionField?.target) {
|
||||
run();
|
||||
}
|
||||
}, [
|
||||
result.labelKey,
|
||||
result.valueKey,
|
||||
JSON.stringify(field.componentProps?.params || {}),
|
||||
canBeRelatedField(fieldInterface),
|
||||
]);
|
||||
|
||||
if (!collectionField) {
|
||||
return {};
|
||||
}
|
||||
|
||||
result.filterKey = `${collectionFieldName}.${result.valueKey}.$in`;
|
||||
result.list = data?.data || [];
|
||||
|
||||
return {
|
||||
list: result.list,
|
||||
valueKey: result.valueKey,
|
||||
labelKey: result.labelKey,
|
||||
onSelected,
|
||||
filterKey: result.filterKey,
|
||||
};
|
||||
};
|
@ -0,0 +1,76 @@
|
||||
import { findFilterTargets, mergeFilter, useApp, useCollection, useFilterBlock } from '@nocobase/client';
|
||||
import { useFieldSchema } from '@nocobase/schema';
|
||||
import { useMemo } from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
export const useTabSearchCollapsibleInputItem = () => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const collection = useCollection();
|
||||
const { getDataBlocks } = useFilterBlock();
|
||||
const collectionField = useMemo(() => collection?.getField(fieldSchema.name as any), [collection, fieldSchema.name]);
|
||||
|
||||
const onSelected = (value, customLabelKey?, filterKey?) => {
|
||||
const { targets, uid } = findFilterTargets(fieldSchema);
|
||||
getDataBlocks().forEach((block) => {
|
||||
const target = targets.find((target) => target.uid === block.uid);
|
||||
if (!target) return;
|
||||
const key = `${uid}${fieldSchema.name}`;
|
||||
const param = block.service.params?.[0] || {};
|
||||
if (!collectionField?.target && customLabelKey) {
|
||||
filterKey = `${customLabelKey}.$includes`;
|
||||
}
|
||||
// 保留原有的 filter
|
||||
let storedFilter = block.service.params?.[1]?.filters || {};
|
||||
if (value.length) {
|
||||
storedFilter[key] = {
|
||||
[filterKey]: value,
|
||||
};
|
||||
} else {
|
||||
if (block.dataLoadingMode === 'manual') {
|
||||
return block.clearData();
|
||||
}
|
||||
delete storedFilter[key];
|
||||
}
|
||||
const mergedFilter = mergeFilter([...Object.values(storedFilter), block.defaultFilter]);
|
||||
if (value.length === 1 && value[0] === 'all') {
|
||||
const currentKey = Object.keys(storedFilter[key]);
|
||||
if (mergedFilter['$and']) {
|
||||
currentKey.forEach((currentValue) => {
|
||||
mergedFilter['$and'].forEach((value, index) => {
|
||||
if (value[currentValue]) {
|
||||
mergedFilter['$and'].splice(index, 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
mergedFilter['$and'] = mergedFilter['$and'].filter(Boolean);
|
||||
} else {
|
||||
delete mergedFilter[currentKey[0]];
|
||||
}
|
||||
storedFilter[key] = {};
|
||||
}
|
||||
storedFilter = _.omitBy(
|
||||
storedFilter,
|
||||
(value) => value === null || value === undefined || (_.isObject(value) && _.isEmpty(value)),
|
||||
);
|
||||
|
||||
return block.doFilter(
|
||||
{
|
||||
...param,
|
||||
page: 1,
|
||||
filter: mergedFilter,
|
||||
},
|
||||
{ filters: storedFilter },
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
onSelected,
|
||||
};
|
||||
};
|
||||
|
||||
export const useIsMobile = () => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const isMobile = Object.values(fieldSchema.root.properties).find((value) => value['x-component'] === 'MContainer');
|
||||
return isMobile;
|
||||
};
|
@ -0,0 +1,33 @@
|
||||
import { ISchema } from '@nocobase/schema';
|
||||
import { uid } from '@nocobase/utils/client';
|
||||
|
||||
export const createTabSearchBlockSchema = (options: {
|
||||
collectionName: string;
|
||||
dataSource: string;
|
||||
blockType: string;
|
||||
}): ISchema => {
|
||||
const { collectionName, dataSource, blockType } = options;
|
||||
return {
|
||||
type: 'void',
|
||||
'x-decorator': 'TabSearchProvider',
|
||||
'x-decorator-props': {
|
||||
collection: collectionName,
|
||||
dataSource,
|
||||
blockType,
|
||||
},
|
||||
// 这个工具栏本来就没什么特别的作用,就是用于显示名称、模板名称,用这个没问题
|
||||
'x-toolbar': 'BlockSchemaToolbar',
|
||||
// FIXME 模板在当前这个 provider 下是不支持的,要么添加相关的代码,要么这个 settings 用自己的
|
||||
'x-settings': 'blockSettings:filterCollapse',
|
||||
'x-component': 'CardItem',
|
||||
'x-filter-targets': [],
|
||||
properties: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-action': 'tabSearch',
|
||||
'x-initializer': 'tabSearch:configureFields',
|
||||
'x-component': 'TabSearch',
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
@ -0,0 +1,24 @@
|
||||
export const createTabSearchItemSchema = (options) => {
|
||||
const { field, itemComponent, label, itemUseProps } = options;
|
||||
return {
|
||||
name: field.key,
|
||||
title: field.uiSchema?.title,
|
||||
Component: 'TabSearchFieldSchemaInitializerGadget',
|
||||
schema: {
|
||||
name: field.name,
|
||||
title: field.uiSchema?.title,
|
||||
type: 'void',
|
||||
'x-toolbar': 'CollapseItemSchemaToolbar',
|
||||
'x-settings': 'fieldSettings:TabSearchItem',
|
||||
'x-component': itemComponent,
|
||||
'x-use-component-props': itemUseProps,
|
||||
'x-component-props': {
|
||||
fieldNames: {
|
||||
label,
|
||||
},
|
||||
interface: field.interface,
|
||||
},
|
||||
properties: {},
|
||||
},
|
||||
};
|
||||
};
|
@ -0,0 +1,54 @@
|
||||
import { Plugin } from '@nocobase/client';
|
||||
import { TabSearch } from './components/TabSearch';
|
||||
import { TabSearchProvider } from './provider/TabSearchProvider';
|
||||
import { TabSearchBlockInitializer } from './initializer/TabSearchBlockInitializer';
|
||||
import { TabSearchFieldItem } from './components/field-item/TabSearchFieldItem';
|
||||
import { TabSearchCollapsibleInputItem } from './components/field-item/TabSearchCollapsibleInputItem';
|
||||
import { TabSearchFieldSchemaInitializerGadget } from './initializer/TabSearchFieldSchemaInitializerGadget';
|
||||
import { tval } from '../../locale';
|
||||
import { useTabSearchFieldItemRelatedProps } from './components/field-item/TabSerachFieldItemRelatedProps';
|
||||
import { useTabSearchFieldItemProps } from './components/field-item/TabSearchFieldItemProps';
|
||||
import { TabSearchFieldSchemaInitializer } from './initializer/TabSearchFieldSchemaInitializer';
|
||||
import { TabSearchItemFieldSettings } from './settings/TabSearchItemFieldSettings';
|
||||
import { TabSearchFieldMItem } from './components/field-item/TabSearchFieldMItem';
|
||||
import { TabSearchCollapsibleInputMItem } from './components/field-item/TabSearchCollapsibleInputMItem';
|
||||
|
||||
class PluginTabSearch extends Plugin {
|
||||
async load() {
|
||||
this.app.addScopes({
|
||||
useTabSearchFieldItemProps,
|
||||
useTabSearchFieldItemRelatedProps,
|
||||
});
|
||||
|
||||
this.app.addComponents({
|
||||
TabSearch,
|
||||
TabSearchProvider,
|
||||
TabSearchBlockInitializer,
|
||||
TabSearchFieldItem,
|
||||
TabSearchFieldMItem,
|
||||
TabSearchCollapsibleInputItem,
|
||||
TabSearchCollapsibleInputMItem,
|
||||
TabSearchFieldSchemaInitializerGadget,
|
||||
});
|
||||
this.app.schemaInitializerManager.add(TabSearchFieldSchemaInitializer);
|
||||
this.schemaSettingsManager.add(TabSearchItemFieldSettings);
|
||||
this.app.schemaInitializerManager.addItem('mobilePage:addBlock', 'filterBlocks', {
|
||||
title: tval('Filter blocks'),
|
||||
type: 'itemGroup',
|
||||
children: [
|
||||
{
|
||||
name: 'tabSearch',
|
||||
title: 'tabSearch',
|
||||
Component: 'TabSearchBlockInitializer',
|
||||
},
|
||||
],
|
||||
});
|
||||
this.app.schemaInitializerManager.addItem('page:addBlock', 'filterBlocks.tabSearch', {
|
||||
name: 'tabSearch',
|
||||
title: 'tabSearch',
|
||||
Component: 'TabSearchBlockInitializer',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default PluginTabSearch;
|
@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import { DataBlockInitializer, Icon, useSchemaInitializer, useSchemaInitializerItem } from '@nocobase/client';
|
||||
import { createTabSearchBlockSchema } from '../create/createTabSearchBlockSchema';
|
||||
|
||||
export const TabSearchBlockInitializer = (props) => {
|
||||
const { filterCollections, onlyCurrentDataSource, hideChildrenIfSingleCollection } = props;
|
||||
const itemConfig = useSchemaInitializerItem();
|
||||
const { insert } = useSchemaInitializer();
|
||||
|
||||
const onCreateBlockSchema = async ({ item }) => {
|
||||
const schema = createTabSearchBlockSchema({
|
||||
dataSource: item.dataSource,
|
||||
collectionName: item.collectionName || item.name,
|
||||
blockType: 'filter',
|
||||
});
|
||||
insert(schema);
|
||||
};
|
||||
|
||||
return (
|
||||
<DataBlockInitializer
|
||||
{...itemConfig}
|
||||
onlyCurrentDataSource={onlyCurrentDataSource}
|
||||
icon="tab-search" // FIXME 这里有 bug,直接写字符串会出错
|
||||
componentType={'TabSearch'}
|
||||
filter={filterCollections}
|
||||
hideChildrenIfSingleCollection={hideChildrenIfSingleCollection}
|
||||
onCreateBlockSchema={onCreateBlockSchema}
|
||||
/>
|
||||
);
|
||||
};
|
@ -0,0 +1,70 @@
|
||||
import { SchemaInitializer, SchemaInitializerItemType, useCollection, useCollectionManager } from '@nocobase/client';
|
||||
import _ from 'lodash';
|
||||
import { tval } from '../../../locale';
|
||||
import { canBeOptionalField, canBeRelatedField, canBeSearchField } from '../utils';
|
||||
import { createTabSearchItemSchema } from '../create/createTabSearchItemSchema';
|
||||
import { useIsMobile } from '../components/field-item/hooks';
|
||||
|
||||
const textFieldsItem: SchemaInitializerItemType<any> = {
|
||||
name: 'textFields',
|
||||
type: 'itemGroup',
|
||||
title: tval('Text fields'),
|
||||
useChildren() {
|
||||
const collection = useCollection();
|
||||
const associatedFields = collection.fields;
|
||||
const isMobield = useIsMobile();
|
||||
const itemComponent = isMobield ? 'TabSearchCollapsibleInputMItem' : 'TabSearchCollapsibleInputItem';
|
||||
const children = associatedFields
|
||||
.map((field) => {
|
||||
if (canBeSearchField(field.interface) && !field['isForeignKey']) {
|
||||
const label = field.name;
|
||||
return createTabSearchItemSchema({ field, itemComponent, label });
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
return children;
|
||||
},
|
||||
};
|
||||
|
||||
const choicesFieldsItem: SchemaInitializerItemType<any> = {
|
||||
name: 'choicesFields',
|
||||
type: 'itemGroup',
|
||||
title: tval('Choices fields'),
|
||||
useChildren() {
|
||||
const collection = useCollection();
|
||||
const optionalList = collection.fields;
|
||||
const cm = useCollectionManager();
|
||||
const isMobield = useIsMobile();
|
||||
const itemComponent = isMobield ? 'TabSearchFieldMItem' : 'TabSearchFieldItem';
|
||||
const optionalChildren = optionalList
|
||||
.map((field) => {
|
||||
const label = cm.getCollection(field.target)?.getPrimaryKey() || 'id';
|
||||
if (canBeOptionalField(field.interface)) {
|
||||
return createTabSearchItemSchema({
|
||||
field,
|
||||
itemComponent,
|
||||
label,
|
||||
itemUseProps: 'useTabSearchFieldItemProps',
|
||||
});
|
||||
} else if (canBeRelatedField(field.interface)) {
|
||||
return createTabSearchItemSchema({
|
||||
field,
|
||||
itemComponent,
|
||||
label,
|
||||
itemUseProps: 'useTabSearchFieldItemRelatedProps',
|
||||
});
|
||||
}
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
return optionalChildren;
|
||||
},
|
||||
};
|
||||
|
||||
export const TabSearchFieldSchemaInitializer = new SchemaInitializer({
|
||||
name: 'tabSearch:configureFields',
|
||||
style: { marginTop: 16 },
|
||||
icon: 'SettingOutlined',
|
||||
title: tval('Configure fields'),
|
||||
items: [textFieldsItem, choicesFieldsItem],
|
||||
});
|
@ -0,0 +1,26 @@
|
||||
import {
|
||||
SchemaInitializerSwitch,
|
||||
useCurrentSchema,
|
||||
useSchemaInitializer,
|
||||
useSchemaInitializerItem,
|
||||
} from '@nocobase/client';
|
||||
import { merge } from '@nocobase/schema';
|
||||
import React from 'react';
|
||||
|
||||
export const TabSearchFieldSchemaInitializerGadget = () => {
|
||||
const itemConfig = useSchemaInitializerItem();
|
||||
const { schema } = itemConfig;
|
||||
const { exists, remove } = useCurrentSchema(schema.name, 'name', itemConfig.find, itemConfig.remove);
|
||||
const { insert } = useSchemaInitializer();
|
||||
|
||||
const onClick = () => {
|
||||
if (exists) {
|
||||
return remove();
|
||||
}
|
||||
const s = merge(schema || {}, itemConfig.schema || {});
|
||||
itemConfig?.schemaInitialize?.(s);
|
||||
insert(s);
|
||||
};
|
||||
|
||||
return <SchemaInitializerSwitch checked={exists} title={itemConfig.title} onClick={onClick} />;
|
||||
};
|
@ -0,0 +1,6 @@
|
||||
import { DataBlockProvider } from '@nocobase/client';
|
||||
import React from 'react';
|
||||
|
||||
export const TabSearchProvider = (props) => {
|
||||
return <DataBlockProvider collection={props.collection}>{props.children}</DataBlockProvider>;
|
||||
};
|
@ -0,0 +1,152 @@
|
||||
import {
|
||||
SchemaSettings,
|
||||
SchemaSettingsDataScope,
|
||||
useCollection,
|
||||
useCollectionManager,
|
||||
useCompile,
|
||||
useDesignable,
|
||||
useFormBlockContext,
|
||||
} from '@nocobase/client';
|
||||
import { useTranslation } from '../../../locale';
|
||||
import { useField, useFieldSchema } from '@nocobase/schema';
|
||||
import _ from 'lodash';
|
||||
import { isTabSearchCollapsibleInputItem } from '../utils';
|
||||
|
||||
export const TabSearchItemFieldSettings = new SchemaSettings({
|
||||
name: 'fieldSettings:TabSearchItem',
|
||||
items: [
|
||||
{
|
||||
name: 'decoratorOptions',
|
||||
type: 'itemGroup',
|
||||
hideIfNoChildren: true,
|
||||
useComponentProps() {
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
title: t('Generic properties'),
|
||||
};
|
||||
},
|
||||
useChildren() {
|
||||
return [
|
||||
{
|
||||
name: 'setTheDataScope',
|
||||
Component: SchemaSettingsDataScope,
|
||||
useComponentProps() {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { form } = useFormBlockContext();
|
||||
const field = useField();
|
||||
const cm = useCollectionManager();
|
||||
const c = useCollection();
|
||||
const collectionField =
|
||||
c.getField(fieldSchema['name']) || cm.getCollectionField(fieldSchema['x-collection-field']);
|
||||
const { dn } = useDesignable();
|
||||
|
||||
return {
|
||||
collectionName: collectionField?.target,
|
||||
defaultFilter: fieldSchema?.['x-component-props']?.params?.filter || {},
|
||||
form: form,
|
||||
onSubmit: ({ filter }) => {
|
||||
_.set(field.componentProps, 'params', {
|
||||
...field.componentProps?.params,
|
||||
filter,
|
||||
});
|
||||
fieldSchema['x-component-props']['params'] = field.componentProps.params;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
'x-component-props': fieldSchema['x-component-props'],
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
useVisible() {
|
||||
const fieldSchema = useFieldSchema();
|
||||
return !isTabSearchCollapsibleInputItem(fieldSchema['x-component']);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'titleField',
|
||||
type: 'select',
|
||||
useComponentProps() {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { t } = useTranslation();
|
||||
const cm = useCollectionManager();
|
||||
const c = useCollection();
|
||||
const collectionField =
|
||||
c.getField(fieldSchema['name']) || cm.getCollectionField(fieldSchema['x-collection-field']);
|
||||
const compile = useCompile();
|
||||
const { dn } = useDesignable();
|
||||
const targetFields = collectionField?.target ? cm.getCollectionFields(collectionField?.target) : [];
|
||||
const options = targetFields
|
||||
.filter((field) => !field?.target && field.type !== 'boolean')
|
||||
.map((field) => ({
|
||||
value: field?.name,
|
||||
label: compile(field?.uiSchema?.title) || field?.name,
|
||||
}));
|
||||
const onTitleFieldChange = (label) => {
|
||||
const schema = {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
};
|
||||
const fieldNames = {
|
||||
...collectionField?.uiSchema?.['x-component-props']?.['fieldNames'],
|
||||
...fieldSchema['x-component-props']?.['fieldNames'],
|
||||
label,
|
||||
};
|
||||
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
||||
fieldSchema['x-component-props']['fieldNames'] = fieldNames;
|
||||
schema['x-component-props'] = fieldSchema['x-component-props'];
|
||||
dn.emit('patch', {
|
||||
schema,
|
||||
});
|
||||
dn.refresh();
|
||||
};
|
||||
|
||||
return {
|
||||
key: 'title-field',
|
||||
title: t('Title field'),
|
||||
options: options,
|
||||
value: fieldSchema['x-component-props']?.fieldNames?.label,
|
||||
onChange: onTitleFieldChange,
|
||||
};
|
||||
},
|
||||
useVisible() {
|
||||
const fieldSchema = useFieldSchema();
|
||||
return !isTabSearchCollapsibleInputItem(fieldSchema['x-component']);
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'divider',
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
type: 'remove',
|
||||
sort: 100,
|
||||
useComponentProps() {
|
||||
const { t } = useTranslation();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { dn } = useDesignable();
|
||||
return {
|
||||
removeParentsIfNoChildren: true,
|
||||
confirm: {
|
||||
title: t('Delete field'),
|
||||
},
|
||||
breakRemoveOn: (s) => {
|
||||
if (isTabSearchCollapsibleInputItem(fieldSchema['x-component'])) {
|
||||
Object.values(s.properties).forEach((value) => {
|
||||
if (isTabSearchCollapsibleInputItem(value['x-component']) && value.name !== fieldSchema.name) {
|
||||
delete s.properties[value.name];
|
||||
}
|
||||
});
|
||||
dn.emit('patch', { schema: s });
|
||||
}
|
||||
return s['x-component'] === 'TabSearch';
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
@ -0,0 +1,25 @@
|
||||
const canBeOptionalFields = ['select', 'multipleSelect', 'radioGroup', 'checkbox', 'checkboxGroup'];
|
||||
const canBeRelatedFields = ['oho', 'obo', 'o2m', 'm2o', 'm2m'];
|
||||
|
||||
const canBeSearchFields = [
|
||||
'input',
|
||||
'textarea',
|
||||
'phone',
|
||||
'email',
|
||||
'url',
|
||||
'integer',
|
||||
'number',
|
||||
'percent',
|
||||
'password',
|
||||
'color',
|
||||
'icon',
|
||||
'formula',
|
||||
];
|
||||
|
||||
export const isTabSearchCollapsibleInputItem = (component: string) =>
|
||||
component === 'TabSearchCollapsibleInputItem' || component === 'TabSearchCollapsibleInputMItem';
|
||||
|
||||
export const canBeOptionalField = (_interface: string) => canBeOptionalFields.includes(_interface);
|
||||
export const canBeRelatedField = (_interface: string) => canBeRelatedFields.includes(_interface);
|
||||
|
||||
export const canBeSearchField = (_interface: string) => canBeSearchFields.includes(_interface);
|
@ -1,5 +1,13 @@
|
||||
import { RecursionField, useField, useFieldSchema } from '@nocobase/schema';
|
||||
import { ActionBarProvider, SortableItem, TabsContextProvider, css, cx, useDesigner } from '@nocobase/client';
|
||||
import {
|
||||
ActionBarProvider,
|
||||
FilterBlockProvider,
|
||||
SortableItem,
|
||||
TabsContextProvider,
|
||||
css,
|
||||
cx,
|
||||
useDesigner,
|
||||
} from '@nocobase/client';
|
||||
import { TabsProps } from 'antd';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
@ -131,7 +139,13 @@ const InternalPage: React.FC = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const MPage = InternalPage as unknown as typeof InternalPage & {
|
||||
const InternalFilterablePage = (props) => (
|
||||
<FilterBlockProvider>
|
||||
<InternalPage {...props} />
|
||||
</FilterBlockProvider>
|
||||
);
|
||||
|
||||
export const MPage = InternalFilterablePage as unknown as typeof InternalPage & {
|
||||
Designer: typeof PageDesigner;
|
||||
};
|
||||
MPage.Designer = PageDesigner;
|
||||
|
639
pnpm-lock.yaml
639
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user