diff --git a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchAction.tsx b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchAction.tsx index 0b5b5f8994..889dda2bc0 100644 --- a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchAction.tsx +++ b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchAction.tsx @@ -37,7 +37,7 @@ function Button() { const { layout } = useContext(WorkbenchBlockContext); const { styles, cx } = useStyles(); return layout === WorkbenchLayout.Grid ? ( -
+
} />
{fieldSchema.title}
diff --git a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchBlock.tsx b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchBlock.tsx index f30474e5f1..5eb0cdadbf 100644 --- a/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchBlock.tsx +++ b/packages/plugins/@nocobase/plugin-block-workbench/src/client/WorkbenchBlock.tsx @@ -17,10 +17,12 @@ import { withDynamicSchemaProps, Icon, useBlockHeight, + useOpenModeContext, + useBlockHeightProps, } from '@nocobase/client'; import { css } from '@emotion/css'; import { Space, List, Avatar, theme } from 'antd'; -import React, { createContext, useState, useEffect } from 'react'; +import React, { createContext, useEffect, useState, useRef, useMemo, useLayoutEffect } from 'react'; import { WorkbenchLayout } from './workbenchBlockSettings'; const ConfigureActionsButton = observer( @@ -31,50 +33,127 @@ const ConfigureActionsButton = observer( }, { displayName: 'WorkbenchConfigureActionsButton' }, ); +function isMobile() { + return window.matchMedia('(max-width: 768px)').matches; +} + +const ResponsiveSpace = () => { + const fieldSchema = useFieldSchema(); + const isMobileMedia = isMobile(); + const { isMobile: underMobileCtx } = useOpenModeContext() || {}; + const { itemsPerRow = 4 } = fieldSchema.parent['x-decorator-props'] || {}; + const isUnderMobile = isMobileMedia || underMobileCtx; + const containerRef = useRef(null); // 引用容器 + const [containerWidth, setContainerWidth] = useState(0); // 容器宽度 + const gap = 8; + // 使用 ResizeObserver 动态获取容器宽度 + useEffect(() => { + const handleResize = () => { + if (containerRef.current) { + setContainerWidth(containerRef.current.offsetWidth); // 更新宽度 + } + }; + // 初始化 ResizeObserver + const resizeObserver = new ResizeObserver(handleResize); + + // 监听容器宽度变化 + if (containerRef.current) { + resizeObserver.observe(containerRef.current); + handleResize(); // 初始化时获取一次宽度 + } + + return () => { + if (containerRef.current) { + resizeObserver.unobserve(containerRef.current); + } + }; + }, []); + + useLayoutEffect(() => { + if (!containerRef.current) return; + + const observer = new ResizeObserver((entries) => { + for (const entry of entries) { + setContainerWidth(entry.contentRect.width); // 更新宽度 + } + }); + + observer.observe(containerRef.current); + + return () => { + observer.unobserve(containerRef.current); + }; + }, []); + + // 计算每个元素的宽度 + const itemWidth = useMemo(() => { + if (isUnderMobile) { + const totalGapWidth = gap * itemsPerRow; + const availableWidth = containerWidth - totalGapWidth; + return availableWidth / itemsPerRow; + } + return 70; + }, [itemsPerRow, gap, containerWidth]); + + // 计算 Avatar 的宽度 + const avatarSize = useMemo(() => { + return isUnderMobile ? (Math.floor(itemWidth * 0.8) > 70 ? 60 : Math.floor(itemWidth * 0.8)) : 54; // Avatar 大小为 item 宽度的 60% + }, [itemWidth, itemsPerRow, containerWidth]); + + return ( +
+ + {fieldSchema.mapProperties((s, key) => ( +
+ +
+ ))} +
+
+ ); +}; const InternalIcons = () => { const fieldSchema = useFieldSchema(); const { designable } = useDesignable(); const { layout = WorkbenchLayout.Grid } = fieldSchema.parent['x-component-props'] || {}; - const [gap, setGap] = useState(8); // 初始 gap 值 - - useEffect(() => { - const calculateGap = () => { - const container = document.getElementsByClassName('mobile-page-content')[0] as any; - if (container) { - const containerWidth = container.offsetWidth - 48; - const itemWidth = 100; // 每个 item 的宽度 - const itemsPerRow = Math.floor(containerWidth / itemWidth); // 每行能容纳的 item 数 - // 计算实际需要的 gap 值 - const totalItemWidth = itemsPerRow * itemWidth; - const totalAvailableWidth = containerWidth; - const totalGapsWidth = totalAvailableWidth - totalItemWidth; - - if (totalGapsWidth > 0) { - setGap(totalGapsWidth / (itemsPerRow - 1)); - } else { - setGap(0); // 如果没有足够的空间,则设置 gap 为 0 - } - } - }; - - window.addEventListener('resize', calculateGap); - calculateGap(); // 初始化时计算 gap - - return () => { - window.removeEventListener('resize', calculateGap); - }; - }, [Object.keys(fieldSchema?.properties || {}).length]); - return (
{layout === WorkbenchLayout.Grid ? ( - - {fieldSchema.mapProperties((s, key) => ( - - ))} - + ) : ( {fieldSchema.mapProperties((s, key) => { @@ -91,6 +170,7 @@ const InternalIcons = () => { overflow: hidden; text-overflow: ellipsis; font-size: 14px; + margin: 0 0 0 0; } .ant-list-item-meta-title button { font-size: 14px; @@ -124,18 +204,26 @@ export const WorkbenchBlock: any = withDynamicSchemaProps( const targetHeight = useBlockHeight(); const { token } = theme.useToken(); const { designable } = useDesignable(); - + const { heightProps } = useBlockHeightProps() || {}; + const { titleHeight } = heightProps || {}; + const internalHeight = 2 * token.paddingLG + token.controlHeight + token.marginLG + titleHeight; return (
diff --git a/packages/plugins/@nocobase/plugin-block-workbench/src/client/workbenchBlockSettings.tsx b/packages/plugins/@nocobase/plugin-block-workbench/src/client/workbenchBlockSettings.tsx index 202500f412..887fe00fe9 100644 --- a/packages/plugins/@nocobase/plugin-block-workbench/src/client/workbenchBlockSettings.tsx +++ b/packages/plugins/@nocobase/plugin-block-workbench/src/client/workbenchBlockSettings.tsx @@ -12,6 +12,8 @@ import { SchemaSettingsSelectItem, useDesignable, SchemaSettingsBlockHeightItem, + SchemaSettingsModalItem, + useOpenModeContext, } from '@nocobase/client'; import { CustomSchemaSettingsBlockTitleItem } from './SchemaSettingsBlockTitleItem'; import React from 'react'; @@ -53,6 +55,49 @@ const ActionPanelLayout = () => { ); }; +export function ActionPanelItemsPerRow() { + const field = useField(); + const fieldSchema = useFieldSchema(); + const { dn } = useDesignable(); + const { t } = useTranslation(); + + return ( + { + const componentProps = fieldSchema['x-decorator-props'] || {}; + componentProps.itemsPerRow = itemsPerRow; + fieldSchema['x-decorator-props'] = componentProps; + field.decoratorProps.ItemsPerRow = itemsPerRow; + dn.emit('patch', { + schema: { + ['x-uid']: fieldSchema['x-uid'], + 'x-decorator-props': fieldSchema['x-decorator-props'], + }, + }); + dn.refresh(); + }} + /> + ); +} + export const workbenchBlockSettings = new SchemaSettings({ name: 'blockSettings:workbench', items: [ @@ -68,6 +113,15 @@ export const workbenchBlockSettings = new SchemaSettings({ name: 'layout', Component: ActionPanelLayout, }, + { + name: 'itemsPerRow', + Component: ActionPanelItemsPerRow, + useVisible() { + const { isMobile } = useOpenModeContext() || {}; + const fieldSchema = useFieldSchema(); + return isMobile && fieldSchema?.['x-component-props']?.layout !== WorkbenchLayout.List; + }, + }, { type: 'remove', name: 'remove', diff --git a/packages/plugins/@nocobase/plugin-block-workbench/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-block-workbench/src/locale/zh-CN.json index 955506753e..e3d734c6a9 100644 --- a/packages/plugins/@nocobase/plugin-block-workbench/src/locale/zh-CN.json +++ b/packages/plugins/@nocobase/plugin-block-workbench/src/locale/zh-CN.json @@ -11,5 +11,7 @@ "Grid": "栅格", "List": "列表", "Add popup": "添加弹窗", - "Add custom request":"添加自定义请求" + "Add custom request":"添加自定义请求", + "At least 1, up to 6": "最多6个,最少一个", + "Items per row":"每行显示个数" } diff --git a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/styles.ts b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/styles.ts index cf4fb22184..d03ac183ed 100644 --- a/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/styles.ts +++ b/packages/plugins/@nocobase/plugin-mobile/src/client/mobile/styles.ts @@ -46,11 +46,11 @@ export const useStyles = createStyles(({ token, css }) => { .nb-action-panel { padding-top: 10px; } - .nb-action-panel .ant-avatar-circle { - width: 48px !important; - height: 48px !important; - line-height: 48px !important; - } + // .nb-action-panel .ant-avatar-circle { + // width: 48px !important; + // height: 48px !important; + // line-height: 48px !important; + // } .nb-chart-block .ant-card .ant-card-body { padding-bottom: 0px; padding-top: 0px;