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 ? (
-
+
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;