mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 13:39:24 +08:00
Merge branch 'main' into next
This commit is contained in:
commit
739fecbbe7
@ -52,8 +52,14 @@ module.exports = (cli) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const pkg = require('../../package.json');
|
const pkg = require('../../package.json');
|
||||||
|
let distTag = 'latest';
|
||||||
|
if (pkg.version.includes('alpha')) {
|
||||||
|
distTag = 'alpha';
|
||||||
|
} else if (pkg.version.includes('beta')) {
|
||||||
|
distTag = 'beta';
|
||||||
|
}
|
||||||
// get latest version
|
// get latest version
|
||||||
const { stdout } = await run('npm', ['info', options.next ? '@nocobase/cli@next' : '@nocobase/cli', 'version'], {
|
const { stdout } = await run('npm', ['info', `@nocobase/cli@${distTag}`, 'version'], {
|
||||||
stdio: 'pipe',
|
stdio: 'pipe',
|
||||||
});
|
});
|
||||||
if (pkg.version === stdout) {
|
if (pkg.version === stdout) {
|
||||||
@ -62,13 +68,7 @@ module.exports = (cli) => {
|
|||||||
await rmAppDir();
|
await rmAppDir();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const currentY = 1 * pkg.version.split('.')[1];
|
await run('yarn', ['add', `@nocobase/cli@${distTag}`, `@nocobase/devtools@${distTag}`, '-W']);
|
||||||
const latestY = 1 * stdout.split('.')[1];
|
|
||||||
if (options.next || currentY > latestY) {
|
|
||||||
await run('yarn', ['add', '@nocobase/cli@next', '@nocobase/devtools@next', '-W']);
|
|
||||||
} else {
|
|
||||||
await run('yarn', ['add', '@nocobase/cli', '@nocobase/devtools', '-W']);
|
|
||||||
}
|
|
||||||
await run('yarn', ['install']);
|
await run('yarn', ['install']);
|
||||||
await downloadPro();
|
await downloadPro();
|
||||||
await runAppCommand('upgrade');
|
await runAppCommand('upgrade');
|
||||||
|
@ -34,6 +34,7 @@ test.describe('where to open a popup and what can be added to it', () => {
|
|||||||
// add blocks
|
// add blocks
|
||||||
await page.getByLabel('schema-initializer-Grid-popup:addNew:addBlock-general').hover();
|
await page.getByLabel('schema-initializer-Grid-popup:addNew:addBlock-general').hover();
|
||||||
await page.getByText('Markdown').click();
|
await page.getByText('Markdown').click();
|
||||||
|
await page.waitForTimeout(500);
|
||||||
await page.getByLabel('schema-initializer-Grid-popup:addNew:addBlock-general').hover();
|
await page.getByLabel('schema-initializer-Grid-popup:addNew:addBlock-general').hover();
|
||||||
await page.getByText('Form').hover();
|
await page.getByText('Form').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Current collection' }).click();
|
await page.getByRole('menuitem', { name: 'Current collection' }).click();
|
||||||
|
@ -18,24 +18,12 @@ import { NocoBaseRecursionField } from '../../../formily/NocoBaseRecursionField'
|
|||||||
import { useAssociationFieldContext, useInsertSchema } from './hooks';
|
import { useAssociationFieldContext, useInsertSchema } from './hooks';
|
||||||
import schema from './schema';
|
import schema from './schema';
|
||||||
|
|
||||||
const InternalNesterCss = css`
|
|
||||||
& .ant-formily-item-layout-vertical {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.ant-card-body {
|
|
||||||
padding: 15px 20px 5px;
|
|
||||||
}
|
|
||||||
.ant-divider-horizontal {
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const InternalNesterCardCss = css`
|
const InternalNesterCardCss = css`
|
||||||
.ant-card-bordered {
|
.ant-card-bordered {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
.ant-card-body {
|
.ant-card-body {
|
||||||
padding: 0px 20px 20px 0px;
|
padding: 0px 20px 0px 0px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -56,6 +44,20 @@ export const InternalNester = observer(
|
|||||||
labelWrap = true,
|
labelWrap = true,
|
||||||
} = fieldSchema?.['x-component-props'] || {};
|
} = fieldSchema?.['x-component-props'] || {};
|
||||||
|
|
||||||
|
const InternalNesterCss = css`
|
||||||
|
margin-top: 0.4em;
|
||||||
|
|
||||||
|
& .ant-formily-item-layout-vertical {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.ant-card-body {
|
||||||
|
padding: ${token.padding}px ${token.paddingLG}px;
|
||||||
|
}
|
||||||
|
.ant-divider-horizontal {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
insertNester(schema.Nester);
|
insertNester(schema.Nester);
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -11,8 +11,19 @@ import { genStyleHook } from '../__builtins__';
|
|||||||
|
|
||||||
const useStyles = genStyleHook('nb-grid-card', (token) => {
|
const useStyles = genStyleHook('nb-grid-card', (token) => {
|
||||||
const { componentCls } = token;
|
const { componentCls } = token;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
[componentCls]: {
|
[componentCls]: {
|
||||||
|
'.nb-action-bar': {
|
||||||
|
borderRadius: token.borderRadiusBlock,
|
||||||
|
marginBottom: `${token.marginBlock / 2}px !important`,
|
||||||
|
},
|
||||||
|
|
||||||
|
'.ant-list-pagination': {
|
||||||
|
borderRadius: token.borderRadiusBlock,
|
||||||
|
marginTop: `${token.marginBlock / 2}px !important`,
|
||||||
|
},
|
||||||
|
|
||||||
'& > .nb-block-item': {
|
'& > .nb-block-item': {
|
||||||
marginBottom: token.marginLG,
|
marginBottom: token.marginLG,
|
||||||
'& > .nb-action-bar:has(:first-child:not(:empty))': {
|
'& > .nb-action-bar:has(:first-child:not(:empty))': {
|
||||||
|
@ -28,15 +28,10 @@ const itemCss = css`
|
|||||||
const gridCardCss = css`
|
const gridCardCss = css`
|
||||||
height: 100%;
|
height: 100%;
|
||||||
> .ant-card-body {
|
> .ant-card-body {
|
||||||
padding: 24px 24px 0px;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
button {
|
|
||||||
margin-bottom: 0px !important;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.nb-action-bar {
|
.nb-action-bar {
|
||||||
padding: 5px 0;
|
padding-top: 5px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ import { getCardItemSchema } from '../../../block-provider';
|
|||||||
import { NocoBaseRecursionField } from '../../../formily/NocoBaseRecursionField';
|
import { NocoBaseRecursionField } from '../../../formily/NocoBaseRecursionField';
|
||||||
import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps';
|
import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps';
|
||||||
import { withSkeletonComponent } from '../../../hoc/withSkeletonComponent';
|
import { withSkeletonComponent } from '../../../hoc/withSkeletonComponent';
|
||||||
|
import { useToken } from '../../../style/useToken';
|
||||||
import { SortableItem } from '../../common';
|
import { SortableItem } from '../../common';
|
||||||
import { SchemaComponentOptions } from '../../core';
|
import { SchemaComponentOptions } from '../../core';
|
||||||
import { useDesigner, useProps } from '../../hooks';
|
import { useDesigner, useProps } from '../../hooks';
|
||||||
@ -141,6 +142,7 @@ const InternalGridCard = withSkeletonComponent(
|
|||||||
},
|
},
|
||||||
[fieldSchema.properties],
|
[fieldSchema.properties],
|
||||||
);
|
);
|
||||||
|
const { token } = useToken();
|
||||||
|
|
||||||
const onPaginationChange: PaginationProps['onChange'] = useCallback(
|
const onPaginationChange: PaginationProps['onChange'] = useCallback(
|
||||||
(page, pageSize) => {
|
(page, pageSize) => {
|
||||||
@ -207,7 +209,7 @@ const InternalGridCard = withSkeletonComponent(
|
|||||||
...columnCount,
|
...columnCount,
|
||||||
sm: columnCount.xs,
|
sm: columnCount.xs,
|
||||||
xl: columnCount.lg,
|
xl: columnCount.lg,
|
||||||
gutter: [rowGutter, rowGutter],
|
gutter: [token.marginBlock / 2, token.marginBlock / 2],
|
||||||
}}
|
}}
|
||||||
renderItem={(item, index) => {
|
renderItem={(item, index) => {
|
||||||
return (
|
return (
|
||||||
|
@ -400,7 +400,7 @@ export function Uploader({ rules, ...props }: UploadProps) {
|
|||||||
if (pendingFiles.length) {
|
if (pendingFiles.length) {
|
||||||
setUploadedList(valueList);
|
setUploadedList(valueList);
|
||||||
} else {
|
} else {
|
||||||
onChange?.([...value, ...valueList]);
|
onChange?.([...(value || []), ...valueList]);
|
||||||
setUploadedList([]);
|
setUploadedList([]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,13 +18,20 @@ import { WorkbenchLayout } from './workbenchBlockSettings';
|
|||||||
const useStyles = createStyles(({ token, css }) => ({
|
const useStyles = createStyles(({ token, css }) => ({
|
||||||
// 支持 css object 的写法
|
// 支持 css object 的写法
|
||||||
action: css`
|
action: css`
|
||||||
|
display: flex;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: 0;
|
border: 0;
|
||||||
height: auto;
|
height: auto;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
padding-top: 8px;
|
||||||
|
`,
|
||||||
|
avatar: css`
|
||||||
|
width: 4em;
|
||||||
`,
|
`,
|
||||||
title: css`
|
title: css`
|
||||||
margin-top: ${token.marginSM}px;
|
margin-top: ${token.marginSM}px;
|
||||||
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
`,
|
`,
|
||||||
@ -39,9 +46,9 @@ function Button() {
|
|||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const title = compile(fieldSchema.title);
|
const title = compile(fieldSchema.title);
|
||||||
return layout === WorkbenchLayout.Grid ? (
|
return layout === WorkbenchLayout.Grid ? (
|
||||||
<div title={title} style={{ width: '100%', overflow: 'hidden' }} className="nb-action-panel-container">
|
<div title={fieldSchema.title} className={cx(styles.avatar)}>
|
||||||
<Avatar style={{ backgroundColor }} size={54} icon={<Icon type={icon} />} />
|
<Avatar style={{ backgroundColor }} size={48} icon={<Icon type={icon} />} />
|
||||||
<div className={cx(styles.title)}>{title}</div>
|
<div className={cx(styles.title)}>{fieldSchema.title}</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<span>{title}</span>
|
<span>{title}</span>
|
||||||
@ -57,6 +64,7 @@ export const WorkbenchAction = withDynamicSchemaProps((props) => {
|
|||||||
<Component
|
<Component
|
||||||
className={cx(className, styles.action, 'nb-action-panel')}
|
className={cx(className, styles.action, 'nb-action-panel')}
|
||||||
{...others}
|
{...others}
|
||||||
|
type="text"
|
||||||
icon={null}
|
icon={null}
|
||||||
title={<Button />}
|
title={<Button />}
|
||||||
confirmTitle={fieldSchema.title}
|
confirmTitle={fieldSchema.title}
|
||||||
|
@ -7,23 +7,21 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { css } from '@emotion/css';
|
|
||||||
import { observer, useFieldSchema } from '@formily/react';
|
import { observer, useFieldSchema } from '@formily/react';
|
||||||
import {
|
import {
|
||||||
CollectionContext,
|
CollectionContext,
|
||||||
|
createStyles,
|
||||||
DataSourceContext,
|
DataSourceContext,
|
||||||
DndContext,
|
DndContext,
|
||||||
Icon,
|
Icon,
|
||||||
NocoBaseRecursionField,
|
NocoBaseRecursionField,
|
||||||
useBlockHeight,
|
useOpenModeContext,
|
||||||
useDesignable,
|
|
||||||
useSchemaInitializerRender,
|
useSchemaInitializerRender,
|
||||||
withDynamicSchemaProps,
|
withDynamicSchemaProps,
|
||||||
useBlockHeightProps,
|
|
||||||
useOpenModeContext,
|
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { Avatar, List, Space, theme } from 'antd';
|
import { Avatar, Space } from 'antd';
|
||||||
import React, { createContext, useEffect, useState, useRef, useMemo, useLayoutEffect } from 'react';
|
import { Grid, List } from 'antd-mobile';
|
||||||
|
import React, { createContext } from 'react';
|
||||||
import { WorkbenchLayout } from './workbenchBlockSettings';
|
import { WorkbenchLayout } from './workbenchBlockSettings';
|
||||||
|
|
||||||
const ConfigureActionsButton = observer(
|
const ConfigureActionsButton = observer(
|
||||||
@ -46,105 +44,27 @@ const ResponsiveSpace = () => {
|
|||||||
const isMobileMedia = isMobile();
|
const isMobileMedia = isMobile();
|
||||||
const { isMobile: underMobileCtx } = useOpenModeContext() || {};
|
const { isMobile: underMobileCtx } = useOpenModeContext() || {};
|
||||||
const { itemsPerRow = 4 } = fieldSchema.parent['x-decorator-props'] || {};
|
const { itemsPerRow = 4 } = fieldSchema.parent['x-decorator-props'] || {};
|
||||||
const isUnderMobile = isMobileMedia || underMobileCtx;
|
|
||||||
const containerRef = useRef(null); // 引用容器
|
|
||||||
const [containerWidth, setContainerWidth] = useState(0); // 容器宽度
|
|
||||||
// 使用 ResizeObserver 动态获取容器宽度
|
|
||||||
useEffect(() => {
|
|
||||||
const handleResize = () => {
|
|
||||||
if (containerRef.current) {
|
|
||||||
setContainerWidth(containerRef.current.offsetWidth); // 更新宽度
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// 初始化 ResizeObserver
|
|
||||||
const resizeObserver = new ResizeObserver(handleResize);
|
|
||||||
|
|
||||||
// 监听容器宽度变化
|
if (underMobileCtx || isMobileMedia) {
|
||||||
if (containerRef.current) {
|
return (
|
||||||
resizeObserver.observe(containerRef.current);
|
<Grid columns={itemsPerRow} gap={gap}>
|
||||||
handleResize(); // 初始化时获取一次宽度
|
{fieldSchema.mapProperties((s, key) => {
|
||||||
}
|
return (
|
||||||
|
<Grid.Item style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }} key={key}>
|
||||||
return () => {
|
<NocoBaseRecursionField name={key} schema={s} />
|
||||||
if (containerRef.current) {
|
</Grid.Item>
|
||||||
resizeObserver.unobserve(containerRef.current);
|
);
|
||||||
}
|
})}
|
||||||
};
|
</Grid>
|
||||||
}, []);
|
);
|
||||||
|
}
|
||||||
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 (
|
return (
|
||||||
<div ref={containerRef} style={{ width: '100%' }}>
|
<Space wrap size={gap} align="start">
|
||||||
<Space
|
{fieldSchema.mapProperties((s, key) => {
|
||||||
wrap
|
return <NocoBaseRecursionField name={key} schema={s} />;
|
||||||
style={{ width: '100%', display: 'flex' }}
|
})}
|
||||||
size={gap}
|
</Space>
|
||||||
align="start"
|
|
||||||
className={css`
|
|
||||||
.ant-space-item {
|
|
||||||
width: ${isUnderMobile ? itemWidth + 'px' : '100%'}
|
|
||||||
display: flex;
|
|
||||||
.ant-nb-action {
|
|
||||||
padding: ${isUnderMobile ? '4px 0px' : null};
|
|
||||||
}
|
|
||||||
.nb-action-panel-container {
|
|
||||||
width: ${itemWidth}px !important;
|
|
||||||
}
|
|
||||||
.ant-avatar-circle {
|
|
||||||
width: ${avatarSize}px !important;
|
|
||||||
height: ${avatarSize}px !important;
|
|
||||||
line-height: ${avatarSize}px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
{fieldSchema.mapProperties((s, key) => (
|
|
||||||
<div
|
|
||||||
key={key}
|
|
||||||
style={
|
|
||||||
isUnderMobile && {
|
|
||||||
flexBasis: `${itemWidth}px`,
|
|
||||||
flexShrink: 0,
|
|
||||||
flexGrow: 0,
|
|
||||||
display: 'flex',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<NocoBaseRecursionField name={key} schema={s} />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -157,36 +77,17 @@ const InternalIcons = () => {
|
|||||||
{layout === WorkbenchLayout.Grid ? (
|
{layout === WorkbenchLayout.Grid ? (
|
||||||
<ResponsiveSpace />
|
<ResponsiveSpace />
|
||||||
) : (
|
) : (
|
||||||
<List itemLayout="horizontal">
|
<List>
|
||||||
{fieldSchema.mapProperties((s, key) => {
|
{fieldSchema.mapProperties((s, key) => {
|
||||||
const icon = s['x-component-props']?.['icon'];
|
const icon = s['x-component-props']?.['icon'];
|
||||||
const backgroundColor = s['x-component-props']?.['iconColor'];
|
const backgroundColor = s['x-component-props']?.['iconColor'];
|
||||||
return (
|
return (
|
||||||
<List.Item
|
<List.Item
|
||||||
key={key}
|
key={key}
|
||||||
className={css`
|
prefix={<Avatar style={{ backgroundColor }} icon={<Icon type={icon} />} />}
|
||||||
.ant-list-item-meta-avatar {
|
onClick={() => {}}
|
||||||
margin-inline-end: 0px !important;
|
|
||||||
}
|
|
||||||
.ant-list-item-meta-title {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
font-size: 14px;
|
|
||||||
margin: 0 0 0 0;
|
|
||||||
}
|
|
||||||
.ant-list-item-meta-title button {
|
|
||||||
font-size: 14px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
width: 100%;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
<List.Item.Meta
|
<NocoBaseRecursionField name={key} schema={s} />
|
||||||
avatar={<Avatar style={{ backgroundColor }} icon={<Icon type={icon} />} />}
|
|
||||||
title={<NocoBaseRecursionField name={key} schema={s} key={key} />}
|
|
||||||
></List.Item.Meta>
|
|
||||||
</List.Item>
|
</List.Item>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@ -199,44 +100,55 @@ const InternalIcons = () => {
|
|||||||
|
|
||||||
export const WorkbenchBlockContext = createContext({ layout: 'grid' });
|
export const WorkbenchBlockContext = createContext({ layout: 'grid' });
|
||||||
|
|
||||||
|
const useStyles = createStyles(({ token, css }) => ({
|
||||||
|
containerClass: css`
|
||||||
|
&.list {
|
||||||
|
margin: -${token.paddingLG}px;
|
||||||
|
border-radius: ${(token as any).borderRadiusBlock}px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.adm-list {
|
||||||
|
--padding-left: ${token.paddingLG}px;
|
||||||
|
--padding-right: ${token.paddingLG}px;
|
||||||
|
|
||||||
|
.adm-list-item-content-main {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
height: auto;
|
||||||
|
box-shadow: none;
|
||||||
|
padding: 16px 32px;
|
||||||
|
margin: -12px -32px;
|
||||||
|
width: calc(100% + 64px);
|
||||||
|
text-align: start;
|
||||||
|
color: ${token.colorText};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button[aria-label*='schema-initializer-WorkbenchBlock.ActionBar-workbench:configureActions'] {
|
||||||
|
margin-bottom: ${token.paddingLG}px;
|
||||||
|
margin-left: ${token.paddingLG}px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}));
|
||||||
|
|
||||||
export const WorkbenchBlock: any = withDynamicSchemaProps(
|
export const WorkbenchBlock: any = withDynamicSchemaProps(
|
||||||
(props) => {
|
(props) => {
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { layout = 'grid' } = fieldSchema['x-component-props'] || {};
|
const { layout = 'grid' } = fieldSchema['x-component-props'] || {};
|
||||||
const { title } = fieldSchema['x-decorator-props'] || {};
|
const { styles } = useStyles();
|
||||||
const targetHeight = useBlockHeight();
|
|
||||||
const { token } = theme.useToken();
|
|
||||||
const { designable } = useDesignable();
|
|
||||||
const titleHeight = title ? token.fontSizeLG * token.lineHeightLG + token.padding * 2 - 1 : 0;
|
|
||||||
const internalHeight = 2 * token.paddingLG + token.controlHeight + token.marginLG + titleHeight;
|
|
||||||
const warperHeight =
|
|
||||||
targetHeight - (designable ? internalHeight : 2 * token.paddingLG + token.marginLG + titleHeight);
|
|
||||||
const targetWarperHeight = warperHeight > 0 ? warperHeight + 'px' : '100%';
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={`nb-action-penal-container ${layout} ${styles.containerClass}`}>
|
||||||
className="nb-action-penal-container"
|
<WorkbenchBlockContext.Provider value={{ layout }}>
|
||||||
style={{ height: targetHeight ? targetHeight - 2 * token.paddingLG - gap - titleHeight : '100%' }}
|
<DataSourceContext.Provider value={undefined}>
|
||||||
>
|
<CollectionContext.Provider value={undefined}>{props.children}</CollectionContext.Provider>
|
||||||
<div
|
</DataSourceContext.Provider>
|
||||||
className={css`
|
</WorkbenchBlockContext.Provider>
|
||||||
.nb-action-panel-warp {
|
|
||||||
height: ${targetHeight ? targetWarperHeight : '100%'};
|
|
||||||
overflow-y: auto;
|
|
||||||
margin-left: -24px;
|
|
||||||
margin-right: -24px;
|
|
||||||
padding-left: 24px;
|
|
||||||
padding-right: 24px;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<WorkbenchBlockContext.Provider value={{ layout }}>
|
|
||||||
<DataSourceContext.Provider value={undefined}>
|
|
||||||
<CollectionContext.Provider value={undefined}>{props.children}</CollectionContext.Provider>
|
|
||||||
</DataSourceContext.Provider>
|
|
||||||
</WorkbenchBlockContext.Provider>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -104,6 +104,7 @@ export class FileCollectionTemplate extends CollectionTemplate {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
name: 'url',
|
name: 'url',
|
||||||
deletable: false,
|
deletable: false,
|
||||||
|
length: 1024,
|
||||||
uiSchema: {
|
uiSchema: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
title: `{{t("URL")}}`,
|
title: `{{t("URL")}}`,
|
||||||
|
@ -71,6 +71,7 @@ export default defineCollection({
|
|||||||
comment: '网络访问地址',
|
comment: '网络访问地址',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
name: 'url',
|
name: 'url',
|
||||||
|
length: 1024,
|
||||||
// formula: '{{ storage.baseUrl }}{{ path }}/{{ filename }}'
|
// formula: '{{ storage.baseUrl }}{{ path }}/{{ filename }}'
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* 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 { DataTypes } from 'sequelize';
|
||||||
|
import { Migration } from '@nocobase/server';
|
||||||
|
|
||||||
|
export default class extends Migration {
|
||||||
|
on = 'afterLoad'; // 'beforeLoad' or 'afterLoad'
|
||||||
|
appVersion = '<1.5.14';
|
||||||
|
|
||||||
|
async up() {
|
||||||
|
const queryInterface = this.db.sequelize.getQueryInterface();
|
||||||
|
const CollectionRepo = this.db.getRepository('collections');
|
||||||
|
const FieldRepo = this.db.getRepository('fields');
|
||||||
|
await this.db.sequelize.transaction(async (transaction) => {
|
||||||
|
const collections = await CollectionRepo.find({
|
||||||
|
filter: {
|
||||||
|
'options.template': 'file',
|
||||||
|
},
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
collections.push({
|
||||||
|
name: 'attachments',
|
||||||
|
});
|
||||||
|
for (const item of collections) {
|
||||||
|
const collection = this.db.getCollection(item.name) || this.db.collection(item);
|
||||||
|
const tableName = collection.getTableNameWithSchema();
|
||||||
|
await queryInterface.changeColumn(
|
||||||
|
tableName,
|
||||||
|
'url',
|
||||||
|
{
|
||||||
|
type: DataTypes.STRING(1024),
|
||||||
|
},
|
||||||
|
{ transaction },
|
||||||
|
);
|
||||||
|
await FieldRepo.update({
|
||||||
|
filter: {
|
||||||
|
collectionName: item.name,
|
||||||
|
name: 'url',
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
length: 1024,
|
||||||
|
},
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -7,13 +7,12 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import crypto from 'crypto';
|
import { uid } from '@nocobase/utils';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
export function getFilename(req, file, cb) {
|
export function getFilename(req, file, cb) {
|
||||||
crypto.pseudoRandomBytes(16, function (err, raw) {
|
const baseName = path.basename(file.originalname, path.extname(file.originalname));
|
||||||
cb(err, err ? undefined : `${raw.toString('hex')}${path.extname(file.originalname)}`);
|
cb(null, `${baseName}-${uid(6)}${path.extname(file.originalname)}`);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const cloudFilenameGetter = (storage) => (req, file, cb) => {
|
export const cloudFilenameGetter = (storage) => (req, file, cb) => {
|
||||||
|
@ -56,14 +56,27 @@ test.describe('zIndex', () => {
|
|||||||
await page.getByLabel('Close lightbox').click();
|
await page.getByLabel('Close lightbox').click();
|
||||||
|
|
||||||
// 3. 进入第二层子页面,然后点击图片预览, 图片不能被子页面盖住
|
// 3. 进入第二层子页面,然后点击图片预览, 图片不能被子页面盖住
|
||||||
await page.getByLabel('action-Action-Edit-update-').click();
|
await page.getByLabel('action-Action-Edit-update-').click({
|
||||||
|
position: {
|
||||||
|
x: 5,
|
||||||
|
y: 5,
|
||||||
|
},
|
||||||
|
});
|
||||||
await page.getByRole('link', { name: title }).nth(2).click();
|
await page.getByRole('link', { name: title }).nth(2).click();
|
||||||
await page.waitForTimeout(300);
|
await page.waitForTimeout(300);
|
||||||
await check(2);
|
await check(2);
|
||||||
await page.getByLabel('Close lightbox').click();
|
await page.getByLabel('Close lightbox').click();
|
||||||
|
|
||||||
// 4. 进入第三层子页面,然后点击图片预览, 图片不能被子页面盖住
|
// 4. 进入第三层子页面,然后点击图片预览, 图片不能被子页面盖住
|
||||||
await page.getByLabel('action-Action-Edit-update-').nth(2).click();
|
await page
|
||||||
|
.getByLabel('action-Action-Edit-update-')
|
||||||
|
.nth(2)
|
||||||
|
.click({
|
||||||
|
position: {
|
||||||
|
x: 5,
|
||||||
|
y: 5,
|
||||||
|
},
|
||||||
|
});
|
||||||
await page.getByRole('link', { name: title }).nth(3).click();
|
await page.getByRole('link', { name: title }).nth(3).click();
|
||||||
await page.waitForTimeout(300);
|
await page.waitForTimeout(300);
|
||||||
await check(3);
|
await check(3);
|
||||||
|
@ -21,8 +21,10 @@ export const InternalSettings = () => {
|
|||||||
const style = useMemo(() => {
|
const style = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
marginBottom: token.marginBlock,
|
marginBottom: token.marginBlock,
|
||||||
|
borderRadius: token.borderRadiusBlock,
|
||||||
|
overflow: 'hidden',
|
||||||
};
|
};
|
||||||
}, [token.marginBlock]);
|
}, [token.borderRadiusBlock, token.marginBlock]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SortableItem className={cx('nb-mobile-setting')} style={style}>
|
<SortableItem className={cx('nb-mobile-setting')} style={style}>
|
||||||
|
@ -23,6 +23,7 @@ import {
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { isDesktop } from 'react-device-detect';
|
import { isDesktop } from 'react-device-detect';
|
||||||
|
|
||||||
|
import { theme } from 'antd';
|
||||||
import { ActionDrawerUsedInMobile, useToAdaptActionDrawerToMobile } from '../adaptor-of-desktop/ActionDrawer';
|
import { ActionDrawerUsedInMobile, useToAdaptActionDrawerToMobile } from '../adaptor-of-desktop/ActionDrawer';
|
||||||
import { useToAdaptFilterActionToMobile } from '../adaptor-of-desktop/FilterAction';
|
import { useToAdaptFilterActionToMobile } from '../adaptor-of-desktop/FilterAction';
|
||||||
import { InternalPopoverNesterUsedInMobile } from '../adaptor-of-desktop/InternalPopoverNester';
|
import { InternalPopoverNesterUsedInMobile } from '../adaptor-of-desktop/InternalPopoverNester';
|
||||||
@ -92,11 +93,13 @@ export const Mobile = () => {
|
|||||||
<GlobalThemeProvider
|
<GlobalThemeProvider
|
||||||
theme={{
|
theme={{
|
||||||
token: {
|
token: {
|
||||||
marginBlock: 18,
|
paddingPageHorizontal: 8,
|
||||||
borderRadiusBlock: 0,
|
paddingPageVertical: 8,
|
||||||
boxShadowTertiary: 'none',
|
marginBlock: 12,
|
||||||
|
borderRadiusBlock: 8,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
},
|
},
|
||||||
|
algorithm: theme.compactAlgorithm,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AntdAppProvider className={`mobile-container ${componentCls} ${hashId}`}>
|
<AntdAppProvider className={`mobile-container ${componentCls} ${hashId}`}>
|
||||||
|
@ -8,43 +8,30 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { genStyleHook } from '@nocobase/client';
|
import { genStyleHook } from '@nocobase/client';
|
||||||
import { PageBackgroundColor } from '../constants';
|
|
||||||
|
|
||||||
export const useStyles = genStyleHook('nb-mobile', (token) => {
|
export const useStyles = genStyleHook('nb-mobile', (token) => {
|
||||||
const { componentCls } = token;
|
const { componentCls } = token;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
[componentCls]: {
|
[componentCls]: {
|
||||||
|
// 调整移动端 Grid card 区块顶部按钮和卡片之间的间距
|
||||||
|
'--nb-spacing': '12px',
|
||||||
|
|
||||||
WebkitOverflowScrolling: 'touch',
|
WebkitOverflowScrolling: 'touch',
|
||||||
display: 'initial',
|
display: 'initial',
|
||||||
|
|
||||||
'& ::-webkit-scrollbar': {
|
'& ::-webkit-scrollbar': {
|
||||||
display: 'none',
|
display: 'none',
|
||||||
},
|
},
|
||||||
body: {
|
|
||||||
backgroundColor: PageBackgroundColor,
|
|
||||||
},
|
|
||||||
'.nb-details .ant-formily-item-feedback-layout-loose': {
|
'.nb-details .ant-formily-item-feedback-layout-loose': {
|
||||||
marginBottom: '5px',
|
marginBottom: '5px',
|
||||||
},
|
},
|
||||||
'.nb-details .ant-formily-item-layout-vertical .ant-formily-item-label': {
|
'.nb-details .ant-formily-item-layout-vertical .ant-formily-item-label': {
|
||||||
marginBottom: '-8px',
|
marginBottom: '-8px',
|
||||||
},
|
},
|
||||||
'.ant-card .ant-card-body': {
|
|
||||||
paddingBottom: '10px',
|
|
||||||
paddingTop: '10px',
|
|
||||||
},
|
|
||||||
'.ant-pagination-simple': {
|
'.ant-pagination-simple': {
|
||||||
marginTop: '0px !important',
|
marginTop: '0px !important',
|
||||||
},
|
},
|
||||||
'.nb-action-penal-container': {
|
|
||||||
marginTop: '-10px',
|
|
||||||
marginBottom: '-10px',
|
|
||||||
},
|
|
||||||
'.nb-action-penal-container button[aria-label*="schema-initializer-WorkbenchBlock.ActionBar-workbench:configureActions"]':
|
|
||||||
{
|
|
||||||
marginBottom: '10px',
|
|
||||||
},
|
|
||||||
'.ant-list-item': {
|
'.ant-list-item': {
|
||||||
paddingTop: '8px',
|
paddingTop: '8px',
|
||||||
paddingBottom: '8px',
|
paddingBottom: '8px',
|
||||||
@ -58,9 +45,6 @@ export const useStyles = genStyleHook('nb-mobile', (token) => {
|
|||||||
paddingBottom: '0px',
|
paddingBottom: '0px',
|
||||||
paddingTop: '0px',
|
paddingTop: '0px',
|
||||||
},
|
},
|
||||||
'.nb-chart-block .noco-card-item': {
|
|
||||||
marginBottom: '-13px',
|
|
||||||
},
|
|
||||||
'.ant-table-thead button[aria-label*="schema-initializer-TableV2-table:configureColumns"] > span:last-child': {
|
'.ant-table-thead button[aria-label*="schema-initializer-TableV2-table:configureColumns"] > span:last-child': {
|
||||||
display: 'none !important',
|
display: 'none !important',
|
||||||
},
|
},
|
||||||
@ -90,22 +74,6 @@ export const useStyles = genStyleHook('nb-mobile', (token) => {
|
|||||||
'.ant-pagination .ant-pagination-item-active': {
|
'.ant-pagination .ant-pagination-item-active': {
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
},
|
},
|
||||||
'.ant-card-body .nb-action-bar .ant-btn': {
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '8px',
|
|
||||||
'& span': {
|
|
||||||
display: 'contents',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'.ant-card-body .nb-action-bar .ant-btn-icon': {
|
|
||||||
marginInlineEnd: '0px !important',
|
|
||||||
},
|
|
||||||
'.ant-card-body .nb-table-container': {
|
|
||||||
marginRight: '-20px',
|
|
||||||
marginLeft: '-10px',
|
|
||||||
},
|
|
||||||
'.nb-action-bar button[aria-label*="schema-initializer-ActionBar-table:configureActions"] > span:last-child': {
|
'.nb-action-bar button[aria-label*="schema-initializer-ActionBar-table:configureActions"] > span:last-child': {
|
||||||
display: 'none !important',
|
display: 'none !important',
|
||||||
},
|
},
|
||||||
@ -124,6 +92,11 @@ export const useStyles = genStyleHook('nb-mobile', (token) => {
|
|||||||
'[data-menu-id$="-theme"]': {
|
'[data-menu-id$="-theme"]': {
|
||||||
display: 'none',
|
display: 'none',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'& > .ant-menu > .ant-menu-item': {
|
||||||
|
marginInline: 8,
|
||||||
|
width: `calc(100% - 16px)`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -7,12 +7,16 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { useToken } from '@nocobase/client';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { FC, useEffect } from 'react';
|
import React, { FC, useEffect } from 'react';
|
||||||
|
import { PageBackgroundColor } from '../../../constants';
|
||||||
|
|
||||||
export const MobilePageContentContainer: FC<{ hideTabBar?: boolean }> = ({ children, hideTabBar }) => {
|
export const MobilePageContentContainer: FC<{ hideTabBar?: boolean }> = ({ children, hideTabBar }) => {
|
||||||
const [mobileTabBarHeight, setMobileTabBarHeight] = React.useState(0);
|
const [mobileTabBarHeight, setMobileTabBarHeight] = React.useState(0);
|
||||||
const [mobilePageHeader, setMobilePageHeader] = React.useState(0);
|
const [mobilePageHeader, setMobilePageHeader] = React.useState(0);
|
||||||
|
const { token } = useToken();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const navigationBar = _.last(document.querySelectorAll<HTMLDivElement>('.mobile-page-header'));
|
const navigationBar = _.last(document.querySelectorAll<HTMLDivElement>('.mobile-page-header'));
|
||||||
setMobilePageHeader(navigationBar?.offsetHeight);
|
setMobilePageHeader(navigationBar?.offsetHeight);
|
||||||
@ -34,6 +38,9 @@ export const MobilePageContentContainer: FC<{ hideTabBar?: boolean }> = ({ child
|
|||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
overflowX: 'hidden',
|
overflowX: 'hidden',
|
||||||
|
backgroundColor: PageBackgroundColor,
|
||||||
|
paddingInline: token.paddingPageHorizontal,
|
||||||
|
paddingBlock: token.paddingPageVertical,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user