diff --git a/lerna.json b/lerna.json index 0703c88ea5..49f47f15df 100644 --- a/lerna.json +++ b/lerna.json @@ -2,9 +2,7 @@ "version": "1.4.0-alpha.9", "npmClient": "yarn", "useWorkspaces": true, - "npmClientArgs": [ - "--ignore-engines" - ], + "npmClientArgs": ["--ignore-engines"], "command": { "version": { "forcePublish": true, diff --git a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/lazyLoadAssociationFields.test.ts b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/lazyLoadAssociationFields.test.ts index b7d31ab719..2f93f536e4 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/lazyLoadAssociationFields.test.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/lazyLoadAssociationFields.test.ts @@ -106,13 +106,13 @@ test.describe('association fields', () => { await expect(page.getByLabel('Root')).toBeVisible(); await page.getByTestId('select-object-multiple').click(); - await page.getByRole('option', { name: 'Member' }).click(); + await page.getByTitle('Member').locator('div').click(); // 再次点击,关闭下拉框。 await page.getByTestId('select-object-multiple').click(); - await expect(page.getByLabel('Admin')).toBeVisible(); + await expect(page.getByTestId('select-object-multiple').getByLabel('Admin')).toBeVisible(); await expect(page.getByLabel('Member')).toBeHidden(); - await expect(page.getByLabel('Root')).toBeVisible(); + await expect(page.getByTestId('select-object-multiple').getByLabel('Root')).toBeVisible(); await page.getByLabel('schema-initializer-Grid-form:configureFields-users').hover(); await page.getByRole('menuitem', { name: 'Nickname' }).click(); @@ -120,8 +120,8 @@ test.describe('association fields', () => { await page.mouse.move(200, 0); await page.waitForTimeout(200); - await expect(page.getByLabel('Admin')).toBeVisible(); + await expect(page.getByTestId('select-object-multiple').getByLabel('Admin')).toBeVisible(); await expect(page.getByLabel('Member')).toBeHidden(); - await expect(page.getByLabel('Root')).toBeVisible(); + await expect(page.getByTestId('select-object-multiple').getByLabel('Root')).toBeVisible(); }); }); diff --git a/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/hideColumn.test.ts b/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/hideColumn.test.ts index 4d5a3190f1..8a14b3db12 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/hideColumn.test.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/table/__e2e__/hideColumn.test.ts @@ -21,6 +21,7 @@ test.describe('hide column', () => { .hover(); await page.getByRole('menuitem', { name: 'Hide column question-circle' }).click(); await page.mouse.move(500, 0); + await page.getByLabel('block-item-CardItem-users-form').click(); // 2. Sub table: hide column await page.getByRole('button', { name: 'Role name' }).hover(); diff --git a/packages/core/client/src/schema-component/antd/date-picker/index.ts b/packages/core/client/src/schema-component/antd/date-picker/index.ts index a1c2c8937a..d67d585932 100644 --- a/packages/core/client/src/schema-component/antd/date-picker/index.ts +++ b/packages/core/client/src/schema-component/antd/date-picker/index.ts @@ -8,3 +8,4 @@ */ export * from './DatePicker'; +export * from './util'; diff --git a/packages/core/client/src/schema-component/antd/date-picker/util.ts b/packages/core/client/src/schema-component/antd/date-picker/util.ts index ebee73bfd8..b34f17c4cb 100644 --- a/packages/core/client/src/schema-component/antd/date-picker/util.ts +++ b/packages/core/client/src/schema-component/antd/date-picker/util.ts @@ -77,7 +77,7 @@ export const moment2str = (value?: Dayjs | null, options: Moment2strOptions = {} const handleChangeOnFilter = (value, picker, showTime) => { const format = showTime ? 'YYYY-MM-DD HH:mm:ss' : getPickerFormat(picker); if (value) { - return value.format(format); + return dayjs(value).format(format); } return value; }; diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/belongsTo/schemaSettings1.test.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/belongsTo/schemaSettings1.test.ts index 2d81ec58ea..f602be81e9 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/belongsTo/schemaSettings1.test.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/belongsTo/schemaSettings1.test.ts @@ -96,7 +96,7 @@ test.describe('form item & create form', () => { .getByLabel('block-item-CollectionField-general-form-general.oneToOneBelongsTo-oneToOneBelongsTo') .getByTestId(/select-object/) .click(); - await page.getByRole('option', { name: String(records[0].id), exact: true }).click(); + await page.getByTitle(String(records[0].id), { exact: true }).click(); }, expectReadonly: async () => { await expect( @@ -165,7 +165,7 @@ test.describe('form item & create form', () => { .getByLabel('block-item-CollectionField-general-form-general.oneToOneBelongsTo-oneToOneBelongsTo') .getByTestId('select-object-single') .click(); - await expect(page.getByRole('option', { name: String(records[0].id), exact: true })).toBeVisible(); + await expect(page.getByTitle(String(records[0].id), { exact: true })).toBeVisible(); await expect(page.getByRole('option')).toHaveCount(1); }); diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/belongsTo/schemaSettings2.test.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/belongsTo/schemaSettings2.test.ts index 1a06b327e7..29dd7e2812 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/belongsTo/schemaSettings2.test.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/fields/belongsTo/schemaSettings2.test.ts @@ -155,7 +155,7 @@ test.describe('form item & edit form', () => { .getByLabel('block-item-CollectionField-general-form-general.oneToOneBelongsTo-oneToOneBelongsTo') .getByTestId(/select-object/) .click(); - await expect(page.getByRole('option', { name: String(recordsOfUser[0].id), exact: true })).toBeVisible(); + await expect(page.getByTitle(String(recordsOfUser[0].id), { exact: true })).toBeVisible(); await expect(page.getByRole('option')).toHaveCount(2); }); diff --git a/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/utils.ts b/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/utils.ts index 7229b6489a..b4b800e814 100644 --- a/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/utils.ts +++ b/packages/plugins/@nocobase/plugin-data-source-main/src/client/__e2e__/utils.ts @@ -332,7 +332,7 @@ export class CollectionSettings { private async ['File storage'](value: string) { await this.page.getByLabel('block-item-RemoteSelect-collections-File storage').getByTestId('select-single').click(); - await this.page.getByRole('option', { name: value }).click(); + await this.page.getByTitle(value).click(); } private async ['Collection display name'](value: string) { diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.tsx index 41d7012378..8e7c3915cd 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.tsx +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile-layout/mobile-tab-bar/MobileTabBar.tsx @@ -32,7 +32,7 @@ export const MobileTabBar: FC & { Page: typeof MobileTabBarPage; Link: typeof MobileTabBarLink; } = ({ enableTabBar = true }) => { - const { styles } = useStyles(); + const { styles } = useStyles() as any; const { designable } = useDesignable(); const { routeList, activeTabBarItem, resource, refresh } = useMobileRoutes(); const validRouteList = routeList.filter((item) => item.schemaUid || isInnerLink(item.options?.url)); diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/MobilePage.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/MobilePage.tsx index 9eaed0c876..a84bc6ee23 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/MobilePage.tsx +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/MobilePage.tsx @@ -7,9 +7,25 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { RemoteSchemaComponent } from '@nocobase/client'; +import { RemoteSchemaComponent, AssociatedFields } from '@nocobase/client'; import React, { useCallback } from 'react'; import { Outlet, useParams } from 'react-router-dom'; +import { Button as MobileButton, Form as MobileForm, List as MobileList, Dialog as MobileDialog } from 'antd-mobile'; +import { MobilePicker } from './components/MobilePicker'; +import { MobileDateTimePicker } from './components/MobileDatePicker'; + +const mobileComponents = { + Button: MobileButton, + Select: MobilePicker, + DatePicker: MobileDateTimePicker, + UnixTimestamp: MobileDateTimePicker, + Modal: MobileDialog, + AssociatedFields: ( +
+ +
+ ), +}; export const MobilePage = () => { const { pageSchemaUid } = useParams<{ pageSchemaUid: string }>(); @@ -37,6 +53,7 @@ export const MobilePage = () => { NotFoundPage={'MobileNotFoundPage'} memoized={false} onPageNotFind={onPageNotFind} + components={mobileComponents} /> {/* 用于渲染子页面 */} diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/components/MobileDatePicker.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/components/MobileDatePicker.tsx new file mode 100644 index 0000000000..bb00b76f7a --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/components/MobileDatePicker.tsx @@ -0,0 +1,91 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { useState, useCallback } from 'react'; +import { DatePicker } from 'antd-mobile'; +import { mapDatePicker, DatePicker as NBDatePicker } from '@nocobase/client'; +import { connect, mapProps, mapReadPretty } from '@formily/react'; +import { useTranslation } from 'react-i18next'; + +const MobileDateTimePicker = connect( + (props) => { + const { t } = useTranslation(); + const { + value, + onChange, + dateFormat = 'YYYY-MM-DD', + timeFormat = 'HH:mm', + showTime = false, + picker, + ...rest + } = props; + const [visible, setVisible] = useState(false); + + // 性能优化:使用 useCallback 缓存函数 + const handleConfirm = useCallback( + (value) => { + setVisible(false); + const selectedDateTime = new Date(value); + onChange(selectedDateTime); + }, + [showTime, onChange], + ); + + // 清空选择的日期和时间 + const handleClear = useCallback(() => { + setVisible(false); + onChange(null); + }, [onChange]); + + const labelRenderer = useCallback((type: string, data: number) => { + switch (type) { + case 'year': + return data; + case 'quarter': + return data; + default: + return data; + } + }, []); + return ( + <> +
setVisible(true)}> + setVisible(true)} + value={value} + picker={picker} + {...rest} + popupStyle={{ display: 'none' }} + style={{ pointerEvents: 'none', width: '100%' }} + /> +
+ {t('Clear')}} + onClose={() => { + setVisible(false); + }} + precision={showTime ? 'second' : picker === 'date' ? 'day' : picker} + renderLabel={labelRenderer} + min={new Date(1000, 0, 1)} + max={new Date(9999, 11, 31)} + onConfirm={(val) => { + handleConfirm(val); + }} + /> + + ); + }, + mapProps(mapDatePicker()), + mapReadPretty(NBDatePicker.ReadPretty), +); + +export { MobileDateTimePicker }; diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/components/MobilePicker.tsx b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/components/MobilePicker.tsx new file mode 100644 index 0000000000..3291e6d6ea --- /dev/null +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/pages/dynamic-page/components/MobilePicker.tsx @@ -0,0 +1,112 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React, { useEffect, useMemo, useState } from 'react'; +import { Button, CheckList, Popup, SearchBar } from 'antd-mobile'; +import { connect, mapProps } from '@formily/react'; +import { Select } from '@nocobase/client'; +import { useTranslation } from 'react-i18next'; + +const MobilePicker = connect( + (props) => { + const { value, onChange, options = [], mode } = props; + const { t } = useTranslation(); + const [visible, setVisible] = useState(false); + const [selected, setSelected] = useState(value || []); + const [searchText, setSearchText] = useState(null); + + const filteredItems = useMemo(() => { + if (searchText) { + return options.filter((item) => item.label.toLowerCase().includes(searchText.toLowerCase())); + } + return options; + }, [options, searchText]); + + const handleConfirm = () => { + onChange(selected); + setVisible(false); + }; + useEffect(() => { + !visible && setSearchText(null); + }, [visible]); + + return ( + <> +
setVisible(true)}> +