diff --git a/packages/core/client/src/modules/blocks/data-blocks/grid-card/createGridCardBlockUISchema.ts b/packages/core/client/src/modules/blocks/data-blocks/grid-card/createGridCardBlockUISchema.ts index 10d739957..6fe80655e 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/grid-card/createGridCardBlockUISchema.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/grid-card/createGridCardBlockUISchema.ts @@ -30,6 +30,8 @@ export const createGridCardBlockUISchema = (options: { }, runWhenParamsChanged: true, rowKey, + isLinkable: false, + needInfiniteScroll: false, layoutDirection: 'column', }, 'x-component': 'BlockItem', diff --git a/packages/core/client/src/modules/blocks/data-blocks/grid-card/gridCardBlockSettings.ts b/packages/core/client/src/modules/blocks/data-blocks/grid-card/gridCardBlockSettings.ts index b7971a657..194382f89 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/grid-card/gridCardBlockSettings.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/grid-card/gridCardBlockSettings.ts @@ -277,6 +277,54 @@ export const gridCardBlockSettings = new SchemaSettings({ }; }, }, + { + name: 'setLinkable', + type: 'switch', + useComponentProps() { + const { t } = useTranslation(); + const fieldSchema = useFieldSchema(); + const field = useField(); + const { dn } = useDesignable(); + return { + title: t('Set Linkable'), + checked: fieldSchema['x-decorator-props']?.['isLinkable'] ?? false, + onChange: (isLinkable) => { + _.set(fieldSchema, 'x-decorator-props.isLinkable', isLinkable); + field.decoratorProps.isLinkable = isLinkable; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': fieldSchema['x-decorator-props'], + }, + }); + }, + }; + }, + }, + { + name: 'setInfiniteScroll', + type: 'switch', + useComponentProps() { + const { t } = useTranslation(); + const fieldSchema = useFieldSchema(); + const field = useField(); + const { dn } = useDesignable(); + return { + title: t('Set InfiniteScroll'), + checked: fieldSchema['x-decorator-props']?.['needInfiniteScroll'], + onChange: (needInfiniteScroll) => { + _.set(fieldSchema, 'x-decorator-props.needInfiniteScroll', needInfiniteScroll); + field.decoratorProps.needInfiniteScroll = needInfiniteScroll; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': fieldSchema['x-decorator-props'], + }, + }); + }, + }; + }, + }, { name: 'ConvertReferenceToDuplicate', Component: SchemaSettingsTemplate, diff --git a/packages/core/client/src/schema-component/antd/grid-card/GridCard.Decorator.tsx b/packages/core/client/src/schema-component/antd/grid-card/GridCard.Decorator.tsx index 66de5fbd9..fb5f96726 100644 --- a/packages/core/client/src/schema-component/antd/grid-card/GridCard.Decorator.tsx +++ b/packages/core/client/src/schema-component/antd/grid-card/GridCard.Decorator.tsx @@ -33,6 +33,7 @@ const InternalGridCardBlockProvider = (props) => { service, resource, columnCount: props.columnCount, + needInfiniteScroll: props.needInfiniteScroll, }} > diff --git a/packages/core/client/src/schema-component/antd/grid-card/GridCard.Item.tsx b/packages/core/client/src/schema-component/antd/grid-card/GridCard.Item.tsx index 09f52ca04..bdc665221 100644 --- a/packages/core/client/src/schema-component/antd/grid-card/GridCard.Item.tsx +++ b/packages/core/client/src/schema-component/antd/grid-card/GridCard.Item.tsx @@ -1,11 +1,14 @@ import { css } from '@emotion/css'; +import { useCollection } from '@tachybase/client'; import { ObjectField } from '@tachybase/schema'; -import { useField } from '@tachybase/schema'; +import { useField, useFieldSchema } from '@tachybase/schema'; import { Card } from 'antd'; import React from 'react'; import { useCollectionParentRecordData } from '../../../data-source/collection-record/CollectionRecordProvider'; import { RecordProvider } from '../../../record-provider'; import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps'; +import { useNavigate } from 'react-router-dom'; +import { useGridCardDetailUrl } from './hooks'; const itemCss = css` display: flex; @@ -17,13 +20,28 @@ const itemCss = css` `; export const GridCardItem = withDynamicSchemaProps((props) => { + const collection = useCollection(); const field = useField(); + const fieldSchema = useFieldSchema(); const parentRecordData = useCollectionParentRecordData(); + const navigate = useNavigate(); + const detailUrl = useGridCardDetailUrl({ collection, field, fieldSchema }); + // XXX: 实现的有些丑陋, 需要想想有没有更好的办法 + const handleClick = () => { + // 1. 依赖schema的层级,不合适 + const parentSchema = fieldSchema?.parent?.parent; + const isLinkable = parentSchema?.['x-decorator-props']?.isLinkable; + if (isLinkable) { + // 2. 固定的链接格式写法,不合适; 需要想个能配置的办法 + navigate(detailUrl); + } + }; return ( .ant-card-body { padding: 24px 24px 0px; @@ -33,8 +51,10 @@ export const GridCardItem = withDynamicSchemaProps((props) => { padding: 5px 0; } `} + onClick={handleClick} >
+ {/* @ts-ignore */} {props.children} diff --git a/packages/core/client/src/schema-component/antd/grid-card/GridCard.tsx b/packages/core/client/src/schema-component/antd/grid-card/GridCard.tsx index 5646f2a53..bf486c31f 100644 --- a/packages/core/client/src/schema-component/antd/grid-card/GridCard.tsx +++ b/packages/core/client/src/schema-component/antd/grid-card/GridCard.tsx @@ -2,7 +2,7 @@ import { css, cx } from '@emotion/css'; import { ArrayField } from '@tachybase/schema'; import { RecursionField, Schema, useField, useFieldSchema } from '@tachybase/schema'; import { List as AntdList, Col, PaginationProps } from 'antd'; -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { SortableItem } from '../../common'; import { SchemaComponentOptions } from '../../core'; import { useDesigner, useProps } from '../../hooks'; @@ -12,6 +12,7 @@ import { GridCardItem } from './GridCard.Item'; import { useGridCardActionBarProps } from './hooks'; import { defaultColumnCount, pageSizeOptions } from './options'; import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps'; +import { InfiniteScroll } from '../../common/infinite-scroll/infinite-scroll'; const rowGutter = { md: 12, @@ -59,8 +60,7 @@ const designerCss = css` const InternalGridCard = (props) => { // 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema const { columnCount: columnCountProp, pagination } = useProps(props); - - const { service, columnCount: _columnCount = defaultColumnCount } = useGridCardBlockContext(); + const { service, columnCount: _columnCount = defaultColumnCount, needInfiniteScroll } = useGridCardBlockContext(); const columnCount = columnCountProp || _columnCount; const { run, params } = service; const meta = service?.data?.meta; @@ -68,6 +68,7 @@ const InternalGridCard = (props) => { const field = useField(); const Designer = useDesigner(); const [schemaMap] = useState(new Map()); + const getSchema = useCallback( (key) => { if (!schemaMap.has(key)) { @@ -99,6 +100,31 @@ const InternalGridCard = (props) => { [run, params], ); + /* 以下为无限滚动逻辑 */ + // XXX: 需要仔细梳理这里的逻辑, 目前的实现有点效果问题 + const [data, setData] = useState(field.value || []); + const [hasMore, setHasMore] = useState(true); + + const loadMore = async () => { + const { count, pageSize = 10, page = 1 } = meta || {}; + await onPaginationChange(page + 1, pageSize); + setHasMore(false); + }; + + useEffect(() => { + const { count, pageSize = 10, page = 1 } = meta || {}; + const hasMore = count > page * pageSize; + + setData((val = []) => { + const currentVal = field.value || []; + return [...val, ...currentVal]; + }); + + setHasMore(hasMore); + }, [meta?.page]); + + /* 以上为无限滚动逻辑 */ + return ( { { pageSizeOptions, } } - dataSource={field.value} + dataSource={needInfiniteScroll ? data : field.value} grid={{ ...columnCount, sm: columnCount.xs, @@ -142,6 +168,7 @@ const InternalGridCard = (props) => { }} loading={service?.loading} /> + {needInfiniteScroll && } diff --git a/packages/core/client/src/schema-component/antd/grid-card/hooks.ts b/packages/core/client/src/schema-component/antd/grid-card/hooks.ts index d4d00ebb1..ac2c64a26 100644 --- a/packages/core/client/src/schema-component/antd/grid-card/hooks.ts +++ b/packages/core/client/src/schema-component/antd/grid-card/hooks.ts @@ -10,3 +10,13 @@ export const useGridCardActionBarProps = () => { spaceProps, }; }; + +export const useGridCardDetailUrl = ({ collection, field, fieldSchema }) => { + const name = 'detail'; + const { datasource = 'main', name: collectionName } = collection; + const { record } = field; + const primaryKey = 'id'; + const primaryKeyValue = record[primaryKey]; + + return `/mobile/${name}/${datasource}/${collectionName}/${primaryKeyValue}`; +}; diff --git a/packages/core/client/src/schema-component/antd/grid-card/infinite-scroll/config-prodider.tsx b/packages/core/client/src/schema-component/antd/grid-card/infinite-scroll/config-prodider.tsx deleted file mode 100644 index 75b58ef9e..000000000 --- a/packages/core/client/src/schema-component/antd/grid-card/infinite-scroll/config-prodider.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import React, { useContext } from 'react'; -import type { FC, ReactNode } from 'react'; - -// TODO: 待继续 -// import { Locale } from '../../locales/base'; -// import zhCN from '../../locales/zh-CN'; -type Locale = {}; -const zhCN = {}; - -type Config = { - locale: Locale; -}; - -export const defaultConfigRef: { - current: Config; -} = { - current: { - locale: zhCN, - }, -}; - -export function setDefaultConfig(config: Config) { - defaultConfigRef.current = config; -} - -export function getDefaultConfig() { - return defaultConfigRef.current; -} - -const ConfigContext = React.createContext(null); - -export type ConfigProviderProps = Config & { - children?: ReactNode; -}; - -export const ConfigProvider: FC = (props) => { - const { children, ...config } = props; - const parentConfig = useConfig(); - - return ( - - {children} - - ); -}; - -export function useConfig() { - return useContext(ConfigContext) ?? getDefaultConfig(); -} diff --git a/packages/core/client/src/schema-component/antd/grid-card/infinite-scroll/locale.ts b/packages/core/client/src/schema-component/antd/grid-card/infinite-scroll/locale.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/core/client/src/schema-component/antd/grid-card/infinite-scroll/utils.ts b/packages/core/client/src/schema-component/antd/grid-card/infinite-scroll/utils.ts deleted file mode 100644 index 76b84910e..000000000 --- a/packages/core/client/src/schema-component/antd/grid-card/infinite-scroll/utils.ts +++ /dev/null @@ -1,85 +0,0 @@ -import classNames from 'classnames'; -import type { CSSProperties, ReactElement } from 'react'; -import React, { AriaAttributes } from 'react'; - -const canUseDom = !!( - typeof window !== 'undefined' && - typeof document !== 'undefined' && - window.document && - window.document.createElement -); - -type ScrollElement = HTMLElement | Window; - -const defaultRoot = canUseDom ? window : undefined; - -const overflowStylePatterns = ['scroll', 'auto', 'overlay']; - -function isElement(node: Element) { - const ELEMENT_NODE_TYPE = 1; - return node.nodeType === ELEMENT_NODE_TYPE; -} -export function getScrollParent( - el: Element, - root: ScrollElement | null | undefined = defaultRoot, -): Window | Element | null | undefined { - let node = el; - - while (node && node !== root && isElement(node)) { - if (node === document.body) { - return root; - } - const { overflowY } = window.getComputedStyle(node); - if (overflowStylePatterns.includes(overflowY) && node.scrollHeight > node.clientHeight) { - return node; - } - node = node.parentNode as Element; - } - return root; -} - -export function mergeProps(a: A, b: B): B & A; -export function mergeProps(a: A, b: B, c: C): C & B & A; -export function mergeProps(...items: any[]) { - const ret: any = {}; - items.forEach((item) => { - Object.keys(item).forEach((key) => { - if (item[key] !== undefined) { - ret[key] = item[key]; - } - }); - }); - return ret; -} - -export type NativeProps = { - className?: string; - style?: CSSProperties & Partial>; - tabIndex?: number; -} & AriaAttributes; - -export function withNativeProps

