Merge branch 'next' into develop

This commit is contained in:
nocobase[bot]
2025-09-10 11:24:41 +00:00
5 changed files with 54 additions and 27 deletions
@@ -9,9 +9,11 @@
import { connect, mapProps, mapReadPretty } from '@formily/react';
import React from 'react';
import { css } from '@emotion/css';
import classNames from 'classnames';
import { lazy } from '../../../lazy-helper';
import { isVariable } from '../../../variables/utils/isVariable';
import { Input, ReadPretty as InputReadPretty } from '../input';
import { ReadPretty as InputReadPretty } from '../input';
import { useStyles } from './style';
const ReactQuill = lazy(() => import('react-quill'));
@@ -37,9 +39,23 @@ export const RichText = connect(
];
const { value, defaultValue, onChange, disabled, modules: propsModules, formats: propsFormats } = props;
const resultValue = isVariable(value || defaultValue) ? undefined : value || '';
const quillDisabled = css`
.ql-container.ql-disabled {
background-color: #f5f5f5; /* 灰色背景 */
color: #999;
opacity: 0.7;
cursor: not-allowed;
pointer-events: none;
border: 1px solid #d9d9d9; /* 模拟 input 的禁用边框 */
border-radius: 6px;
}
`;
return wrapSSR(
<ReactQuill
className={`${componentCls} ${hashId}`}
className={classNames(componentCls, hashId, quillDisabled, {
'is-disabled': disabled,
})}
modules={propsModules || modules}
formats={propsFormats || formats}
value={resultValue}
+29 -21
View File
@@ -13,6 +13,7 @@ import path from 'path';
import fs from 'fs/promises';
import Application from './application';
import { SystemLogger } from '@nocobase/logger';
import { sleep } from '@nocobase/utils';
export const QUEUE_DEFAULT_INTERVAL = 250;
@@ -86,9 +87,10 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
if (!this.connected) {
return;
}
const { logger } = this.options;
const event = this.events.get(channel);
if (!event) {
console.warn(`memory queue (${channel}) not found, skipping...`);
logger.warn(`memory queue (${channel}) not found, skipping...`);
return;
}
if (!event.idle()) {
@@ -98,12 +100,12 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
const reading = this.reading.get(channel) || [];
const count = (event.concurrency || QUEUE_DEFAULT_CONCURRENCY) - reading.length;
if (count <= 0) {
console.debug(
`memory queue (${channel}) is already reading as max concurrency (${reading.length}), waiting last reading to end...`,
);
// logger.debug(
// `memory queue (${channel}) is already reading as max concurrency (${reading.length}), waiting last reading to end...`,
// );
return;
}
console.debug(`reading more from queue (${channel}), count: ${count}`);
logger.debug(`reading more from queue (${channel}), count: ${count}`);
this.read(channel, count).forEach((promise) => {
reading.push(promise);
// eslint-disable-next-line promise/catch-or-return
@@ -117,7 +119,7 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
this.reading.set(channel, reading);
};
constructor(private options: { appName: string }) {
constructor(private options: { appName: string; logger: SystemLogger }) {
this.emitter.setMaxListeners(0);
}
@@ -132,20 +134,21 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
async loadFromStorage() {
let queues = {};
let exists = false;
const { logger } = this.options;
try {
await fs.stat(this.storagePath);
exists = true;
} catch (ex) {
console.info(`memory queue storage file not found, skip`);
logger.info(`memory queue storage file not found, skip`);
}
if (exists) {
try {
const queueJson = await fs.readFile(this.storagePath);
queues = JSON.parse(queueJson.toString());
console.debug('memory queue loaded from storage', queues);
logger.debug('memory queue loaded from storage', queues);
await fs.unlink(this.storagePath);
} catch (ex) {
console.error('failed to load queue from storage', ex);
logger.error('failed to load queue from storage', ex);
}
}
this.queues = new Map(Object.entries(queues));
@@ -159,12 +162,13 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
return acc;
}, {});
const { logger } = this.options;
if (Object.keys(queues).length) {
await fs.mkdir(path.dirname(this.storagePath), { recursive: true });
await fs.writeFile(this.storagePath, JSON.stringify(queues));
console.debug('memory queue saved to storage', queues);
logger.debug('memory queue saved to storage', queues);
} else {
console.debug('memory queue empty, no need to save to storage');
logger.debug('memory queue empty, no need to save to storage');
}
}
@@ -195,14 +199,15 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
if (!this.connected) {
return;
}
const { logger } = this.options;
this.connected = false;
if (this.processing) {
console.info('memory queue waiting for processing job...');
logger.info('memory queue waiting for processing job...');
await this.processing;
console.info('memory queue job cleaned');
logger.info('memory queue job cleaned');
}
console.log('memory queue gracefully shutting down...');
logger.info('memory queue gracefully shutting down...');
await this.saveToStorage();
}
@@ -242,7 +247,8 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
const queue = this.queues.get(channel);
const message = { id: randomUUID(), content, options };
queue.push(message);
console.debug(`memory queue (${channel}) published message`, content);
const { logger } = this.options;
logger.debug(`memory queue (${channel}) published message`, content);
setImmediate(() => {
this.emitter.emit(channel, channel);
@@ -272,8 +278,9 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
if (!queue?.length) {
return [];
}
const { logger } = this.options;
const messages = queue.slice(0, n);
console.debug(`memory queue (${channel}) read ${messages.length} messages`, messages);
logger.debug(`memory queue (${channel}) read ${messages.length} messages`, messages);
queue.splice(0, messages.length);
const batch = messages.map(({ id, ...message }) => this.process(channel, { id, message }));
return batch;
@@ -282,7 +289,8 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
async process(channel: string, { id, message }) {
const event = this.events.get(channel);
const { content, options: { timeout = QUEUE_DEFAULT_ACK_TIMEOUT, maxRetries = 0, retried = 0 } = {} } = message;
console.debug(`memory queue (${channel}) processing message (${id})...`, content);
const { logger } = this.options;
logger.debug(`memory queue (${channel}) processing message (${id})...`, content);
return (async () =>
event.process(content, {
id,
@@ -290,12 +298,12 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
signal: AbortSignal.timeout(timeout),
}))()
.then(() => {
console.debug(`memory queue (${channel}) consumed message (${id})`);
logger.debug(`memory queue (${channel}) consumed message (${id})`);
})
.catch((ex) => {
if (maxRetries > 0 && retried < maxRetries) {
const currentRetry = retried + 1;
console.warn(
logger.warn(
`memory queue (${channel}) consum message (${id}) failed, retrying (${currentRetry} / ${maxRetries})...`,
ex,
);
@@ -303,7 +311,7 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter {
this.publish(channel, content, { timeout, maxRetries, retried: currentRetry, timestamp: Date.now() });
}, 500);
} else {
console.error(ex);
logger.error(ex);
}
});
}
@@ -322,7 +330,7 @@ export class EventQueue {
protected options: EventQueueOptions = {},
) {
if (app.serving()) {
this.setAdapter(new MemoryEventQueueAdapter({ appName: this.app.name }));
this.setAdapter(new MemoryEventQueueAdapter({ appName: this.app.name, logger: this.app.logger }));
app.on('afterStart', async () => {
await this.connect();
@@ -38,6 +38,7 @@ const MobileDateTimePicker = connect(
timeFormat = 'HH:mm',
showTime = false,
picker,
disabled,
...rest
} = props;
const [visible, setVisible] = useState(false);
@@ -73,11 +74,12 @@ const MobileDateTimePicker = connect(
}, []);
return (
<>
<div onClick={() => setVisible(true)}>
<div onClick={() => !disabled && setVisible(true)}>
<NBDatePicker
onClick={() => setVisible(true)}
value={value}
picker={picker}
disabled={disabled}
{...rest}
popupStyle={{ display: 'none' }}
style={{ pointerEvents: 'none', width: '100%' }}
@@ -15,7 +15,7 @@ import { useTranslation } from 'react-i18next';
const MobilePicker = connect(
(props) => {
const { value, onChange, options = [], mode } = props;
const { value, onChange, options = [], mode, disabled } = props;
const { t } = useTranslation();
const [visible, setVisible] = useState(false);
const [selected, setSelected] = useState(value || []);
@@ -38,12 +38,13 @@ const MobilePicker = connect(
return (
<>
<div onClick={() => setVisible(true)}>
<div onClick={() => !disabled && setVisible(true)}>
<Select
placeholder={t('Select')}
value={value}
dropdownStyle={{ display: 'none' }}
multiple={mode === 'multiple'}
disabled={disabled}
onClear={() => {
setVisible(false);
onChange(null);
@@ -104,7 +104,7 @@ describe('workflow > cluster', () => {
sharedQueues = new Map();
for (const node of cluster.nodes) {
await node.eventQueue.close();
const adapter = new MockMemoryEventQueueAdapter({ appName: node.name });
const adapter = new MockMemoryEventQueueAdapter({ appName: node.name, logger: node.logger });
adapter.setQueues(sharedQueues);
node.eventQueue.setAdapter(adapter);
await node.eventQueue.connect();