diff --git a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/index.tsx b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/index.tsx index 4223bddbb..4232eaa8e 100644 --- a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/index.tsx +++ b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/index.tsx @@ -9,6 +9,7 @@ import { ImageSearchItemView, ImageSearchProvider, ImageSearchView, + MCascader, MCheckbox, MContainer, MDatePicker, @@ -87,6 +88,7 @@ export const MobileCore: React.FC = (props) => { MDatePicker, MRadio, MImageUploader, + MCascader, CollectionField: CollectionField, MSelect, }} diff --git a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/CustomComponent.tsx b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/CustomComponent.tsx index dffa306d3..ea17561bd 100644 --- a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/CustomComponent.tsx +++ b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/CustomComponent.tsx @@ -6,6 +6,7 @@ const customComponent = { 'Upload.Attachment': 'MImageUploader', DatePicker: 'MDatePicker', Select: 'MSelect', + Cascader: 'MCascader', }; export const canMobileField = (componentName: string) => customComponent[componentName]; diff --git a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Cascader/AntdCadcader.tsx b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Cascader/AntdCadcader.tsx new file mode 100644 index 000000000..66b7d1001 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Cascader/AntdCadcader.tsx @@ -0,0 +1,282 @@ +import React, { useEffect, useState } from 'react'; +import { + BlockItem, + CollectionProvider, + useCollectionManager, + useFieldServiceFilter, + useRequest, +} from '@tachybase/client'; +import { observer, useFieldSchema, useForm } from '@tachybase/schema'; +import { isArray } from '@tachybase/utils/client'; + +import { Button, CascaderView, Divider, Popup, SearchBar, Space } from 'antd-mobile'; +import { CloseOutline } from 'antd-mobile-icons'; + +import { useTranslation } from '../../../../../locale'; +import { MobileProvider } from '../../../provider'; +import { MInput } from '../Input'; +import { useStyles } from './style'; + +export const AntdCascader = observer((props) => { + const { service, fieldNames, disabled, multiple, onChange, value, formCascaderValues, index } = props as any; + const fieldSchema = useFieldSchema(); + const cm = useCollectionManager(); + const { styles } = useStyles(); + const collectionField = cm.getCollectionField(fieldSchema['x-collection-field']); + const changOnSelect = fieldSchema['x-component-props']?.changOnSelect || false; + const collection = cm.getCollection(fieldSchema['x-collection-field']); + const isChinaRegion = collectionField.interface === 'chinaRegion'; + const [options, setOptions] = useState([]); + const [popupVisible, setPopupVisible] = useState(false); + const [fieldServiceFilter] = useFieldServiceFilter(service?.params); + const [filter, setFilter] = useState(fieldServiceFilter); + const [searchValue, setSearchValue] = useState(''); + const [cascaderValue, setCascaderValue] = useState(value); + const [valueText, setValueText] = useState(''); + const [flatOption, setFlatOption] = useState([]); + const { run } = useRequest( + { + resource: collection?.name, + action: 'list', + params: { + filter, + tree: true, + }, + }, + { + manual: true, + onSuccess({ data }) { + if (data) { + setOptions(cascadeOption(data)); + const valueOption = flatOptions(data); + setFlatOption(valueOption); + if (value) { + setValueText(getValueText(valueOption, value, '', fieldNames['label'])); + } + } + }, + }, + ); + + const cascadeOption = (option) => { + option.forEach((item) => { + item['value'] = item[fieldNames.value]; + item['label'] = item[fieldNames.label]; + if (item.children) { + item.children = cascadeOption(item.children); + } + }); + return option; + }; + useEffect(() => { + if (!isChinaRegion && collection?.name) { + checkedPopup(); + } + }, [filter]); + useEffect(() => { + if (options.length) { + cascadeOption(options); + } + }, [fieldSchema['x-component-props']?.['fieldNames']]); + + const handleSelect = async (option) => { + if (option) { + setCascaderValue(option[option.length - 1]); + } else { + setCascaderValue({}); + } + }; + + const checkedPopup = () => { + if (collection && collection.name) { + run(); + } + }; + + return ( + +
{ + if (disabled) return; + setPopupVisible(true); + checkedPopup(); + }} + style={{ display: 'flex', alignItems: 'center' }} + > + {value && Object.keys(value).length ? ( + + ) : ( + + )} + {(value && Object.keys(value).length) || (formCascaderValues && index !== 0) ? ( + { + if (index) { + const formValue = [...formCascaderValues]; + if (formCascaderValues.length === 1 && index === 0) { + formValue[index] = {}; + } else { + formValue.splice(index, 1); + } + onChange(formValue); + } else { + onChange({}); + } + }} + /> + ) : null} +
+ { + setPopupVisible(false); + }} + > + + { + const paramsFilter = { ...filter }; + paramsFilter[fieldNames['label']] = { $includes: value }; + setFilter(paramsFilter); + setSearchValue(value); + }} + /> + + handleSelect(extend['items'])} /> + + + + + + +
+ ); +}); + +const getValueText = (option, item, value, label) => { + const text = `${item?.[label] || ''}${value}`; + if (item?.parentId) { + const filterItem = option.find((optionsItem) => optionsItem.id === item.parentId); + const itemText = `/${text}`; + return getValueText(option, filterItem, itemText, label); + } + return text; +}; + +const flatOptions = (options) => { + const flatChildren = (item, option) => { + option.push(item); + if (item?.children) { + item.children.forEach((childrenItem) => { + return flatChildren(childrenItem, option); + }); + } + return option; + }; + const optionflat = []; + options.forEach((value) => { + const flatValue = flatChildren(value, []); + optionflat.push(...flatValue); + }); + return optionflat; +}; + +const getChangValue = (formCascaderValues, cascaderValue, index) => { + const value = []; + if (formCascaderValues && (index || index === 0)) { + formCascaderValues[index] = cascaderValue; + value.push(...formCascaderValues); + } else { + value.push(cascaderValue); + } + return value; +}; + +export const InternalCascader = observer((props) => { + const { value, onChange } = props as any; + const fieldSchema = useFieldSchema(); + const cm = useCollectionManager(); + const collectionField = cm.getCollectionField(fieldSchema['x-collection-field']); + const form = useForm(); + const { t } = useTranslation(); + return ( + <> + {isArray(value) ? ( + <> + {value.map((item, index) => { + const filterProps = { + ...props, + value: item, + formCascaderValues: value || [], + index, + }; + return ; + })} + + ) : ( + + )} + {['m2m', 'o2m'].includes(collectionField.interface) ? ( + <> + + + ) : null} + + ); +}); diff --git a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Cascader/Cadcader.tsx b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Cascader/Cadcader.tsx new file mode 100644 index 000000000..54851dc4f --- /dev/null +++ b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Cascader/Cadcader.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { connect, mapProps, mapReadPretty, useFieldSchema } from '@tachybase/schema'; +import { isArray } from '@tachybase/utils/client'; + +import { InternalCascader } from './AntdCadcader'; +import { ReadPretty } from './ReadPretty'; + +export const MCascader = connect( + InternalCascader, + mapProps((props) => { + return { ...props }; + }), + mapReadPretty(ReadPretty), +); diff --git a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Cascader/ReadPretty.tsx b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Cascader/ReadPretty.tsx new file mode 100644 index 000000000..a30c25772 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Cascader/ReadPretty.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { ArrayField, toArr, useField } from '@tachybase/schema'; + +import { defaultFieldNames } from './defaultFieldNames'; + +export const ReadPretty: React.FC = (props: any) => { + const { fieldNames = defaultFieldNames } = props; + const values = toArr(props.value); + const len = values.length; + const field = useField(); + let dataSource = field.dataSource; + const data = []; + for (const item of values) { + if (typeof item === 'object') { + data.push(item); + } else { + const curr = dataSource?.find((v) => v[fieldNames.value] === item); + dataSource = curr?.[fieldNames.children] || []; + data.push(curr || { label: item, value: item }); + } + } + return ( +
+ {data.map((item, index) => { + return ( + + {typeof item === 'object' ? item[fieldNames.label] : item} + {len > index + 1 && ' / '} + + ); + })} +
+ ); +}; diff --git a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Cascader/defaultFieldNames.ts b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Cascader/defaultFieldNames.ts new file mode 100644 index 000000000..da692434e --- /dev/null +++ b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Cascader/defaultFieldNames.ts @@ -0,0 +1,5 @@ +export const defaultFieldNames = { + label: 'label', + value: 'value', + children: 'children', +}; diff --git a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Cascader/index.tsx b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Cascader/index.tsx new file mode 100644 index 000000000..99fc5f607 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Cascader/index.tsx @@ -0,0 +1 @@ +export * from './Cadcader'; diff --git a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Cascader/style.tsx b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Cascader/style.tsx new file mode 100644 index 000000000..5475a2f87 --- /dev/null +++ b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Cascader/style.tsx @@ -0,0 +1,25 @@ +import { createStyles } from '@tachybase/client'; + +export const useStyles = createStyles(({ css }) => ({ + PopupStyle: css` + .adm-popup-body { + padding: 10px; + .adm-space { + width: 100%; + margin: 10px 0 10px 0; + color: #4592ff; + button { + width: 23vw; + } + } + .adm-divider { + margin: 0; + } + .adm-list-body { + height: 30vh; + overflow: auto; + text-align: center; + } + } + `, +})); diff --git a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Select/AntdSelect.tsx b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Select/AntdSelect.tsx index ebf99c17a..c4bd1ae4c 100644 --- a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Select/AntdSelect.tsx +++ b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Select/AntdSelect.tsx @@ -226,7 +226,6 @@ export const AntdSelect = observer((props) => { if (paramsFilter[fieldNamesLabel]) delete paramsFilter[fieldNamesLabel]; setSearchValue(''); setFilter(paramsFilter); - setSearchValue(''); }} > 确定 diff --git a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Select/schema.ts b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Select/schema.ts deleted file mode 100644 index 9ae7a8073..000000000 --- a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/Select/schema.ts +++ /dev/null @@ -1,131 +0,0 @@ -export default { - Nester: { - type: 'void', - 'x-component': 'AssociationField.Nester', - properties: { - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-initializer': 'form:configureFields', - }, - }, - }, - AddNewer: { - type: 'void', - 'x-component': 'AssociationField.AddNewer', - 'x-action': 'create', - title: '{{ t("Add record") }}', - 'x-component-props': { - className: 'nb-action-popup', - }, - properties: { - tabs: { - type: 'void', - 'x-component': 'Tabs', - 'x-component-props': {}, - 'x-initializer': 'TabPaneInitializersForCreateFormBlock', - properties: { - tab1: { - type: 'void', - title: '{{t("Add new")}}', - 'x-component': 'Tabs.TabPane', - 'x-designer': 'Tabs.Designer', - 'x-component-props': {}, - properties: { - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-initializer': 'popup:addNew:addBlock', - properties: {}, - }, - }, - }, - }, - }, - }, - }, - Selector: { - type: 'void', - 'x-component': 'AssociationField.Selector', - title: '{{ t("Select record") }}', - 'x-component-props': { - className: 'nb-record-picker-selector', - }, - properties: { - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-initializer': 'popup:tableSelector:addBlock', - properties: {}, - }, - footer: { - 'x-component': 'Action.Container.Footer', - 'x-component-props': {}, - properties: { - actions: { - type: 'void', - 'x-component': 'ActionBar', - 'x-component-props': {}, - properties: { - submit: { - title: '{{ t("Submit") }}', - 'x-action': 'submit', - 'x-component': 'Action', - 'x-use-component-props': 'usePickActionProps', - // 'x-designer': 'Action.Designer', - 'x-toolbar': 'ActionSchemaToolbar', - 'x-settings': 'actionSettings:submit', - 'x-component-props': { - type: 'primary', - htmlType: 'submit', - }, - }, - }, - }, - }, - }, - }, - }, - Viewer: { - type: 'void', - title: '{{ t("View record") }}', - 'x-component': 'AssociationField.Viewer', - 'x-component-props': { - className: 'nb-action-popup', - }, - properties: { - tabs: { - type: 'void', - 'x-component': 'Tabs', - 'x-component-props': {}, - 'x-initializer': 'TabPaneInitializers', - properties: { - tab1: { - type: 'void', - title: '{{t("Details")}}', - 'x-component': 'Tabs.TabPane', - 'x-designer': 'Tabs.Designer', - 'x-component-props': {}, - properties: { - grid: { - type: 'void', - 'x-component': 'Grid', - 'x-initializer': 'popup:common:addBlock', - properties: {}, - }, - }, - }, - }, - }, - }, - }, - SubTable: { - type: 'void', - 'x-component': 'AssociationField.SubTable', - 'x-initializer': 'table:configureColumns', - 'x-initializer-props': { - action: false, - }, - properties: {}, - }, -}; diff --git a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/index.ts b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/index.ts index 16936a6cc..e980d18f0 100644 --- a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/index.ts +++ b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/antd-mobile/index.ts @@ -4,3 +4,4 @@ export * from './DatePicker'; export * from './ImageUploader'; export * from './Radio'; export * from './Select'; +export * from './Cascader'; diff --git a/packages/plugins/@tachybase/plugin-mobile-client/src/locale/en-US.json b/packages/plugins/@tachybase/plugin-mobile-client/src/locale/en-US.json index 48c8f14d8..17ee585d3 100644 --- a/packages/plugins/@tachybase/plugin-mobile-client/src/locale/en-US.json +++ b/packages/plugins/@tachybase/plugin-mobile-client/src/locale/en-US.json @@ -45,5 +45,6 @@ "Add block": "Add block", "all": "all", "AllProducts": "AllProducts", - "sort":"sort" + "sort":"sort", + "Add new":"Add new" } diff --git a/packages/plugins/@tachybase/plugin-mobile-client/src/locale/zh-CN.json b/packages/plugins/@tachybase/plugin-mobile-client/src/locale/zh-CN.json index 5988a7fe1..ea2b9f76b 100644 --- a/packages/plugins/@tachybase/plugin-mobile-client/src/locale/zh-CN.json +++ b/packages/plugins/@tachybase/plugin-mobile-client/src/locale/zh-CN.json @@ -48,5 +48,6 @@ "sort": "排序", "Swiper":"轮播图", "TabSearch":"表格搜索", - "ImageSearch":"图片搜索" + "ImageSearch":"图片搜索", + "Add new":"添加" }