(props: P, element: ReactElement) { - const p = { - ...element.props, - }; - if (props.className) { - p.className = classNames(element.props.className, props.className); - } - if (props.style) { - p.style = { - ...p.style, - ...props.style, - }; - } - if (props.tabIndex !== undefined) { - p.tabIndex = props.tabIndex; - } - for (const key in props) { - // if (!props.hasOwnProperty(key)) continue; - if (!Object.hasOwn(props, key)) continue; - if (key.startsWith('data-') || key.startsWith('aria-')) { - p[key] = props[key]; - } - } - return React.cloneElement(element, p); -} diff --git a/packages/core/client/src/schema-component/antd/grid-card/options.ts b/packages/core/client/src/schema-component/antd/grid-card/options.ts index 8df7b54df..a51196e90 100644 --- a/packages/core/client/src/schema-component/antd/grid-card/options.ts +++ b/packages/core/client/src/schema-component/antd/grid-card/options.ts @@ -1,4 +1,4 @@ -export const pageSizeOptions = [12, 24, 60, 96, 120]; +export const pageSizeOptions = [5, 6, 10, 12, 24, 60, 96, 120]; export const gridSizes = ['xs', 'md', 'lg', 'xxl']; export const screenSizeTitleMaps = { xs: 'Phone device', diff --git a/packages/core/client/src/schema-component/antd/grid-card/infinite-scroll/InfiniteScroll.tsx b/packages/core/client/src/schema-component/common/infinite-scroll/infinite-scroll.tsx similarity index 60% rename from packages/core/client/src/schema-component/antd/grid-card/infinite-scroll/InfiniteScroll.tsx rename to packages/core/client/src/schema-component/common/infinite-scroll/infinite-scroll.tsx index d86c57af4..19ef38c89 100644 --- a/packages/core/client/src/schema-component/antd/grid-card/infinite-scroll/InfiniteScroll.tsx +++ b/packages/core/client/src/schema-component/common/infinite-scroll/infinite-scroll.tsx @@ -1,41 +1,20 @@ +import { getScrollParent, merge } from '@tachybase/utils/client'; import { useLockFn, useThrottleFn } from 'ahooks'; -import type { FC, ReactNode } from 'react'; -import React, { useEffect, useRef, useState } from 'react'; -// import { useConfig } from '../config-provider'; -// import DotLoading from '../dot-loading'; -import { NativeProps, getScrollParent, mergeProps, withNativeProps } from './utils'; - -// TODO: 待继续 -const useConfig = () => { - return { - locale: { - InfiniteScroll: { - retry: false, - noMore: false, - failedToLoad: '范德萨发', - }, - common: { - loading: false, - }, - }, - }; -}; - -// TODO: 待继续 -const DotLoading = () => null; +import { Spin } from 'antd'; +import React, { ReactNode, useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import useStyles from './style'; function isWindow(element: any | Window): element is Window { return element === window; } -const classPrefix = `adm-infinite-scroll`; - export type InfiniteScrollProps = { loadMore: (isRetry: boolean) => Promise; hasMore: boolean; threshold?: number; children?: ReactNode | ((hasMore: boolean, failed: boolean, retry: () => void) => ReactNode); -} & NativeProps; +}; const defaultProps: Required> = { threshold: 250, @@ -44,13 +23,17 @@ const defaultProps: Required ), }; -export const InfiniteScroll: FC = (p) => { - const props = mergeProps(defaultProps, p); - +export const InfiniteScroll = ({ + threshold = defaultProps.threshold, + loadMore, + children = defaultProps.children, + hasMore, +}: InfiniteScrollProps) => { + const { styles } = useStyles(); const [failed, setFailed] = useState(false); const doLoadMore = useLockFn(async (isRetry: boolean) => { try { - await props.loadMore(isRetry); + await loadMore(isRetry); } catch (e) { setFailed(true); throw e; @@ -68,7 +51,7 @@ export const InfiniteScroll: FC = (p) => { const { run: check } = useThrottleFn( async () => { if (nextFlagRef.current !== flag) return; - if (!props.hasMore) return; + if (!hasMore) return; const element = elementRef.current; if (!element) return; if (!element.offsetParent) return; @@ -78,14 +61,14 @@ export const InfiniteScroll: FC = (p) => { const rect = element.getBoundingClientRect(); const elementTop = rect.top; const current = isWindow(parent) ? window.innerHeight : parent.getBoundingClientRect().bottom; - if (current >= elementTop - props.threshold) { + if (current >= elementTop - threshold) { const nextFlag = {}; nextFlagRef.current = nextFlag; try { await doLoadMore(false); setFlag(nextFlag); } catch (e) { - console.log('%c Line:88 🍯 e', 'font-size:18px;color:#2eafb0;background:#3f7cff', e); + console.error(e); } } }, @@ -120,39 +103,35 @@ export const InfiniteScroll: FC = (p) => { await doLoadMore(true); setFlag(nextFlagRef.current); } catch (e) { - console.log('%c Line:121 🥥 e', 'font-size:18px;color:#3f7cff;background:#2eafb0', e); + console.error(e); } } - return withNativeProps( - props, -

- {typeof props.children === 'function' ? props.children(props.hasMore, failed, retry) : props.children} -
, + return ( +
+ {typeof children === 'function' ? children(hasMore, failed, retry) : children} +
); }; -const InfiniteScrollContent: FC<{ - hasMore: boolean; - failed: boolean; - retry: () => void; -}> = (props) => { - const { locale } = useConfig(); +const InfiniteScrollContent = (props: { hasMore: boolean; failed: boolean; retry: () => void }) => { + const { t } = useTranslation(); + const { styles } = useStyles(); if (!props.hasMore) { - return {locale.InfiniteScroll.noMore}; + return {t('no more')}; } if (props.failed) { return ( - {locale.InfiniteScroll.failedToLoad} + {t('failed to load')} { props.retry(); }} > - {locale.InfiniteScroll.retry} + {t('retry')} ); @@ -160,14 +139,8 @@ const InfiniteScrollContent: FC<{ return ( <> - {locale.common.loading} - + {t('loading')} + ); }; - -const keepComp = (props) => { - return ; -}; - -keepComp({}); diff --git a/packages/core/client/src/schema-component/common/infinite-scroll/style.tsx b/packages/core/client/src/schema-component/common/infinite-scroll/style.tsx new file mode 100644 index 000000000..b8da5ab64 --- /dev/null +++ b/packages/core/client/src/schema-component/common/infinite-scroll/style.tsx @@ -0,0 +1,21 @@ +import { createStyles } from 'antd-style'; + +const useStyles = createStyles(({ token, css }) => { + return { + infiniteScroll: css` + color: ${token.colorTextDescription}; + padding: 18px; + display: flex; + justify-content: center; + align-items: center; + font-size: ${token.fontSize}; + + &-failed-text { + display: inline-block; + margin-right: 8px; + } + `, + }; +}); + +export default useStyles; diff --git a/packages/core/utils/src/client.ts b/packages/core/utils/src/client.ts index 2cb72b915..e8359b0de 100644 --- a/packages/core/utils/src/client.ts +++ b/packages/core/utils/src/client.ts @@ -19,3 +19,4 @@ export * from './uid'; export * from './url'; export { dayjs, lodash }; export * from './parseHTML'; +export * from './dom'; diff --git a/packages/core/utils/src/dom.ts b/packages/core/utils/src/dom.ts new file mode 100644 index 000000000..d9afd7635 --- /dev/null +++ b/packages/core/utils/src/dom.ts @@ -0,0 +1,35 @@ +export const canUseDom = !!( + typeof window !== 'undefined' && + typeof document !== 'undefined' && + window.document && + window.document.createElement +); + +type ScrollElement = HTMLElement | Window; + +const defaultRoot = canUseDom ? window : undefined; + +const overflowStylePatterns = ['scroll', 'auto', 'overlay']; + +function isElement(node: Element) { + const ELEMENT_NODE_TYPE = 1; + return node.nodeType === ELEMENT_NODE_TYPE; +} +export function getScrollParent( + el: Element, + root: ScrollElement | null | undefined = defaultRoot, +): Window | Element | null | undefined { + let node = el; + + while (node && node !== root && isElement(node)) { + if (node === document.body) { + return root; + } + const { overflowY } = window.getComputedStyle(node); + if (overflowStylePatterns.includes(overflowY) && node.scrollHeight > node.clientHeight) { + return node; + } + node = node.parentNode as Element; + } + return root; +} diff --git a/packages/plugins/@hera/plugin-homepage/src/client/Home.tsx b/packages/plugins/@hera/plugin-homepage/src/client/Home.tsx index 2853ce48b..7dc62c863 100644 --- a/packages/plugins/@hera/plugin-homepage/src/client/Home.tsx +++ b/packages/plugins/@hera/plugin-homepage/src/client/Home.tsx @@ -12,6 +12,9 @@ export const HomePage: React.FC<{}> = () => { 'appends[]': 'pictures', }, }); + + console.log('%c Line:10 🥐 data', 'font-size:18px;color:#4fff4B;background:#e41a6a', data); + if (loading) { return render(); } diff --git a/packages/plugins/@hera/plugin-sancongtou/package.json b/packages/plugins/@hera/plugin-sancongtou/package.json index e7dee2328..80e406891 100644 --- a/packages/plugins/@hera/plugin-sancongtou/package.json +++ b/packages/plugins/@hera/plugin-sancongtou/package.json @@ -6,10 +6,12 @@ "main": "dist/server/index.js", "dependencies": { "@ant-design/icons": "^5.3.6", - "antd-mobile": "^5.35.0" + "antd-mobile": "^5.35.0", + "react-router-dom": "^6.23.1" }, "peerDependencies": { "@tachybase/client": "0.x", + "@tachybase/plugin-mobile-client": "0.x", "@tachybase/server": "0.x", "@tachybase/test": "0.x" }, diff --git a/packages/plugins/@hera/plugin-sancongtou/src/client/components/Test.tsx b/packages/plugins/@hera/plugin-sancongtou/src/client/components/Test.tsx deleted file mode 100644 index 5edf2a3a7..000000000 --- a/packages/plugins/@hera/plugin-sancongtou/src/client/components/Test.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { Button } from 'antd-mobile'; -import React from 'react'; - -export const TestComponent = () => { - const handleClick = () => {}; - return ( - - ); -}; diff --git a/packages/plugins/@hera/plugin-sancongtou/src/client/components/ShareProduct.tsx b/packages/plugins/@hera/plugin-sancongtou/src/client/custom-components/ShareProduct.tsx similarity index 83% rename from packages/plugins/@hera/plugin-sancongtou/src/client/components/ShareProduct.tsx rename to packages/plugins/@hera/plugin-sancongtou/src/client/custom-components/ShareProduct.tsx index c06529116..32d9f2e38 100644 --- a/packages/plugins/@hera/plugin-sancongtou/src/client/components/ShareProduct.tsx +++ b/packages/plugins/@hera/plugin-sancongtou/src/client/custom-components/ShareProduct.tsx @@ -1,21 +1,13 @@ -import { Modal, Toast, Image, Button } from 'antd-mobile'; import { ShareAltOutlined } from '@ant-design/icons'; +import { CustomComponentType } from '@hera/plugin-core/client'; +import { Button, Image, Modal, Toast } from 'antd-mobile'; import React from 'react'; import downloadImage from '../assets/download.svg'; -import { CustomComponentType } from '@hera/plugin-core/client'; +import './style.less'; export const ShareProduct = () => { return ( - @@ -26,7 +18,8 @@ ShareProduct.displayName = 'ShareProduct'; ShareProduct.__componentType = CustomComponentType.CUSTOM_FORM_ITEM; ShareProduct.__componentLabel = '移动端-三聪头-分享产品'; -const showModal = () => { +const showModal = (event) => { + event.stopPropagation(); Modal.show({ bodyStyle: { background: 'transparent', diff --git a/packages/plugins/@hera/plugin-sancongtou/src/client/custom-components/ShowDetail.tsx b/packages/plugins/@hera/plugin-sancongtou/src/client/custom-components/ShowDetail.tsx new file mode 100644 index 000000000..0d99e33e5 --- /dev/null +++ b/packages/plugins/@hera/plugin-sancongtou/src/client/custom-components/ShowDetail.tsx @@ -0,0 +1,23 @@ +import { CustomComponentType } from '@hera/plugin-core/client'; +import React from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import './style.less'; +import { getPathProductDetail } from '../utils/path'; + +// @deprecated +export const ShowDetail = () => { + // const navigate = useNavigate(); + // const handleClick = () => { + // navigate('/mobile/'); + // }; + return ( + + 显示详情 + + ); + Ø; +}; + +ShowDetail.displayName = 'ShowDetail'; +ShowDetail.__componentType = CustomComponentType.CUSTOM_FORM_ITEM; +ShowDetail.__componentLabel = '移动端-三聪头-显示详情'; diff --git a/packages/plugins/@hera/plugin-sancongtou/src/client/custom-components/style.less b/packages/plugins/@hera/plugin-sancongtou/src/client/custom-components/style.less new file mode 100644 index 000000000..2685473d0 --- /dev/null +++ b/packages/plugins/@hera/plugin-sancongtou/src/client/custom-components/style.less @@ -0,0 +1,11 @@ +.m-detail { + /* background-color: red; */ + display: flex; + flex-direction: row; + justify-content: flex-end; +} + +.m-share { + align-self: 'center'; + font-size: 16; +} \ No newline at end of file diff --git a/packages/plugins/@hera/plugin-sancongtou/src/client/index.tsx b/packages/plugins/@hera/plugin-sancongtou/src/client/index.tsx index 8da09673c..a508b369c 100644 --- a/packages/plugins/@hera/plugin-sancongtou/src/client/index.tsx +++ b/packages/plugins/@hera/plugin-sancongtou/src/client/index.tsx @@ -1,10 +1,13 @@ import { Plugin } from '@tachybase/client'; -import { ProductDetail } from './pages/ProductDetail'; -import { ShareProduct } from './components/ShareProduct'; +// import { ProductDetail } from './pages/ProductDetail.1'; +import { ShareProduct } from './custom-components/ShareProduct'; +import { ShowDetail } from './custom-components/ShowDetail'; +import { CardDetailSC, SCCardDetail } from './pages/CardDetail.schema'; +import { getPathProductDetail } from './utils/path'; export class PluginSancongtouClient extends Plugin { async afterAdd() { - // await this.app.pm.add() + await this.app.pm.add(SCCardDetail); } async beforeLoad() {} @@ -12,10 +15,18 @@ export class PluginSancongtouClient extends Plugin { // You can get and modify the app instance here async load() { this.addRoutes(); + this.app.addComponents({ + // 自定义字段组件 + // GridCard: { + // ...GridCard, + // // @ts-ignore + // // Decorator: GridCardDe, + // }, + ShowDetail, ShareProduct, }); - console.log(this.app); + // console.log(this.app); // this.app.addComponents({}) // this.app.addScopes({}) // this.app.addProvider() @@ -23,9 +34,18 @@ export class PluginSancongtouClient extends Plugin { } addRoutes() { - this.app.router.add('sancongtou/detail', { - path: '/mobile/products/detail', - Component: ProductDetail, + // this.app.router.add('sancongtou/detail', { + // path: '/mobile/products/detail', + // Component: ProductDetail, + // }); + this.app.router.add('mobile.productDetail', { + path: getPathProductDetail({ + name: ':name', + dataSource: ':dataSource', + collection: ':collection', + id: ':id', + }), + Component: CardDetailSC, }); } } diff --git a/packages/plugins/@hera/plugin-sancongtou/src/client/pages/CardDetail.provider.tsx b/packages/plugins/@hera/plugin-sancongtou/src/client/pages/CardDetail.provider.tsx new file mode 100644 index 000000000..7a509dd48 --- /dev/null +++ b/packages/plugins/@hera/plugin-sancongtou/src/client/pages/CardDetail.provider.tsx @@ -0,0 +1,27 @@ +import { DataBlockProvider, useCollectionManager } from '@tachybase/client'; +import React from 'react'; +import { useParams } from 'react-router-dom'; +// import { ProductDetail } from './ProductDetail'; + +// THINK: provider 主要作用是定位于, 连接数据库, 不做其他的逻辑 +export const CardDetailProvider = (props) => { + const params = useParams(); + const { collection: collectionName } = params; + const cm = useCollectionManager(); + const collection = cm.getCollection(collectionName); + const primaryKey = collection.getPrimaryKey(); + + return ( + + {props.children} + + ); +}; diff --git a/packages/plugins/@hera/plugin-sancongtou/src/client/pages/CardDetail.schema.tsx b/packages/plugins/@hera/plugin-sancongtou/src/client/pages/CardDetail.schema.tsx new file mode 100644 index 000000000..d3b31f1df --- /dev/null +++ b/packages/plugins/@hera/plugin-sancongtou/src/client/pages/CardDetail.schema.tsx @@ -0,0 +1,78 @@ +import { Plugin, SchemaComponent, useCollectionManager } from '@tachybase/client'; +import { MPage } from '@tachybase/plugin-mobile-client/client'; +import React from 'react'; +import { CardDetailProvider } from './CardDetail.provider'; +import { CardDetailView } from './CardDetail.view'; +import { useScopeCardDetail } from './CardDetail.scope'; +import { useParams } from 'react-router-dom'; + +// THINK: plugin的注册,应该每个组件自己管自己的, 提高内聚性, 就近原则; +// 或者单独写一个轻量的给组件用的 plugin, 用于注册 +// 这里暂时用 SC 的前缀 表明这一用途, schema component +// 对于那些零散的组件, 都在共同的文件夹根目录声明 +// 尝试, 注册部分直接放在 schema 文件里, 会不会更好一些? +// 那么把转化部分也放进来, 会不会更好? 尝试一下 +// 注册部分和转化部分, 都是样板代码, 心智模式无需关注, 只需关注 schema 的声明实现 +// 声明部分, 是数据和视图的桥梁, 数据通过 context, provider 提供. 在 component 里消费 + +// 声明部分 +// THINK: 代码逻辑部分写在这里, 会明显导致卡顿甚至卡死, 需要思考为什么? 目前还是将 provider 外移 +const useSchema = () => { + // const params = useParams(); + // const { collection: collectionName } = params; + // const cm = useCollectionManager(); + // const collection = cm.getCollection(collectionName); + // const primaryKey = collection.getPrimaryKey(); + + return { + type: 'void', + 'x-designer': 'MPage.Designer', + 'x-component': 'MPage', + 'x-component-props': {}, + properties: { + DetailView: { + type: 'void', + // 'x-decorator-props': { + // dataSource: 'main', + // collection: collection, + // action: 'get', + // params: { + // appends: ['main_images'], + // filterByTk: params[primaryKey], + // }, + // }, + 'x-decorator': 'page.provider.CardDetailProvider', + 'x-use-component-props': 'useScopeCardDetail', + 'x-component': 'page.view.CardDetailView', + }, + }, + }; +}; + +// 注册部分 +export class SCCardDetail extends Plugin { + async load() { + this.app.addComponents({ + 'page.provider.CardDetailProvider': CardDetailProvider, + 'page.view.CardDetailView': CardDetailView, + }); + // THINK: 这里不能声明成形如: page.scope.useScopeCardDetail 的形式, 底层没有做处理. + // 也没想好这里的标准应该是什么? 也许需要归类? + this.app.addScopes({ + useScopeCardDetail: useScopeCardDetail, + }); + } +} + +// 转化部分 +export const CardDetailSC = () => { + const schema = useSchema(); + return ( + + ); +}; diff --git a/packages/plugins/@hera/plugin-sancongtou/src/client/pages/CardDetail.scope.ts b/packages/plugins/@hera/plugin-sancongtou/src/client/pages/CardDetail.scope.ts new file mode 100644 index 000000000..260d4a6f9 --- /dev/null +++ b/packages/plugins/@hera/plugin-sancongtou/src/client/pages/CardDetail.scope.ts @@ -0,0 +1,11 @@ +import { useCollectionRecordData } from '@tachybase/client'; + +export const useScopeCardDetail = () => { + const data = useCollectionRecordData(); + const { ['main_images']: mainImages = [] } = data || {}; + const images = mainImages.map((image) => image.url); + + return { + images, + }; +}; diff --git a/packages/plugins/@hera/plugin-sancongtou/src/client/pages/CardDetail.view.tsx b/packages/plugins/@hera/plugin-sancongtou/src/client/pages/CardDetail.view.tsx new file mode 100644 index 000000000..2af94c375 --- /dev/null +++ b/packages/plugins/@hera/plugin-sancongtou/src/client/pages/CardDetail.view.tsx @@ -0,0 +1,37 @@ +import { withDynamicSchemaProps } from '@tachybase/client'; +import { Image, NavBar, Skeleton } from 'antd-mobile'; +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import './style.less'; + +// TODO: 需要提升图片加载速度, 现在是数据库请求然后用 url 去云端拿图片,导致体验有延迟 +export const CardDetailView = withDynamicSchemaProps( + (props) => { + const { images = [] } = props; + const navigate = useNavigate(); + const onBack = () => { + navigate(-1); + }; + return ( +
+ + 产品详情 + +
+ + +
+
+ ); + }, + { displayName: 'CardDetailView' }, +); + +const ImageWithSkeletonCustom = (props) => { + const { imageSrc } = props; + if (imageSrc) { + return ; + } else { + return ; + } +}; diff --git a/packages/plugins/@hera/plugin-sancongtou/src/client/pages/ProductDetail.tsx b/packages/plugins/@hera/plugin-sancongtou/src/client/pages/ProductDetail.tsx deleted file mode 100644 index 2f7ad61f9..000000000 --- a/packages/plugins/@hera/plugin-sancongtou/src/client/pages/ProductDetail.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { NavBar, Image } from 'antd-mobile'; -import React from 'react'; -import detailHeaderImpage from '../assets/detail_lt_header.png'; -import detailContentImage from '../assets/detail_lt_content.png'; -import { css } from '@tachybase/client'; - -export const ProductDetail = () => { - const onBack = () => { - const history = window.history; - history.back(); - }; - - return ( - <> - - 产品详情 - -
- - -
- - ); -}; diff --git a/packages/plugins/@hera/plugin-sancongtou/src/client/pages/style.less b/packages/plugins/@hera/plugin-sancongtou/src/client/pages/style.less new file mode 100644 index 000000000..a8234d6bc --- /dev/null +++ b/packages/plugins/@hera/plugin-sancongtou/src/client/pages/style.less @@ -0,0 +1,19 @@ +.m-product-container { + display: flex; + flex-direction: column; + height: 100vh; + background-color: #f3f3f3; +} +.m-share-image { + flex: 1; + width: 100%; + overflow: auto; + clip-path: inset(0 10px); +} + +.customSkeleton { + --width: 100%; + --height: 50%; + --border-radius: 8px; +} + diff --git a/packages/plugins/@hera/plugin-sancongtou/src/client/utils/path.ts b/packages/plugins/@hera/plugin-sancongtou/src/client/utils/path.ts new file mode 100644 index 000000000..881888f5d --- /dev/null +++ b/packages/plugins/@hera/plugin-sancongtou/src/client/utils/path.ts @@ -0,0 +1,10 @@ +interface ProductDetailParams { + name?: string; + dataSource?: string; + collection: string; + id?: string; +} +export function getPathProductDetail(params: ProductDetailParams) { + const { name = 'detail', dataSource = 'main', collection, id } = params; + return `/mobile/${name}/${dataSource}/${collection}/${id}`; +} diff --git a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/image-search/data.ts b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/image-search/data.ts deleted file mode 100644 index 8512ffbf6..000000000 --- a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/image-search/data.ts +++ /dev/null @@ -1,22 +0,0 @@ -export const images = [ - { - label: '全部商品', - imageUrl: 'https://courier.dzkbdd.cn/static/jx-h5/assets/operator_all_select-884b60ce.png', - }, - { - label: '中国电信', - imageUrl: 'https://courier.dzkbdd.cn/static/jx-h5/assets/operator_dx-7df8411f.png', - }, - { - label: '中国移动', - imageUrl: 'https://courier.dzkbdd.cn/static/jx-h5/assets/operator_yd-7b994595.png', - }, - { - label: '中国联通', - imageUrl: 'https://courier.dzkbdd.cn/static/jx-h5/assets/operator_lt-d9888233.png', - }, - { - label: '中国广电', - imageUrl: 'https://courier.dzkbdd.cn/static/jx-h5/assets/operator_gd-cc48f3e6.png', - }, -]; diff --git a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/image-search/hooks/index.ts b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/image-search/hooks/index.ts index e53fa9d3c..63c1012f9 100644 --- a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/image-search/hooks/index.ts +++ b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/image-search/hooks/index.ts @@ -1,2 +1 @@ -export * from './useAction.ImageSearchItemView'; export * from './useSelect'; diff --git a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/image-search/hooks/useAction.ImageSearchItemView.ts b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/image-search/hooks/useAction.ImageSearchItemView.ts deleted file mode 100644 index 716b6b1e6..000000000 --- a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/image-search/hooks/useAction.ImageSearchItemView.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { useCollection, useDesigner } from '@tachybase/client'; -import { useFieldSchema } from '@tachybase/schema'; -import React from 'react'; -import { useTranslation } from '../../../../../locale'; - -export function useActionImageSearchItemView(props) { - const { list, onSelected, valueKey: _valueKey, labelKey: _labelKey, filterKey } = props; - const fieldSchema = useFieldSchema(); - const collection = useCollection(); - const { t } = useTranslation(); - const collectionField = React.useMemo( - () => collection?.getField(fieldSchema['fieldName'] as any), - [collection, fieldSchema['fieldName']], - ); - 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], filterKey); - }; - - const fieldNames = { - title: labelKey || valueKey, - key: valueKey, - }; - - const itemsData = (list || []).map((item) => { - const { type, ['image_show']: imageObj, [fieldNames.title]: label, [fieldNames.key]: key } = item; - - // TODO: 需要更好的处理url方式 - const origin = location?.origin || ''; - const sourceUrl = imageObj?.[0]?.url; - let imageUrl = sourceUrl ?? ''; - if (!imageUrl.includes('http')) { - imageUrl = `${origin}${sourceUrl}`; - } - - if (type === 'all') { - return { - label: t('AllProducts'), - key: 'all', - imageUrl: imageUrl, - }; - } - - return { - label, - key, - imageUrl: imageUrl, - }; - }); - - return { - collectionField, - Designer, - items: itemsData, - onSelect, - }; -} diff --git a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/image-search/index.ts b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/image-search/index.ts index 11d893461..0fa58485c 100644 --- a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/image-search/index.ts +++ b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/image-search/index.ts @@ -3,6 +3,5 @@ export * from './ImageSearch.initializer'; export * from './ImageSearch.provider'; export * from './ImageSearch.schema'; export * from './ImageSearch.view'; -export * from './data'; export * from './hooks'; export * from './search-item'; diff --git a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/image-search/search-item/ImageSearchItem.view.tsx b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/image-search/search-item/ImageSearchItem.view.tsx index cfe780825..94f70d8e8 100644 --- a/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/image-search/search-item/ImageSearchItem.view.tsx +++ b/packages/plugins/@tachybase/plugin-mobile-client/src/client/core/schema/components/image-search/search-item/ImageSearchItem.view.tsx @@ -1,11 +1,13 @@ -import { SortableItem, css, withDynamicSchemaProps } from '@tachybase/client'; +import { SortableItem, css, useCollection, useDesigner, withDynamicSchemaProps } from '@tachybase/client'; +import { useFieldSchema } from '@tachybase/schema'; import { Image, JumboTabs } from 'antd-mobile'; import React from 'react'; -import { useActionImageSearchItemView } from '../hooks/useAction.ImageSearchItemView'; +import { useTranslation } from '../../../../../locale'; +// import { useActionImageSearchItemView } from '../hooks/useAction.ImageSearchItemView'; export const ImageSearchItemView = withDynamicSchemaProps( (props) => { - const { collectionField, Designer, items, onSelect } = useActionImageSearchItemView(props); + const { collectionField, Designer, items, onSelect } = useAction(props); if (!collectionField) { return null; @@ -25,6 +27,63 @@ export const ImageSearchItemView = withDynamicSchemaProps( { displayName: 'ImageSearchItemView' }, ); +function useAction(props) { + const { list, onSelected, valueKey: _valueKey, labelKey: _labelKey, filterKey } = props; + const fieldSchema = useFieldSchema(); + const collection = useCollection(); + const { t } = useTranslation(); + const collectionField = React.useMemo( + () => collection?.getField(fieldSchema['fieldName'] as any), + [collection, fieldSchema['fieldName']], + ); + 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], filterKey); + }; + + const fieldNames = { + title: labelKey || valueKey, + key: valueKey, + }; + + const itemsData = (list || []).map((item) => { + const { type, ['image_show']: imageObj, [fieldNames.title]: label, [fieldNames.key]: key } = item; + + // TODO: 需要更好的处理url方式 + const origin = location?.origin || ''; + const sourceUrl = imageObj?.[0]?.url; + let imageUrl = sourceUrl ?? ''; + if (!imageUrl.includes('http')) { + imageUrl = `${origin}${sourceUrl}`; + } + + if (type === 'all') { + return { + label: t('AllProducts'), + key: 'all', + imageUrl: imageUrl, + }; + } + + return { + label, + key, + imageUrl: imageUrl, + }; + }); + + return { + collectionField, + Designer, + items: itemsData, + onSelect, + }; +} + const ImageDescription = (props) => { const { srcUrl, label } = props; return ( diff --git a/packages/plugins/@tachybase/plugin-mobile-client/src/client/index.tsx b/packages/plugins/@tachybase/plugin-mobile-client/src/client/index.tsx index f012eab95..6ea88c78b 100644 --- a/packages/plugins/@tachybase/plugin-mobile-client/src/client/index.tsx +++ b/packages/plugins/@tachybase/plugin-mobile-client/src/client/index.tsx @@ -9,6 +9,8 @@ import { mBlockInitializers, mBlockInitializers_deprecated, MobileFormItemInitializers, + SwiperFieldSettings, + SwiperPage, TabSearchFieldSchemaInitializer, TabSearchItemFieldSettings, } from './core/schema'; @@ -27,6 +29,7 @@ export class MobileClientPlugin extends Plugin { this.app.schemaInitializerManager.add(ImageSearchConfigureFields); this.app.schemaInitializerManager.add(TabSearchFieldSchemaInitializer); + this.app.schemaSettingsManager.add(SwiperFieldSettings); this.app.schemaSettingsManager.add(ImageSearchItemFieldSettings); this.app.schemaSettingsManager.add(TabSearchItemFieldSettings); this.app.schemaInitializerManager.add(MobileFormItemInitializers); @@ -82,7 +85,7 @@ export class MobileClientPlugin extends Plugin { }); this.app.router.add('mobile.swiper.page', { path: '/mobile/:name/image/:collection/:field/:fieldParams', - Component: 'SwiperPage', + Component: SwiperPage, }); } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e6acfdac7..275dd79d4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1778,6 +1778,9 @@ importers: '@tachybase/client': specifier: 0.x version: link:../../../core/client + '@tachybase/plugin-mobile-client': + specifier: 0.x + version: link:../../@tachybase/plugin-mobile-client '@tachybase/server': specifier: 0.x version: link:../../../core/server @@ -1787,6 +1790,9 @@ importers: antd-mobile: specifier: ^5.35.0 version: 5.35.0(react-dom@18.2.0)(react@18.2.0) + react-router-dom: + specifier: ^6.23.1 + version: 6.23.1(react-dom@18.2.0)(react@18.2.0) packages/plugins/@nocobase/plugin-acl: dependencies: @@ -10803,6 +10809,11 @@ packages: engines: {node: '>=14.0.0'} dev: true + /@remix-run/router@1.16.1: + resolution: {integrity: sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==} + engines: {node: '>=14.0.0'} + dev: false + /@restart/hooks@0.4.15(react@18.2.0): resolution: {integrity: sha512-cZFXYTxbpzYcieq/mBwSyXgqnGMHoBVh3J7MU0CCoIB4NRZxV9/TuwTBAaLMqpNhC3zTPMCgkQ5Ey07L02Xmcw==} peerDependencies: @@ -26612,6 +26623,19 @@ packages: react-router: 6.22.3(react@18.2.0) dev: true + /react-router-dom@6.23.1(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + dependencies: + '@remix-run/router': 1.16.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-router: 6.23.1(react@18.2.0) + dev: false + /react-router-dom@6.3.0(react-dom@18.1.0)(react@18.1.0): resolution: {integrity: sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==} peerDependencies: @@ -26663,6 +26687,16 @@ packages: react: 18.2.0 dev: true + /react-router@6.23.1(react@18.2.0): + resolution: {integrity: sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + dependencies: + '@remix-run/router': 1.16.1 + react: 18.2.0 + dev: false + /react-router@6.3.0(react@18.1.0): resolution: {integrity: sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==} peerDependencies: